Files
cb3468a5ce 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>
2025-06-15 15:53:27 -04:00

238 lines
7.9 KiB
Plaintext

#define WORKOUT_XP 5
#define EXERCISE_STATUS_DURATION 15 SECONDS
#define SAFE_DRUNK_LEVEL 39
/obj/structure/weightmachine
name = "chest press machine"
desc = "Just looking at this thing makes you feel tired."
icon = 'icons/obj/fluff/gym_equipment.dmi'
icon_state = "stacklifter"
base_icon_state = "stacklifter"
can_buckle = TRUE
density = TRUE
anchored = TRUE
blocks_emissive = EMISSIVE_BLOCK_UNIQUE
///How much we shift the user's pixel y when using the weight machine.
var/pixel_shift_z = -3
///The weight action we give to people that buckle themselves to us.
var/datum/action/push_weights/weight_action
///message when drunk user fails to use the machine
var/drunk_message = "You try for a new record and pull through! Through a muscle that is."
// the total reps you can do before you hit stamcrit based on fitness level
var/static/list/total_workout_reps = list(3, 4, 4, 5, 6, 6, 7)
///List of messages picked when using the machine.
var/static/list/more_weight = list(
"pushing it to the limit!",
"going into overdrive!",
"burning with determination!",
"rising up to the challenge!",
"getting strong now!",
"getting ripped!",
)
///List of messages picked when finished using the machine.
var/static/list/finished_message = list(
"You feel stronger!",
"You feel like you can take on the world!",
"You feel robust!",
"You feel indestructible!",
)
var/static/list/finished_silicon_message = list(
"You feel nothing!",
"No pain, no gain!",
"Chassis hardness rating... Unchanged.",
"You feel the exact same. Nothing.",
)
/obj/structure/weightmachine/Initialize(mapload)
. = ..()
weight_action = new(src)
weight_action.weightpress = src
var/static/list/tool_behaviors
if(!tool_behaviors)
tool_behaviors = string_assoc_nested_list(list(
TOOL_CROWBAR = list(
SCREENTIP_CONTEXT_RMB = "Deconstruct",
),
TOOL_WRENCH = list(
SCREENTIP_CONTEXT_RMB = "Anchor",
),
))
AddElement(/datum/element/contextual_screentip_tools, tool_behaviors)
/obj/structure/weightmachine/Destroy()
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)
/obj/structure/weightmachine/unbuckle_mob(mob/living/buckled_mob, force, can_fall)
. = ..()
weight_action.Remove(buckled_mob)
/obj/structure/weightmachine/wrench_act_secondary(mob/living/user, obj/item/tool)
tool.play_tool_sound(src)
balloon_alert(user, anchored ? "unsecured" : "secured")
anchored = !anchored
return TRUE
/obj/structure/weightmachine/crowbar_act_secondary(mob/living/user, obj/item/tool)
if(anchored)
balloon_alert(user, "still secured!")
return FALSE
tool.play_tool_sound(src)
balloon_alert(user, "deconstructing...")
if (!do_after(user, 10 SECONDS, target = src))
return FALSE
new /obj/item/stack/sheet/iron(get_turf(src), 5)
new /obj/item/stack/rods(get_turf(src), 2)
new /obj/item/chair(get_turf(src))
qdel(src)
return TRUE
/obj/structure/weightmachine/proc/perform_workout(mob/living/user)
if(user.nutrition <= NUTRITION_LEVEL_STARVING)
user.balloon_alert(user, "too hungry to workout!")
return
user.balloon_alert_to_viewers("[pick(more_weight)]")
START_PROCESSING(SSobj, src)
if(do_after(user, 8 SECONDS, src) && user.has_gravity())
// with enough dedication, even clowns can overcome their handicaps
var/clumsy_chance = 30 - (user.mind.get_skill_level(/datum/skill/athletics) * 5)
if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(clumsy_chance))
playsound(src, 'sound/effects/bang.ogg', 50, TRUE)
to_chat(user, span_warning("Your hand slips, causing \the [src] to smash you!"))
user.take_bodypart_damage(rand(2, 5))
end_workout()
return
// awlways a chance for a person not to fail horribly when drunk
if(user.get_drunk_amount() > SAFE_DRUNK_LEVEL && prob(min(user.get_drunk_amount(), 99)))
playsound(src,'sound/effects/bang.ogg', 50, TRUE)
to_chat(user, span_warning(drunk_message))
user.take_bodypart_damage(rand(5, 10), wound_bonus = 10)
end_workout()
return
if(issilicon(user))
user.balloon_alert(user, pick(finished_silicon_message))
else
user.balloon_alert(user, pick(finished_message))
user.adjust_nutrition(-5) // feel the burn
if(iscarbon(user))
var/gravity_modifier = user.has_gravity() > STANDARD_GRAVITY ? 2 : 1
// remember the real xp gain is from sleeping after working out
user.mind.adjust_experience(/datum/skill/athletics, WORKOUT_XP * gravity_modifier)
user.apply_status_effect(/datum/status_effect/exercised, EXERCISE_STATUS_DURATION)
end_workout()
/obj/structure/weightmachine/proc/end_workout()
playsound(src, 'sound/machines/click.ogg', 60, TRUE)
STOP_PROCESSING(SSobj, src)
icon_state = initial(icon_state)
/// roughly 8 seconds for 1 workout rep
#define WORKOUT_LENGTH 8
/obj/structure/weightmachine/process(seconds_per_tick)
if(!has_buckled_mobs())
end_workout()
return FALSE
var/mutable_appearance/workout = mutable_appearance(icon, "[base_icon_state]-o", ABOVE_MOB_LAYER, src, appearance_flags = KEEP_APART)
workout = color_atom_overlay(workout)
flick_overlay_view(workout, 0.8 SECONDS)
flick("[base_icon_state]-u", src)
var/mob/living/user = buckled_mobs[1]
animate(user, pixel_z = pixel_shift_z, time = WORKOUT_LENGTH * 0.5, flags = ANIMATION_PARALLEL|ANIMATION_RELATIVE)
animate(pixel_z = -pixel_shift_z, time = WORKOUT_LENGTH * 0.5, flags = ANIMATION_PARALLEL)
playsound(user, 'sound/machines/creak.ogg', 60, TRUE)
if(!iscarbon(user) || isnull(user.mind))
return TRUE
var/affected_gravity = user.has_gravity()
if (!affected_gravity)
return TRUE // No weight? I could do this all day
var/gravity_modifier = affected_gravity > STANDARD_GRAVITY ? 0.75 : 1
// the amount of workouts you can do before you hit stamcrit
var/workout_reps = total_workout_reps[user.mind.get_skill_level(/datum/skill/athletics)] * gravity_modifier
// total stamina drain of 1 workout calculated based on the workout length
var/stamina_exhaustion = FLOOR(user.maxHealth / workout_reps / WORKOUT_LENGTH, 0.1)
var/obj/item/organ/cyberimp/chest/spine/potential_spine = user.get_organ_slot(ORGAN_SLOT_SPINE)
if(istype(potential_spine))
stamina_exhaustion *= potential_spine.athletics_boost_multiplier
if(HAS_TRAIT(user, TRAIT_STRENGTH)) //The strong get reductions to stamina damage taken while exercising
stamina_exhaustion *= 0.5
user.adjustStaminaLoss(stamina_exhaustion * seconds_per_tick)
return TRUE
#undef WORKOUT_LENGTH
/**
* Weight lifter subtype
*/
/obj/structure/weightmachine/weightlifter
name = "incline bench press"
icon_state = "benchpress"
base_icon_state = "benchpress"
pixel_shift_z = 5
drunk_message = "You raise the bar over you trying to balance it with one hand, keyword tried."
#undef WORKOUT_XP
#undef EXERCISE_STATUS_DURATION
#undef SAFE_DRUNK_LEVEL