Files
nevimer 00ccf0c6b5 Merge remote-tracking branch 'tgstation/master' into upstream-feb12-2026
# Conflicts:
#	.github/CODEOWNERS
#	.github/workflows/compile_changelogs.yml
#	.github/workflows/stale.yml
#	SQL/database_changelog.md
#	_maps/map_files/CatwalkStation/CatwalkStation_2023.dmm
#	code/__DEFINES/atom_hud.dm
#	code/__DEFINES/inventory.dm
#	code/__DEFINES/mobs.dm
#	code/__DEFINES/species_clothing_paths.dm
#	code/__DEFINES/subsystems.dm
#	code/__DEFINES/surgery.dm
#	code/__HELPERS/global_lists.dm
#	code/_globalvars/lists/maintenance_loot.dm
#	code/_globalvars/traits/_traits.dm
#	code/controllers/subsystem/minor_mapping.dm
#	code/controllers/subsystem/processing/quirks.dm
#	code/controllers/subsystem/shuttle.dm
#	code/datums/components/palette.dm
#	code/datums/components/surgery_initiator.dm
#	code/datums/diseases/advance/advance.dm
#	code/datums/hud.dm
#	code/datums/mood.dm
#	code/datums/mutations/chameleon.dm
#	code/datums/quirks/negative_quirks/nyctophobia.dm
#	code/datums/status_effects/debuffs/debuffs.dm
#	code/datums/status_effects/debuffs/drunk.dm
#	code/datums/status_effects/debuffs/slime/slime_leech.dm
#	code/datums/weather/weather.dm
#	code/game/data_huds.dm
#	code/game/objects/items.dm
#	code/game/objects/items/devices/scanners/health_analyzer.dm
#	code/game/objects/items/frog_statue.dm
#	code/game/objects/items/rcd/RLD.dm
#	code/game/objects/items/robot/items/hypo.dm
#	code/game/objects/items/stacks/medical.dm
#	code/game/objects/items/stacks/wrap.dm
#	code/game/objects/items/storage/garment.dm
#	code/game/objects/items/tools/medical/defib.dm
#	code/game/objects/items/weaponry.dm
#	code/game/objects/items/weaponry/melee/misc.dm
#	code/game/objects/structures/crates_lockers/closets/secure/security.dm
#	code/game/objects/structures/curtains.dm
#	code/game/objects/structures/dresser.dm
#	code/game/objects/structures/girders.dm
#	code/game/objects/structures/maintenance.dm
#	code/game/objects/structures/mirror.dm
#	code/modules/admin/greyscale_modify_menu.dm
#	code/modules/admin/verbs/light_debug.dm
#	code/modules/antagonists/ashwalker/ashwalker.dm
#	code/modules/antagonists/heretic/knowledge/starting_lore.dm
#	code/modules/antagonists/ninja/ninjaDrainAct.dm
#	code/modules/art/paintings.dm
#	code/modules/client/preferences.dm
#	code/modules/client/verbs/ooc.dm
#	code/modules/clothing/head/wig.dm
#	code/modules/events/disease_outbreak.dm
#	code/modules/holodeck/holo_effect.dm
#	code/modules/jobs/job_types/head_of_security.dm
#	code/modules/jobs/job_types/security_officer.dm
#	code/modules/library/skill_learning/generic_skillchips/point.dm
#	code/modules/mining/lavaland/ash_flora.dm
#	code/modules/mining/lavaland/mining_loot/megafauna/ash_drake.dm
#	code/modules/mob/dead/new_player/new_player.dm
#	code/modules/mob/living/basic/guardian/guardian.dm
#	code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm
#	code/modules/mob/living/carbon/carbon.dm
#	code/modules/mob/living/carbon/human/human.dm
#	code/modules/mob/living/carbon/human/human_defines.dm
#	code/modules/mob/living/carbon/life.dm
#	code/modules/mob/living/living.dm
#	code/modules/mob/living/living_defines.dm
#	code/modules/mob/mob.dm
#	code/modules/mob_spawn/ghost_roles/mining_roles.dm
#	code/modules/mod/mod_control.dm
#	code/modules/mod/modules/modules_general.dm
#	code/modules/modular_computers/computers/item/computer_ui.dm
#	code/modules/paperwork/paper.dm
#	code/modules/paperwork/paperbin.dm
#	code/modules/power/lighting/light.dm
#	code/modules/projectiles/guns/energy/kinetic_accelerator.dm
#	code/modules/projectiles/projectile.dm
#	code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm
#	code/modules/reagents/chemistry/reagents/food_reagents.dm
#	code/modules/reagents/chemistry/reagents/other_reagents.dm
#	code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
#	code/modules/research/xenobiology/crossbreeding/_clothing.dm
#	code/modules/research/xenobiology/crossbreeding/prismatic.dm
#	code/modules/surgery/advanced/brainwashing.dm
#	code/modules/surgery/advanced/lobotomy.dm
#	code/modules/surgery/amputation.dm
#	code/modules/surgery/blood_filter.dm
#	code/modules/surgery/bodyparts/_bodyparts.dm
#	code/modules/surgery/brain_surgery.dm
#	code/modules/surgery/cavity_implant.dm
#	code/modules/surgery/coronary_bypass.dm
#	code/modules/surgery/gastrectomy.dm
#	code/modules/surgery/healing.dm
#	code/modules/surgery/limb_augmentation.dm
#	code/modules/surgery/organ_manipulation.dm
#	code/modules/surgery/revival.dm
#	code/modules/surgery/sleeper_protocol.dm
#	code/modules/surgery/surgery_helpers.dm
#	code/modules/surgery/surgery_step.dm
#	code/modules/unit_tests/_unit_tests.dm
#	code/modules/unit_tests/designs.dm
#	code/modules/unit_tests/icon_state_worn.dm
#	code/modules/unit_tests/screenshots/screenshot_antag_icons_cultist.png
#	code/modules/unit_tests/screenshots/screenshot_antag_icons_headrevolutionary.png
#	code/modules/unit_tests/screenshots/screenshot_antag_icons_provocateur.png
#	code/modules/unit_tests/screenshots/screenshot_husk_body.png
#	code/modules/unit_tests/screenshots/screenshot_husk_body_missing_limbs.png
#	icons/map_icons/clothing/head/_head.dmi
#	icons/map_icons/clothing/shoes.dmi
#	icons/map_icons/items/_item.dmi
#	icons/mob/huds/hud.dmi
#	icons/mob/inhands/64x64_lefthand.dmi
#	icons/mob/inhands/64x64_righthand.dmi
#	icons/obj/machines/computer.dmi
#	tgui/packages/tgui/interfaces/OperatingComputer.jsx
#	tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/MainPage.tsx
#	tgui/packages/tgui/interfaces/PreferencesMenu/types.ts
#	tgui/packages/tgui/interfaces/SurgeryInitiator.tsx
#	tools/icon_cutter/check.py
2026-02-12 23:50:09 -05:00

