diff --git a/code/__DEFINES/actionspeed_modification.dm b/code/__DEFINES/actionspeed_modification.dm new file mode 100644 index 00000000000..40fbd3fe26d --- /dev/null +++ b/code/__DEFINES/actionspeed_modification.dm @@ -0,0 +1,2 @@ +//ids +#define ACTIONSPEED_ID_SANITY "sanity_component" diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index 55346f4f153..9762b0e7c92 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -264,7 +264,7 @@ GLOBAL_LIST_EMPTY(species_list) if(holding) holdingnull = FALSE //Users hand started holding something, check to see if it's still holding that - delay *= user.do_after_coefficent() + delay *= user.cached_multiplicative_actions_slowdown var/datum/progressbar/progbar if(progress) @@ -318,9 +318,6 @@ GLOBAL_LIST_EMPTY(species_list) LAZYREMOVE(user.do_afters, target) LAZYREMOVE(target.targeted_by, user) -/mob/proc/do_after_coefficent() // This gets added to the delay on a do_after, default 1 - . = 1 - return ///Timed action involving at least one mob user and a list of targets. /proc/do_after_mob(mob/user, list/targets, time = 3 SECONDS, uninterruptible = FALSE, progress = TRUE, datum/callback/extra_checks, required_mobility_flags = MOBILITY_STAND) @@ -332,6 +329,8 @@ GLOBAL_LIST_EMPTY(species_list) return FALSE var/user_loc = user.loc + time *= user.cached_multiplicative_actions_slowdown + var/drifting = FALSE if(!user.Process_Spacemove(0) && user.inertia_dir) drifting = TRUE diff --git a/code/datums/components/mood.dm b/code/datums/components/mood.dm index b39d4b417a1..65da13931e7 100644 --- a/code/datums/components/mood.dm +++ b/code/datums/components/mood.dm @@ -208,26 +208,32 @@ if(SANITY_INSANE to SANITY_CRAZY) setInsanityEffect(MAJOR_INSANITY_PEN) master.add_movespeed_modifier(/datum/movespeed_modifier/sanity/insane) + master.add_actionspeed_modifier(/datum/actionspeed_modifier/low_sanity) sanity_level = 6 if(SANITY_CRAZY to SANITY_UNSTABLE) setInsanityEffect(MINOR_INSANITY_PEN) master.add_movespeed_modifier(/datum/movespeed_modifier/sanity/crazy) + master.add_actionspeed_modifier(/datum/actionspeed_modifier/low_sanity) sanity_level = 5 if(SANITY_UNSTABLE to SANITY_DISTURBED) setInsanityEffect(0) master.add_movespeed_modifier(/datum/movespeed_modifier/sanity/disturbed) + master.add_actionspeed_modifier(/datum/actionspeed_modifier/low_sanity) sanity_level = 4 if(SANITY_DISTURBED to SANITY_NEUTRAL) setInsanityEffect(0) master.remove_movespeed_modifier(MOVESPEED_ID_SANITY) + master.remove_actionspeed_modifier(ACTIONSPEED_ID_SANITY) sanity_level = 3 if(SANITY_NEUTRAL+1 to SANITY_GREAT+1) //shitty hack but +1 to prevent it from responding to super small differences setInsanityEffect(0) master.remove_movespeed_modifier(MOVESPEED_ID_SANITY) + master.add_actionspeed_modifier(/datum/actionspeed_modifier/high_sanity) sanity_level = 2 if(SANITY_GREAT+1 to INFINITY) setInsanityEffect(0) master.remove_movespeed_modifier(MOVESPEED_ID_SANITY) + master.add_actionspeed_modifier(/datum/actionspeed_modifier/high_sanity) sanity_level = 1 update_mood_icon() diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm index c0ffc35e788..3130dd9a749 100644 --- a/code/datums/status_effects/status_effect.dm +++ b/code/datums/status_effects/status_effect.dm @@ -73,10 +73,6 @@ return duration = world.time + original_duration -//do_after modifier! -/datum/status_effect/proc/interact_speed_modifier() - return 1 - //clickdelay/nextmove modifiers! /datum/status_effect/proc/nextmove_modifier() return 1 diff --git a/code/datums/status_effects/wound_effects.dm b/code/datums/status_effects/wound_effects.dm index 90da3e61833..51db0a3038b 100644 --- a/code/datums/status_effects/wound_effects.dm +++ b/code/datums/status_effects/wound_effects.dm @@ -145,14 +145,25 @@ // bones /datum/status_effect/wound/blunt -/datum/status_effect/wound/blunt/interact_speed_modifier() - var/mob/living/carbon/C = owner +/datum/status_effect/wound/blunt/on_apply() + . = ..() + RegisterSignal(owner, COMSIG_MOB_SWAP_HANDS, .proc/on_swap_hands) + on_swap_hands() - if(C.get_active_hand() == linked_limb) - to_chat(C, "The [lowertext(linked_wound)] in your [linked_limb.name] slows your progress!") - return linked_wound.interaction_efficiency_penalty +/datum/status_effect/wound/blunt/on_remove() + . = ..() + UnregisterSignal(owner, COMSIG_MOB_SWAP_HANDS) + var/mob/living/carbon/wound_owner = owner + wound_owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/blunt_wound) - return 1 +/datum/status_effect/wound/blunt/proc/on_swap_hands() + SIGNAL_HANDLER + + var/mob/living/carbon/wound_owner = owner + if(wound_owner.get_active_hand() == linked_limb) + wound_owner.add_actionspeed_modifier(/datum/actionspeed_modifier/blunt_wound, (linked_wound.interaction_efficiency_penalty - 1)) + else + wound_owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/blunt_wound) /datum/status_effect/wound/blunt/nextmove_modifier() var/mob/living/carbon/C = owner diff --git a/code/modules/actionspeed/_actionspeed_modifier.dm b/code/modules/actionspeed/_actionspeed_modifier.dm new file mode 100644 index 00000000000..fe244e66532 --- /dev/null +++ b/code/modules/actionspeed/_actionspeed_modifier.dm @@ -0,0 +1,175 @@ +/*! Actionspeed modification datums. + + How action speed for mobs works + +Action speed is now calculated by using modifier datums which are added to mobs. Some of them (nonvariable ones) are globally cached, the variable ones are instanced and changed based on need. + +This gives us the ability to have multiple sources of actionspeed, reliabily keep them applied and remove them when they should be + +THey can have unique sources and a bunch of extra fancy flags that control behaviour + +Previously trying to update action speed was a shot in the dark that usually meant mobs got stuck going faster or slower + +Actionspeed modification list is a simple key = datum system. Key will be the datum's ID if it is overridden to not be null, or type if it is not. + +DO NOT override datum IDs unless you are going to have multiple types that must overwrite each other. It's more efficient to use types, ID functionality is only kept for cases where dynamic creation of modifiers need to be done. + +When update actionspeed is called, the list of items is iterated, according to flags priority and a bunch of conditions +this spits out a final calculated value which is used as a modifer to last_move + modifier for calculating when a mob +can next move + +*/ + +/datum/actionspeed_modifier + /// Whether or not this is a variable modifier. Variable modifiers can NOT be ever auto-cached. ONLY CHECKED VIA INITIAL(), EFFECTIVELY READ ONLY (and for very good reason) + var/variable = FALSE + + /// Unique ID. You can never have different modifications with the same ID. By default, this SHOULD NOT be set. Only set it for cases where you're dynamically making modifiers/need to have two types overwrite each other. If unset, uses path (converted to text) as ID. + var/id + + /// Higher ones override lower priorities. This is NOT used for ID, ID must be unique, if it isn't unique the newer one overwrites automatically if overriding. + var/priority = 0 + var/flags = NONE + + /// Multiplicative slowdown + var/multiplicative_slowdown = 0 + + /// Other modification datums this conflicts with. + var/conflicts_with + +/datum/actionspeed_modifier/New() + . = ..() + if(!id) + id = "[type]" //We turn the path into a string. + +GLOBAL_LIST_EMPTY(actionspeed_modification_cache) + +/// Grabs a STATIC MODIFIER datum from cache. YOU MUST NEVER EDIT THESE DATUMS, OR IT WILL AFFECT ANYTHING ELSE USING IT TOO! +/proc/get_cached_actionspeed_modifier(modtype) + if(!ispath(modtype, /datum/actionspeed_modifier)) + CRASH("[modtype] is not a actionspeed modification typepath.") + var/datum/actionspeed_modifier/actionspeed_mod = modtype + if(initial(actionspeed_mod.variable)) + CRASH("[modtype] is a variable modifier, and can never be cached.") + actionspeed_mod = GLOB.actionspeed_modification_cache[modtype] + if(!actionspeed_mod) + actionspeed_mod = GLOB.actionspeed_modification_cache[modtype] = new modtype + return actionspeed_mod + +///Add a action speed modifier to a mob. If a variable subtype is passed in as the first argument, it will make a new datum. If ID conflicts, it will overwrite the old ID. +/mob/proc/add_actionspeed_modifier(datum/actionspeed_modifier/type_or_datum, update = TRUE) + if(ispath(type_or_datum)) + if(!initial(type_or_datum.variable)) + type_or_datum = get_cached_actionspeed_modifier(type_or_datum) + else + type_or_datum = new type_or_datum + var/datum/actionspeed_modifier/existing = LAZYACCESS(actionspeed_modification, type_or_datum.id) + if(existing) + if(existing == type_or_datum) //same thing don't need to touch + return TRUE + remove_actionspeed_modifier(existing, FALSE) + if(length(actionspeed_modification)) + BINARY_INSERT(type_or_datum.id, actionspeed_modification, datum/actionspeed_modifier, type_or_datum, priority, COMPARE_VALUE) + LAZYSET(actionspeed_modification, type_or_datum.id, type_or_datum) + if(update) + update_actionspeed() + return TRUE + +/// Remove a action speed modifier from a mob, whether static or variable. +/mob/proc/remove_actionspeed_modifier(datum/actionspeed_modifier/type_id_datum, update = TRUE) + var/key + if(ispath(type_id_datum)) + key = initial(type_id_datum.id) || "[type_id_datum]" //id if set, path set to string if not. + else if(!istext(type_id_datum)) //if it isn't text it has to be a datum, as it isn't a type. + key = type_id_datum.id + else //assume it's an id + key = type_id_datum + if(!LAZYACCESS(actionspeed_modification, key)) + return FALSE + LAZYREMOVE(actionspeed_modification, key) + if(update) + update_actionspeed(FALSE) + return TRUE + +/*! Used for variable slowdowns like hunger/health loss/etc, works somewhat like the old list-based modification adds. Returns the modifier datum if successful + How this SHOULD work is: + 1. Ensures type_id_datum one way or another refers to a /variable datum. This makes sure it can't be cached. This includes if it's already in the modification list. + 2. Instantiate a new datum if type_id_datum isn't already instantiated + in the list, using the type. Obviously, wouldn't work for ID only. + 3. Add the datum if necessary using the regular add proc + 4. If any of the rest of the args are not null (see: multiplicative slowdown), modify the datum + 5. Update if necessary +*/ +/mob/proc/add_or_update_variable_actionspeed_modifier(datum/actionspeed_modifier/type_id_datum, update = TRUE, multiplicative_slowdown) + var/modified = FALSE + var/inject = FALSE + var/datum/actionspeed_modifier/final + if(istext(type_id_datum)) + final = LAZYACCESS(actionspeed_modification, type_id_datum) + if(!final) + CRASH("Couldn't find existing modification when provided a text ID.") + else if(ispath(type_id_datum)) + if(!initial(type_id_datum.variable)) + CRASH("Not a variable modifier") + final = LAZYACCESS(actionspeed_modification, initial(type_id_datum.id) || "[type_id_datum]") + if(!final) + final = new type_id_datum + inject = TRUE + modified = TRUE + else + if(!initial(type_id_datum.variable)) + CRASH("Not a variable modifier") + final = type_id_datum + if(!LAZYACCESS(actionspeed_modification, final.id)) + inject = TRUE + modified = TRUE + if(!isnull(multiplicative_slowdown)) + final.multiplicative_slowdown = multiplicative_slowdown + modified = TRUE + if(inject) + add_actionspeed_modifier(final, FALSE) + if(update && modified) + update_actionspeed(TRUE) + return final + +///Is there a actionspeed modifier for this mob +/mob/proc/has_actionspeed_modifier(datum/actionspeed_modifier/datum_type_id) + var/key + if(ispath(datum_type_id)) + key = initial(datum_type_id.id) || "[datum_type_id]" + else if(istext(datum_type_id)) + key = datum_type_id + else + key = datum_type_id.id + return LAZYACCESS(actionspeed_modification, key) + +/// Go through the list of actionspeed modifiers and calculate a final actionspeed. ANY ADD/REMOVE DONE IN UPDATE_actionspeed MUST HAVE THE UPDATE ARGUMENT SET AS FALSE! +/mob/proc/update_actionspeed() + . = 0 + var/list/conflict_tracker = list() + for(var/key in get_actionspeed_modifiers()) + var/datum/actionspeed_modifier/M = actionspeed_modification[key] + var/conflict = M.conflicts_with + var/amt = M.multiplicative_slowdown + if(conflict) + // Conflicting modifiers prioritize the larger slowdown or the larger speedup + // We purposefuly don't handle mixing speedups and slowdowns on the same id + if(abs(conflict_tracker[conflict]) < abs(amt)) + conflict_tracker[conflict] = amt + else + continue + . += amt + cached_multiplicative_actions_slowdown = . + +///Adds a default action speed +/mob/proc/initialize_actionspeed() + add_or_update_variable_actionspeed_modifier(/datum/actionspeed_modifier/base, multiplicative_slowdown = 1) + +/// Get the action speed modifiers list of the mob +/mob/proc/get_actionspeed_modifiers() + . = LAZYCOPY(actionspeed_modification) + for(var/id in actionspeed_mod_immunities) + . -= id + +/// Checks if a action speed modifier is valid and not missing any data +/proc/actionspeed_data_null_check(datum/actionspeed_modifier/M) //Determines if a data list is not meaningful and should be discarded. + . = !(M.multiplicative_slowdown) diff --git a/code/modules/actionspeed/modifiers/base.dm b/code/modules/actionspeed/modifiers/base.dm new file mode 100644 index 00000000000..97c5124c3bc --- /dev/null +++ b/code/modules/actionspeed/modifiers/base.dm @@ -0,0 +1,2 @@ +/datum/actionspeed_modifier/base + variable = TRUE diff --git a/code/modules/actionspeed/modifiers/mood.dm b/code/modules/actionspeed/modifiers/mood.dm new file mode 100644 index 00000000000..d8ed005d42b --- /dev/null +++ b/code/modules/actionspeed/modifiers/mood.dm @@ -0,0 +1,7 @@ +/datum/actionspeed_modifier/low_sanity + multiplicative_slowdown = 0.25 + id = ACTIONSPEED_ID_SANITY + +/datum/actionspeed_modifier/high_sanity + multiplicative_slowdown = -0.1 + id = ACTIONSPEED_ID_SANITY diff --git a/code/modules/actionspeed/modifiers/status_effects.dm b/code/modules/actionspeed/modifiers/status_effects.dm new file mode 100644 index 00000000000..615346b7902 --- /dev/null +++ b/code/modules/actionspeed/modifiers/status_effects.dm @@ -0,0 +1,5 @@ +/datum/actionspeed_modifier/timecookie + multiplicative_slowdown = -0.05 + +/datum/actionspeed_modifier/blunt_wound + variable = TRUE diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index b0f0e6363ac..7543af69243 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -984,20 +984,6 @@ set_usable_hands(usable_hands - 1) -/mob/living/carbon/do_after_coefficent() - . = ..() - var/datum/component/mood/mood = src.GetComponent(/datum/component/mood) //Currently, only carbons or higher use mood, move this once that changes. - if(mood) - switch(mood.sanity) //Alters do_after delay based on how sane you are - if(-INFINITY to SANITY_DISTURBED) - . *= 1.25 - if(SANITY_NEUTRAL to INFINITY) - . *= 0.90 - - for(var/i in status_effects) - var/datum/status_effect/S = i - . *= S.interact_speed_modifier() - /mob/living/carbon/proc/create_internal_organs() for(var/X in internal_organs) var/obj/item/organ/I = X diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 3373c2a10df..8cb38602ab3 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1158,10 +1158,6 @@ riding_datum.handle_vehicle_layer() . = ..(target, force, check_loc) -/mob/living/carbon/human/do_after_coefficent() - . = ..() - . *= physiology.do_after_speed - /mob/living/carbon/human/updatehealth() . = ..() dna?.species.spec_updatehealth(src) diff --git a/code/modules/mob/living/carbon/human/physiology.dm b/code/modules/mob/living/carbon/human/physiology.dm index 4d411deb53d..2a197b64ee2 100644 --- a/code/modules/mob/living/carbon/human/physiology.dm +++ b/code/modules/mob/living/carbon/human/physiology.dm @@ -23,7 +23,5 @@ var/hunger_mod = 1 //% of hunger rate taken per tick. - var/do_after_speed = 1 //Speed mod for do_after. Lower is better. If temporarily adjusting, please only modify using *= and /=, so you don't interrupt other calculations. - /datum/physiology/New() armor = new diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index dcce8a61193..d5df002705c 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -77,6 +77,7 @@ set_nutrition(rand(NUTRITION_LEVEL_START_MIN, NUTRITION_LEVEL_START_MAX)) . = ..() update_config_movespeed() + initialize_actionspeed() update_movespeed(TRUE) /** diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index f1afd6339c1..63dca4857dc 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -28,6 +28,12 @@ var/list/movespeed_mod_immunities //Lazy list, see mob_movespeed.dm /// The calculated mob speed slowdown based on the modifiers list var/cached_multiplicative_slowdown + /// List of action speed modifiers applying to this mob + var/list/actionspeed_modification //Lazy list, see mob_movespeed.dm + /// List of action speed modifiers ignored by this mob. List -> List (id) -> List (sources) + var/list/actionspeed_mod_immunities //Lazy list, see mob_movespeed.dm + /// The calculated mob action speed slowdown based on the modifiers list + var/cached_multiplicative_actions_slowdown /// List of action hud items the user has var/list/datum/action/actions /// A special action? No idea why this lives here diff --git a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm index f49b4c7ded1..3f072210a85 100644 --- a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm +++ b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm @@ -317,15 +317,12 @@ duration = 600 /datum/status_effect/timecookie/on_apply() - if(ishuman(owner)) - var/mob/living/carbon/human/H - H.physiology.do_after_speed *= 0.95 + owner.add_actionspeed_modifier(/datum/actionspeed_modifier/timecookie) return ..() /datum/status_effect/timecookie/on_remove() - if(ishuman(owner)) - var/mob/living/carbon/human/H - H.physiology.do_after_speed /= 0.95 + owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/timecookie) + return ..() /datum/status_effect/lovecookie id = "lovecookie" diff --git a/tgstation.dme b/tgstation.dme index 84bd7705fc7..41ff7d67395 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -23,6 +23,7 @@ #include "code\__DEFINES\_tick.dm" #include "code\__DEFINES\access.dm" #include "code\__DEFINES\achievements.dm" +#include "code\__DEFINES\actionspeed_modification.dm" #include "code\__DEFINES\admin.dm" #include "code\__DEFINES\antagonists.dm" #include "code\__DEFINES\atmospherics.dm" @@ -1327,6 +1328,10 @@ #include "code\game\turfs\open\floor\plating\misc_plating.dm" #include "code\game\turfs\open\space\space.dm" #include "code\game\turfs\open\space\transit.dm" +#include "code\modules\actionspeed\_actionspeed_modifier.dm" +#include "code\modules\actionspeed\modifiers\base.dm" +#include "code\modules\actionspeed\modifiers\mood.dm" +#include "code\modules\actionspeed\modifiers\status_effects.dm" #include "code\modules\admin\admin.dm" #include "code\modules\admin\admin_investigate.dm" #include "code\modules\admin\admin_ranks.dm"