mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-27 07:08:00 +01:00
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. /🆑
This commit is contained in:
@@ -34,10 +34,13 @@ GLOBAL_LIST_INIT(bioscrambler_organs_blacklist, typecacheof(list (
|
||||
/obj/item/organ/internal/brain,
|
||||
/obj/item/organ/internal/body_egg,
|
||||
/obj/item/organ/internal/cyberimp,
|
||||
/obj/item/organ/internal/ears/dullahan,
|
||||
/obj/item/organ/internal/eyes/dullahan,
|
||||
/obj/item/organ/internal/heart/cursed,
|
||||
/obj/item/organ/internal/heart/demon,
|
||||
/obj/item/organ/internal/lungs,
|
||||
/obj/item/organ/internal/monster_core,
|
||||
/obj/item/organ/internal/tongue/dullahan,
|
||||
/obj/item/organ/internal/vocal_cords/colossus,
|
||||
/obj/item/organ/internal/zombie_infection,
|
||||
)))
|
||||
|
||||
@@ -813,6 +813,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
|
||||
///Trait applied to turfs when an atmos holosign is placed on them. It will stop firedoors from closing.
|
||||
#define TRAIT_FIREDOOR_STOP "firedoor_stop"
|
||||
|
||||
///Trait applied to turf blocked by a containment field
|
||||
#define TRAIT_CONTAINMENT_FIELD "containment_field"
|
||||
|
||||
/// Trait applied when the MMI component is added to an [/obj/item/integrated_circuit]
|
||||
#define TRAIT_COMPONENT_MMI "component_mmi"
|
||||
|
||||
|
||||
@@ -599,6 +599,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
|
||||
),
|
||||
/turf = list(
|
||||
"TRAIT_CHASM_STOPPED" = TRAIT_CHASM_STOPPED,
|
||||
"TRAIT_CONTAINMENT_FIELD" = TRAIT_CONTAINMENT_FIELD,
|
||||
"TRAIT_FIREDOOR_STOP" = TRAIT_FIREDOOR_STOP,
|
||||
"TRAIT_HYPERSPACE_STOPPED" = TRAIT_HYPERSPACE_STOPPED,
|
||||
"TRAIT_IMMERSE_STOPPED" = TRAIT_IMMERSE_STOPPED,
|
||||
|
||||
@@ -360,8 +360,9 @@
|
||||
target = potentially_closer
|
||||
//if we lost that target get a new one
|
||||
if(!target || QDELETED(target))
|
||||
target = find_new_target()
|
||||
foreboding_nosebleed(target)
|
||||
var/mob/living/new_target = find_new_target()
|
||||
new_target?.ominous_nosebleed()
|
||||
target = new_target
|
||||
return ..()
|
||||
|
||||
///Searches the living list for the closest target, and begins chasing them down.
|
||||
@@ -380,22 +381,6 @@
|
||||
closest_target = target
|
||||
return closest_target
|
||||
|
||||
/// gives a little fluff warning that someone is being hunted.
|
||||
/datum/component/singularity/bloodthirsty/proc/foreboding_nosebleed(mob/living/target)
|
||||
if(!iscarbon(target))
|
||||
to_chat(target, span_warning("You feel a bit nauseous for just a moment."))
|
||||
return
|
||||
var/mob/living/carbon/carbon_target = target
|
||||
var/obj/item/bodypart/head = carbon_target.get_bodypart(BODY_ZONE_HEAD)
|
||||
if(head)
|
||||
if(HAS_TRAIT(carbon_target, TRAIT_NOBLOOD))
|
||||
to_chat(carbon_target, span_notice("You get a headache."))
|
||||
return
|
||||
head.adjustBleedStacks(5)
|
||||
carbon_target.visible_message(span_notice("[carbon_target] gets a nosebleed."), span_warning("You get a nosebleed."))
|
||||
return
|
||||
to_chat(target, span_warning("You feel a bit nauseous for just a moment."))
|
||||
|
||||
#undef CHANCE_TO_MOVE_TO_TARGET
|
||||
#undef CHANCE_TO_MOVE_TO_TARGET_BLOODTHIRSTY
|
||||
#undef CHANCE_TO_CHANGE_TARGET_BLOODTHIRSTY
|
||||
|
||||
@@ -364,11 +364,8 @@
|
||||
visible_message(span_danger("[src] shuts down due to lack of power!"), \
|
||||
"If this message is ever seen, something is wrong.",
|
||||
span_hear("You hear heavy droning fade out."))
|
||||
active = FALSE
|
||||
deactivate()
|
||||
log_game("[src] deactivated due to lack of power at [AREACOORD(src)]")
|
||||
for(var/d in GLOB.cardinals)
|
||||
cleanup_field(d)
|
||||
update_appearance()
|
||||
else
|
||||
update_appearance()
|
||||
for(var/d in GLOB.cardinals)
|
||||
@@ -463,7 +460,6 @@
|
||||
balloon_alert(user, "malfunctioning!")
|
||||
else
|
||||
balloon_alert(user, "no access!")
|
||||
|
||||
return
|
||||
|
||||
add_fingerprint(user)
|
||||
@@ -493,13 +489,13 @@
|
||||
user.visible_message(span_notice("[user] turned \the [src] off."), \
|
||||
span_notice("You turn off \the [src]."), \
|
||||
span_hear("You hear heavy droning fade out."))
|
||||
active = FALSE
|
||||
deactivate()
|
||||
user.log_message("deactivated [src].", LOG_GAME)
|
||||
else
|
||||
user.visible_message(span_notice("[user] turned \the [src] on."), \
|
||||
span_notice("You turn on \the [src]."), \
|
||||
span_hear("You hear heavy droning."))
|
||||
active = ACTIVE_SETUPFIELDS
|
||||
activate()
|
||||
user.log_message("activated [src].", LOG_GAME)
|
||||
add_fingerprint(user)
|
||||
|
||||
@@ -513,6 +509,19 @@
|
||||
balloon_alert(user, "access controller shorted")
|
||||
return TRUE
|
||||
|
||||
/// Turn the machine on with side effects
|
||||
/obj/machinery/power/shieldwallgen/proc/activate()
|
||||
active = ACTIVE_SETUPFIELDS
|
||||
AddElement(/datum/element/give_turf_traits, string_list(list(TRAIT_CONTAINMENT_FIELD)))
|
||||
|
||||
/// Turn the machine off with side effects
|
||||
/obj/machinery/power/shieldwallgen/proc/deactivate()
|
||||
active = FALSE
|
||||
for(var/d in GLOB.cardinals)
|
||||
cleanup_field(d)
|
||||
update_appearance()
|
||||
RemoveElement(/datum/element/give_turf_traits, string_list(list(TRAIT_CONTAINMENT_FIELD)))
|
||||
|
||||
//////////////Containment Field START
|
||||
/obj/machinery/shieldwall
|
||||
name = "shield wall"
|
||||
@@ -538,6 +547,7 @@
|
||||
L.investigate_log("has been gibbed by [src].", INVESTIGATE_DEATHS)
|
||||
L.gib(DROP_ALL_REMAINS)
|
||||
RegisterSignal(src, COMSIG_ATOM_SINGULARITY_TRY_MOVE, PROC_REF(block_singularity))
|
||||
AddElement(/datum/element/give_turf_traits, string_list(list(TRAIT_CONTAINMENT_FIELD)))
|
||||
|
||||
/obj/machinery/shieldwall/Destroy()
|
||||
gen_primary = null
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
var/immortal = FALSE
|
||||
///Do we stay in one place?
|
||||
var/immobile = FALSE
|
||||
///Chance per second that we will move
|
||||
var/move_chance = ANOMALY_MOVECHANCE
|
||||
|
||||
/obj/effect/anomaly/Initialize(mapload, new_lifespan, drops_core = TRUE)
|
||||
. = ..()
|
||||
@@ -76,8 +78,12 @@
|
||||
return ..()
|
||||
|
||||
/obj/effect/anomaly/proc/anomalyEffect(seconds_per_tick)
|
||||
if(!immobile && SPT_PROB(ANOMALY_MOVECHANCE, seconds_per_tick))
|
||||
step(src,pick(GLOB.alldirs))
|
||||
if(SPT_PROB(move_chance, seconds_per_tick))
|
||||
move_anomaly()
|
||||
|
||||
/// Move in a direction
|
||||
/obj/effect/anomaly/proc/move_anomaly()
|
||||
step(src, pick(GLOB.alldirs))
|
||||
|
||||
/obj/effect/anomaly/proc/detonate()
|
||||
return
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
icon_state = "bioscrambler"
|
||||
aSignal = /obj/item/assembly/signaler/anomaly/bioscrambler
|
||||
immortal = TRUE
|
||||
pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE | PASSCLOSEDTURF | PASSMACHINE | PASSSTRUCTURE | PASSDOORS
|
||||
layer = ABOVE_MOB_LAYER
|
||||
/// Who are we moving towards?
|
||||
var/datum/weakref/pursuit_target
|
||||
/// Cooldown for every anomaly pulse
|
||||
COOLDOWN_DECLARE(pulse_cooldown)
|
||||
/// How many seconds between each anomaly pulses
|
||||
@@ -11,11 +15,52 @@
|
||||
/// Range of the anomaly pulse
|
||||
var/range = 5
|
||||
|
||||
/obj/effect/anomaly/bioscrambler/Initialize(mapload, new_lifespan, drops_core)
|
||||
. = ..()
|
||||
pursuit_target = WEAKREF(find_nearest_target())
|
||||
|
||||
/obj/effect/anomaly/bioscrambler/anomalyEffect(seconds_per_tick)
|
||||
. = ..()
|
||||
if(!COOLDOWN_FINISHED(src, pulse_cooldown))
|
||||
return
|
||||
|
||||
COOLDOWN_START(src, pulse_cooldown, pulse_delay)
|
||||
for(var/mob/living/carbon/nearby in range(range, src))
|
||||
for(var/mob/living/carbon/nearby in hearers(range, src))
|
||||
nearby.bioscramble(name)
|
||||
|
||||
/obj/effect/anomaly/bioscrambler/move_anomaly()
|
||||
var/mob/living/current_target = pursuit_target?.resolve()
|
||||
if (QDELETED(current_target))
|
||||
pursuit_target = null
|
||||
if (isnull(pursuit_target) || prob(20))
|
||||
var/mob/living/new_target = find_nearest_target()
|
||||
if (isnull(new_target))
|
||||
pursuit_target = null
|
||||
else if (new_target != current_target)
|
||||
current_target = new_target
|
||||
pursuit_target = WEAKREF(new_target)
|
||||
new_target.ominous_nosebleed()
|
||||
if (isnull(pursuit_target))
|
||||
return
|
||||
var/turf/step_turf = get_step(src, get_dir(src, current_target))
|
||||
if (!HAS_TRAIT(step_turf, TRAIT_CONTAINMENT_FIELD))
|
||||
Move(step_turf)
|
||||
|
||||
/// Returns the closest conscious carbon on our z level or null if there somehow isn't one
|
||||
/obj/effect/anomaly/bioscrambler/proc/find_nearest_target()
|
||||
var/closest_distance = INFINITY
|
||||
var/mob/living/carbon/closest_target = null
|
||||
for(var/mob/living/carbon/target in GLOB.player_list)
|
||||
if (target.z != z)
|
||||
continue
|
||||
if (target.status_effects & GODMODE)
|
||||
continue
|
||||
if (target.stat >= UNCONSCIOUS)
|
||||
continue // Don't just haunt a corpse
|
||||
var/distance_from_target = get_dist(src, target)
|
||||
if(distance_from_target >= closest_distance)
|
||||
continue
|
||||
closest_distance = distance_from_target
|
||||
closest_target = target
|
||||
|
||||
return closest_target
|
||||
|
||||
@@ -1477,3 +1477,13 @@
|
||||
if (!IS_ORGANIC_LIMB(target_part) || (target_part.bodypart_flags & BODYPART_PSEUDOPART))
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/mob/living/carbon/ominous_nosebleed()
|
||||
var/obj/item/bodypart/head = get_bodypart(BODY_ZONE_HEAD)
|
||||
if(isnull(head))
|
||||
return ..()
|
||||
if(HAS_TRAIT(src, TRAIT_NOBLOOD))
|
||||
to_chat(src, span_notice("You get a headache."))
|
||||
return
|
||||
head.adjustBleedStacks(5)
|
||||
visible_message(span_notice("[src] gets a nosebleed."), span_warning("You get a nosebleed."))
|
||||
|
||||
@@ -2426,6 +2426,10 @@ GLOBAL_LIST_EMPTY(fire_appearances)
|
||||
apply_damage(rand(5,10), BRUTE, BODY_ZONE_CHEST)
|
||||
lattice.deconstruct(FALSE)
|
||||
|
||||
/// Prints an ominous message if something bad is going to happen to you
|
||||
/mob/living/proc/ominous_nosebleed()
|
||||
to_chat(src, span_warning("You feel a bit nauseous for just a moment."))
|
||||
|
||||
/**
|
||||
* Proc used by different station pets such as Ian and Poly so that some of their data can persist between rounds.
|
||||
* This base definition only contains a trait and comsig to stop memory from being (over)written.
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
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)
|
||||
|
||||
@@ -208,6 +208,7 @@ no power level overlay is currently in the overlays list.
|
||||
air_update_turf(TRUE, FALSE)
|
||||
INVOKE_ASYNC(src, PROC_REF(cleanup))
|
||||
addtimer(CALLBACK(src, PROC_REF(cool_down)), 5 SECONDS)
|
||||
RemoveElement(/datum/element/give_turf_traits, string_list(list(TRAIT_CONTAINMENT_FIELD)))
|
||||
|
||||
/obj/machinery/field/generator/proc/cool_down()
|
||||
if(active || warming_up <= 0)
|
||||
@@ -220,6 +221,7 @@ no power level overlay is currently in the overlays list.
|
||||
/obj/machinery/field/generator/proc/turn_on()
|
||||
active = FG_CHARGING
|
||||
addtimer(CALLBACK(src, PROC_REF(warm_up)), 5 SECONDS)
|
||||
AddElement(/datum/element/give_turf_traits, string_list(list(TRAIT_CONTAINMENT_FIELD)))
|
||||
|
||||
/obj/machinery/field/generator/proc/warm_up()
|
||||
if(!active)
|
||||
|
||||
@@ -386,16 +386,8 @@
|
||||
/obj/singularity/proc/can_move(turf/considered_turf)
|
||||
if(!considered_turf)
|
||||
return FALSE
|
||||
if((locate(/obj/machinery/field/containment) in considered_turf) || (locate(/obj/machinery/shieldwall) in considered_turf))
|
||||
if (HAS_TRAIT(considered_turf, TRAIT_CONTAINMENT_FIELD))
|
||||
return FALSE
|
||||
else if(locate(/obj/machinery/field/generator) in considered_turf)
|
||||
var/obj/machinery/field/generator/check_generator = locate(/obj/machinery/field/generator) in considered_turf
|
||||
if(check_generator?.active)
|
||||
return FALSE
|
||||
else if(locate(/obj/machinery/power/shieldwallgen) in considered_turf)
|
||||
var/obj/machinery/power/shieldwallgen/check_shield = locate(/obj/machinery/power/shieldwallgen) in considered_turf
|
||||
if(check_shield?.active)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/obj/singularity/proc/event()
|
||||
|
||||
Reference in New Issue
Block a user