337 lines
12 KiB
Plaintext

// 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
var/static/list/forbidden_types = typecacheof(list(
/obj/docking_port,
/obj/effect/abstract,
/obj/effect/atmos_shield,
/obj/effect/collapse,
/obj/effect/constructing_effect,
/obj/effect/dummy/phased_mob,
/obj/effect/ebeam,
/obj/effect/fishing_float,
/obj/effect/hotspot,
/obj/effect/landmark,
/obj/effect/light_emitter/tendril,
/obj/effect/mapping_helpers,
/obj/effect/particle_effect/ion_trails,
/obj/effect/particle_effect/sparks,
/obj/effect/portal,
/obj/effect/projectile,
/obj/effect/spectre_of_resurrection,
/obj/effect/temp_visual,
/obj/effect/wisp,
/obj/energy_ball,
/obj/narsie,
/obj/projectile,
/obj/singularity,
/obj/structure/lattice,
/obj/structure/stone_tile,
/obj/structure/ore_vent,
))
/datum/component/chasm/Initialize(turf/target, mapload)
if(!isturf(parent))
return COMPONENT_INCOMPATIBLE
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
ADD_TRAIT(parent, TRAIT_AI_AVOID_TURF, REF(src))
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))
RegisterSignal(parent, COMSIG_ATOM_INTERCEPT_TELEPORTING, PROC_REF(block_teleport))
//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)
parent.AddComponent(/datum/component/fishing_spot, GLOB.preset_fish_sources[/datum/fish_source/chasm])
/datum/component/chasm/UnregisterFromParent()
REMOVE_TRAIT(parent, TRAIT_AI_AVOID_TURF, REF(src))
storage = null
/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/block_teleport()
return TRUE
/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
var/falling_atom = LAZYACCESS(falling_atoms, falling_ref)
if(falling_atom && falling_atom > 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 & MOVETYPES_NOT_TOUCHING_GROUND))
return CHASM_REGISTER_SIGNALS
for(var/atom/thing_to_check as anything in parent)
if(HAS_TRAIT(thing_to_check, TRAIT_CHASM_STOPPER))
return CHASM_NOT_DROPPING
if(!ismob(dropped_thing))
return CHASM_DROPPING
//Flies right over the chasm
var/mob/victim = dropped_thing
if(victim.buckled && droppable(victim.buckled) != CHASM_DROPPING)
return CHASM_REGISTER_SIGNALS
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())
LAZYREMOVE(falling_atoms, falling_ref)
return
LAZYSET(falling_atoms, falling_ref, (falling_atoms[falling_ref] || 0) + 1)
var/turf/below_turf = target_turf
var/atom/parent = src.parent
if(LAZYACCESS(falling_atoms, falling_ref) > 1)
return // We're already handling this
if(SEND_SIGNAL(dropped_thing, COMSIG_MOVABLE_CHASM_DROPPED, parent) & COMPONENT_NO_CHASM_DROP)
return
// Free (if possible) and drop all buckled mobs separately, so drivers can escape their doomed vehicle if they're not glued to it
for(var/mob/living/buckled as anything in dropped_thing.buckled_mobs)
dropped_thing.unbuckle_mob(buckled)
drop_stuff(buckled)
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.adjust_brute_loss(30)
LAZYREMOVE(falling_atoms, falling_ref)
return
// send to oblivion
if (isliving(dropped_thing))
var/mob/living/falling_mob = dropped_thing
ADD_TRAIT(falling_mob, TRAIT_NO_TRANSFORM, REF(src))
falling_mob.Stun(20 SECONDS, ignore_canstun = TRUE)
if (HAS_MIND_TRAIT(falling_mob, TRAIT_NAIVE))
falling_mob.do_alert_animation()
dropped_thing.visible_message(span_boldwarning("[dropped_thing] kicks [dropped_thing.p_their()] legs in the air, as if running in place!"))
dropped_thing.Shake(1, 0, 2 SECONDS, 0.3 SECONDS)
sleep(3 SECONDS)
if (get_turf(falling_mob) != get_turf(parent))
REMOVE_TRAIT(falling_mob, TRAIT_NO_TRANSFORM, REF(src))
falling_mob.Paralyze(17 SECONDS, ignore_canstun = TRUE) // Wow nice job
return
dropped_thing.visible_message(span_boldwarning("[dropped_thing] falls into [parent]!"), span_userdanger("[oblivion_message]"))
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 = (locate() in parent) || new(parent)
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))
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))
else if(isliving(dropped_thing))
var/mob/living/fallen_mob = dropped_thing
REMOVE_TRAIT(fallen_mob, TRAIT_NO_TRANSFORM, REF(src))
if (fallen_mob.stat != DEAD)
fallen_mob.investigate_log("has died from falling into a chasm.", INVESTIGATE_DEATHS)
if(issilicon(fallen_mob))
//Silicons are held together by hopes and dreams, unfortunately, I'm having a nightmare
var/mob/living/silicon/robot/fallen_borg = fallen_mob
fallen_borg.mmi = null
fallen_mob.death(TRUE)
fallen_mob.apply_damage(300)
LAZYREMOVE(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)
///Global list needed to let fishermen with a rescue hook fish fallen mobs from any place
GLOBAL_LIST_EMPTY(chasm_fallen_mobs)
/**
* 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)
/obj/effect/abstract/chasm_storage/Entered(atom/movable/arrived)
. = ..()
if(isliving(arrived))
RegisterSignal(arrived, COMSIG_LIVING_REVIVE, PROC_REF(on_revive))
LAZYADD(GLOB.chasm_fallen_mobs[get_chasm_category(loc)], arrived)
/obj/effect/abstract/chasm_storage/Exited(atom/movable/gone)
. = ..()
if(isliving(gone))
UnregisterSignal(gone, COMSIG_LIVING_REVIVE)
LAZYREMOVE(GLOB.chasm_fallen_mobs[get_chasm_category(loc)], gone)
/obj/effect/abstract/chasm_storage/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
. = ..()
var/old_cat = get_chasm_category(old_turf)
var/new_cat = get_chasm_category(new_turf)
var/list/mobs = list()
for(var/mob/fallen in src)
mobs += fallen
LAZYREMOVE(GLOB.chasm_fallen_mobs[old_cat], mobs)
LAZYADD(GLOB.chasm_fallen_mobs[new_cat], mobs)
/**
* Returns a key to store, remove and access fallen mobs depending on the z-level.
* This stops rescuing people from places that are waaaaaaaay too far-fetched.
*/
/proc/get_chasm_category(turf/turf)
var/z_level = turf?.z
var/area/area = get_area(turf)
if(istype(area, /area/shuttle)) //shuttle move between z-levels, so they're a special case.
return area
if(is_away_level(z_level))
return ZTRAIT_AWAY
if(is_mining_level(z_level))
return ZTRAIT_MINING
if(is_station_level(z_level))
return ZTRAIT_STATION
if(is_centcom_level(z_level))
return ZTRAIT_CENTCOM
if(is_reserved_level(z_level))
return ZTRAIT_RESERVED
return ZTRAIT_SPACE_RUINS
#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.
*/
/obj/effect/abstract/chasm_storage/proc/on_revive(mob/living/escapee)
SIGNAL_HANDLER
var/turf/turf = get_turf(src)
if(turf.GetComponent(/datum/component/chasm))
turf.visible_message(span_boldwarning("After a long climb, [escapee] leaps out of [turf]!"))
else
playsound(turf, 'sound/effects/bang.ogg', 50, TRUE)
turf.visible_message(span_boldwarning("[escapee] busts through [turf], leaping out of the chasm below"))
turf.ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR)
ADD_TRAIT(escapee, TRAIT_MOVE_FLYING, CHASM_TRAIT) //Otherwise they instantly fall back in
escapee.forceMove(turf)
escapee.throw_at(get_edge_target_turf(turf, 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