mirror of
https://github.com/yogstation13/Yogstation.git
synced 2025-02-26 09:04:50 +00:00
* more interesting singularity movement * Some minor fixups + remove debug print * No longer affects nar nar * use block() * stuff
564 lines
16 KiB
Plaintext
564 lines
16 KiB
Plaintext
#define SINGULARITY_QUADRANT_SIZE 3
|
|
#define SINGULARITY_QUADRANT_DISTANCE 15
|
|
#define SINGULARITY_INTEREST_OBJECT 7.5
|
|
#define SINGULARITY_INTEREST_NONSPACE 2
|
|
|
|
/obj/singularity
|
|
name = "gravitational singularity"
|
|
desc = "A gravitational singularity."
|
|
icon = 'icons/obj/singularity.dmi'
|
|
icon_state = "singularity_s1"
|
|
anchored = TRUE
|
|
density = TRUE
|
|
move_resist = INFINITY
|
|
layer = MASSIVE_OBJ_LAYER
|
|
light_range = 6
|
|
appearance_flags = LONG_GLIDE
|
|
var/current_size = 1
|
|
var/allowed_size = 1
|
|
var/contained = 1 //Are we going to move around?
|
|
var/energy = 100 //How strong are we?
|
|
var/dissipate = 1 //Do we lose energy over time?
|
|
var/dissipate_delay = 10
|
|
var/dissipate_track = 0
|
|
var/dissipate_strength = 1 //How much energy do we lose?
|
|
var/move_self = 1 //Do we move on our own?
|
|
var/grav_pull = 4 //How many tiles out do we pull?
|
|
var/consume_range = 0 //How many tiles out do we eat
|
|
var/event_chance = 10 //Prob for event each tick
|
|
var/turf/random_target = null // a randomly chosen target.
|
|
var/target = null //its target. moves towards the target if it has one
|
|
var/last_failed_movement = 0//Will not move in the same dir if it couldnt before, will help with the getting stuck on fields thing
|
|
var/last_warning
|
|
var/consumedSupermatter = 0 //If the singularity has eaten a supermatter shard and can go to stage six
|
|
var/maxStage = 0 //The largest stage this singularity has been
|
|
var/does_targeting = TRUE
|
|
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF
|
|
obj_flags = CAN_BE_HIT | DANGEROUS_POSSESSION
|
|
|
|
/obj/singularity/Initialize(mapload, starting_energy = 50)
|
|
//CARN: admin-alert for chuckle-fuckery.
|
|
admin_investigate_setup()
|
|
|
|
src.energy = starting_energy
|
|
. = ..()
|
|
START_PROCESSING(SSobj, src)
|
|
GLOB.poi_list |= src
|
|
GLOB.singularities |= src
|
|
for(var/obj/machinery/power/singularity_beacon/singubeacon in GLOB.machines)
|
|
if(singubeacon.active)
|
|
target = singubeacon
|
|
break
|
|
return
|
|
|
|
/obj/singularity/Destroy()
|
|
if(maxStage)
|
|
var/shardstage = text2path("/obj/item/singularity_shard/stage[maxStage]")
|
|
var/turf/T = get_turf(src)
|
|
new shardstage(T, src)
|
|
STOP_PROCESSING(SSobj, src)
|
|
GLOB.poi_list.Remove(src)
|
|
GLOB.singularities.Remove(src)
|
|
return ..()
|
|
|
|
/obj/singularity/Move(atom/newloc, direct)
|
|
if(current_size >= STAGE_FIVE || check_turfs_in(direct))
|
|
last_failed_movement = 0//Reset this because we moved
|
|
return ..()
|
|
else
|
|
last_failed_movement = direct
|
|
return 0
|
|
|
|
/obj/singularity/attack_hand(mob/user)
|
|
consume(user)
|
|
return TRUE
|
|
|
|
/obj/singularity/attack_paw(mob/user)
|
|
consume(user)
|
|
|
|
/obj/singularity/attack_alien(mob/user)
|
|
consume(user)
|
|
|
|
/obj/singularity/attack_animal(mob/user)
|
|
consume(user)
|
|
|
|
/obj/singularity/attackby(obj/item/W, mob/user, params)
|
|
consume(user)
|
|
return 1
|
|
|
|
/obj/singularity/Process_Spacemove() //The singularity stops drifting for no man!
|
|
return 0
|
|
|
|
/obj/singularity/blob_act(obj/structure/blob/B)
|
|
return
|
|
|
|
/obj/singularity/attack_tk(mob/user)
|
|
if(iscarbon(user))
|
|
var/mob/living/carbon/C = user
|
|
C.visible_message(span_danger("[C]'s head begins to collapse in on itself!"), span_userdanger("Your head feels like it's collapsing in on itself! This was really not a good idea!"), span_italics("You hear something crack and explode in gore."))
|
|
var/turf/T = get_turf(C)
|
|
for(var/i in 1 to 3)
|
|
C.apply_damage(30, BRUTE, BODY_ZONE_HEAD)
|
|
new /obj/effect/gibspawner/generic(T, C)
|
|
sleep(1)
|
|
C.ghostize()
|
|
var/obj/item/bodypart/head/rip_u = C.get_bodypart(BODY_ZONE_HEAD)
|
|
rip_u.dismember(BURN) //nice try jedi
|
|
qdel(rip_u)
|
|
|
|
/obj/singularity/ex_act(severity, target)
|
|
var/energy_loss_ratio = 0
|
|
var/stage_collapse = null
|
|
switch(severity)
|
|
if(EXPLODE_DEVASTATE)
|
|
stage_collapse = STAGE_TWO
|
|
energy_loss_ratio = 0.60
|
|
if(EXPLODE_HEAVY)
|
|
stage_collapse = STAGE_ONE
|
|
energy_loss_ratio = 0.40
|
|
if(EXPLODE_LIGHT)
|
|
energy_loss_ratio = 0.30
|
|
|
|
if(stage_collapse && current_size <= stage_collapse)
|
|
investigate_log("has been destroyed by an explosion", INVESTIGATE_SINGULO)
|
|
qdel(src)
|
|
return
|
|
|
|
energy -= round(energy * energy_loss_ratio)
|
|
check_energy()
|
|
|
|
if(energy <= 60)
|
|
investigate_log("collapsed due to low energy after an explosion.", INVESTIGATE_SINGULO)
|
|
qdel(src)
|
|
return
|
|
|
|
|
|
/obj/singularity/bullet_act(obj/item/projectile/P)
|
|
qdel(P)
|
|
return BULLET_ACT_HIT //Will there be an impact? Who knows. Will we see it? No.
|
|
|
|
|
|
/obj/singularity/Bump(atom/A)
|
|
consume(A)
|
|
return
|
|
|
|
|
|
/obj/singularity/Bumped(atom/movable/AM)
|
|
consume(AM)
|
|
|
|
|
|
/obj/singularity/process()
|
|
if(current_size >= STAGE_TWO)
|
|
move()
|
|
radiation_pulse(src, min(9000, (energy*4.5)+1000), RAD_DISTANCE_COEFFICIENT*0.5)
|
|
if(prob(event_chance))//Chance for it to run a special event TODO:Come up with one or two more that fit
|
|
event()
|
|
eat()
|
|
dissipate()
|
|
check_energy()
|
|
|
|
return
|
|
|
|
|
|
/obj/singularity/attack_ai() //to prevent ais from gibbing themselves when they click on one.
|
|
return
|
|
|
|
|
|
/obj/singularity/proc/admin_investigate_setup()
|
|
var/turf/T = get_turf(src)
|
|
last_warning = world.time
|
|
var/count = locate(/obj/machinery/field/containment) in urange(30, src, 1)
|
|
if(!count)
|
|
message_admins("A singulo has been created without containment fields active at [ADMIN_VERBOSEJMP(T)].")
|
|
investigate_log("was created at [AREACOORD(T)]. [count?"":"<font color='red'>No containment fields were active</font>"]", INVESTIGATE_SINGULO)
|
|
|
|
/obj/singularity/proc/dissipate()
|
|
if(!dissipate)
|
|
return
|
|
if(dissipate_track >= dissipate_delay)
|
|
src.energy -= dissipate_strength
|
|
dissipate_track = 0
|
|
else
|
|
dissipate_track++
|
|
|
|
|
|
/obj/singularity/proc/expand(force_size = 0)
|
|
var/temp_allowed_size = src.allowed_size
|
|
if(force_size)
|
|
temp_allowed_size = force_size
|
|
if(temp_allowed_size >= STAGE_SIX && !consumedSupermatter)
|
|
temp_allowed_size = STAGE_FIVE
|
|
switch(temp_allowed_size)
|
|
if(STAGE_ONE)
|
|
current_size = STAGE_ONE
|
|
icon = 'icons/obj/singularity.dmi'
|
|
icon_state = "singularity_s1"
|
|
pixel_x = 0
|
|
pixel_y = 0
|
|
grav_pull = 4
|
|
consume_range = 0
|
|
dissipate_delay = 10
|
|
dissipate_track = 0
|
|
dissipate_strength = 1
|
|
if(maxStage < 1)
|
|
maxStage = 1
|
|
if(STAGE_TWO)
|
|
if(check_cardinals_range(1, TRUE))
|
|
current_size = STAGE_TWO
|
|
icon = 'icons/effects/96x96.dmi'
|
|
icon_state = "singularity_s3"
|
|
pixel_x = -32
|
|
pixel_y = -32
|
|
grav_pull = 6
|
|
consume_range = 1
|
|
dissipate_delay = 5
|
|
dissipate_track = 0
|
|
dissipate_strength = 5
|
|
if(maxStage < 2)
|
|
maxStage = 2
|
|
if(STAGE_THREE)
|
|
if(check_cardinals_range(2, TRUE))
|
|
current_size = STAGE_THREE
|
|
icon = 'icons/effects/160x160.dmi'
|
|
icon_state = "singularity_s5"
|
|
pixel_x = -64
|
|
pixel_y = -64
|
|
grav_pull = 8
|
|
consume_range = 2
|
|
dissipate_delay = 4
|
|
dissipate_track = 0
|
|
dissipate_strength = 20
|
|
if(maxStage < 3)
|
|
maxStage = 3
|
|
if(STAGE_FOUR)
|
|
if(check_cardinals_range(3, TRUE))
|
|
current_size = STAGE_FOUR
|
|
icon = 'icons/effects/224x224.dmi'
|
|
icon_state = "singularity_s7"
|
|
pixel_x = -96
|
|
pixel_y = -96
|
|
grav_pull = 10
|
|
consume_range = 3
|
|
dissipate_delay = 10
|
|
dissipate_track = 0
|
|
dissipate_strength = 10
|
|
if(maxStage < 4)
|
|
maxStage = 4
|
|
if(STAGE_FIVE)//this one also lacks a check for gens because it eats everything
|
|
current_size = STAGE_FIVE
|
|
icon = 'icons/effects/288x288.dmi'
|
|
icon_state = "singularity_s9"
|
|
pixel_x = -128
|
|
pixel_y = -128
|
|
grav_pull = 10
|
|
consume_range = 4
|
|
dissipate = 0 //It cant go smaller due to e loss
|
|
if(maxStage < 5)
|
|
maxStage = 5
|
|
if(STAGE_SIX) //This only happens if a stage 5 singulo consumes a supermatter shard.
|
|
current_size = STAGE_SIX
|
|
icon = 'icons/effects/352x352.dmi'
|
|
icon_state = "singularity_s11"
|
|
pixel_x = -160
|
|
pixel_y = -160
|
|
grav_pull = 15
|
|
consume_range = 5
|
|
dissipate = 0
|
|
if(maxStage < 6)
|
|
maxStage = 6
|
|
if(current_size == allowed_size)
|
|
investigate_log("<font color='red'>grew to size [current_size]</font>", INVESTIGATE_SINGULO)
|
|
return 1
|
|
else if(current_size < (--temp_allowed_size))
|
|
expand(temp_allowed_size)
|
|
else
|
|
return 0
|
|
|
|
|
|
/obj/singularity/proc/check_energy()
|
|
if(energy <= 0)
|
|
investigate_log("collapsed.", INVESTIGATE_SINGULO)
|
|
qdel(src)
|
|
return 0
|
|
switch(energy)//Some of these numbers might need to be changed up later -Mport
|
|
if(1 to 199)
|
|
allowed_size = STAGE_ONE
|
|
if(200 to 499)
|
|
allowed_size = STAGE_TWO
|
|
if(500 to 999)
|
|
allowed_size = STAGE_THREE
|
|
if(1000 to 1999)
|
|
allowed_size = STAGE_FOUR
|
|
if(2000 to INFINITY)
|
|
if(energy >= 3000 && consumedSupermatter)
|
|
allowed_size = STAGE_SIX
|
|
else
|
|
allowed_size = STAGE_FIVE
|
|
if(current_size != allowed_size)
|
|
expand()
|
|
return 1
|
|
|
|
|
|
/obj/singularity/proc/eat()
|
|
for(var/tile in spiral_range_turfs(grav_pull, src))
|
|
var/turf/T = tile
|
|
if(!T || !isturf(loc))
|
|
continue
|
|
if(get_dist(T, src) > consume_range)
|
|
T.singularity_pull(src, current_size)
|
|
else
|
|
consume(T)
|
|
for(var/thing in T)
|
|
if(isturf(loc) && thing != src)
|
|
var/atom/movable/X = thing
|
|
if(get_dist(X, src) > consume_range)
|
|
X.singularity_pull(src, current_size)
|
|
else
|
|
consume(X)
|
|
CHECK_TICK
|
|
return
|
|
|
|
|
|
/obj/singularity/proc/consume(atom/A)
|
|
var/gain = A.singularity_act(current_size, src)
|
|
src.energy += gain
|
|
if(istype(A, /obj/machinery/power/supermatter_crystal) && !consumedSupermatter)
|
|
desc = "[initial(desc)] It glows fiercely with inner fire."
|
|
name = "supermatter-charged [initial(name)]"
|
|
consumedSupermatter = 1
|
|
set_light(10)
|
|
return
|
|
|
|
|
|
/obj/singularity/proc/move(force_move = 0)
|
|
if(!move_self)
|
|
return 0
|
|
|
|
var/movement_dir = pick(GLOB.alldirs - last_failed_movement)
|
|
|
|
if(force_move)
|
|
movement_dir = force_move
|
|
|
|
|
|
if (target && prob(60))
|
|
movement_dir = get_dir(src,target) //moves to a singulo beacon, if there is one
|
|
else if (!target && random_target && prob(55))
|
|
var/new_movement_dir = get_dir(src, random_target)
|
|
if (last_failed_movement == movement_dir)
|
|
random_target = null
|
|
else
|
|
movement_dir = new_movement_dir
|
|
|
|
step(src, movement_dir)
|
|
|
|
if (random_target && (random_target.z != z || get_dist(src, random_target) <= 2))
|
|
random_target = null
|
|
|
|
if (does_targeting && !random_target && prob(50))
|
|
pick_random_target()
|
|
|
|
/obj/singularity/proc/check_cardinals_range(steps, retry_with_move = FALSE)
|
|
. = length(GLOB.cardinals) //Should be 4.
|
|
for(var/i in GLOB.cardinals)
|
|
. -= check_turfs_in(i, steps) //-1 for each working direction
|
|
if(. && retry_with_move) //If there's still a positive value it means it didn't pass. Retry with move if applicable
|
|
for(var/i in GLOB.cardinals)
|
|
if(step(src, i)) //Move in each direction.
|
|
if(check_cardinals_range(steps, FALSE)) //New location passes, return true.
|
|
return TRUE
|
|
. = !.
|
|
|
|
/obj/singularity/proc/pick_random_target()
|
|
var/list/sections = list()
|
|
for (var/section_x = -SINGULARITY_QUADRANT_DISTANCE - SINGULARITY_QUADRANT_SIZE; section_x < SINGULARITY_QUADRANT_DISTANCE + SINGULARITY_QUADRANT_SIZE; section_x += SINGULARITY_QUADRANT_SIZE)
|
|
for (var/section_y = -SINGULARITY_QUADRANT_DISTANCE - SINGULARITY_QUADRANT_SIZE; section_y < SINGULARITY_QUADRANT_DISTANCE + SINGULARITY_QUADRANT_SIZE; section_y += SINGULARITY_QUADRANT_SIZE)
|
|
var/turf/section_loc = locate(x + section_x, y + section_y, z)
|
|
var/turf/bottom_corner = locate(x + section_x + SINGULARITY_QUADRANT_SIZE - 1, y + section_y + SINGULARITY_QUADRANT_SIZE - 1, z)
|
|
if (!section_loc || !istype(section_loc) || !bottom_corner || !istype(bottom_corner))
|
|
continue
|
|
var/list/box = block(section_loc, bottom_corner)
|
|
var/interest = 0
|
|
for (var/turf/T in box)
|
|
if (!isspaceturf(T))
|
|
interest += SINGULARITY_INTEREST_NONSPACE
|
|
var/objs = 0
|
|
for (var/A in T.contents)
|
|
if (istype(A, /atom/movable))
|
|
objs += 1
|
|
interest += CEILING(objs / SINGULARITY_INTEREST_OBJECT, 0.5)
|
|
sections[section_loc] = interest
|
|
var/turf/section = pickweight(sections)
|
|
if (section && istype(section))
|
|
random_target = section
|
|
|
|
/obj/singularity/proc/check_turfs_in(direction = 0, step = 0)
|
|
if(!direction)
|
|
return 0
|
|
var/steps = 0
|
|
if(!step)
|
|
switch(current_size)
|
|
if(STAGE_ONE)
|
|
steps = 1
|
|
if(STAGE_TWO)
|
|
steps = 3//Yes this is right
|
|
if(STAGE_THREE)
|
|
steps = 3
|
|
if(STAGE_FOUR)
|
|
steps = 4
|
|
if(STAGE_FIVE)
|
|
steps = 5
|
|
else
|
|
steps = step
|
|
var/list/turfs = list()
|
|
var/turf/T = src.loc
|
|
for(var/i = 1 to steps)
|
|
T = get_step(T,direction)
|
|
if(!isturf(T))
|
|
return 0
|
|
turfs.Add(T)
|
|
var/dir2 = 0
|
|
var/dir3 = 0
|
|
switch(direction)
|
|
if(NORTH||SOUTH)
|
|
dir2 = 4
|
|
dir3 = 8
|
|
if(EAST||WEST)
|
|
dir2 = 1
|
|
dir3 = 2
|
|
var/turf/T2 = T
|
|
for(var/j = 1 to steps-1)
|
|
T2 = get_step(T2,dir2)
|
|
if(!isturf(T2))
|
|
return 0
|
|
turfs.Add(T2)
|
|
for(var/k = 1 to steps-1)
|
|
T = get_step(T,dir3)
|
|
if(!isturf(T))
|
|
return 0
|
|
turfs.Add(T)
|
|
for(var/turf/T3 in turfs)
|
|
if(isnull(T3))
|
|
continue
|
|
if(!can_move(T3))
|
|
return 0
|
|
return 1
|
|
|
|
|
|
/obj/singularity/proc/can_move(turf/T)
|
|
if(!T)
|
|
return 0
|
|
if((locate(/obj/machinery/field/containment) in T)||(locate(/obj/machinery/shieldwall) in T))
|
|
return 0
|
|
else if(locate(/obj/machinery/field/generator) in T)
|
|
var/obj/machinery/field/generator/G = locate(/obj/machinery/field/generator) in T
|
|
if(G && G.active)
|
|
return 0
|
|
else if(locate(/obj/machinery/shieldwallgen) in T)
|
|
var/obj/machinery/shieldwallgen/S = locate(/obj/machinery/shieldwallgen) in T
|
|
if(S && S.active)
|
|
return 0
|
|
return 1
|
|
|
|
|
|
/obj/singularity/proc/event()
|
|
var/numb = rand(1,4)
|
|
switch(numb)
|
|
if(1)//EMP
|
|
emp_area()
|
|
if(2)//Stun mobs who lack optic scanners
|
|
mezzer()
|
|
if(3,4) //Sets all nearby mobs on fire
|
|
if(current_size < STAGE_SIX)
|
|
return 0
|
|
combust_mobs()
|
|
else
|
|
return 0
|
|
return 1
|
|
|
|
|
|
/obj/singularity/proc/combust_mobs()
|
|
for(var/mob/living/carbon/C in urange(20, src, 1))
|
|
C.visible_message(span_warning("[C]'s skin bursts into flame!"), \
|
|
span_userdanger("You feel an inner fire as your skin bursts into flames!"))
|
|
C.adjust_fire_stacks(5)
|
|
C.IgniteMob()
|
|
return
|
|
|
|
|
|
/obj/singularity/proc/mezzer()
|
|
for(var/mob/living/carbon/M in oviewers(8, src))
|
|
if(isbrain(M)) //Ignore brains
|
|
continue
|
|
|
|
if(M.stat == CONSCIOUS)
|
|
if (ishuman(M))
|
|
var/mob/living/carbon/human/H = M
|
|
if(istype(H.glasses, /obj/item/clothing/glasses/meson))
|
|
var/obj/item/clothing/glasses/meson/MS = H.glasses
|
|
if(MS.vision_flags == SEE_TURFS)
|
|
to_chat(H, span_notice("You look directly into the [src.name], good thing you had your protective eyewear on!"))
|
|
return
|
|
|
|
M.apply_effect(60, EFFECT_STUN)
|
|
M.visible_message(span_danger("[M] stares blankly at the [src.name]!"), \
|
|
span_userdanger("You look directly into the [src.name] and feel weak."))
|
|
return
|
|
|
|
|
|
/obj/singularity/proc/emp_area()
|
|
empulse(src, 8, 10)
|
|
return
|
|
|
|
/obj/singularity/singularity_act()
|
|
var/gain = (energy/2)
|
|
var/dist = max((current_size - 2),1)
|
|
explosion(src.loc,(dist),(dist*2),(dist*4))
|
|
qdel(src)
|
|
return(gain)
|
|
|
|
/obj/item/singularity_shard
|
|
name = "singularity shard"
|
|
desc = "THIS SHOULDN'T EXIST. TELL A CODER HOW YOU GOT THIS."
|
|
icon_state = "singularity_shard_s1"
|
|
resistance_flags = INDESTRUCTIBLE
|
|
var/all_powerful = FALSE /// will it spawn an actual singularity when someone suicides with it
|
|
|
|
/obj/item/singularity_shard/suicide_act(mob/living/carbon/user)
|
|
user.visible_message(span_suicide("[user] is trying to break open the [src]! It looks like [user.p_theyre()] trying to commit suicide!"))
|
|
addtimer(CALLBACK(user, /mob/.proc/gib), 99)
|
|
addtimer(CALLBACK(src, .proc/spawnsing), 100)
|
|
return MANUAL_SUICIDE
|
|
|
|
/obj/item/singularity_shard/proc/spawnsing()
|
|
var/turf/T = get_turf(src)
|
|
if(all_powerful)
|
|
new /obj/singularity(T, src)
|
|
qdel(src)
|
|
else
|
|
new /obj/item/toy/spinningtoy(T, src)
|
|
|
|
/obj/item/singularity_shard/stage1
|
|
icon_state = "singularity_shard_s1"
|
|
desc = "A radiant shard of what was once an all-consuming maw of the void. This one reached stage 1."
|
|
|
|
/obj/item/singularity_shard/stage2
|
|
icon_state = "singularity_shard_s2"
|
|
desc = "A radiant shard of what was once an all-consuming maw of the void. This one reached stage 2."
|
|
|
|
/obj/item/singularity_shard/stage3
|
|
icon_state = "singularity_shard_s3"
|
|
desc = "A radiant shard of what was once an all-consuming maw of the void. This one reached stage 3."
|
|
|
|
/obj/item/singularity_shard/stage4
|
|
icon_state = "singularity_shard_s4"
|
|
desc = "A radiant shard of what was once an all-consuming maw of the void. This one reached stage 4."
|
|
|
|
/obj/item/singularity_shard/stage5
|
|
icon_state = "singularity_shard_s5"
|
|
desc = "A radiant shard of what was once an all-consuming maw of the void. This one reached stage 5."
|
|
|
|
/obj/item/singularity_shard/stage6
|
|
icon_state = "singularity_shard_s6"
|
|
desc = "A radiant shard of what was once an all-consuming maw of the void. This one reached stage 6 and looks particularly unstable."
|
|
all_powerful = TRUE
|