Merge branch 'master' of github.com:tgstation/tgstation into upstream-2026-06-23

This commit is contained in:
Roxy
2026-06-23 15:51:55 -04:00
1007 changed files with 40287 additions and 33507 deletions
@@ -7,7 +7,7 @@
///what blackboard key are we setting
var/weather_key
/datum/component/ai_listen_to_weather/Initialize(weather_type = /datum/weather/ash_storm, weather_key = BB_STORM_APPROACHING)
/datum/component/ai_listen_to_weather/Initialize(weather_type = /datum/weather/particle/ash_storm, weather_key = BB_STORM_APPROACHING)
if(!isliving(parent))
return COMPONENT_INCOMPATIBLE
src.weather_type = weather_type
+1 -1
View File
@@ -98,7 +98,7 @@
baked_result.reagents.add_reagent_list(added_reagents)
if(who_baked_us)
ADD_TRAIT(baked_result, TRAIT_FOOD_CHEF_MADE, who_baked_us)
ADD_TRAIT(baked_result, TRAIT_HANDMADE, who_baked_us)
if(original_object.custom_materials)
baked_result.set_custom_materials(original_object.custom_materials, 1)
+1
View File
@@ -97,6 +97,7 @@
casting_spell = FALSE
if(!COOLDOWN_FINISHED(src, heal_cooldown))
to_chat(source, span_danger("Your chuuni powers are on cooldown! You fail to heal!"))
return
COOLDOWN_START(src, heal_cooldown, CHUUNIBYOU_COOLDOWN_TIME)
+1 -1
View File
@@ -120,7 +120,7 @@
/datum/crafting_recipe/vim
name = "Vim"
result = /obj/vehicle/sealed/car/vim
result = /obj/vehicle/sealed/mecha/vim
reqs = list(
/obj/item/clothing/head/helmet/space/eva = 1,
/obj/item/bodypart/leg/left/robot = 1,
+13 -9
View File
@@ -24,6 +24,8 @@ Behavior that's still missing from this component that original food items had t
var/food_flags = NONE
///Bitfield of the types of this food
var/foodtypes = NONE
///The main food complexity (read: quality) when handmade. It dictates the strength of the effect that this edible gives when eaten.
var/handmade_complexity = FOOD_COMPLEXITY_0
///Amount of seconds it takes to eat this food
var/eat_time = 3 SECONDS
///Defines how much it lowers someones satiety (Need to eat, essentialy)
@@ -59,6 +61,7 @@ Behavior that's still missing from this component that original food items had t
datum/callback/on_consume,
datum/callback/check_liked,
reagent_purity = 0.5,
handmade_complexity = FOOD_COMPLEXITY_0
)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
@@ -72,6 +75,7 @@ Behavior that's still missing from this component that original food items had t
src.foodtypes = foodtypes
src.eat_time = eat_time
src.eatverbs = string_list(eatverbs)
src.handmade_complexity = handmade_complexity
/datum/component/edible/RegisterWithParent()
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(examine))
@@ -136,6 +140,7 @@ Behavior that's still missing from this component that original food items had t
datum/callback/on_consume,
datum/callback/check_liked,
reagent_purity = 0.5,
handmade_complexity,
)
. = ..()
@@ -269,7 +274,7 @@ Behavior that's still missing from this component that original food items had t
examine_list += span_green("It is made of finest ingredients prolonging the effect!")
var/datum/mind/mind = user.mind
if(mind && HAS_TRAIT_FROM(owner, TRAIT_FOOD_CHEF_MADE, REF(mind)))
if(mind && HAS_TRAIT_FROM(owner, TRAIT_HANDMADE, REF(mind)))
examine_list += span_green("[owner] was made by you!")
if(!(food_flags & FOOD_IN_CONTAINER))
@@ -638,15 +643,14 @@ Behavior that's still missing from this component that original food items had t
var/quality_label = GLOB.food_quality_description[food_quality]
to_chat(gourmand, span_notice("That's \an [quality_label] meal."))
/// Get the complexity of the crafted food
/// Get the complexity of the crafted food. Some ingredients may influence this value.
/datum/component/edible/proc/get_recipe_complexity()
var/list/extra_complexity = list(0)
SEND_SIGNAL(parent, COMSIG_FOOD_GET_EXTRA_COMPLEXITY, extra_complexity)
var/complexity_to_add = extra_complexity[1]
if(!HAS_TRAIT(parent, TRAIT_FOOD_CHEF_MADE) || !istype(parent, /obj/item/food))
return complexity_to_add // It is factory made. Soulless.
var/obj/item/food/food = parent
return food.crafting_complexity + complexity_to_add
var/complexity = FOOD_COMPLEXITY_0
if(HAS_TRAIT(parent, TRAIT_HANDMADE))
complexity += handmade_complexity
var/list/complexity_holder = list(complexity)
SEND_SIGNAL(parent, COMSIG_FOOD_GET_EXTRA_COMPLEXITY, complexity_holder)
return complexity_holder[1]
/// Get food quality adjusted according to eater's preferences
/datum/component/edible/proc/get_perceived_food_quality(mob/living/eater)
+1 -1
View File
@@ -167,7 +167,7 @@
SEND_SIGNAL(parent, COMSIG_ITEM_GRILLED, grilled_result)
SEND_SIGNAL(grilled_result, COMSIG_ITEM_GRILLED_RESULT, parent)
if(who_placed_us)
ADD_TRAIT(grilled_result, TRAIT_FOOD_CHEF_MADE, who_placed_us)
ADD_TRAIT(grilled_result, TRAIT_HANDMADE, who_placed_us)
grill_source.visible_message("<span class='[positive_result ? "notice" : "warning"]'>[parent] turns into \a [grilled_result]!</span>")
grilled_result.pixel_x = original_object.pixel_x
@@ -18,7 +18,7 @@
var/list/starting_signals = list()
var/list/ending_signals = list()
for(var/datum/weather/weather_type as anything in typesof(/datum/weather))
for(var/datum/weather/weather_type as anything in valid_subtypesof(/datum/weather))
starting_signals += COMSIG_WEATHER_TELEGRAPH(weather_type)
ending_signals += COMSIG_WEATHER_END(weather_type)
+14 -5
View File
@@ -136,13 +136,22 @@
if(source.client.intended_direction && check_on_move.Invoke(TRUE)) //You use jet when press keys. yes.
trail?.generate_effect()
/// Handles all active 0g movement, including both manual (trying to move a direction in 0g) and automatic (drifting idly in 0g)
/datum/component/jetpack/proc/stabilize(mob/source, movement_dir, continuous_move, backup)
SIGNAL_HANDLER
if(!continuous_move && movement_dir)
return COMSIG_MOVABLE_STOP_SPACEMOVE
// Check if we have the fuel to stop this. Do NOT consume any fuel, just check
// This is done because things other then us can use our fuel
if(stabilize && check_on_move.Invoke(FALSE))
/*
* Checks if we should stop any active movement
*
* Obviously we stop all forms of drifting if we have stabilizers active (allowing free space movement)
* Less obviously, we stop need to stop drift if we are trying to move *while passively drifting*
* (Without checking the latter, jetpacks will act very weird and jank. As you move in one direction,
* you will simultaneously drift in that direction, causing you to jump/skip a tile every so often.)
*
* Either way, we need to check that we have the "fuel" to stop this
* DO NOT CONSUME FUEL HERE, just check if we have it
* This is done because things other then us can use our fuel
*/
if((stabilize || (!continuous_move && movement_dir)) && check_on_move.Invoke(FALSE))
return COMSIG_MOVABLE_STOP_SPACEMOVE
return NONE
@@ -8,6 +8,10 @@
#define MESSAGE_VOICE "voice"
/// the tone it should be said in
#define MESSAGE_PITCH "pitch"
/// the blip base it should be said in
#define MESSAGE_BLIP_BASE "blip_base"
/// the blip base it should be said in
#define MESSAGE_BLIP_NUMBER "blip_number"
/// Simple element that will deterministically set a value based on stuff that the source has heard and will then compel the source to repeat it.
/// Requires a valid AI Blackboard.
@@ -24,6 +28,8 @@
var/static/list/invalid_voice = list(
MESSAGE_VOICE = "invalid",
MESSAGE_PITCH = 0,
MESSAGE_BLIP_BASE = "male",
MESSAGE_BLIP_NUMBER = 1,
)
/datum/component/listen_and_repeat/Initialize(list/desired_phrases, blackboard_key)
@@ -68,6 +74,8 @@
var/atom/movable/movable_speaker = speaker
speaker_sound[MESSAGE_VOICE] = movable_speaker.voice || "invalid"
speaker_sound[MESSAGE_PITCH] = (movable_speaker.pitch && SStts.pitch_enabled ? movable_speaker.pitch : 0)
speaker_sound[MESSAGE_BLIP_BASE] = movable_speaker.blip_base || "male"
speaker_sound[MESSAGE_BLIP_NUMBER] = movable_speaker.blip_number || 1
if(over_radio && prob(RADIO_IGNORE_CHANCE))
return
@@ -99,6 +107,9 @@
if(islist(speech_buffer[selected_phrase]))
to_return[MESSAGE_VOICE] = speech_buffer[selected_phrase][MESSAGE_VOICE]
to_return[MESSAGE_PITCH] = speech_buffer[selected_phrase][MESSAGE_PITCH]
to_return[MESSAGE_BLIP_BASE] = speech_buffer[selected_phrase][MESSAGE_BLIP_BASE]
to_return[MESSAGE_BLIP_NUMBER] = speech_buffer[selected_phrase][MESSAGE_BLIP_NUMBER]
controller.override_blackboard_key(blackboard_key, to_return)
@@ -117,3 +128,5 @@
#undef MESSAGE_VOICE
#undef MESSAGE_PITCH
#undef MESSAGE_LINE
#undef MESSAGE_BLIP_BASE
#undef MESSAGE_BLIP_NUMBER
+89
View File
@@ -0,0 +1,89 @@
/**
* A component that allows the attached mind to listen prayers from other mobs.
* Unlike admins, they cannot smite the person for it, also the prayers are anonymous unless the person says who he's in the message.
*/
/datum/component/listen_prayers
///A callback called before the prayer is heard. May alter the message or prevent it from being heard if it returns FALSE
var/datum/callback/pre_prayer_callback
///This will be affixed to the "deities" that have heard this message.
var/deity_name
///The action that the owner can use to ignore the prayers
var/datum/action/innate/listen_prayers/toggle
///The description we give to the instance of the listen_prayers toggle.
var/toggle_desc
/datum/component/listen_prayers/Initialize(datum/callback/pre_prayer_callback, deity_name, toggle_desc)
if (!istype(parent, /datum/mind))
return COMPONENT_INCOMPATIBLE
src.pre_prayer_callback = pre_prayer_callback
src.deity_name = deity_name
src.toggle_desc = toggle_desc
/datum/component/listen_prayers/Destroy()
pre_prayer_callback = null
return ..()
/datum/component/listen_prayers/RegisterWithParent()
RegisterSignal(SSdcs, COMSIG_GLOB_SEND_PRAYER, PROC_REF(on_sent_prayer))
var/datum/mind/mind = parent
var/datum/action/innate/listen_prayers/toggle = new(mind)
if(toggle_desc)
toggle.desc = toggle_desc
if(mind.current)
toggle.Grant(mind.current)
/datum/component/listen_prayers/UnregisterFromParent()
UnregisterSignal(SSdcs, COMSIG_GLOB_SEND_PRAYER)
QDEL_NULL(toggle)
/datum/component/listen_prayers/proc/on_sent_prayer(source, mob/praying, message, prayer_type, symbol, list/deities_that_listened)
SIGNAL_HANDLER
var/datum/mind/mind = parent
if(!mind.current || mind.current.stat >= UNCONSCIOUS || !mind.current.client) //You can't hear prayers if unconscious or disconnected
return
if(!isliving(praying) || praying.stat == DEAD)
return FALSE //I don't see any reason in hell to why dead people should be allowed into this. This isn't a knockoff TRAIT_SIXTHSENSE.
if(praying == mind.current) //Ignore prayers coming from ourselves.
return
if(mind.current.client in GLOB.admins) //This is redundant if we're adminning
return
if(HAS_MIND_TRAIT(mind.current, TRAIT_DONT_HEAR_PRAYERS))
return
var/list/arguments = args.Copy(2)
if(pre_prayer_callback && !pre_prayer_callback.Invoke(arguments))
return
prayer_type = arguments[ARG_PRAYER_TYPE]
symbol = arguments[ARG_PRAYER_SYMBOL]
message = "[icon2html(arguments[ARG_PRAYER_SYMBOL], mind.current.client)]<b><font color=[GLOB.prayer_type_to_font_color[prayer_type]]>[prayer_type]: </font></b> [span_notice(arguments[ARG_PRAYER_MSG])]"
to_chat(mind.current, custom_boxed_message(GLOB.prayer_type_to_message_box[prayer_type], message))
SEND_SOUND(mind.current, sound('sound/effects/pray.ogg'))
if(deity_name)
deities_that_listened += deity_name
///An action to stop hearing prayers on command. Doesn't do a whole lot without the associated component
/datum/action/innate/listen_prayers
name = "Listen Prayers"
button_icon = 'icons/hud/actions.dmi'
button_icon_state = "pray"
desc = "Allows you to eavesdrop on prayers from the world around you."
active = TRUE
/datum/action/innate/listen_prayers/Destroy()
REMOVE_TRAIT(owner, TRAIT_DONT_HEAR_PRAYERS, ACTION_TRAIT)
return ..()
/datum/action/innate/listen_prayers/is_action_active(atom/movable/screen/movable/action_button/current_button)
return !HAS_TRAIT_FROM(owner, TRAIT_DONT_HEAR_PRAYERS, ACTION_TRAIT)
/datum/action/innate/listen_prayers/Activate()
active = TRUE
REMOVE_TRAIT(owner, TRAIT_DONT_HEAR_PRAYERS, ACTION_TRAIT)
to_chat(owner, span_green("You are ready to listen to prayers once again."))
build_all_button_icons(UPDATE_BUTTON_BACKGROUND)
/datum/action/innate/listen_prayers/Deactivate()
active = FALSE
ADD_TRAIT(owner, TRAIT_DONT_HEAR_PRAYERS, ACTION_TRAIT)
to_chat(owner, span_green("You stop listening to prayers."))
build_all_button_icons(UPDATE_BUTTON_BACKGROUND)
+4 -2
View File
@@ -226,11 +226,14 @@
. = ..()
if(action != "keypad")
return TRUE
var/atom/source = parent
playsound(source, SFX_TERMINAL_TYPE, 50, FALSE)
var/digit = params["digit"]
switch(digit)
//locking it back up
if("C")
var/atom/source = parent
numeric_input = ""
//you can't lock it if it's already locked or lacks a lock code.
if(source.atom_storage.locked || isnull(lock_code))
@@ -249,7 +252,6 @@
//unlocking the current code.
if(numeric_input != lock_code)
return TRUE
var/atom/source = parent
source.atom_storage.set_locked(STORAGE_NOT_LOCKED)
numeric_input = ""
return TRUE
+4 -4
View File
@@ -69,9 +69,9 @@
user.name = target.name
user.reset_perspective(target)
target.AddElement(/datum/element/weather_listener, /datum/weather/ash_storm, ZTRAIT_ASHSTORM, GLOB.ash_storm_sounds)
target.AddElement(/datum/element/weather_listener, /datum/weather/particle/ash_storm, ZTRAIT_ASHSTORM, GLOB.ash_storm_sounds)
target.AddElement(/datum/element/weather_listener, /datum/weather/snow_storm, ZTRAIT_SNOWSTORM, GLOB.snowstorm_sounds)
target.AddElement(/datum/element/weather_listener, /datum/weather/rain_storm, ZTRAIT_RAINSTORM, GLOB.rain_storm_sounds)
target.AddElement(/datum/element/weather_listener, /datum/weather/particle/rain_storm, ZTRAIT_RAINSTORM, GLOB.rain_storm_sounds)
target.AddElement(/datum/element/weather_listener, /datum/weather/sand_storm, ZTRAIT_SANDSTORM, GLOB.sand_storm_sounds)
RegisterSignal(target, COMSIG_QDELETING, PROC_REF(end_possession))
@@ -84,8 +84,8 @@
var/mob/poltergeist = parent
possessed.RemoveElement(/datum/element/weather_listener, /datum/weather/ash_storm, ZTRAIT_ASHSTORM, GLOB.ash_storm_sounds)
possessed.RemoveElement(/datum/element/weather_listener, /datum/weather/rain_storm, ZTRAIT_RAINSTORM, GLOB.rain_storm_sounds)
possessed.RemoveElement(/datum/element/weather_listener, /datum/weather/particle/ash_storm, ZTRAIT_ASHSTORM, GLOB.ash_storm_sounds)
possessed.RemoveElement(/datum/element/weather_listener, /datum/weather/particle/rain_storm, ZTRAIT_RAINSTORM, GLOB.rain_storm_sounds)
possessed.RemoveElement(/datum/element/weather_listener, /datum/weather/sand_storm, ZTRAIT_SANDSTORM, GLOB.sand_storm_sounds)
possessed.RemoveElement(/datum/element/weather_listener, /datum/weather/snow_storm, ZTRAIT_SNOWSTORM, GLOB.snowstorm_sounds)
UnregisterSignal(possessed, COMSIG_QDELETING)
@@ -41,7 +41,12 @@
INVOKE_ASYNC(src, PROC_REF(slow_mine), user, target)
return
var/mob/living/driver = null
if (pass_driver && length(user.buckled_mobs))
driver = user.buckled_mobs[1]
last_bumpmine_tick = world.time
SEND_SIGNAL(parent, COMSIG_PROFICIENT_MINER_MINED, mineral_wall, driver || user)
mineral_wall.gets_drilled(source)
/datum/component/proficient_miner/proc/slow_mine(mob/living/user, turf/closed/mineral/mineral_wall)
@@ -65,4 +70,5 @@
playsound(user, pick(mine_sounds), 50)
if(istype(mineral_wall))
SEND_SIGNAL(parent, COMSIG_PROFICIENT_MINER_MINED, mineral_wall, driver || user)
mineral_wall.gets_drilled(driver || user)
+71 -20
View File
@@ -119,24 +119,35 @@
return COMPONENT_DRIVER_BLOCK_MOVE
var/mob/living/living_parent = parent
step(living_parent, direction)
var/modified_move_delay = uses_native_speed ? living_parent.cached_multiplicative_slowdown : vehicle_move_delay
if(HAS_TRAIT(user, TRAIT_ROUGHRIDER)) // YEEHAW!
switch(HAS_TRAIT(user, TRAIT_PRIMITIVE) ? SANITY_LEVEL_GREAT : user.mob_mood?.sanity_level)
if(SANITY_LEVEL_GREAT)
modified_move_delay *= 0.8
if(SANITY_LEVEL_NEUTRAL)
modified_move_delay *= 0.9
if(SANITY_LEVEL_DISTURBED)
modified_move_delay *= 1
if(SANITY_LEVEL_CRAZY)
modified_move_delay *= 1.1
if(SANITY_LEVEL_INSANE)
modified_move_delay *= 1.2
var/modified_move_delay = get_move_delay(living_parent, user, direction)
if(NSCOMPONENT(direction) && EWCOMPONENT(direction))
modified_move_delay = FLOOR(modified_move_delay * sqrt(2), world.tick_lag)
COOLDOWN_START(src, vehicle_move_cooldown, modified_move_delay)
return ..()
/// Calculates and returns movement delay for a certain direction
/datum/component/riding/creature/proc/get_move_delay(mob/living/living_parent, mob/living/user, direction)
var/modified_move_delay = uses_native_speed ? living_parent.cached_multiplicative_slowdown : vehicle_move_delay
if(HAS_TRAIT(user, TRAIT_ROUGHRIDER)) // YEEHAW!
modified_move_delay *= get_roughrider_mult(user)
return modified_move_delay
/datum/component/riding/creature/proc/get_roughrider_mult(mob/living/user)
if (HAS_TRAIT(user, TRAIT_PRIMITIVE))
return 0.8
switch(user.mob_mood?.sanity_level)
if(SANITY_LEVEL_GREAT)
return 0.8
if(SANITY_LEVEL_NEUTRAL)
return 0.9
if(SANITY_LEVEL_DISTURBED)
return 1
if(SANITY_LEVEL_CRAZY)
return 1.1
if(SANITY_LEVEL_INSANE)
return 1.2
return 1
/// Yeets the rider off, used for animals and cyborgs, redefined for humans who shove their piggyback rider off
/datum/component/riding/creature/proc/force_dismount(mob/living/rider, throw_range = 8, throw_speed = 3, gentle = FALSE)
var/atom/movable/movable_parent = parent
@@ -566,19 +577,53 @@
keytype = /obj/item/key/lasso
uses_native_speed = TRUE
rider_traits = list(TRAIT_NO_FLOATING_ANIM, TRAIT_TENTACLE_IMMUNE)
/// Flat speed boost to ourselves
var/flat_speed_mod = -9.5
/// Last direction we've moved in
var/last_move_dir = null
/// Current speed boost
var/speed_boost = 0
/// Speed boost per time equivalent tile of movement in the same direction
/// Not directly per tile as we move faster and thus would accelerate faster
var/rush_speed_boost = -0.3
/// Maximum speed boost we can have
var/maximum_boost = -3
/// Have we spawned an afterimage last move?
var/spawned_last_move = FALSE
/datum/component/riding/creature/goliath/deathmatch
keytype = null
/datum/component/riding/creature/goliath/Initialize(mob/living/riding_mob, force, ride_check_flags)
/datum/component/riding/creature/goliath/driver_move(atom/movable/movable_parent, mob/living/user, direction)
if (speed_boost != maximum_boost)
return ..()
// At maximum acceleration, start spawning afterimages
var/turf/old_loc = movable_parent.loc
. = ..()
var/mob/living/basic/mining/goliath/goliath = parent
goliath.add_movespeed_modifier(/datum/movespeed_modifier/goliath_mount)
if (. & COMPONENT_DRIVER_BLOCK_MOVE)
return
if (!spawned_last_move && istype(old_loc))
new /obj/effect/temp_visual/decoy/fading(old_loc, movable_parent, 150)
spawned_last_move = !spawned_last_move
/datum/component/riding/creature/goliath/Destroy(force)
var/mob/living/basic/mining/goliath/goliath = parent
goliath.remove_movespeed_modifier(/datum/movespeed_modifier/goliath_mount)
return ..()
/datum/component/riding/creature/goliath/get_move_delay(mob/living/living_parent, mob/living/user, direction)
var/move_delay = living_parent.cached_multiplicative_slowdown + flat_speed_mod
// We give grace of 2 ticks of stopped movement, or 0.1 seconds, in case of SSinput not being able to process all inputs in a single tick
if (last_move_dir != direction || vehicle_move_cooldown + 0.1 SECONDS < world.time)
last_move_dir = direction
// If we're "drifting" only halve our speed instead
if (last_move_dir & direction)
speed_boost /= 2
else
speed_boost = 0
var/modified_move_delay = move_delay + max(maximum_boost, speed_boost)
speed_boost += rush_speed_boost * (modified_move_delay / move_delay)
// Apply roughrider boost last
if(HAS_TRAIT(user, TRAIT_ROUGHRIDER))
modified_move_delay *= get_roughrider_mult(user)
return modified_move_delay
/datum/component/riding/creature/goliath/get_rider_offsets_and_layers(pass_index, mob/offsetter)
return list(
@@ -646,12 +691,18 @@
. = ..()
var/mob/living/basic/mining/goldgrub/goldgrub = parent
goldgrub.add_movespeed_modifier(/datum/movespeed_modifier/goldgrub_mount)
RegisterSignal(goldgrub, COMSIG_PROFICIENT_MINER_MINED, PROC_REF(on_mined))
/datum/component/riding/creature/goldgrub/Destroy(force)
var/mob/living/basic/mining/goldgrub/goldgrub = parent
goldgrub.remove_movespeed_modifier(/datum/movespeed_modifier/goldgrub_mount)
return ..()
/datum/component/riding/creature/goldgrub/proc/on_mined(datum/source, turf/closed/wall/mineral/rock, mob/living/user)
SIGNAL_HANDLER
// Reset movement cooldown once you've dug a tile
COOLDOWN_RESET(src, vehicle_move_cooldown)
/datum/component/riding/creature/goldgrub/get_rider_offsets_and_layers(pass_index, mob/offsetter)
return list(
TEXT_NORTH = list(0, 3),
+1 -1
View File
@@ -4,7 +4,7 @@
/datum/component/storm_hating
/// Types of weather which trigger the effect
var/static/list/stormy_weather = list(
/datum/weather/ash_storm,
/datum/weather/particle/ash_storm,
/datum/weather/snow_storm,
/datum/weather/void_storm,
)
+1 -1
View File
@@ -284,7 +284,7 @@
if(!isnull(throwforce_on))
source.throwforce = throwforce_off
if(!isnull(throw_speed_on))
source.throw_speed = throwforce_off
source.throw_speed = throw_speed_off
if(LAZYLEN(attack_verb_continuous_on))
source.attack_verb_continuous = attack_verb_continuous_off
+1 -1
View File
@@ -119,7 +119,7 @@
src.force_multiplier = force_multiplier
if(!isnull(force_wielded))
src.force_wielded = force_wielded
if(isnull(force_unwielded))
if(!isnull(force_unwielded))
src.force_unwielded = force_unwielded
if(icon_wielded)
src.icon_wielded = icon_wielded
+161
View File
@@ -0,0 +1,161 @@
/**
* Walking Aid Component
*
* Add this to an item to allow it to act as a cane (or crutch) while held.
* Items with this component will help mobs avoid limping from broken
* leg bones, and lessen the slowdown caused by missing legs.
*
* Used by canes, crutches, and pole-like items such as spears and staffs.
*/
/datum/component/walking_aid
dupe_mode = COMPONENT_DUPE_UNIQUE
/// Causes a mob to waddle (wiggle) while walking when holding this object
var/waddling = FALSE
/// If set, the parent item must have this trait for the support to function
var/required_trait
/// Weakref to the mob currently being supported
var/datum/weakref/current_user_ref
/// The amount of slowdown to reduce for a limbless leg
var/limbless_slowdown_modifier = 0.6 // reduces slowdown by 40%
/datum/component/walking_aid/Initialize(limbless_slowdown_modifier = 0.6, required_trait = null, waddling = FALSE)
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
src.waddling = waddling
src.required_trait = required_trait
src.limbless_slowdown_modifier = limbless_slowdown_modifier
/datum/component/walking_aid/Destroy(force)
remove_support()
return ..()
/datum/component/walking_aid/RegisterWithParent()
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip))
RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(on_drop))
RegisterSignal(parent, COMSIG_ATOM_EXAMINE_TAGS, PROC_REF(get_examine_tags))
RegisterSignal(parent, SIGNAL_ADDTRAIT(TRAIT_WIELDED), PROC_REF(update_legs))
RegisterSignal(parent, SIGNAL_REMOVETRAIT(TRAIT_WIELDED), PROC_REF(update_legs))
RegisterSignal(parent, SIGNAL_ADDTRAIT(required_trait), PROC_REF(update_legs))
RegisterSignal(parent, SIGNAL_REMOVETRAIT(required_trait), PROC_REF(update_legs))
/datum/component/walking_aid/UnregisterFromParent()
UnregisterSignal(parent, list(
COMSIG_ITEM_EQUIPPED,
COMSIG_ITEM_DROPPED,
COMSIG_ATOM_EXAMINE_TAGS,
SIGNAL_ADDTRAIT(TRAIT_WIELDED),
SIGNAL_REMOVETRAIT(TRAIT_WIELDED),
SIGNAL_ADDTRAIT(required_trait),
SIGNAL_REMOVETRAIT(required_trait),
))
remove_support()
/datum/component/walking_aid/proc/on_equip(datum/source, mob/equipper, slot)
SIGNAL_HANDLER
remove_support()
if(!(slot & ITEM_SLOT_HANDS))
return
if(!isliving(equipper))
return
apply_support(equipper)
/datum/component/walking_aid/proc/on_drop(datum/source, mob/user)
SIGNAL_HANDLER
remove_support()
/datum/component/walking_aid/proc/get_examine_tags(atom/source, mob/user, list/examine_list)
SIGNAL_HANDLER
examine_list["walking-aid"] = "It can help lessen the slowdown caused from a missing or injured leg, when held on the same side as the injury."
// Updates our leg status when wielded/unwielded a two handed walking aid like a spear
/datum/component/walking_aid/proc/update_legs(atom/source)
SIGNAL_HANDLER
var/mob/living/user = current_user_ref?.resolve()
user?.update_usable_leg_status()
/datum/component/walking_aid/proc/apply_support(mob/living/user)
if(current_user_ref)
remove_support()
current_user_ref = WEAKREF(user)
RegisterSignal(user, COMSIG_CARBON_LIMPING, PROC_REF(handle_limping))
RegisterSignal(user, COMSIG_LIVING_LIMBLESS_SLOWDOWN, PROC_REF(handle_slowdown))
user.update_usable_leg_status()
if(waddling)
user.AddElementTrait(TRAIT_WADDLING, REF(src), /datum/element/waddling)
/datum/component/walking_aid/proc/remove_support()
var/mob/living/user = current_user_ref?.resolve()
current_user_ref = null
if(isnull(user))
return
UnregisterSignal(user, list(COMSIG_CARBON_LIMPING, COMSIG_LIVING_LIMBLESS_SLOWDOWN))
user.update_usable_leg_status()
if(waddling)
REMOVE_TRAIT(user, TRAIT_WADDLING, REF(src))
/datum/component/walking_aid/proc/is_active()
// if both hands are holding it, then it is not being used for support
if(HAS_TRAIT(parent, TRAIT_WIELDED))
return FALSE
if(isnull(required_trait))
return TRUE
return HAS_TRAIT(parent, required_trait)
/datum/component/walking_aid/proc/handle_limping(mob/living/user, obj/item/bodypart/limping_leg)
SIGNAL_HANDLER
if(!is_active())
return NONE
if(isnull(limping_leg))
return NONE
var/supported_zone = get_supported_leg_zone(user)
if(isnull(supported_zone))
return NONE
if(limping_leg.body_zone != supported_zone)
return NONE
return COMPONENT_CANCEL_LIMP
/datum/component/walking_aid/proc/handle_slowdown(mob/living/user, limbless_slowdown, list/slowdown_mods)
SIGNAL_HANDLER
if(!is_active())
return
if(!iscarbon(user))
return
var/mob/living/carbon/carbon_user = user
var/leg_amount = carbon_user.usable_legs
if(leg_amount >= carbon_user.default_num_legs)
return
if(!leg_amount) // someday support dual-wielding crutches but for now they are destined to waddle
return
var/supported_zone = get_supported_leg_zone(user)
if(isnull(supported_zone))
return
if(carbon_user.get_bodypart(supported_zone)) // make sure their leg is actually missing
return
slowdown_mods += limbless_slowdown_modifier
/datum/component/walking_aid/proc/get_supported_leg_zone(mob/living/user)
var/held_hand_zone = user.get_hand_zone_of_item(parent)
switch(held_hand_zone)
if(BODY_ZONE_R_ARM)
return BODY_ZONE_R_LEG
if(BODY_ZONE_L_ARM)
return BODY_ZONE_L_LEG
else
return null