Files
Bubberstation/code/datums/components/chasm.dm
Ghom d4cdd6b63e Replaces lava and chasm's "safeties" and ignoring turf slowdown on catwalks with traits and a new element. (#76376)
## About The Pull Request
This adds a new element for movables that grants turfs they're in
traits, changes lava and the chasm component to check for traits
instead, ditto for turf slowdown. It also implements another trait that
prevents wet floor from slipping people, as well as some other changes
(feel free to opine on them really):
- Tables and conveyor belts now stop turf slowdown, much like catwalks,
as I imagine people walking on them are not really touching the floor.
(I'd include protection against lava too... until they melt, but that'd
mean finding a way to have these objects burn in the first place, and
lava code is still stupid despite a years old refactor I did)
- Tables also stop slippery turfs from slipping (bananas, soaps etc.
still apply). I wish there were a way to make some objects slippery by
coating them in water vapor or splashing water/lube, but that's outside
the scope of this PR.
- Fixed an edge case in which a mob standing on a lava turf would be
left permanently visually on fire if the lava is changed to another kind
of turf.
- Removed unused code from stone tiles.

I'm going to include these traits in that global list for admin-added
traits... tomorrow perhaps. 💤

## Why It's Good For The Game
Replacing some hard-coded mechanics with easier to use traits and an
element, which I also need for the submerge element PR.

## Changelog

🆑
refactor: Replaced hardcoded "safeties" for lava, chasms and ignoring
turf slowdowns on catwalks with traits.
balance: much like catwalks, tables and conveyors also disable turf
slowdowns.
balance: slippery turfs won't slip you when walking on a table.
fix: Fixed an edge case in which a mob standing on a lava turf would be
left visually but permanently on fire if the lava is changed to another
kind of turf.
/🆑
2023-07-07 10:04:33 +01:00

274 lines
10 KiB
Plaintext

