mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-27 10:02:12 +00:00
You can no longer climb ladders from anchored buckleables (#72808) This PR prevents you from climbing ladders while buckled to something that is anchored. You can still climb while buckled to non-anchored things like wheelchairs or people. Co-authored-by: Ryll Ryll <3589655+Ryll-Ryll@users.noreply.github.com>
323 lines
9.9 KiB
Plaintext
323 lines
9.9 KiB
Plaintext
// Basic ladder. By default links to the z-level above/below.
|
|
/obj/structure/ladder
|
|
name = "ladder"
|
|
desc = "A sturdy metal ladder."
|
|
icon = 'icons/obj/structures.dmi'
|
|
icon_state = "ladder11"
|
|
anchored = TRUE
|
|
obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN
|
|
var/obj/structure/ladder/down //the ladder below this one
|
|
var/obj/structure/ladder/up //the ladder above this one
|
|
var/crafted = FALSE
|
|
/// travel time for ladder in deciseconds
|
|
var/travel_time = 1 SECONDS
|
|
|
|
/obj/structure/ladder/Initialize(mapload, obj/structure/ladder/up, obj/structure/ladder/down)
|
|
..()
|
|
GLOB.ladders += src
|
|
if (up)
|
|
src.up = up
|
|
up.down = src
|
|
up.update_appearance()
|
|
if (down)
|
|
src.down = down
|
|
down.up = src
|
|
down.update_appearance()
|
|
|
|
register_context()
|
|
|
|
return INITIALIZE_HINT_LATELOAD
|
|
|
|
/obj/structure/ladder/add_context(atom/source, list/context, obj/item/held_item, mob/user)
|
|
if(up)
|
|
context[SCREENTIP_CONTEXT_LMB] = "Climb up"
|
|
if(down)
|
|
context[SCREENTIP_CONTEXT_RMB] = "Climb down"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
|
|
/obj/structure/ladder/examine(mob/user)
|
|
. = ..()
|
|
. += span_info("<b>Left-click</b> it to start moving up; <b>Right-click</b> to start moving down.")
|
|
|
|
/obj/structure/ladder/Destroy(force)
|
|
GLOB.ladders -= src
|
|
disconnect()
|
|
return ..()
|
|
|
|
/obj/structure/ladder/LateInitialize()
|
|
// By default, discover ladders above and below us vertically
|
|
var/turf/T = get_turf(src)
|
|
var/obj/structure/ladder/L
|
|
|
|
if (!down)
|
|
L = locate() in SSmapping.get_turf_below(T)
|
|
if (L)
|
|
if(crafted == L.crafted)
|
|
down = L
|
|
L.up = src // Don't waste effort looping the other way
|
|
L.update_appearance()
|
|
if (!up)
|
|
L = locate() in SSmapping.get_turf_above(T)
|
|
if (L)
|
|
if(crafted == L.crafted)
|
|
up = L
|
|
L.down = src // Don't waste effort looping the other way
|
|
L.update_appearance()
|
|
|
|
update_appearance()
|
|
|
|
/obj/structure/ladder/proc/disconnect()
|
|
if(up && up.down == src)
|
|
up.down = null
|
|
up.update_appearance()
|
|
if(down && down.up == src)
|
|
down.up = null
|
|
down.update_appearance()
|
|
up = down = null
|
|
|
|
/obj/structure/ladder/update_icon_state()
|
|
icon_state = "ladder[up ? 1 : 0][down ? 1 : 0]"
|
|
return ..()
|
|
|
|
/obj/structure/ladder/singularity_pull()
|
|
if (!(resistance_flags & INDESTRUCTIBLE))
|
|
visible_message(span_danger("[src] is torn to pieces by the gravitational pull!"))
|
|
qdel(src)
|
|
|
|
/obj/structure/ladder/proc/use(mob/user, going_up = TRUE)
|
|
if(!in_range(src, user) || DOING_INTERACTION(user, DOAFTER_SOURCE_CLIMBING_LADDER))
|
|
return
|
|
|
|
if(!up && !down)
|
|
balloon_alert(user, "doesn't lead anywhere!")
|
|
return
|
|
if(going_up ? !up : !down)
|
|
balloon_alert(user, "can't go any further [going_up ? "up" : "down"]")
|
|
return
|
|
if(user.buckled && user.buckled.anchored)
|
|
balloon_alert(user, "buckled to something anchored!")
|
|
return
|
|
if(travel_time)
|
|
INVOKE_ASYNC(src, PROC_REF(start_travelling), user, going_up)
|
|
else
|
|
travel(user, going_up)
|
|
add_fingerprint(user)
|
|
|
|
/obj/structure/ladder/proc/start_travelling(mob/user, going_up)
|
|
show_initial_fluff_message(user, going_up)
|
|
if(do_after(user, travel_time, target = src, interaction_key = DOAFTER_SOURCE_CLIMBING_LADDER))
|
|
travel(user, going_up)
|
|
|
|
/// The message shown when the player starts climbing the ladder
|
|
/obj/structure/ladder/proc/show_initial_fluff_message(mob/user, going_up)
|
|
var/up_down = going_up ? "up" : "down"
|
|
user.balloon_alert_to_viewers("climbing [up_down]...")
|
|
|
|
/obj/structure/ladder/proc/travel(mob/user, going_up = TRUE, is_ghost = FALSE)
|
|
var/obj/structure/ladder/ladder = going_up ? up : down
|
|
if(!ladder)
|
|
balloon_alert(user, "there's nothing that way!")
|
|
return
|
|
var/response = SEND_SIGNAL(user, COMSIG_LADDER_TRAVEL, src, ladder, going_up)
|
|
if(response & LADDER_TRAVEL_BLOCK)
|
|
return
|
|
|
|
var/turf/target = get_turf(ladder)
|
|
user.zMove(target = target, z_move_flags = ZMOVE_CHECK_PULLEDBY|ZMOVE_ALLOW_BUCKLED|ZMOVE_INCLUDE_PULLED)
|
|
|
|
if(!is_ghost)
|
|
show_final_fluff_message(user, ladder, going_up)
|
|
|
|
// to avoid having players hunt for the pixels of a ladder that goes through several stories and is
|
|
// partially covered by the sprites of their mobs, a radial menu will be displayed over them.
|
|
// this way players can keep climbing up or down with ease until they reach an end.
|
|
if(ladder.up && ladder.down)
|
|
ladder.show_options(user, is_ghost)
|
|
|
|
/// The messages shown after the player has finished climbing. Players can see this happen from either src or the destination so we've 2 POVs here
|
|
/obj/structure/ladder/proc/show_final_fluff_message(mob/user, obj/structure/ladder/destination, going_up)
|
|
var/up_down = going_up ? "up" : "down"
|
|
|
|
//POV of players around the source
|
|
visible_message(span_notice("[user] climbs [up_down] [src]."))
|
|
//POV of players around the destination
|
|
user.balloon_alert_to_viewers("climbed [up_down]")
|
|
|
|
/// Shows a radial menu that players can use to climb up and down a stair.
|
|
/obj/structure/ladder/proc/show_options(mob/user, is_ghost = FALSE)
|
|
var/list/tool_list = list()
|
|
tool_list["Up"] = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH)
|
|
tool_list["Down"] = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH)
|
|
|
|
var/datum/callback/check_menu
|
|
if(!is_ghost)
|
|
check_menu = CALLBACK(src, PROC_REF(check_menu), user)
|
|
var/result = show_radial_menu(user, src, tool_list, custom_check = check_menu, require_near = !is_ghost, tooltips = TRUE)
|
|
|
|
var/going_up
|
|
switch(result)
|
|
if("Up")
|
|
going_up = TRUE
|
|
if("Down")
|
|
going_up = FALSE
|
|
else
|
|
return
|
|
|
|
if(is_ghost || !travel_time)
|
|
travel(user, going_up, is_ghost)
|
|
else
|
|
INVOKE_ASYNC(src, PROC_REF(start_travelling), user, going_up)
|
|
|
|
/obj/structure/ladder/proc/check_menu(mob/user, is_ghost)
|
|
if(user.incapacitated() || (!user.Adjacent(src)))
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/obj/structure/ladder/attack_hand(mob/user, list/modifiers)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
use(user)
|
|
|
|
/obj/structure/ladder/attack_hand_secondary(mob/user, list/modifiers)
|
|
. = ..()
|
|
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
|
|
return
|
|
use(user, going_up = FALSE)
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
|
|
//Not be called when right clicking as a monkey. attack_hand_secondary() handles that.
|
|
/obj/structure/ladder/attack_paw(mob/user, list/modifiers)
|
|
use(user)
|
|
return TRUE
|
|
|
|
/obj/structure/ladder/attack_alien(mob/user, list/modifiers)
|
|
use(user)
|
|
return TRUE
|
|
|
|
/obj/structure/ladder/attack_alien_secondary(mob/user, list/modifiers)
|
|
. = ..()
|
|
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
|
|
return
|
|
use(user, going_up = FALSE)
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
|
|
/obj/structure/ladder/attack_larva(mob/user, list/modifiers)
|
|
use(user)
|
|
return TRUE
|
|
|
|
/obj/structure/ladder/attack_larva_secondary(mob/user, list/modifiers)
|
|
. = ..()
|
|
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
|
|
return
|
|
use(user, going_up = FALSE)
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
|
|
/obj/structure/ladder/attack_animal(mob/user, list/modifiers)
|
|
use(user)
|
|
return TRUE
|
|
|
|
/obj/structure/ladder/attack_animal_secondary(mob/user, list/modifiers)
|
|
. = ..()
|
|
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
|
|
return
|
|
use(user, going_up = FALSE)
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
|
|
/obj/structure/ladder/attack_slime(mob/user, list/modifiers)
|
|
use(user)
|
|
return TRUE
|
|
|
|
/obj/structure/ladder/attack_slime_secondary(mob/user, list/modifiers)
|
|
. = ..()
|
|
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
|
|
return
|
|
use(user, going_up = FALSE)
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
|
|
/obj/structure/ladder/attackby(obj/item/item, mob/user, params)
|
|
use(user)
|
|
return TRUE
|
|
|
|
/obj/structure/ladder/attackby_secondary(obj/item/item, mob/user, params)
|
|
. = ..()
|
|
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
|
|
return
|
|
use(user, going_up = FALSE)
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
|
|
/obj/structure/ladder/attack_robot(mob/living/silicon/robot/user)
|
|
if(user.Adjacent(src))
|
|
use(user)
|
|
return TRUE
|
|
|
|
/obj/structure/ladder/attack_robot_secondary(mob/living/silicon/robot/user)
|
|
. = ..()
|
|
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN || !user.Adjacent(src))
|
|
return
|
|
use(user, going_up = FALSE)
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
|
|
/obj/structure/ladder/attack_pai(mob/user, list/modifiers)
|
|
use(user)
|
|
return TRUE
|
|
|
|
/obj/structure/ladder/attack_pai_secondary(mob/user, list/modifiers)
|
|
. = ..()
|
|
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
|
|
return
|
|
use(user, going_up = FALSE)
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
|
|
//ATTACK GHOST IGNORING PARENT RETURN VALUE
|
|
/obj/structure/ladder/attack_ghost(mob/dead/observer/user)
|
|
ghost_use(user)
|
|
return ..()
|
|
|
|
///Ghosts use the byond default popup menu function on right click, so this is going to work a little differently for them.
|
|
/obj/structure/ladder/proc/ghost_use(mob/user)
|
|
if (!up && !down)
|
|
balloon_alert(user, "doesn't lead anywhere!")
|
|
return
|
|
if(!up) //only goes down
|
|
travel(user, going_up = FALSE, is_ghost = TRUE)
|
|
else if(!down) //only goes up
|
|
travel(user, going_up = TRUE, is_ghost = TRUE)
|
|
else //goes both ways
|
|
show_options(user, is_ghost = TRUE)
|
|
|
|
// Indestructible away mission ladders which link based on a mapped ID and height value rather than X/Y/Z.
|
|
/obj/structure/ladder/unbreakable
|
|
name = "sturdy ladder"
|
|
desc = "An extremely sturdy metal ladder."
|
|
resistance_flags = INDESTRUCTIBLE
|
|
var/id
|
|
var/height = 0 // higher numbers are considered physically higher
|
|
|
|
/obj/structure/ladder/unbreakable/LateInitialize()
|
|
// Override the parent to find ladders based on being height-linked
|
|
if (!id || (up && down))
|
|
update_appearance()
|
|
return
|
|
|
|
for(var/obj/structure/ladder/unbreakable/unbreakable_ladder in GLOB.ladders)
|
|
if (unbreakable_ladder.id != id)
|
|
continue // not one of our pals
|
|
if (!down && unbreakable_ladder.height == height - 1)
|
|
down = unbreakable_ladder
|
|
unbreakable_ladder.up = src
|
|
unbreakable_ladder.update_appearance()
|
|
if (up)
|
|
break // break if both our connections are filled
|
|
else if (!up && unbreakable_ladder.height == height + 1)
|
|
up = unbreakable_ladder
|
|
unbreakable_ladder.down = src
|
|
unbreakable_ladder.update_appearance()
|
|
if (down)
|
|
break // break if both our connections are filled
|
|
|
|
update_appearance()
|
|
|
|
/obj/structure/ladder/crafted
|
|
crafted = TRUE
|