diff --git a/code/__DEFINES/cooldowns.dm b/code/__DEFINES/cooldowns.dm new file mode 100644 index 00000000000..88520028f4d --- /dev/null +++ b/code/__DEFINES/cooldowns.dm @@ -0,0 +1,70 @@ +//// COOLDOWN SYSTEMS +/* + * We have 2 cooldown systems: timer cooldowns (divided between stoppable and regular) and world.time cooldowns. + * + * When to use each? + * + * * Adding a commonly-checked cooldown, like on a subsystem to check for processing + * * * Use the world.time ones, as they are cheaper. + * + * * Adding a rarely-used one for special situations, such as giving an uncommon item a cooldown on a target. + * * * Timer cooldown, as adding a new variable on each mob to track the cooldown of said uncommon item is going too far. + * + * * Triggering events at the end of a cooldown. + * * * Timer cooldown, registering to its signal. + * + * * Being able to check how long left for the cooldown to end. + * * * Either world.time or stoppable timer cooldowns, depending on the other factors. Regular timer cooldowns do not support this. + * + * * Being able to stop the timer before it ends. + * * * Either world.time or stoppable timer cooldowns, depending on the other factors. Regular timer cooldowns do not support this. +*/ + + +/* + * Cooldown system based on an datum-level associative lazylist using timers. +*/ + +//INDEXES +#define COOLDOWN_BORG_SELF_REPAIR "borg_self_repair" + + +//TIMER COOLDOWN MACROS + +#define COMSIG_CD_STOP(cd_index) "cooldown_[cd_index]" +#define COMSIG_CD_RESET(cd_index) "cd_reset_[cd_index]" + +#define TIMER_COOLDOWN_START(cd_source, cd_index, cd_time) LAZYSET(cd_source.cooldowns, cd_index, addtimer(CALLBACK(GLOBAL_PROC, /proc/end_cooldown, cd_source, cd_index), cd_time)) + +#define TIMER_COOLDOWN_CHECK(cd_source, cd_index) LAZYACCESS(cd_source.cooldowns, cd_index) + +#define TIMER_COOLDOWN_END(cd_source, cd_index) LAZYREMOVE(cd_source.cooldowns, cd_index) + +/* + * Stoppable timer cooldowns. + * Use indexes the same as the regular tiemr cooldowns. + * They make use of the TIMER_COOLDOWN_CHECK() and TIMER_COOLDOWN_END() macros the same, just not the TIMER_COOLDOWN_START() one. + * A bit more expensive than the regular timers, but can be reset before they end and the time left can be checked. +*/ + +#define S_TIMER_COOLDOWN_START(cd_source, cd_index, cd_time) LAZYSET(cd_source.cooldowns, cd_index, addtimer(CALLBACK(GLOBAL_PROC, /proc/end_cooldown, cd_source, cd_index), cd_time, TIMER_STOPPABLE)) + +#define S_TIMER_COOLDOWN_RESET(cd_source, cd_index) reset_cooldown(cd_source, cd_index) + +#define S_TIMER_COOLDOWN_TIMELEFT(cd_source, cd_index) (timeleft(TIMER_COOLDOWN_CHECK(cd_source, cd_index))) + + +/* + * Cooldown system based on storing world.time on a variable, plus the cooldown time. + * Better performance over timer cooldowns, lower control. Same functionality. +*/ + +#define COOLDOWN_DECLARE(cd_index) var/##cd_index = 0 + +#define COOLDOWN_START(cd_source, cd_index, cd_time) (cd_source.cd_index = world.time + cd_time) + +#define COOLDOWN_CHECK(cd_source, cd_index) (cd_source.cd_index < world.time) + +#define COOLDOWN_RESET(cd_source, cd_index) cd_source.cd_index = 0 + +#define COOLDOWN_TIMELEFT(cd_source, cd_index) (max(0, cd_source.cd_index - world.time)) diff --git a/code/datums/action.dm b/code/datums/action.dm index 52eee38f51e..591b8667f77 100644 --- a/code/datums/action.dm +++ b/code/datums/action.dm @@ -519,10 +519,9 @@ background_icon_state = "bg_agent" icon_icon = 'icons/mob/actions/actions_items.dmi' button_icon_state = "deploy_box" - ///Cooldown between deploys. Uses world.time - var/cooldown = 0 ///The type of closet this action spawns. var/boxtype = /obj/structure/closet/cardboard/agent + COOLDOWN_DECLARE(box_cooldown) ///Handles opening and closing the box. /datum/action/item_action/agent_box/Trigger() @@ -538,11 +537,12 @@ if(!isturf(owner.loc)) //Don't let the player use this to escape mechs/welded closets. to_chat(owner, "You need more space to activate this implant!") return - if(cooldown < world.time - 100) - var/box = new boxtype(owner.drop_location()) - owner.forceMove(box) - cooldown = world.time - owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE) + if(COOLDOWN_CHECK(src, box_cooldown)) + return + COOLDOWN_START(src, box_cooldown, 10 SECONDS) + var/box = new boxtype(owner.drop_location()) + owner.forceMove(box) + owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE) //Preset for spells /datum/action/spell_action diff --git a/code/datums/components/caltrop.dm b/code/datums/components/caltrop.dm index 1e91d771685..73fd0e16ef8 100644 --- a/code/datums/components/caltrop.dm +++ b/code/datums/components/caltrop.dm @@ -3,8 +3,8 @@ var/max_damage var/probability var/flags + COOLDOWN_DECLARE(caltrop_cooldown) - var/cooldown = 0 /datum/component/caltrop/Initialize(_min_damage = 0, _max_damage = 0, _probability = 100, _flags = NONE) min_damage = _min_damage @@ -50,7 +50,9 @@ if(HAS_TRAIT(H, TRAIT_LIGHT_STEP)) damage *= 0.75 - if(cooldown < world.time - 10) //cooldown to avoid message spam. + + if(!COOLDOWN_CHECK(src, caltrop_cooldown)) + COOLDOWN_START(src, caltrop_cooldown, 1 SECONDS) //cooldown to avoid message spam. var/atom/A = parent if(!H.incapacitated(ignore_restraints = TRUE)) H.visible_message("[H] steps on [A].", \ @@ -59,6 +61,5 @@ H.visible_message("[H] slides on [A]!", \ "You slide on [A]!") - cooldown = world.time H.apply_damage(damage, BRUTE, picked_def_zone, wound_bonus = CANT_WOUND) H.Paralyze(60) diff --git a/code/datums/datum.dm b/code/datums/datum.dm index 4608b0ceb68..bf0f877ed01 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -49,6 +49,14 @@ /// A weak reference to another datum var/datum/weakref/weak_reference + /* + * Lazy associative list of currently active cooldowns. + * + * cooldowns [ COOLDOWN_INDEX ] = add_timer() + * add_timer() returns the truthy value of -1 when not stoppable, and else a truthy numeric index + */ + var/list/cooldowns + #ifdef TESTING var/running_find_references var/last_find_references = 0 @@ -221,3 +229,34 @@ qdel(D) else return returned + +/** + * Callback called by a timer to end an associative-list-indexed cooldown. + * + * Arguments: + * * source - datum storing the cooldown + * * index - string index storing the cooldown on the cooldowns associative list + * + * This sends a signal reporting the cooldown end. + */ +/proc/end_cooldown(datum/source, index) + if(QDELETED(source)) + return + SEND_SIGNAL(source, COMSIG_CD_STOP(index)) + TIMER_COOLDOWN_END(source, index) + + +/** + * Proc used by stoppable timers to end a cooldown before the time has ran out. + * + * Arguments: + * * source - datum storing the cooldown + * * index - string index storing the cooldown on the cooldowns associative list + * + * This sends a signal reporting the cooldown end, passing the time left as an argument. + */ +/proc/reset_cooldown(datum/source, index) + if(QDELETED(source)) + return + SEND_SIGNAL(source, COMSIG_CD_RESET(index), S_TIMER_COOLDOWN_TIMELEFT(source, index)) + TIMER_COOLDOWN_END(source, index) diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index d6ea1077e2c..e18db2f9ab8 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -67,7 +67,7 @@ var/obj/machinery/door/airlock/closeOther var/justzap = FALSE var/obj/item/electronics/airlock/electronics - var/shockCooldown = FALSE //Prevents multiple shocks from happening + COOLDOWN_DECLARE(shockCooldown) var/obj/item/note //Any papers pinned to the airlock var/detonated = FALSE var/abandoned = FALSE @@ -404,14 +404,14 @@ /obj/machinery/door/airlock/proc/shock(mob/living/user, prb) if(!istype(user) || !hasPower()) // unpowered, no shock return FALSE - if(shockCooldown > world.time) + if(COOLDOWN_CHECK(src, shockCooldown)) return FALSE //Already shocked someone recently? if(!prob(prb)) return FALSE //you lucked out, no shock for you do_sparks(5, TRUE, src) var/check_range = TRUE if(electrocute_mob(user, get_area(src), src, 1, check_range)) - shockCooldown = world.time + 10 + COOLDOWN_START(src, shockCooldown, 1 SECONDS) return TRUE else return FALSE diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm index 8ee7bac787c..1ed4cc23af1 100644 --- a/code/game/objects/items/robot/robot_upgrades.dm +++ b/code/game/objects/items/robot/robot_upgrades.dm @@ -273,7 +273,6 @@ require_module = 1 var/repair_amount = -1 var/repair_tick = 1 - var/msg_cooldown = 0 var/on = FALSE var/powercost = 10 var/datum/action/toggle_action @@ -355,14 +354,14 @@ cyborg.cell.use(5) repair_tick = 0 - if((world.time - 2000) > msg_cooldown ) + if(!TIMER_COOLDOWN_CHECK(src, COOLDOWN_BORG_SELF_REPAIR)) + TIMER_COOLDOWN_START(src, COOLDOWN_BORG_SELF_REPAIR, 200 SECONDS) var/msgmode = "standby" if(cyborg.health < 0) msgmode = "critical" else if(cyborg.health < cyborg.maxHealth) msgmode = "normal" to_chat(cyborg, "Self-repair is active in [msgmode] mode.") - msg_cooldown = world.time else deactivate_sr() diff --git a/tgstation.dme b/tgstation.dme index c9d58366bfe..e2cec0e435c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -37,6 +37,7 @@ #include "code\__DEFINES\configuration.dm" #include "code\__DEFINES\construction.dm" #include "code\__DEFINES\contracts.dm" +#include "code\__DEFINES\cooldowns.dm" #include "code\__DEFINES\cult.dm" #include "code\__DEFINES\diseases.dm" #include "code\__DEFINES\DNA.dm"