mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-13 18:33:22 +00:00
* Dimensional Anomaly (#69512) About The Pull Request Everyone has been asking: "When will there be an anomaly like the bioscrambler, but for the space station? Please, we need more things which replace objects with different objects from the same typepath." Well I made it and it looked like ass because non-tiling floor and walls look terrible, so then I made this instead. Dimensional.mp4 The "dimensional anomaly" shifts matter into a parallel dimension where objects are made out of something else. Like the Bioscrambler anomaly, it does not expire on its own and only leaves when someone signals it or uses an anomaly remover. When it spawns it picks a "theme" and converts terrain around it until it covers a 7x7 square, then it teleports somewhere else and picks a new theme. A lot of these themes are relatively benign like "meat", "fancy carpet", or "gold". Some of them are kind of annoying like "icebox" because it creates floor which slows you down, or "clown" because bananium is intentionally annoying. Some of them are actively dangerous, mostly "uranium" and "plasma". The main problem this will usually cause for crewmembers is decreasing area security. When it replaces doors it replaces them with ones which don't have any access control, and it will also replace RWalls with normal and much more vulnerable walls which will make breaking and entering significantly easier until someone has taken the time to fix the damage. But also sometimes it will irradiate them, you never know. The fact that sometimes the changes are benign (or provide uncommon materials) and might be happening in places you don't care about access to might encourage people to push their luck and leave it alone until it starts turning the captain's office into a bamboo room or repainting medbay a fetching shade of flammable purple, which I would consider a success. Armour.mp4 If you successfully harvest the anomaly core you can place it into the reactive armour to get Reactive Barricade Armour, which shifts your dimension when you take damage and attempts to place some randomised (not terribly durable) objects between you and hopefully your attacker (it really just picks up to four random unoccupied tiles next to you). If you're EMPed then the changes it make to the environment will often be as unpleasant for you as they are for a pursuer, and significantly more likely to harm both of you rather than just provide obstacles. Other changes: I split anomalies out into their own dmi file, seems to be all the rage lately. I moved the anomaly placing code into a datum instead of the event because I wanted to reuse it but if you have a better idea about where I could have put it let me know. This also fixes a bug where the material spreader component wasn't working when I applied plasma materials to something, the extra whitespace was parsing as another argument for some reason and meant it would runtime. Supermatter delamination was still pointing to Delimber anomalies instead of Bioscrambler. * Dimensional Anomaly * Fixes the upstream merge skew Co-authored-by: Jacquerel <hnevard@gmail.com> Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> Co-authored-by: GoldenAlpharex <jerego1234@hotmail.com>
636 lines
19 KiB
Plaintext
636 lines
19 KiB
Plaintext
//Anomalies, used for events. Note that these DO NOT work by themselves; their procs are called by the event datum.
|
|
|
|
/// Chance of taking a step per second
|
|
#define ANOMALY_MOVECHANCE 45
|
|
|
|
/obj/effect/anomaly
|
|
name = "anomaly"
|
|
desc = "A mysterious anomaly, seen commonly only in the region of space that the station orbits..."
|
|
icon = 'icons/effects/anomalies.dmi'
|
|
icon_state = "vortex"
|
|
density = FALSE
|
|
anchored = TRUE
|
|
light_range = 3
|
|
|
|
var/obj/item/assembly/signaler/anomaly/aSignal = /obj/item/assembly/signaler/anomaly
|
|
var/area/impact_area
|
|
|
|
var/lifespan = 990
|
|
var/death_time
|
|
|
|
var/countdown_colour
|
|
var/obj/effect/countdown/anomaly/countdown
|
|
|
|
/// Do we drop a core when we're neutralized?
|
|
var/drops_core = TRUE
|
|
///Do we keep on living forever?
|
|
var/immortal = FALSE
|
|
///Do we stay in one place?
|
|
var/immobile = FALSE
|
|
|
|
/obj/effect/anomaly/Initialize(mapload, new_lifespan, drops_core = TRUE)
|
|
. = ..()
|
|
|
|
SSpoints_of_interest.make_point_of_interest(src)
|
|
|
|
START_PROCESSING(SSobj, src)
|
|
impact_area = get_area(src)
|
|
|
|
if (!impact_area)
|
|
return INITIALIZE_HINT_QDEL
|
|
|
|
src.drops_core = drops_core
|
|
|
|
aSignal = new aSignal(src)
|
|
aSignal.code = rand(1,100)
|
|
aSignal.anomaly_type = type
|
|
|
|
var/frequency = rand(MIN_FREE_FREQ, MAX_FREE_FREQ)
|
|
if(ISMULTIPLE(frequency, 2))//signaller frequencies are always uneven!
|
|
frequency++
|
|
aSignal.set_frequency(frequency)
|
|
|
|
if(new_lifespan)
|
|
lifespan = new_lifespan
|
|
death_time = world.time + lifespan
|
|
countdown = new(src)
|
|
if(countdown_colour)
|
|
countdown.color = countdown_colour
|
|
countdown.start()
|
|
|
|
/obj/effect/anomaly/process(delta_time)
|
|
anomalyEffect(delta_time)
|
|
if(death_time < world.time && !immortal)
|
|
if(loc)
|
|
detonate()
|
|
qdel(src)
|
|
|
|
/obj/effect/anomaly/Destroy()
|
|
STOP_PROCESSING(SSobj, src)
|
|
QDEL_NULL(countdown)
|
|
if(aSignal)
|
|
QDEL_NULL(aSignal)
|
|
return ..()
|
|
|
|
/obj/effect/anomaly/proc/anomalyEffect(delta_time)
|
|
if(!immobile && DT_PROB(ANOMALY_MOVECHANCE, delta_time))
|
|
step(src,pick(GLOB.alldirs))
|
|
|
|
/obj/effect/anomaly/proc/detonate()
|
|
return
|
|
|
|
/obj/effect/anomaly/ex_act(severity, target)
|
|
if(severity >= EXPLODE_DEVASTATE)
|
|
qdel(src)
|
|
|
|
/obj/effect/anomaly/proc/anomalyNeutralize()
|
|
new /obj/effect/particle_effect/fluid/smoke/bad(loc)
|
|
|
|
if(drops_core)
|
|
aSignal.forceMove(drop_location())
|
|
aSignal = null
|
|
// else, anomaly core gets deleted by qdel(src).
|
|
|
|
qdel(src)
|
|
|
|
/obj/effect/anomaly/attackby(obj/item/weapon, mob/user, params)
|
|
if(weapon.tool_behaviour == TOOL_ANALYZER)
|
|
to_chat(user, span_notice("Analyzing... [src]'s unstable field is fluctuating along frequency [format_frequency(aSignal.frequency)], code [aSignal.code]."))
|
|
return TRUE
|
|
|
|
return ..()
|
|
|
|
///////////////////////
|
|
|
|
/atom/movable/warp_effect
|
|
plane = GRAVITY_PULSE_PLANE
|
|
appearance_flags = PIXEL_SCALE|LONG_GLIDE // no tile bound so you can see it around corners and so
|
|
icon = 'icons/effects/light_overlays/light_352.dmi'
|
|
icon_state = "light"
|
|
pixel_x = -176
|
|
pixel_y = -176
|
|
|
|
/obj/effect/anomaly/grav
|
|
name = "gravitational anomaly"
|
|
icon = 'icons/effects/effects.dmi'
|
|
icon_state = "shield2"
|
|
density = FALSE
|
|
aSignal = /obj/item/assembly/signaler/anomaly/grav
|
|
var/boing = 0
|
|
///Warp effect holder for displacement filter to "pulse" the anomaly
|
|
var/atom/movable/warp_effect/warp
|
|
|
|
/obj/effect/anomaly/grav/Initialize(mapload, new_lifespan, drops_core)
|
|
. = ..()
|
|
var/static/list/loc_connections = list(
|
|
COMSIG_ATOM_ENTERED = .proc/on_entered,
|
|
)
|
|
AddElement(/datum/element/connect_loc, loc_connections)
|
|
|
|
warp = new(src)
|
|
vis_contents += warp
|
|
|
|
/obj/effect/anomaly/grav/Destroy()
|
|
vis_contents -= warp
|
|
warp = null
|
|
return ..()
|
|
|
|
/obj/effect/anomaly/grav/anomalyEffect(delta_time)
|
|
..()
|
|
boing = 1
|
|
for(var/obj/O in orange(4, src))
|
|
if(!O.anchored)
|
|
step_towards(O,src)
|
|
for(var/mob/living/M in range(0, src))
|
|
gravShock(M)
|
|
for(var/mob/living/M in orange(4, src))
|
|
if(!M.mob_negates_gravity())
|
|
step_towards(M,src)
|
|
for(var/obj/O in range(0,src))
|
|
if(!O.anchored)
|
|
if(isturf(O.loc))
|
|
var/turf/T = O.loc
|
|
if(T.underfloor_accessibility < UNDERFLOOR_INTERACTABLE && HAS_TRAIT(O, TRAIT_T_RAY_VISIBLE))
|
|
continue
|
|
var/mob/living/target = locate() in view(4,src)
|
|
if(target && !target.stat)
|
|
O.throw_at(target, 5, 10)
|
|
|
|
//anomaly quickly contracts then slowly expands it's ring
|
|
animate(warp, time = delta_time*3, transform = matrix().Scale(0.5,0.5))
|
|
animate(time = delta_time*7, transform = matrix())
|
|
|
|
/obj/effect/anomaly/grav/proc/on_entered(datum/source, atom/movable/AM)
|
|
SIGNAL_HANDLER
|
|
gravShock(AM)
|
|
|
|
/obj/effect/anomaly/grav/Bump(atom/A)
|
|
gravShock(A)
|
|
|
|
/obj/effect/anomaly/grav/Bumped(atom/movable/AM)
|
|
gravShock(AM)
|
|
|
|
/obj/effect/anomaly/grav/proc/gravShock(mob/living/A)
|
|
if(boing && isliving(A) && !A.stat)
|
|
A.Paralyze(40)
|
|
var/atom/target = get_edge_target_turf(A, get_dir(src, get_step_away(A, src)))
|
|
A.throw_at(target, 5, 1)
|
|
boing = 0
|
|
|
|
/obj/effect/anomaly/grav/high
|
|
var/datum/proximity_monitor/advanced/gravity/grav_field
|
|
|
|
/obj/effect/anomaly/grav/high/Initialize(mapload, new_lifespan)
|
|
. = ..()
|
|
INVOKE_ASYNC(src, .proc/setup_grav_field)
|
|
|
|
/obj/effect/anomaly/grav/high/proc/setup_grav_field()
|
|
grav_field = new(src, 7, TRUE, rand(0, 3))
|
|
|
|
/obj/effect/anomaly/grav/high/detonate()
|
|
for(var/obj/machinery/gravity_generator/main/the_generator in GLOB.machines)
|
|
if(is_station_level(the_generator.z))
|
|
the_generator.blackout()
|
|
|
|
/obj/effect/anomaly/grav/high/Destroy()
|
|
QDEL_NULL(grav_field)
|
|
. = ..()
|
|
|
|
/////////////////////
|
|
|
|
/obj/effect/anomaly/flux
|
|
name = "flux wave anomaly"
|
|
icon_state = "flux"
|
|
density = TRUE
|
|
aSignal = /obj/item/assembly/signaler/anomaly/flux
|
|
var/canshock = FALSE
|
|
var/shockdamage = 20
|
|
var/explosive = FLUX_EXPLOSIVE
|
|
|
|
/obj/effect/anomaly/flux/Initialize(mapload, new_lifespan, drops_core = TRUE, explosive = FLUX_EXPLOSIVE)
|
|
. = ..()
|
|
src.explosive = explosive
|
|
var/static/list/loc_connections = list(
|
|
COMSIG_ATOM_ENTERED = .proc/on_entered,
|
|
)
|
|
AddElement(/datum/element/connect_loc, loc_connections)
|
|
|
|
/obj/effect/anomaly/flux/anomalyEffect()
|
|
..()
|
|
canshock = TRUE
|
|
for(var/mob/living/M in range(0, src))
|
|
mobShock(M)
|
|
|
|
/obj/effect/anomaly/flux/update_overlays()
|
|
. = ..()
|
|
. += emissive_appearance(icon, icon_state, alpha=src.alpha)
|
|
|
|
/obj/effect/anomaly/flux/proc/on_entered(datum/source, atom/movable/AM)
|
|
SIGNAL_HANDLER
|
|
mobShock(AM)
|
|
|
|
/obj/effect/anomaly/flux/Bump(atom/A)
|
|
mobShock(A)
|
|
|
|
/obj/effect/anomaly/flux/Bumped(atom/movable/AM)
|
|
mobShock(AM)
|
|
|
|
/obj/effect/anomaly/flux/proc/mobShock(mob/living/M)
|
|
if(canshock && istype(M))
|
|
canshock = FALSE
|
|
M.electrocute_act(shockdamage, name, flags = SHOCK_NOGLOVES)
|
|
|
|
/obj/effect/anomaly/flux/detonate()
|
|
switch(explosive)
|
|
if(FLUX_EXPLOSIVE)
|
|
explosion(src, devastation_range = 1, heavy_impact_range = 4, light_impact_range = 16, flash_range = 18) //Low devastation, but hits a lot of stuff.
|
|
if(FLUX_LOW_EXPLOSIVE)
|
|
explosion(src, heavy_impact_range = 1, light_impact_range = 4, flash_range = 6)
|
|
if(FLUX_NO_EXPLOSION)
|
|
new /obj/effect/particle_effect/sparks(loc)
|
|
|
|
/////////////////////
|
|
|
|
/obj/effect/anomaly/bluespace
|
|
name = "bluespace anomaly"
|
|
icon = 'icons/obj/weapons/guns/projectiles.dmi'
|
|
icon_state = "bluespace"
|
|
density = TRUE
|
|
aSignal = /obj/item/assembly/signaler/anomaly/bluespace
|
|
|
|
/obj/effect/anomaly/bluespace/anomalyEffect()
|
|
..()
|
|
for(var/mob/living/M in range(1,src))
|
|
do_teleport(M, locate(M.x, M.y, M.z), 4, channel = TELEPORT_CHANNEL_BLUESPACE)
|
|
|
|
/obj/effect/anomaly/bluespace/Bumped(atom/movable/AM)
|
|
if(isliving(AM))
|
|
do_teleport(AM, locate(AM.x, AM.y, AM.z), 8, channel = TELEPORT_CHANNEL_BLUESPACE)
|
|
|
|
/obj/effect/anomaly/bluespace/detonate()
|
|
var/turf/T = pick(get_area_turfs(impact_area))
|
|
if(!T)
|
|
return
|
|
|
|
// Calculate new position (searches through beacons in world)
|
|
var/obj/item/beacon/chosen
|
|
var/list/possible = list()
|
|
for(var/obj/item/beacon/W in GLOB.teleportbeacons)
|
|
possible += W
|
|
|
|
if(possible.len > 0)
|
|
chosen = pick(possible)
|
|
|
|
if(!chosen)
|
|
return
|
|
|
|
// Calculate previous position for transition
|
|
var/turf/FROM = T // the turf of origin we're travelling FROM
|
|
var/turf/TO = get_turf(chosen) // the turf of origin we're travelling TO
|
|
|
|
playsound(TO, 'sound/effects/phasein.ogg', 100, TRUE)
|
|
priority_announce("Massive bluespace translocation detected.", "Anomaly Alert", ANNOUNCER_MASSIVEBSPACEANOMALIES) //SKYRAT EDIT CHANGE
|
|
|
|
var/list/flashers = list()
|
|
for(var/mob/living/carbon/C in viewers(TO, null))
|
|
if(C.flash_act())
|
|
flashers += C
|
|
|
|
var/y_distance = TO.y - FROM.y
|
|
var/x_distance = TO.x - FROM.x
|
|
for (var/atom/movable/A in urange(12, FROM )) // iterate thru list of mobs in the area
|
|
if(istype(A, /obj/item/beacon))
|
|
continue // don't teleport beacons because that's just insanely stupid
|
|
if(iscameramob(A))
|
|
continue // Don't mess with AI eye, blob eye, xenobio or advanced cameras
|
|
if(A.anchored)
|
|
continue
|
|
|
|
var/turf/newloc = locate(A.x + x_distance, A.y + y_distance, TO.z) // calculate the new place
|
|
if(!A.Move(newloc) && newloc) // if the atom, for some reason, can't move, FORCE them to move! :) We try Move() first to invoke any movement-related checks the atom needs to perform after moving
|
|
A.forceMove(newloc)
|
|
|
|
if(ismob(A) && !(A in flashers)) // don't flash if we're already doing an effect
|
|
var/mob/give_sparkles = A
|
|
if(give_sparkles.client)
|
|
blue_effect(give_sparkles)
|
|
|
|
/obj/effect/anomaly/bluespace/proc/blue_effect(mob/make_sparkle)
|
|
make_sparkle.overlay_fullscreen("bluespace_flash", /atom/movable/screen/fullscreen/bluespace_sparkle, 1)
|
|
addtimer(CALLBACK(make_sparkle, /mob/.proc/clear_fullscreen, "bluespace_flash"), 2 SECONDS)
|
|
|
|
/////////////////////
|
|
|
|
/obj/effect/anomaly/pyro
|
|
name = "pyroclastic anomaly"
|
|
icon_state = "pyroclastic"
|
|
var/ticks = 0
|
|
/// How many seconds between each gas release
|
|
var/releasedelay = 10
|
|
aSignal = /obj/item/assembly/signaler/anomaly/pyro
|
|
|
|
/obj/effect/anomaly/pyro/anomalyEffect(delta_time)
|
|
..()
|
|
ticks += delta_time
|
|
if(ticks < releasedelay)
|
|
return
|
|
else
|
|
ticks -= releasedelay
|
|
var/turf/open/T = get_turf(src)
|
|
if(istype(T))
|
|
T.atmos_spawn_air("o2=5;plasma=5;TEMP=1000")
|
|
|
|
/obj/effect/anomaly/pyro/detonate()
|
|
INVOKE_ASYNC(src, .proc/makepyroslime)
|
|
|
|
/obj/effect/anomaly/pyro/proc/makepyroslime()
|
|
var/turf/open/T = get_turf(src)
|
|
if(istype(T))
|
|
T.atmos_spawn_air("o2=500;plasma=500;TEMP=1000") //Make it hot and burny for the new slime
|
|
var/new_colour = pick("red", "orange")
|
|
var/mob/living/simple_animal/slime/S = new(T, new_colour)
|
|
S.rabid = TRUE
|
|
S.amount_grown = SLIME_EVOLUTION_THRESHOLD
|
|
S.Evolve()
|
|
var/datum/action/innate/slime/reproduce/A = new
|
|
A.Grant(S)
|
|
|
|
var/list/mob/dead/observer/candidates = poll_candidates_for_mob("Do you want to play as a pyroclastic anomaly slime?", ROLE_SENTIENCE, null, 10 SECONDS, S, POLL_IGNORE_PYROSLIME)
|
|
if(LAZYLEN(candidates))
|
|
var/mob/dead/observer/chosen = pick(candidates)
|
|
S.key = chosen.key
|
|
S.mind.special_role = ROLE_PYROCLASTIC_SLIME
|
|
var/policy = get_policy(ROLE_PYROCLASTIC_SLIME)
|
|
if (policy)
|
|
to_chat(S, policy)
|
|
S.log_message("was made into a slime by pyroclastic anomaly", LOG_GAME)
|
|
|
|
/////////////////////
|
|
|
|
/obj/effect/anomaly/bhole
|
|
name = "vortex anomaly"
|
|
icon_state = "vortex"
|
|
desc = "That's a nice station you have there. It'd be a shame if something happened to it."
|
|
aSignal = /obj/item/assembly/signaler/anomaly/vortex
|
|
|
|
/obj/effect/anomaly/bhole/anomalyEffect()
|
|
..()
|
|
if(!isturf(loc)) //blackhole cannot be contained inside anything. Weird stuff might happen
|
|
qdel(src)
|
|
return
|
|
|
|
grav(rand(0,3), rand(2,3), 50, 25)
|
|
|
|
//Throwing stuff around!
|
|
for(var/obj/O in range(2,src))
|
|
if(O == src)
|
|
return //DON'T DELETE YOURSELF GOD DAMN
|
|
if(!O.anchored)
|
|
var/mob/living/target = locate() in view(4,src)
|
|
if(target && !target.stat)
|
|
O.throw_at(target, 7, 5)
|
|
else
|
|
SSexplosions.med_mov_atom += O
|
|
|
|
/obj/effect/anomaly/bhole/proc/grav(r, ex_act_force, pull_chance, turf_removal_chance)
|
|
for(var/t = -r, t < r, t++)
|
|
affect_coord(x+t, y-r, ex_act_force, pull_chance, turf_removal_chance)
|
|
affect_coord(x-t, y+r, ex_act_force, pull_chance, turf_removal_chance)
|
|
affect_coord(x+r, y+t, ex_act_force, pull_chance, turf_removal_chance)
|
|
affect_coord(x-r, y-t, ex_act_force, pull_chance, turf_removal_chance)
|
|
|
|
/obj/effect/anomaly/bhole/proc/affect_coord(x, y, ex_act_force, pull_chance, turf_removal_chance)
|
|
//Get turf at coordinate
|
|
var/turf/T = locate(x, y, z)
|
|
if(isnull(T))
|
|
return
|
|
|
|
//Pulling and/or ex_act-ing movable atoms in that turf
|
|
if(prob(pull_chance))
|
|
for(var/obj/O in T.contents)
|
|
if(O.anchored)
|
|
switch(ex_act_force)
|
|
if(EXPLODE_DEVASTATE)
|
|
SSexplosions.high_mov_atom += O
|
|
if(EXPLODE_HEAVY)
|
|
SSexplosions.med_mov_atom += O
|
|
if(EXPLODE_LIGHT)
|
|
SSexplosions.low_mov_atom += O
|
|
else
|
|
step_towards(O,src)
|
|
for(var/mob/living/M in T.contents)
|
|
step_towards(M,src)
|
|
|
|
//Damaging the turf
|
|
if( T && prob(turf_removal_chance) )
|
|
switch(ex_act_force)
|
|
if(EXPLODE_DEVASTATE)
|
|
SSexplosions.highturf += T
|
|
if(EXPLODE_HEAVY)
|
|
SSexplosions.medturf += T
|
|
if(EXPLODE_LIGHT)
|
|
SSexplosions.lowturf += T
|
|
|
|
/obj/effect/anomaly/bioscrambler
|
|
name = "bioscrambler anomaly"
|
|
icon_state = "bioscrambler"
|
|
aSignal = /obj/item/assembly/signaler/anomaly/bioscrambler
|
|
immortal = TRUE
|
|
/// Cooldown for every anomaly pulse
|
|
COOLDOWN_DECLARE(pulse_cooldown)
|
|
/// How many seconds between each anomaly pulses
|
|
var/pulse_delay = 15 SECONDS
|
|
/// Range of the anomaly pulse
|
|
var/range = 5
|
|
///Lists for zones and bodyparts to swap and randomize
|
|
var/static/list/zones = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
|
|
var/static/list/chests
|
|
var/static/list/heads
|
|
var/static/list/l_arms
|
|
var/static/list/r_arms
|
|
var/static/list/l_legs
|
|
var/static/list/r_legs
|
|
|
|
/obj/effect/anomaly/bioscrambler/Initialize(mapload, new_lifespan, drops_core)
|
|
. = ..()
|
|
if(!chests)
|
|
chests = typesof(/obj/item/bodypart/chest)
|
|
if(!heads)
|
|
heads = typesof(/obj/item/bodypart/head)
|
|
if(!l_arms)
|
|
l_arms = typesof(/obj/item/bodypart/l_arm)
|
|
if(!r_arms)
|
|
r_arms = typesof(/obj/item/bodypart/r_arm)
|
|
if(!l_legs)
|
|
l_legs = typesof(/obj/item/bodypart/l_leg)
|
|
if(!r_legs)
|
|
r_legs = typesof(/obj/item/bodypart/r_leg)
|
|
|
|
/obj/effect/anomaly/bioscrambler/anomalyEffect(delta_time)
|
|
. = ..()
|
|
|
|
if(!COOLDOWN_FINISHED(src, pulse_cooldown))
|
|
return
|
|
|
|
COOLDOWN_START(src, pulse_cooldown, pulse_delay)
|
|
|
|
swap_parts(range)
|
|
|
|
/obj/effect/anomaly/bioscrambler/proc/swap_parts(swap_range)
|
|
for(var/mob/living/carbon/nearby in range(swap_range, src))
|
|
if(nearby.run_armor_check(attack_flag = BIO, absorb_text = "Your armor protects you from [src]!") >= 100)
|
|
continue //We are protected
|
|
var/picked_zone = pick(zones)
|
|
var/obj/item/bodypart/picked_user_part = nearby.get_bodypart(picked_zone)
|
|
var/obj/item/bodypart/picked_part
|
|
switch(picked_zone)
|
|
if(BODY_ZONE_HEAD)
|
|
picked_part = pick(heads)
|
|
if(BODY_ZONE_CHEST)
|
|
picked_part = pick(chests)
|
|
if(BODY_ZONE_L_ARM)
|
|
picked_part = pick(l_arms)
|
|
if(BODY_ZONE_R_ARM)
|
|
picked_part = pick(r_arms)
|
|
if(BODY_ZONE_L_LEG)
|
|
picked_part = pick(l_legs)
|
|
if(BODY_ZONE_R_LEG)
|
|
picked_part = pick(r_legs)
|
|
var/obj/item/bodypart/new_part = new picked_part()
|
|
new_part.replace_limb(nearby, TRUE)
|
|
if(picked_user_part)
|
|
qdel(picked_user_part)
|
|
nearby.update_body(TRUE)
|
|
balloon_alert(nearby, "something has changed about you")
|
|
|
|
/obj/effect/anomaly/hallucination
|
|
name = "hallucination anomaly"
|
|
icon_state = "hallucination"
|
|
aSignal = /obj/item/assembly/signaler/anomaly/hallucination
|
|
/// Time passed since the last effect, increased by delta_time of the SSobj
|
|
var/ticks = 0
|
|
/// How many seconds between each small hallucination pulses
|
|
var/release_delay = 5
|
|
|
|
/obj/effect/anomaly/hallucination/anomalyEffect(delta_time)
|
|
. = ..()
|
|
ticks += delta_time
|
|
if(ticks < release_delay)
|
|
return
|
|
ticks -= release_delay
|
|
var/turf/open/our_turf = get_turf(src)
|
|
if(istype(our_turf))
|
|
hallucination_pulse(our_turf, 5)
|
|
|
|
/obj/effect/anomaly/hallucination/detonate()
|
|
var/turf/open/our_turf = get_turf(src)
|
|
if(istype(our_turf))
|
|
hallucination_pulse(our_turf, 10)
|
|
|
|
/obj/effect/anomaly/hallucination/proc/hallucination_pulse(turf/open/location, range)
|
|
for(var/mob/living/carbon/human/near in view(location, range))
|
|
// If they are immune to hallucinations.
|
|
if (HAS_TRAIT(near, TRAIT_MADNESS_IMMUNE) || (near.mind && HAS_TRAIT(near.mind, TRAIT_MADNESS_IMMUNE)))
|
|
continue
|
|
|
|
// Blind people don't get hallucinations.
|
|
if (near.is_blind())
|
|
continue
|
|
|
|
// Everyone else gets hallucinations.
|
|
var/dist = sqrt(1 / max(1, get_dist(near, location)))
|
|
near.hallucination += 50 * dist
|
|
near.hallucination = clamp(near.hallucination, 0, 150)
|
|
var/list/messages = list(
|
|
"You feel your conscious mind fall apart!",
|
|
"Reality warps around you!",
|
|
"Something's wispering around you!",
|
|
"You are going insane!",
|
|
)
|
|
to_chat(near, span_warning(pick(messages)))
|
|
|
|
/////////////////////
|
|
|
|
/obj/effect/anomaly/dimensional
|
|
name = "dimensional anomaly"
|
|
icon_state = "dimensional"
|
|
aSignal = /obj/item/assembly/signaler/anomaly/dimensional
|
|
immortal = TRUE
|
|
immobile = TRUE
|
|
/// Range of effect, if left alone anomaly will convert a 2(range)+1 squared area.
|
|
var/range = 3
|
|
/// List of turfs this anomaly will try to transform before relocating
|
|
var/list/turf/target_turfs = new()
|
|
/// Current anomaly 'theme', dictates what tiles to create.
|
|
var/datum/dimension_theme/theme
|
|
/// Effect displaying on the anomaly to represent the theme.
|
|
var/mutable_appearance/theme_icon
|
|
|
|
/obj/effect/anomaly/dimensional/Initialize(mapload, new_lifespan, drops_core)
|
|
. = ..()
|
|
overlays += mutable_appearance('icons/effects/effects.dmi', "dimensional_overlay")
|
|
|
|
animate(src, transform = matrix()*0.85, time = 3, loop = -1)
|
|
animate(transform = matrix(), time = 3, loop = -1)
|
|
|
|
/obj/effect/anomaly/dimensional/anomalyEffect(delta_time)
|
|
. = ..()
|
|
transmute_area()
|
|
|
|
/**
|
|
* Transforms a turf in our prepared area.
|
|
*/
|
|
/obj/effect/anomaly/dimensional/proc/transmute_area()
|
|
if (!theme)
|
|
prepare_area()
|
|
if (!target_turfs.len)
|
|
relocate()
|
|
return
|
|
|
|
var/turf/affected_turf = target_turfs[1]
|
|
new /obj/effect/temp_visual/transmute_tile_flash(affected_turf)
|
|
theme.apply_theme(affected_turf)
|
|
target_turfs.Remove(affected_turf)
|
|
|
|
/**
|
|
* Prepare a new area for transformation into a new theme.
|
|
*/
|
|
/obj/effect/anomaly/dimensional/proc/prepare_area()
|
|
var/datum/dimension_theme/themes = new()
|
|
theme = themes.get_random_theme()
|
|
apply_theme_icon()
|
|
|
|
target_turfs = new()
|
|
var/list/turfs = spiral_range_turfs(range, src)
|
|
for (var/turf/turf in turfs)
|
|
if (theme.can_convert(turf))
|
|
target_turfs.Add(turf)
|
|
|
|
/**
|
|
* Applies an overlay icon based on the current theme.
|
|
*/
|
|
/obj/effect/anomaly/dimensional/proc/apply_theme_icon()
|
|
overlays -= theme_icon
|
|
theme_icon = mutable_appearance(theme.icon, theme.icon_state, FLOAT_LAYER - 1, appearance_flags = appearance_flags | RESET_TRANSFORM)
|
|
theme_icon.blend_mode = BLEND_INSET_OVERLAY
|
|
overlays += theme_icon
|
|
|
|
/**
|
|
* Moves the anomaly somewhere else and announces it.
|
|
*/
|
|
/obj/effect/anomaly/dimensional/proc/relocate()
|
|
var/datum/anomaly_placer/placer = new()
|
|
var/area/new_area = placer.findValidArea()
|
|
var/turf/new_turf = placer.findValidTurf(new_area)
|
|
|
|
priority_announce("Dimensional instability relocated. Expected location: [new_area.name].", "Anomaly Alert")
|
|
src.forceMove(new_turf)
|
|
prepare_area()
|
|
|
|
/obj/effect/temp_visual/transmute_tile_flash
|
|
icon = 'icons/effects/effects.dmi'
|
|
icon_state = "shield-flash"
|
|
duration = 3
|
|
|
|
#undef ANOMALY_MOVECHANCE
|