Files
CHOMPStation2/code/datums/elements/climbable.dm
CHOMPStation2StaffMirrorBot 1b8f394a14 [MIRROR] Makes uses of do_after sane (#11582)
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
2025-09-07 23:02:27 -04:00

252 lines
9.2 KiB
Plaintext

///Climbable element. Allows climbing an object using mousedrag or a verb
/datum/element/climbable
element_flags = ELEMENT_DETACH_ON_HOST_DESTROY|ELEMENT_BESPOKE
argument_hash_start_idx = 2
VAR_PRIVATE/list/current_climbers
VAR_PRIVATE/climb_delay = 3.5 SECONDS
VAR_PRIVATE/climb_to_adjacent_turf = FALSE // for railings
/datum/element/climbable/Attach(obj/target, var/climb_delay = 3.5 SECONDS, var/vaulting = FALSE)
. = ..()
if(!isobj(target))
return ELEMENT_INCOMPATIBLE
src.climb_delay = climb_delay
src.climb_to_adjacent_turf = vaulting
RegisterSignal(target, COMSIG_CLIMBABLE_START_CLIMB, PROC_REF(start_climb))
RegisterSignal(target, COMSIG_CLIMBABLE_SHAKE_CLIMBERS, PROC_REF(shaken))
RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(move_shaken))
RegisterSignal(target, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
target.verbs += /obj/proc/climb_on
ADD_TRAIT(target, TRAIT_CLIMBABLE, ELEMENT_TRAIT(type))
/datum/element/climbable/Detach(obj/source)
UnregisterSignal(source, COMSIG_CLIMBABLE_START_CLIMB)
UnregisterSignal(source, COMSIG_CLIMBABLE_SHAKE_CLIMBERS)
UnregisterSignal(source, COMSIG_MOVABLE_MOVED)
UnregisterSignal(source, COMSIG_PARENT_EXAMINE)
source.verbs -= /obj/proc/climb_on
REMOVE_TRAIT(source, TRAIT_CLIMBABLE, ELEMENT_TRAIT(type))
return ..()
/// Starts climbing the object
/datum/element/climbable/proc/start_climb(var/obj/climbed_thing, mob/user)
SIGNAL_HANDLER
var/mob/living/H = user
if(istype(H) && can_climb(climbed_thing,H))
addtimer(CALLBACK(src, PROC_REF(do_climb), climbed_thing, user, climb_delay), 0, TIMER_DELETE_ME) // Isolate from signal handler
/// Check if the mob is in any condition to climb the object, if the destination is blocked, and how to climb it
/datum/element/climbable/proc/can_climb(var/obj/climbed_thing, var/mob/living/user, post_climb_check=0)
if(user.is_incorporeal()) // No! Bad shadekin!
return FALSE
var/list/climbers = LAZYACCESS(current_climbers, climbed_thing)
if (!can_touch(climbed_thing, user) || (!post_climb_check && (user in climbers)))
return FALSE
if (!user.Adjacent(climbed_thing))
to_chat(user, span_danger("You can't climb there, the way is blocked."))
return FALSE
var/obj/occupied = can_climb_turf(climbed_thing)
if(occupied)
to_chat(user, span_danger("There's \a [occupied] in the way."))
return FALSE
// Railings are a bit snowflakey, but needed for when you climb from their turf to their facing turf!
if(climb_to_adjacent_turf)
if(get_turf(user) == get_turf(climbed_thing))
occupied = can_climb_neighbor_turf(climbed_thing)
if(occupied)
to_chat(user, span_danger("You can't climb there, there's \a [occupied] in the way."))
return FALSE
return TRUE
/// Performs the wait and any remaining checks before the climb resolves.
/datum/element/climbable/proc/do_climb(var/obj/climbed_thing, var/mob/living/user, var/delay_time)
if(QDELETED(user) || QDELETED(climbed_thing))
return
user.visible_message(span_warning("[user] starts climbing onto \the [climbed_thing]!"))
LAZYADDASSOCLIST(current_climbers, climbed_thing, user)
if(do_after(user,(issmall(user) ? delay_time * 0.6 : delay_time), target = user))
if(can_climb(climbed_thing, user, post_climb_check=1))
climb_to(climbed_thing, user)
if(get_turf(user) == get_turf(climbed_thing))
user.visible_message(span_warning("[user] climbs onto \the [climbed_thing]!"))
else
user.visible_message(span_warning("[user] climbed over \the [climbed_thing]!"))
else
to_chat(user, span_warning("You fail to climb onto \the [climbed_thing]."))
LAZYREMOVEASSOC(current_climbers, climbed_thing, user)
/// Resolve the climb by moving the mob to its final destination.
/datum/element/climbable/proc/climb_to(var/obj/climbed_thing, var/mob/living/user)
if(climb_to_adjacent_turf && get_turf(user) == get_turf(climbed_thing))
user.forceMove(get_step(climbed_thing, climbed_thing.dir))
else
user.forceMove(get_turf(climbed_thing))
/// Check if a mob is capable of climbing at all
/datum/element/climbable/proc/can_touch(var/obj/climbed_thing, var/mob/user)
if (!user)
return 0
if(!climbed_thing.Adjacent(user))
return 0
if (user.restrained() || user.buckled)
to_chat(user, span_notice("You need your hands and legs free for this."))
return 0
if (user.stat || user.paralysis || user.sleeping || user.lying || user.weakened)
return 0
if (isAI(user))
to_chat(user, span_notice("You need hands for this."))
return 0
return 1
/// Shakes an object, called from movement so it needs to be cleaned up a bit!
/datum/element/climbable/proc/move_shaken(obj/climbed_thing, atom/oldloc, direction, forced, list/old_locs, momentum_change)
SIGNAL_HANDLER
if(!forced) // Don't perform this if going up stairs
shaken(climbed_thing, null)
/// Shakes an object, if anyone is climbing it, causes them to fall off it.
/datum/element/climbable/proc/shaken(var/obj/climbed_thing, var/mob/user)
SIGNAL_HANDLER
var/list/climbers = LAZYACCESS(current_climbers, climbed_thing)
// You cannot shake yourself
if(user) // Crates pass null on open because no user
if(!LAZYLEN(climbers) || (user in climbers))
return
user.visible_message(span_warning("\The [user] shakes \the [climbed_thing]."), span_notice("You shake \the [climbed_thing]."))
for(var/mob/living/M in climbers)
M.Weaken(1)
to_chat(M, span_danger("You topple as you are shaken off \the [climbed_thing]!"))
climbers.Cut(1,2)
for(var/mob/living/M in get_turf(climbed_thing))
if(M.is_incorporeal())
continue
if(M.lying) //No spamming this on people.
continue
if(M.pulling == climbed_thing) // Pulling stuff up stairs can get weird
continue
M.Weaken(3)
to_chat(M, span_danger("You topple as \the [climbed_thing] moves under you!"))
if(prob(25))
var/damage = rand(15,30)
var/mob/living/carbon/human/H = M
if(!istype(H))
to_chat(H, span_danger("You land heavily!"))
M.adjustBruteLoss(damage)
continue
var/obj/item/organ/external/affecting
switch(pick(list("ankle","wrist","head","knee","elbow")))
if("ankle")
affecting = H.get_organ(pick(BP_L_FOOT, BP_R_FOOT))
if("knee")
affecting = H.get_organ(pick(BP_L_LEG, BP_R_LEG))
if("wrist")
affecting = H.get_organ(pick(BP_L_HAND, BP_R_HAND))
if("elbow")
affecting = H.get_organ(pick(BP_L_ARM, BP_R_ARM))
if("head")
affecting = H.get_organ(BP_HEAD)
if(affecting)
to_chat(M, span_danger("You land heavily on your [affecting.name]!"))
affecting.take_damage(damage, 0)
if(affecting.parent)
affecting.parent.add_autopsy_data("Misadventure", damage)
else
to_chat(H, span_danger("You land heavily!"))
H.adjustBruteLoss(damage)
H.UpdateDamageIcon()
H.updatehealth()
/datum/element/climbable/proc/on_examine(datum/source, mob/user, list/examine_texts)
SIGNAL_HANDLER
examine_texts += span_notice("It looks climbable.")
// Cliff climbing requires climbing gear.
/datum/element/climbable/cliff/do_climb(obj/climbed_thing, mob/living/user, delay_time)
// Special snowflake handling, because north facing cliffs require half the time
var/obj/structure/cliff/C = climbed_thing
if(C.is_double_cliff)
delay_time /= 2
. = ..(climbed_thing, user, delay_time)
/datum/element/climbable/cliff/can_climb(var/obj/climbed_thing, var/mob/living/user, post_climb_check=0)
if(ishuman(user))
var/mob/living/carbon/human/H = user
var/obj/item/clothing/shoes/shoes = H.shoes
if(shoes && shoes.rock_climbing)
return ..() // Do the other checks too.
to_chat(user, span_warning("\The [climbed_thing] is too steep to climb unassisted."))
return FALSE
// Breaks if climbed while unanchored, railings.
/datum/element/climbable/unanchored_can_break/climb_to(var/obj/climbed_thing, var/mob/living/user)
. = ..()
if(!climbed_thing.anchored)
climbed_thing.take_damage(9999) // Fatboy, was originally maxhealth, but that var doesn't exist on everything
// Table flipping is important!
/datum/element/climbable/table/climb_to(var/obj/climbed_thing, mob/living/mover)
var/obj/structure/table/TBL = climbed_thing
if(TBL.flipped == 1 && mover.loc == TBL.loc)
var/turf/T = get_step(climbed_thing, TBL.dir)
if(T.Enter(mover))
return T
return ..()
/// Verb for climbing objects, calls same signal as mouse drop
/obj/proc/climb_on()
set name = "Climb structure"
set desc = "Climbs onto a structure."
set category = "Object"
set src in oview(1)
SEND_SIGNAL(src, COMSIG_CLIMBABLE_START_CLIMB, usr)
/// Checks if something is blocking our climb destination, ignores climbable objects
/proc/can_climb_turf(var/obj/climbed_thing)
var/turf/T = get_turf(climbed_thing)
if(!T || !istype(T))
return "empty void"
if(T.density)
return T
for(var/obj/O in T.contents)
if(O && O.density && !(O.flags & ON_BORDER) && !HAS_TRAIT(O,TRAIT_CLIMBABLE)) //ON_BORDER structures are handled by the Adjacent() check.
return O
return 0
/// Check if the destination turf for vaulting is blocked by something. Extremely similar to above.
/proc/can_climb_neighbor_turf(var/obj/climbed_thing)
var/turf/T = get_step(climbed_thing, climbed_thing.dir)
if(!T || !istype(T))
return 0
if(T.density == 1)
return T
for(var/obj/O in T.contents)
if(O && O.density && !(O.flags & ON_BORDER && !(turn(O.dir, 180) & climbed_thing.dir)) && !HAS_TRAIT(O,TRAIT_CLIMBABLE))
return O
return 0