Files
Bubberstation/code/modules/power/singularity/containment_field.dm
Jacquerel 16438d0d4e Bioscrambler Anomaly chases you (#82555)
## About The Pull Request

I heard reports that people just ignore the bioscrambler anomaly because
basically you just don't go into that room any more and depending on
where it spawned, that's no big deal.
That won't do.

Now the Bioscrambler will be attracted to the nearest sign of advanced
thinking life (read: nearest humanoid mob controlled by a player) and
will very slowly pursue them, travelling through walls and obstacles in
order to do so.

Also if it decides to target you, you will get a foreboding psychic
warning like with the dark matteor, because I think it's funny for dire
warnings to have multiple obscuring sources.

The Bioscrambler can be blocked with containment fields if you want to
make an overly-elaborate pen for it.
To accomplish this I refactored containment fields a little bit to apply
turf traits instead of making four different `locate()` checks for
different objects. Those files smell bad.

Oh also I moved the dullahan organs to the Bioscrambler blacklist
because they runtimed while I was testing it (see also: my other
incoming PRs) and I can't see any other reasonable way to fix it (they
expect to be in an abstract body zone...)

## Why It's Good For The Game

Anomalies are generally meant to be problems that you deal with or face
some kind of consequence.
Because the Bioscrambler isn't a timed anomaly with a dramatic
detonation effect, being spawned in a poorly-trafficked area could
simply mean that it isn't a problem to anyone.
Now it will make sure that it is a problem for someone until someone
gets rid of it.

I thought this solution was funnier than making it do something zany if
you leave it alone for 3 minutes.

## Changelog

🆑
balance: The Bioscrambler will now actively attempt to get closer to
living targets rather than chilling in a closet nobody goes into (unless
you trap it in a containment field).
balance: Because it can now travel through walls, the Bioscrambler will
no longer transform you THROUGH walls.
/🆑
2024-04-12 13:49:34 -04:00

169 lines
5.1 KiB
Plaintext

/obj/machinery/field/containment
name = "containment field"
desc = "An energy field."
icon = 'icons/obj/machines/engine/singularity.dmi'
icon_state = "Contain_F"
density = FALSE
move_resist = INFINITY
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
use_power = NO_POWER_USE
interaction_flags_atom = NONE
interaction_flags_machine = NONE
can_atmos_pass = ATMOS_PASS_NO
light_range = 4
layer = ABOVE_OBJ_LAYER
explosion_block = INFINITY
///First of the generators producing the containment field
var/obj/machinery/field/generator/field_gen_1 = null
///Second of the generators producing the containment field
var/obj/machinery/field/generator/field_gen_2 = null
/obj/machinery/field/containment/Initialize(mapload)
AddElement(/datum/element/blocks_explosives)
. = ..()
air_update_turf(TRUE, TRUE)
RegisterSignal(src, COMSIG_ATOM_SINGULARITY_TRY_MOVE, PROC_REF(block_singularity))
var/static/list/loc_connections = list(
COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
)
AddElement(/datum/element/connect_loc, loc_connections)
AddElement(/datum/element/give_turf_traits, string_list(list(TRAIT_CONTAINMENT_FIELD)))
/obj/machinery/field/containment/Destroy()
if(field_gen_1)
field_gen_1.fields -= src
field_gen_1 = null
if(field_gen_2)
field_gen_2.fields -= src
field_gen_2 = null
can_atmos_pass = ATMOS_PASS_YES
air_update_turf(TRUE, FALSE)
return ..()
//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/machinery/field/containment/attack_hand(mob/user, list/modifiers)
if(get_dist(src, user) > 1)
return FALSE
else
shock(user)
return TRUE
/obj/machinery/field/containment/attackby(obj/item/W, mob/user, params)
shock(user)
return TRUE
/obj/machinery/field/containment/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
switch(damage_type)
if(BURN)
playsound(loc, 'sound/effects/empulse.ogg', 75, TRUE)
if(BRUTE)
playsound(loc, 'sound/effects/empulse.ogg', 75, TRUE)
/obj/machinery/field/containment/blob_act(obj/structure/blob/B)
return FALSE
/obj/machinery/field/containment/ex_act(severity, target)
return FALSE
/obj/machinery/field/containment/attack_animal(mob/living/simple_animal/user, list/modifiers)
if(!field_gen_1 || !field_gen_2)
qdel(src)
return
if(ismegafauna(user))
user.visible_message(span_warning("[user] glows fiercely as the containment field flickers out!"))
field_gen_1.calc_power(INFINITY) //rip that 'containment' field
user.adjustHealth(-user.obj_damage)
else
return ..()
/obj/machinery/field/containment/proc/on_entered(datum/source, atom/movable/considered_atom)
SIGNAL_HANDLER
if(isliving(considered_atom))
var/mob/living/living_moving_through_field = considered_atom
if(!living_moving_through_field.incorporeal_move)
shock(considered_atom)
if(ismachinery(considered_atom) || isstructure(considered_atom) || ismecha(considered_atom))
bump_field(considered_atom)
/obj/machinery/field/containment/proc/set_master(master1,master2)
if(!master1 || !master2)
return FALSE
field_gen_1 = master1
field_gen_2 = master2
return TRUE
/obj/machinery/field/containment/proc/block_singularity()
SIGNAL_HANDLER
return SINGULARITY_TRY_MOVE_BLOCK
/obj/machinery/field/containment/shock(mob/living/user)
if(!field_gen_1 || !field_gen_2)
qdel(src)
return FALSE
..()
/obj/machinery/field/containment/Move()
qdel(src)
return FALSE
// Abstract Field Class
// Used for overriding certain procs
/obj/machinery/field
///Used to add a delay between shocks. In some cases this used to crash servers by spawning hundreds of sparks every second.
var/has_shocked = FALSE
/obj/machinery/field/Bumped(atom/movable/mover)
if(has_shocked)
return
if(isliving(mover))
shock(mover)
return
if(ismachinery(mover) || isstructure(mover) || isvehicle(mover))
bump_field(mover)
return
/obj/machinery/field/CanAllowThrough(atom/movable/mover, border_dir)
. = ..()
if(has_shocked || isliving(mover) || ismachinery(mover) || isstructure(mover) || ismecha(mover))
return FALSE
/obj/machinery/field/proc/shock(mob/living/user)
var/shock_damage = min(rand(30,40),rand(30,40))
if(iscarbon(user))
user.Paralyze(10 SECONDS)
user.electrocute_act(shock_damage, src, 1)
else if(issilicon(user))
if(prob(20))
user.Stun(40)
user.take_overall_damage(burn = shock_damage)
user.visible_message(span_danger("[user.name] is shocked by the [src.name]!"), \
span_userdanger("Energy pulse detected, system damaged!"), \
span_hear("You hear an electrical crack."))
user.updatehealth()
bump_field(user)
/obj/machinery/field/proc/clear_shock()
has_shocked = FALSE
/obj/machinery/field/proc/bump_field(atom/movable/considered_atom as mob|obj)
if(has_shocked)
return FALSE
has_shocked = TRUE
do_sparks(5, TRUE, considered_atom.loc)
var/atom/target = get_edge_target_turf(considered_atom, get_dir(src, get_step_away(considered_atom, src)))
if(isliving(considered_atom))
to_chat(considered_atom, span_userdanger("The field repels you with tremendous force!"))
playsound(src, 'sound/effects/gravhit.ogg', 50, TRUE)
considered_atom.throw_at(target, 200, 4)
addtimer(CALLBACK(src, PROC_REF(clear_shock)), 0.5 SECONDS)