diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm index 948c0129ae..79058f7b19 100644 --- a/code/_onclick/ai.dm +++ b/code/_onclick/ai.dm @@ -70,7 +70,7 @@ CtrlClickOn(A) return - if(world.time <= next_move) + if(!CheckActionCooldown(immediate = TRUE)) return if(aicamera.in_camera_mode) diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index 2d264a3561..c909ce03d8 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -10,7 +10,7 @@ /atom/Click(location,control,params) if(flags_1 & INITIALIZED_1) SEND_SIGNAL(src, COMSIG_CLICK, location, control, params, usr) - usr.ClickOn(src, params) + usr.CommonClickOn(src, params) /atom/DblClick(location,control,params) if(flags_1 & INITIALIZED_1) @@ -20,6 +20,21 @@ if(flags_1 & INITIALIZED_1) usr.MouseWheelOn(src, delta_x, delta_y, params) +/** + * Common mob click code + */ +/mob/proc/CommonClickOn(atom/A, params) + set waitfor = FALSE // oh no you don't + if(mob_transforming) + return + if(SEND_SIGNAL(src, COMSIG_MOB_CLICKON, A, params) & COMSIG_MOB_CANCEL_CLICKON) + return + if(ClickOn(A, params)) + FlushLastAction() + else + DiscardLastAction() + + /* Standard mob ClickOn() Handles exceptions: Buildmode, middle click, modified clicks, mech actions @@ -37,12 +52,6 @@ if(check_click_intercept(params,A)) return - if(mob_transforming) - return - - if(SEND_SIGNAL(src, COMSIG_MOB_CLICKON, A, params) & COMSIG_MOB_CANCEL_CLICKON) - return - var/list/modifiers = params2list(params) if(modifiers["shift"] && modifiers["middle"]) ShiftMiddleClickOn(A) @@ -72,7 +81,7 @@ face_atom(A) - if(!CheckActionCooldown()) + if(!CheckActionCooldown(immediate = TRUE)) return if(!modifiers["catcher"] && A.IsObscured()) @@ -85,7 +94,7 @@ if(restrained()) DelayNextAction(CLICK_CD_HANDCUFFED) RestrainedClickOn(A) - return + return TRUE if(in_throw_mode) throw_item(A) diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm index d825c10a95..c9e53fc10e 100644 --- a/code/_onclick/cyborg.dm +++ b/code/_onclick/cyborg.dm @@ -33,7 +33,7 @@ CtrlClickOn(A) return - if(!CheckActionCooldown()) + if(!CheckActionCooldown(immediate = TRUE)) return face_atom(A) // change direction to face what you clicked on diff --git a/code/modules/mob/clickdelay.dm b/code/modules/mob/clickdelay.dm index 6f181eaf64..82b0e851af 100644 --- a/code/modules/mob/clickdelay.dm +++ b/code/modules/mob/clickdelay.dm @@ -13,8 +13,18 @@ // last_action is not a hard cooldown and different items can check for different delays. /// Generic clickdelay variable. Marks down the last world.time we did something that should cause or impact generic clickdelay. This should be directly set or set using [DelayNextAction()]. This should only be checked using [CheckActionCooldown()]. var/last_action = 0 + /** + * The difference between the above and this is this is set immediately before even the pre-attack begins to ensure clickdelay is respected. + * Then, it is flushed or discarded using [FlushLastAttack()] or [DiscardLastAttack()] respectively. + */ + + var/last_action_immediate = 0 /// Generic clickdelay variable. Next world.time we should be able to do something that respects generic clickdelay. This should be set using [DelayNextAction()] This should only be checked using [CheckActionCooldown()]. var/next_action = 0 + /// Ditto + var/next_action_immediate = 0 + /// Default clickdelay for an UnarmedAttack() that successfully passes. Respects action_cooldown_mod. + var/unarmed_attack_speed = CLICK_CD_MELEE /// Simple modification variable multiplied to next action modifier on adjust and on checking time since last action using [CheckActionCooldown()]. /// This should only be manually modified using multipliers. var/action_cooldown_mod = 1 @@ -24,11 +34,14 @@ // Resisting - While resisting will give generic clickdelay, it is also on its own resist delay system. However, resisting does not check generic movedelay. // Resist cooldown should only be set at the start of a resist chain - whether this is clicking an alert button, pressing or hotkeying the resist button, or moving to resist out of a locker. - /// Special clickdelay variable for resisting. Last time we did a special action like resisting. This should only be set using [MarkResistTime()]. This should only be checked using [CheckResistCooldown()]. + /* + * Special clickdelay variable for resisting. Last time we did a special action like resisting. This should only be set using [MarkResistTime()]. + * Use [CheckResistCooldown()] to check cooldowns, this should only be used for the resist action bar visual. + */ var/last_resist = 0 /// How long we should wait before allowing another resist. This should only be manually modified using multipliers. var/resist_cooldown = CLICK_CD_RESIST - /// Minimum world time for another resist. + /// Minimum world time for another resist. This should only be checked using [CheckResistCooldown()]. var/next_resist = 0 /** @@ -38,19 +51,30 @@ * * amount - Amount to delay by * * ignore_mod - ignores next action adjust and mult * * considered_action - Defaults to TRUE - If TRUE, sets last_action to world.time. + * * immediate - defaults to TRUE - if TRUE, writes to cached/last_attack_immediate instead of last_attack. This ensures it can't collide with any delay checks in the actual attack. */ -/mob/proc/DelayNextAction(amount = 0, ignore_mod = FALSE, considered_action = TRUE) +/mob/proc/DelayNextAction(amount = 0, ignore_mod = FALSE, considered_action = TRUE, immediate = TRUE) if(considered_action) - last_action = world.time - next_action = max(next_action, world.time + (ignore_mod? amount : (amount * action_cooldown_mod + action_cooldown_adjust))) + (immediate? last_action_immediate : last_action) = world.time + (immediate? next_action_immediate : next_action) = max(next_action, world.time + (ignore_mod? amount : (amount * action_cooldown_mod + action_cooldown_adjust))) + +/** + * Get estimated time of next attack. + */ +/mob/proc/EstimatedNextActionTime() + var/attack_speed = unarmed_attack_speed + var/obj/item/I = get_active_held_item() + if(I) + attack_speed = I.attack_speed + return max(next_action, last_action + attack_speed) /** * Sets our next action to. The difference is DelayNextAction cannot reduce next_action under any circumstances while this can. */ -/mob/proc/SetNextAction(amount = 0, ignore_mod = FALSE, considered_action = TRUE) +/mob/proc/SetNextAction(amount = 0, ignore_mod = FALSE, considered_action = TRUE, immediate = TRUE) if(considered_action) - last_action = world.time - next_action = world.time + (ignore_mod? amount : (amount * action_cooldown_mod + action_cooldown_adjust)) + (immediate? last_action_immediate : last_action) = world.time + (immediate? next_action_immediate : next_action) = world.time + (ignore_mod? amount : (amount * action_cooldown_mod + action_cooldown_adjust)) /** * Checks if we can do another action. @@ -61,9 +85,25 @@ * * from_next_action - Defaults to FALSE. Should we check from the tail end of next_action instead of last_action? * * ignore_mod - Defaults to FALSE. Ignore all adjusts and multipliers. Do not use this unless you know what you are doing and have a good reason. * * ignore_next_action - Defaults to FALSE. Ignore next_action and only care about cooldown param and everything else. Generally unused. + * * immediate - Defaults to FALSE. Checks last action using immediate, used on the head end of an attack. This is to prevent colliding attacks in case of sleep. Not that you should sleep() in an attack but.. y'know. */ -/mob/proc/CheckActionCooldown(cooldown = 0.5, from_next_action = FALSE, ignore_mod = FALSE, ignore_next_action = FALSE) - return (ignore_next_action || (world.time >= next_action)) && (world.time >= ((from_next_action? next_action : last_action) + max(0, ignore_mod? cooldown : (cooldown * action_cooldown_mod + action_cooldown_adjust)))) +/mob/proc/CheckActionCooldown(cooldown = 0.5, from_next_action = FALSE, ignore_mod = FALSE, ignore_next_action = FALSE, immediate = FALSE) + return (ignore_next_action || (world.time >= (immediate? next_action_immediate : next_action))) && \ + (world.time >= ((from_next_action? (immediate? next_action_immediate : next_action) : (immediate? last_action_immediate : last_action)) + max(0, ignore_mod? cooldown : (cooldown * action_cooldown_mod + action_cooldown_adjust)))) + +/** + * Flushes last_action and next_action + */ +/mob/proc/FlushCurrentAction() + last_action = last_action_immediate + next_action = next_action_immediate + +/** + * Discards last_action and next_action + */ +/mob/proc/DiscardCurrentAction() + last_action_immediate = last_action + next_action_immediate = next_action /** * Checks if we can resist again. diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 6cd0bd7d4f..a4a800e9c4 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -736,7 +736,7 @@ return FALSE resist_restraints() //trying to remove cuffs. - // DO NOT GIVE CLICKDELAY - last_special handles this. + // DO NOT GIVE CLICKDELAY return FALSE /// Proc to resist a grab. moving_resist is TRUE if this began by someone attempting to move. Return FALSE if still grabbed/failed to break out. Use this instead of resist_grab() directly. diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index b037221e2c..5495d37297 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -53,7 +53,6 @@ var/hallucination = 0 //Directly affects how long a mob will hallucinate for - var/last_special = 0 //Used by the resist verb, likely used to prevent players from bypassing next_move by logging in/out. var/timeofdeath = 0 //Allows mobs to move through dense areas without restriction. For instance, in space or out of holder objects.