Files
Yogstation/code/modules/power/singularity/singularity.dm
Lucy bd464f21b8 More interesting singularity movement (#12877)
* more interesting singularity movement

* Some minor fixups + remove debug print

* No longer affects nar nar

* use block()

* stuff
2021-12-19 13:29:24 +00:00

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