/// List of weakrefs to containers for things which have fallen into chasms
GLOBAL_LIST_INIT(chasm_storage, list())
// Used by /turf/open/chasm and subtypes to implement the "dropping" mechanic
/datum/component/chasm
var/turf/target_turf
var/obj/effect/abstract/chasm_storage/storage
var/fall_message = "GAH! Ah... where are you?"
var/oblivion_message = "You stumble and stare into the abyss before you. It stares back, and you fall into the enveloping dark."
/// List of refs to falling objects -> how many levels deep we've fallen
var/static/list/falling_atoms = list()
var/static/list/forbidden_types = typecacheof(list(
/obj/singularity,
/obj/energy_ball,
/obj/narsie,
/obj/docking_port,
/obj/structure/lattice,
/obj/structure/stone_tile,
/obj/projectile,
/obj/effect/projectile,
/obj/effect/portal,
/obj/effect/abstract,
/obj/effect/hotspot,
/obj/effect/landmark,
/obj/effect/temp_visual,
/obj/effect/light_emitter/tendril,
/obj/effect/collapse,
/obj/effect/particle_effect/ion_trails,
/obj/effect/dummy/phased_mob,
/obj/effect/mapping_helpers,
/obj/effect/wisp,
/obj/effect/ebeam,
/obj/effect/fishing_lure,
))
/datum/component/chasm/Initialize(turf/target, mapload)
RegisterSignal(parent, SIGNAL_ADDTRAIT(TRAIT_CHASM_STOPPED), PROC_REF(on_chasm_stopped))
RegisterSignal(parent, SIGNAL_REMOVETRAIT(TRAIT_CHASM_STOPPED), PROC_REF(on_chasm_no_longer_stopped))
target_turf = target
RegisterSignal(parent, COMSIG_ATOM_ABSTRACT_ENTERED, PROC_REF(entered))
RegisterSignal(parent, COMSIG_ATOM_ABSTRACT_EXITED, PROC_REF(exited))
RegisterSignal(parent, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, PROC_REF(initialized_on))
//allow catwalks to give the turf the CHASM_STOPPED trait before dropping stuff when the turf is changed.
//otherwise don't do anything because turfs and areas are initialized before movables.
if(!mapload)
addtimer(CALLBACK(src, PROC_REF(drop_stuff)), 0)
src.parent.AddElement(/datum/element/lazy_fishing_spot, FISHING_SPOT_PRESET_CHASM)
/datum/component/chasm/UnregisterFromParent()
remove_storage()
/**
* Deletes the chasm storage object and removes empty weakrefs from global list
*/
/datum/component/chasm/proc/remove_storage()
if (!storage)
return
QDEL_NULL(storage)
var/list/chasm_storage = list()
for (var/datum/weakref/ref as anything in GLOB.chasm_storage)
if (!ref.resolve())
continue
chasm_storage += ref
GLOB.chasm_storage = chasm_storage
/datum/component/chasm/proc/entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
SIGNAL_HANDLER
drop_stuff()
/datum/component/chasm/proc/exited(datum/source, atom/movable/exited)
SIGNAL_HANDLER
UnregisterSignal(exited, list(COMSIG_MOVETYPE_FLAG_DISABLED, COMSIG_LIVING_SET_BUCKLED, COMSIG_MOVABLE_THROW_LANDED))
/datum/component/chasm/proc/initialized_on(datum/source, atom/movable/movable, mapload)
SIGNAL_HANDLER
drop_stuff(movable)
/datum/component/chasm/proc/on_chasm_stopped(datum/source)
SIGNAL_HANDLER
UnregisterSignal(source, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_EXITED, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON))
for(var/atom/movable/movable as anything in source)
UnregisterSignal(movable, list(COMSIG_MOVETYPE_FLAG_DISABLED, COMSIG_LIVING_SET_BUCKLED, COMSIG_MOVABLE_THROW_LANDED))
/datum/component/chasm/proc/on_chasm_no_longer_stopped(datum/source)
SIGNAL_HANDLER
RegisterSignal(parent, COMSIG_ATOM_ENTERED, PROC_REF(entered))
RegisterSignal(parent, COMSIG_ATOM_EXITED, PROC_REF(exited))
RegisterSignal(parent, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, PROC_REF(initialized_on))
drop_stuff()
#define CHASM_NOT_DROPPING 0
#define CHASM_DROPPING 1
///Doesn't drop the movable, but registers a few signals to try again if the conditions change.
#define CHASM_REGISTER_SIGNALS 2
/datum/component/chasm/proc/drop_stuff(atom/movable/dropped_thing)
if(HAS_TRAIT(parent, TRAIT_CHASM_STOPPED))
return
var/atom/atom_parent = parent
var/to_check = dropped_thing ? list(dropped_thing) : atom_parent.contents
for (var/atom/movable/thing as anything in to_check)
var/dropping = droppable(thing)
switch(dropping)
if(CHASM_DROPPING)
INVOKE_ASYNC(src, PROC_REF(drop), thing)
if(CHASM_REGISTER_SIGNALS)
RegisterSignals(thing, list(COMSIG_MOVETYPE_FLAG_DISABLED, COMSIG_LIVING_SET_BUCKLED, COMSIG_MOVABLE_THROW_LANDED), PROC_REF(drop_stuff), TRUE)
/datum/component/chasm/proc/droppable(atom/movable/dropped_thing)
var/datum/weakref/falling_ref = WEAKREF(dropped_thing)
// avoid an infinite loop, but allow falling a large distance
if(falling_atoms[falling_ref] && falling_atoms[falling_ref] > 30)
return CHASM_NOT_DROPPING
if(is_type_in_typecache(dropped_thing, forbidden_types) || (!isliving(dropped_thing) && !isobj(dropped_thing)))
return CHASM_NOT_DROPPING
if(dropped_thing.throwing || (dropped_thing.movement_type & (FLOATING|FLYING)))
return CHASM_REGISTER_SIGNALS
//Flies right over the chasm
if(ismob(dropped_thing))
var/mob/M = dropped_thing
if(M.buckled) //middle statement to prevent infinite loops just in case!
var/mob/buckled_to = M.buckled
if((!ismob(M.buckled) || (buckled_to.buckled != M)) && !droppable(M.buckled))
return CHASM_REGISTER_SIGNALS
if(ishuman(dropped_thing))
var/mob/living/carbon/human/victim = dropped_thing
if(istype(victim.belt, /obj/item/wormhole_jaunter))
var/obj/item/wormhole_jaunter/jaunter = victim.belt
var/turf/chasm = get_turf(victim)
var/fall_into_chasm = jaunter.chasm_react(victim)
if(!fall_into_chasm)
chasm.visible_message(span_boldwarning("[victim] falls into the [chasm]!")) //To freak out any bystanders
return fall_into_chasm ? CHASM_DROPPING : CHASM_NOT_DROPPING
return CHASM_DROPPING
#undef CHASM_NOT_DROPPING
#undef CHASM_DROPPING
#undef CHASM_REGISTER_SIGNALS
/datum/component/chasm/proc/drop(atom/movable/dropped_thing)
var/datum/weakref/falling_ref = WEAKREF(dropped_thing)
//Make sure the item is still there after our sleep
if(!dropped_thing || !falling_ref?.resolve())
falling_atoms -= falling_ref
return
falling_atoms[falling_ref] = (falling_atoms[falling_ref] || 0) + 1
var/turf/below_turf = target_turf
var/atom/parent = src.parent
if(falling_atoms[falling_ref] > 1)
return // We're already handling this
if(below_turf)
if(HAS_TRAIT(dropped_thing, TRAIT_CHASM_DESTROYED))
qdel(dropped_thing)
return
// send to the turf below
dropped_thing.visible_message(span_boldwarning("[dropped_thing] falls into [parent]!"), span_userdanger("[fall_message]"))
below_turf.visible_message(span_boldwarning("[dropped_thing] falls from above!"))
dropped_thing.forceMove(below_turf)
if(isliving(dropped_thing))
var/mob/living/fallen = dropped_thing
fallen.Paralyze(100)
fallen.adjustBruteLoss(30)
falling_atoms -= falling_ref
return
// send to oblivion
dropped_thing.visible_message(span_boldwarning("[dropped_thing] falls into [parent]!"), span_userdanger("[oblivion_message]"))
if (isliving(dropped_thing))
var/mob/living/falling_mob = dropped_thing
falling_mob.notransform = TRUE
falling_mob.Paralyze(20 SECONDS)
var/oldtransform = dropped_thing.transform
var/oldcolor = dropped_thing.color
var/oldalpha = dropped_thing.alpha
var/oldoffset = dropped_thing.pixel_y
animate(dropped_thing, transform = matrix() - matrix(), alpha = 0, color = rgb(0, 0, 0), time = 10)
for(var/i in 1 to 5)
//Make sure the item is still there after our sleep
if(!dropped_thing || QDELETED(dropped_thing))
return
dropped_thing.pixel_y--
sleep(0.2 SECONDS)
//Make sure the item is still there after our sleep
if(!dropped_thing || QDELETED(dropped_thing))
return
if(HAS_TRAIT(dropped_thing, TRAIT_CHASM_DESTROYED))
qdel(dropped_thing)
return
if(!storage)
storage = new(get_turf(parent))
RegisterSignal(storage, COMSIG_ATOM_EXITED, PROC_REF(left_chasm))
GLOB.chasm_storage += WEAKREF(storage)
if(storage.contains(dropped_thing))
return
dropped_thing.alpha = oldalpha
dropped_thing.color = oldcolor
dropped_thing.transform = oldtransform
dropped_thing.pixel_y = oldoffset
if(dropped_thing.forceMove(storage))
if (isliving(dropped_thing))
RegisterSignal(dropped_thing, COMSIG_LIVING_REVIVE, PROC_REF(on_revive))
else
parent.visible_message(span_boldwarning("[parent] spits out [dropped_thing]!"))
dropped_thing.throw_at(get_edge_target_turf(parent, pick(GLOB.alldirs)), rand(1, 10), rand(1, 10))
if(isliving(dropped_thing))
var/mob/living/fallen_mob = dropped_thing
fallen_mob.notransform = FALSE
if (fallen_mob.stat != DEAD)
fallen_mob.investigate_log("has died from falling into a chasm.", INVESTIGATE_DEATHS)
fallen_mob.death(TRUE)
fallen_mob.apply_damage(300)
falling_atoms -= falling_ref
/**
* Called when something has left the chasm depths storage.
* Arguments
*
* * source - Chasm object holder.
* * gone - Item which has just left the chasm contents.
*/
/datum/component/chasm/proc/left_chasm(atom/source, atom/movable/gone)
SIGNAL_HANDLER
UnregisterSignal(gone, COMSIG_LIVING_REVIVE)
#define CHASM_TRAIT "chasm trait"
/**
* Called if something comes back to life inside the pit. Expected sources are badmins and changelings.
* Ethereals should take enough damage to be smashed and not revive.
*
* Arguments
* * escapee - Lucky guy who just came back to life at the bottom of a hole.
*/
/datum/component/chasm/proc/on_revive(mob/living/escapee)
SIGNAL_HANDLER
var/atom/parent = src.parent
parent.visible_message(span_boldwarning("After a long climb, [escapee] leaps out of [parent]!"))
ADD_TRAIT(escapee, TRAIT_MOVE_FLYING, CHASM_TRAIT) //Otherwise they instantly fall back in
escapee.forceMove(get_turf(parent))
escapee.throw_at(get_edge_target_turf(parent, pick(GLOB.alldirs)), rand(1, 10), rand(1, 10))
REMOVE_TRAIT(escapee, TRAIT_MOVE_FLYING, CHASM_TRAIT)
escapee.Paralyze(20 SECONDS, TRUE)
UnregisterSignal(escapee, COMSIG_LIVING_REVIVE)
#undef CHASM_TRAIT
/**
* An abstract object which is basically just a bag that the chasm puts people inside
*/
/obj/effect/abstract/chasm_storage
name = "chasm depths"
desc = "The bottom of a hole. You shouldn't be able to interact with this."
anchored = TRUE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
/obj/effect/abstract/chasm_storage/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_SECLUDED_LOCATION, INNATE_TRAIT)