Handrails, visual shuttle buckles, and buckling feedback changes (#91504)

## About The Pull Request

1. Adds Handrails, currently mapper only. [Sprite ported from
Baystation](https://github.com/Baystation12/Baystation12/blob/dev/icons/obj/structures/handrail.dmi).
This is just a small fluff object designed to be put onto shuttles.
Players can click on it (or mouse drop) to buckle to it (grab it),
keeping them from being thrown around on shuttle launch.


![image](https://github.com/user-attachments/assets/b5bba651-d6ad-4e12-bb13-988ad5bc64ca)

2. Adds Shuttle chair restraints. [Sprite ported from
Aurora](https://github.com/Aurorastation/Aurora.3/blob/master/icons/obj/structure/chairs.dmi),
though altered a decent amount. They flip up with no one seated and flip
down when someone buckles, but they have no gameplay effects (ie, they
don't require a do-after, they don't block hands, etc etc)


![image](https://github.com/user-attachments/assets/4118a174-2443-4da2-8126-649dccfdcb65)


![image](https://github.com/user-attachments/assets/d40fb7b4-f7d2-4052-8f6c-41dfda62cf95)

3. Some objects now have unique feedback messages for buckling - you sit
on chairs, or lay down on beds. This message will change depending on if
the mob is restrained, so it's a bit more obvious when someone is tied
to a chair vs just sitting down.


![image](https://github.com/user-attachments/assets/9ea1da9f-ae25-4008-8ca4-8202f7508290)

4. If you're buckled on a shuttle which has "knockdown" force, you won't
be paralyzed - instead, just knocked down and immobilized for a short
period (so you can still use your hands / act).

## Why It's Good For The Game

1. Gives mappers some additional fluff for shuttles, especially if their
shuttle has knockdown force.

2. Adds muh immersion to shuttle rides. Just a flavor thing.

3. Adds muh immersion to sitting down at the bar. Again just a flavor
thing.

4. I did this to make handrails work, but I can find an alternate
workaround if so desired.

## Changelog

🆑 Melbert, sprites from Baystation / Aurorastation
add: Adds grabbable handrails (mapper only for now)
image: Adds a visual effect to buckling to shuttle seats
qol: Different objects have different chat messages for buckling. 
qol: If you're buckled in on a violent shuttle ride, you will be knocked
down and immobilized, but not fully stunned (ie: can still use hands).
/🆑

---------

Co-authored-by: san7890 <the@san7890.com>
This commit is contained in:
MrMelbert
2025-06-09 17:40:48 +00:00
committed by GitHub
co-authored by san7890
parent 9216dae113
commit 0343a41b42
9 changed files with 316 additions and 18 deletions
+33 -16
View File
@@ -324,14 +324,23 @@
. = buckle_mob(M, check_loc = check_loc)
if(.)
if(M == user)
M.visible_message(span_notice("[M] buckles [M.p_them()]self to [src]."),\
span_notice("You buckle yourself to [src]."),\
span_hear("You hear metal clanking."))
else
M.visible_message(span_warning("[user] buckles [M] to [src]!"),\
span_warning("[user] buckles you to [src]!"),\
span_hear("You hear metal clanking."))
buckle_feedback(M, user)
/// Feedback displayed to nearby players after a mob is buckled to src.
/atom/movable/proc/buckle_feedback(mob/living/being_buckled, mob/buckler)
if(being_buckled == buckler)
buckler.visible_message(
span_notice("[buckler] buckles [buckler.p_them()]self to [src]."),
span_notice("You buckle yourself to [src]."),
span_hear("You hear metal clanking."),
)
else
buckler.visible_message(
span_warning("[buckler] buckles [being_buckled] to [src]!"),
span_warning("[buckler] buckles you to [src]!"),
span_hear("You hear metal clanking."),
)
/**
* Handles a user unbuckling a mob from src and sends a visible_message
*
@@ -346,16 +355,24 @@
return
var/mob/living/M = unbuckle_mob(buckled_mob)
if(M)
if(M != user)
M.visible_message(span_notice("[user] unbuckles [M] from [src]."),\
span_notice("[user] unbuckles you from [src]."),\
span_hear("You hear metal clanking."))
else
M.visible_message(span_notice("[M] unbuckles [M.p_them()]self from [src]."),\
span_notice("You unbuckle yourself from [src]."),\
span_hear("You hear metal clanking."))
unbuckle_feedback(M, user)
add_fingerprint(user)
if(isliving(M.pulledby))
var/mob/living/L = M.pulledby
L.set_pull_offsets(M, L.grab_state)
return M
/// Feedback displayed to nearby players after a mob is unbuckled from src.
/atom/movable/proc/unbuckle_feedback(mob/living/unbuckled_mob, mob/unbuckler)
if(unbuckled_mob == unbuckler)
unbuckler.visible_message(
span_notice("[unbuckler] unbuckles [unbuckler.p_them()]self from [src]."),
span_notice("You unbuckle yourself from [src]."),
span_hear("You hear metal clanking."),
)
else
unbuckler.visible_message(
span_notice("[unbuckler] unbuckles [unbuckled_mob] from [src]."),
span_notice("[unbuckler] unbuckles you from [src]."),
span_hear("You hear metal clanking."),
)
@@ -21,6 +21,34 @@
return ..()
/obj/structure/bed/nest/buckle_feedback(mob/living/being_buckled, mob/buckler)
if(being_buckled == buckler)
being_buckled.visible_message(
span_notice("[buckler] lays down on [src], wrapping [buckler.p_them()]self in a thick, sticky resin."),
span_notice("You lay down on [src], wrapping yourself in a thick, sticky resin."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
else
being_buckled.visible_message(
span_notice("[buckler] lays [being_buckled] down on [src], wrapping [being_buckled.p_them()] in a thick, sticky resin."),
span_notice("[buckler] lays you down on [src], wrapping you in a thick, sticky resin."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
/obj/structure/bed/nest/unbuckle_feedback(mob/living/being_unbuckled, mob/unbuckler)
if(being_unbuckled == unbuckler)
being_unbuckled.visible_message(
span_notice("[unbuckler] pulls [unbuckler.p_them()]self free from the sticky nest!"),
span_notice("You pull yourself free from the sticky nest!"),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
else
being_unbuckled.visible_message(
span_notice("[unbuckler] pulls [being_unbuckled] free from the sticky nest!"),
span_notice("[unbuckler] pulls you free from the sticky nest!"),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
/obj/structure/bed/nest/user_unbuckle_mob(mob/living/captive, mob/living/hero)
if(!length(buckled_mobs))
return
@@ -38,6 +38,40 @@
update_buckle_vars(dir)
register_context()
/obj/structure/bed/buckle_feedback(mob/living/being_buckled, mob/buckler)
if(HAS_TRAIT(being_buckled, TRAIT_RESTRAINED))
return ..()
if(being_buckled == buckler)
being_buckled.visible_message(
span_notice("[buckler] lays down on [src]."),
span_notice("You lay down on [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
else
being_buckled.visible_message(
span_notice("[buckler] lays [being_buckled] down on [src]."),
span_notice("[buckler] lays you down on [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
/obj/structure/bed/unbuckle_feedback(mob/living/being_unbuckled, mob/unbuckler)
if(HAS_TRAIT(being_unbuckled, TRAIT_RESTRAINED))
return ..()
if(being_unbuckled == unbuckler)
being_unbuckled.visible_message(
span_notice("[unbuckler] gets up from [src]."),
span_notice("You get up from [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
else
being_unbuckled.visible_message(
span_notice("[unbuckler] pulls [being_unbuckled] up from [src]."),
span_notice("[unbuckler] pulls you up from [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
/obj/structure/bed/examine(mob/user)
. = ..()
if (can_deconstruct)
@@ -33,6 +33,40 @@
if(can_buckle && fishing_modifier)
AddComponent(/datum/component/adjust_fishing_difficulty, fishing_modifier)
/obj/structure/chair/buckle_feedback(mob/living/being_buckled, mob/buckler)
if(HAS_TRAIT(being_buckled, TRAIT_RESTRAINED))
return ..()
if(being_buckled == buckler)
being_buckled.visible_message(
span_notice("[buckler] sits down on [src]."),
span_notice("You sit down on [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
else
being_buckled.visible_message(
span_notice("[buckler] sits [being_buckled] down on [src]."),
span_notice("[buckler] sits you down on [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
/obj/structure/chair/unbuckle_feedback(mob/living/being_unbuckled, mob/unbuckler)
if(HAS_TRAIT(being_unbuckled, TRAIT_RESTRAINED))
return ..()
if(being_unbuckled == unbuckler)
being_unbuckled.visible_message(
span_notice("[unbuckler] stands up from [src]."),
span_notice("You stand up from [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
else
being_unbuckled.visible_message(
span_notice("[unbuckler] stands [being_unbuckled] up from [src]."),
span_notice("[unbuckler] stands you up from [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
/obj/structure/chair/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
if(same_z_layer || !has_armrest)
return ..()
@@ -222,6 +256,9 @@
buildstacktype = /obj/item/stack/sheet/mineral/titanium
buckle_sound = SFX_SEATBELT_BUCKLE
unbuckle_sound = SFX_SEATBELT_UNBUCKLE
resistance_flags = FIRE_PROOF
max_integrity = 120
custom_materials = list(/datum/material/titanium = SHEET_MATERIAL_AMOUNT)
/obj/structure/chair/comfy/shuttle/electrify_self(obj/item/assembly/shock_kit/input_shock_kit, mob/user, list/overlays_from_child_procs)
if(!overlays_from_child_procs)
@@ -230,6 +267,42 @@
overlays_from_child_procs = list(echair_overlay)
. = ..()
/obj/structure/chair/comfy/shuttle/buckle_feedback(mob/living/being_buckled, mob/buckler)
if(being_buckled == buckler)
being_buckled.visible_message(
span_notice("[buckler] sits down on [src], pulling the overhead restraint down to secure [buckler.p_them()]self."),
span_notice("You sit down on [src], pulling the overhead restraint down to secure yourself."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
else
being_buckled.visible_message(
span_notice("[buckler] sits [being_buckled] down on [src], pulling the overhead restraint down to secure [buckler.p_them()]."),
span_notice("[buckler] sits you down on [src], pulling the overhead restraint down to secure you."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
/obj/structure/chair/comfy/shuttle/unbuckle_feedback(mob/living/being_unbuckled, mob/unbuckler)
if(being_unbuckled == unbuckler)
being_unbuckled.visible_message(
span_notice("[unbuckler] flips the overhead restraint up, standing up from [src]."),
span_notice("You flip the overhead restraint up, standing up from [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
else
being_unbuckled.visible_message(
span_notice("[unbuckler] flips the overhead restraint up, standing [being_unbuckled] up from [src]."),
span_notice("[unbuckler] flips the overhead restraint up, standing you up from [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
/obj/structure/chair/comfy/shuttle/update_overlays()
. = ..()
if(has_buckled_mobs())
. += mutable_appearance(icon, "[icon_state]_down_front", ABOVE_MOB_LAYER + 0.01)
. += mutable_appearance(icon, "[icon_state]_down_behind", src.layer + 0.01)
else
. += mutable_appearance(icon, "[icon_state]_up", src.layer + 0.01)
/obj/structure/chair/comfy/shuttle/tactical
name = "tactical chair"
@@ -618,3 +691,76 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/chair/stool/bar, 0)
desc = "Oh, so this is like the fucked up Monopoly rules where there are no rules and you can pick up and place the musical chairs as you please."
particles = new /particles/musical_notes
origin_type = /obj/structure/chair/musical
/obj/structure/handrail
name = "handrail"
desc = "Hold on tight!"
icon = 'icons/obj/handrail.dmi'
icon_state = "handrail"
anchored = TRUE
can_buckle = TRUE
buckle_lying = NO_BUCKLE_LYING
resistance_flags = FIRE_PROOF
max_integrity = 50
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT)
layer = OBJ_LAYER
/obj/structure/handrail/attack_hand(mob/living/user, list/modifiers)
return ..() || mouse_buckle_handling(user, user)
/obj/structure/handrail/is_user_buckle_possible(mob/living/target, mob/user, check_loc = TRUE)
return ..() && user == target && !HAS_TRAIT(target, TRAIT_HANDS_BLOCKED)
/obj/structure/handrail/post_buckle_mob(mob/living/buckled_mob)
RegisterSignal(buckled_mob, SIGNAL_ADDTRAIT(TRAIT_HANDS_BLOCKED), PROC_REF(stop_buckle))
var/z_offset = 0
var/w_offset = 0
if(dir & NORTH)
z_offset = -4
else if(dir & SOUTH)
z_offset = 8
if(dir & EAST)
w_offset = -8
else if(dir & WEST)
w_offset = 8
buckled_mob.add_offsets(type, z_add = z_offset, w_add = w_offset)
/obj/structure/handrail/post_unbuckle_mob(mob/living/unbuckled_mob)
UnregisterSignal(unbuckled_mob, SIGNAL_ADDTRAIT(TRAIT_HANDS_BLOCKED))
unbuckled_mob.remove_offsets(type)
/obj/structure/handrail/proc/stop_buckle(mob/living/source, ...)
SIGNAL_HANDLER
source.visible_message(
span_warning("[source] loses [source.p_their()] grip on [src]!"),
span_warning("You lose your grip on [src]!"),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
vision_distance = COMBAT_MESSAGE_RANGE,
)
unbuckle_mob(source, TRUE, TRUE)
/obj/structure/handrail/buckle_feedback(mob/living/being_buckled, mob/buckler)
buckler.visible_message(
span_notice("[buckler] grabs [src] tight, keeping [buckler.p_them()]self upright."),
span_notice("You grab [src] tight, keeping yourself upright."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
vision_distance = COMBAT_MESSAGE_RANGE,
)
/obj/structure/handrail/unbuckle_feedback(mob/living/being_unbuckled, mob/unbuckler)
if(being_unbuckled == unbuckler)
being_unbuckled.visible_message(
span_notice("[unbuckler] lets go of [src]."),
span_notice("You let go of [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
vision_distance = COMBAT_MESSAGE_RANGE,
)
else
being_unbuckled.visible_message(
span_warning("[unbuckler] forces [being_unbuckled] to let go of [src]!"),
span_warning("[unbuckler] forces you to let go of [src]!"),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
vision_distance = COMBAT_MESSAGE_RANGE,
)
@@ -71,6 +71,40 @@
QDEL_NULL(weight_action)
return ..()
/obj/structure/weightmachine/buckle_feedback(mob/living/being_buckled, mob/buckler)
if(HAS_TRAIT(being_buckled, TRAIT_RESTRAINED))
return ..()
if(being_buckled == buckler)
being_buckled.visible_message(
span_notice("[buckler] lays down on [src]."),
span_notice("You lay down on [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
else
being_buckled.visible_message(
span_notice("[buckler] lays [being_buckled] down on [src]."),
span_notice("[buckler] lays you down on [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
/obj/structure/weightmachine/unbuckle_feedback(mob/living/being_unbuckled, mob/unbuckler)
if(HAS_TRAIT(being_unbuckled, TRAIT_RESTRAINED))
return ..()
if(being_unbuckled == unbuckler)
being_unbuckled.visible_message(
span_notice("[unbuckler] gets up from [src]."),
span_notice("You get up from [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
else
being_unbuckled.visible_message(
span_notice("[unbuckler] pulls [being_unbuckled] up from [src]."),
span_notice("[unbuckler] pulls you up from [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
/obj/structure/weightmachine/buckle_mob(mob/living/buckled, force, check_loc)
. = ..()
weight_action.Grant(buckled)
@@ -1077,6 +1077,40 @@
breath_mask = null
return ..()
/obj/structure/table/optable/buckle_feedback(mob/living/being_buckled, mob/buckler)
if(HAS_TRAIT(being_buckled, TRAIT_RESTRAINED))
return ..()
if(being_buckled == buckler)
being_buckled.visible_message(
span_notice("[buckler] lays down on [src]."),
span_notice("You lay down on [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
else
being_buckled.visible_message(
span_notice("[buckler] lays [being_buckled] down on [src]."),
span_notice("[buckler] lays you down on [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
/obj/structure/table/optable/unbuckle_feedback(mob/living/being_unbuckled, mob/unbuckler)
if(HAS_TRAIT(being_unbuckled, TRAIT_RESTRAINED))
return ..()
if(being_unbuckled == unbuckler)
being_unbuckled.visible_message(
span_notice("[unbuckler] gets up from [src]."),
span_notice("You get up from [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
else
being_unbuckled.visible_message(
span_notice("[unbuckler] pulls [being_unbuckled] up from [src]."),
span_notice("[unbuckler] pulls you up from [src]."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
/obj/structure/table/optable/add_context(atom/source, list/context, obj/item/held_item, mob/living/user)
. = ..()
if(isnull(held_item))
@@ -325,8 +325,13 @@ All ShuttleMove procs go here
. = ..()
var/knockdown = movement_force["KNOCKDOWN"]
if(knockdown)
Paralyze(knockdown)
if(knockdown > 0)
if(buckled)
// If we're buckled, no stun but we'll still be floored and frozen
Knockdown(knockdown)
Immobilize(knockdown * 0.5)
else
Paralyze(knockdown)
/mob/living/simple_animal/hostile/megafauna/onShuttleMove(turf/newT, turf/oldT, list/movement_force, move_dir, obj/docking_port/stationary/old_dock, obj/docking_port/mobile/moving_dock)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B