From be29b4dbf682e8827fc67fe815ffa526ff4b16d0 Mon Sep 17 00:00:00 2001 From: Drathek <76988376+Drulikar@users.noreply.github.com> Date: Wed, 1 May 2024 09:08:41 -0700 Subject: [PATCH] Lurker invis toggling (#6148) # About the pull request This PR is sort of a different implementation of #6127 opting against balloon alerts for devour that does several things: - Lurker invis recharge is now 20s base (up from 15s) - Lurker invis can now be toggled giving you 90% of the time you didn't use (and with 0.5s of double click prevention like I previously gave to all /datum/action/xeno_action/activable ) - Bumping still ends invisibility but refunds 50% of the time you didn't use - Devouring now ends invisibility but refunds 50% of the time you didn't use - Removed balloon alerts for defender crest toggling - Code refactor to actually utilize xeno ability cooldowns means that CDR can affect the ability now, but xenos have very little access to CDR - Changes the invisibility time remaining status message to also mention invisibility recharge time. # Explain why it's good for the game Gives lurkers more flexibility to have their ability ready when they may need it, resolves the issue of lurkers trying to sneak a devour, and makes bumping slightly less punishing. The increase in the recharge time is intended to make an ambush more calculated offering marines a little more time to retaliate before the lurker may vanish again. # Testing Photographs and Procedure
Screenshots & Videos https://youtu.be/ODEF7E6lDrI
# Changelog :cl: Drathek balance: Lurker invisibility recharge time is now 20s (up from 15s) balance: Lurker invisibility now ends when devouring but refunds 50% of time unused balance: Lurker invisibility bump now refunds 50% of time unused balance: Lurker invisibility can now be toggled refunding 90% of time unused (with dbl click prevention) del: Removed defender crest toggle balloon alerts fix: Lurker invisibility code is refactored to properly use cooldowns and now doesn't incorrectly get interrupted by bump code add: Lurker invisibility recharge time is now displayed in status tab /:cl: --- .../abilities/defender/defender_powers.dm | 2 - .../abilities/lurker/lurker_abilities.dm | 2 - .../abilities/lurker/lurker_powers.dm | 81 ++++++++++++------- .../living/carbon/xenomorph/castes/Lurker.dm | 51 ++++++------ code/modules/mob/mob_grab.dm | 1 + 5 files changed, 78 insertions(+), 59 deletions(-) diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/defender/defender_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/defender/defender_powers.dm index 98c0c19f68f7..d7a4f987623a 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/defender/defender_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/defender/defender_powers.dm @@ -17,7 +17,6 @@ if(xeno.crest_defense) to_chat(xeno, SPAN_XENOWARNING("We lower our crest.")) - xeno.balloon_alert(xeno, "crest lowered") xeno.ability_speed_modifier += speed_debuff xeno.armor_deflection_buff += armor_buff @@ -26,7 +25,6 @@ xeno.update_icons() else to_chat(xeno, SPAN_XENOWARNING("We raise our crest.")) - xeno.balloon_alert(xeno, "crest raised") xeno.ability_speed_modifier -= speed_debuff xeno.armor_deflection_buff -= armor_buff diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm index 8a829d8d6bc0..17677c8427af 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm @@ -18,8 +18,6 @@ macro_path = /datum/action/xeno_action/verb/verb_lurker_invisibility ability_primacy = XENO_PRIMARY_ACTION_2 action_type = XENO_ACTION_CLICK - xeno_cooldown = 1 // This ability never goes off cooldown 'naturally'. Cooldown is applied manually as a super-large value in the use_ability proc - // and reset by the behavior_delegate whenever the ability ends (because it can be ended by things like slashes, that we can't easily track here) plasma_cost = 20 var/duration = 30 SECONDS // 30 seconds base diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm index 0d1bcc99281a..51f23f22a09f 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_powers.dm @@ -1,8 +1,10 @@ -/datum/action/xeno_action/activable/pounce/lurker/additional_effects_always() +/datum/action/xeno_action/activable/pounce/lurker/additional_effects(mob/living/living_mob) var/mob/living/carbon/xenomorph/xeno = owner if(!istype(xeno)) return + RegisterSignal(xeno, COMSIG_XENO_SLASH_ADDITIONAL_EFFECTS_SELF, PROC_REF(remove_freeze), TRUE) // Suppresses runtime ever we pounce again before slashing + var/found = FALSE for(var/mob/living/carbon/human/human in get_turf(xeno)) if(human.stat == DEAD) @@ -12,14 +14,8 @@ if(found) var/datum/action/xeno_action/onclick/lurker_invisibility/lurker_invis = get_xeno_action_by_type(xeno, /datum/action/xeno_action/onclick/lurker_invisibility) - lurker_invis.invisibility_off() - -/datum/action/xeno_action/activable/pounce/lurker/additional_effects(mob/living/living_mob) - var/mob/living/carbon/xenomorph/xeno = owner - if(!istype(xeno)) - return - - RegisterSignal(xeno, COMSIG_XENO_SLASH_ADDITIONAL_EFFECTS_SELF, PROC_REF(remove_freeze), TRUE) // Suppresses runtime ever we pounce again before slashing + if(lurker_invis) + lurker_invis.invisibility_off() // Full cooldown /datum/action/xeno_action/activable/pounce/lurker/proc/remove_freeze(mob/living/carbon/xenomorph/xeno) SIGNAL_HANDLER @@ -29,21 +25,33 @@ UnregisterSignal(xeno, COMSIG_XENO_SLASH_ADDITIONAL_EFFECTS_SELF) end_pounce_freeze() +/datum/action/xeno_action/onclick/lurker_invisibility/can_use_action() + if(!..()) + return FALSE + var/mob/living/carbon/xenomorph/xeno = owner + return xeno.deselect_timer < world.time // We clicked the same ability in a very short time + /datum/action/xeno_action/onclick/lurker_invisibility/use_ability(atom/targeted_atom) var/mob/living/carbon/xenomorph/xeno = owner - if (!istype(xeno)) + if(!istype(xeno)) return - - if (!action_cooldown_check()) + if(!action_cooldown_check()) return - - if (!check_and_use_plasma_owner()) + if(!check_and_use_plasma_owner()) return - animate(xeno, alpha = alpha_amount, time = 0.1 SECONDS, easing = QUAD_EASING) + xeno.deselect_timer = world.time + 5 // Half a second to prevent double clicks + + if(xeno.stealth) + invisibility_off(0.9) // Near full refund of remaining time + return ..() + + button.icon_state = "template_active" xeno.update_icons() // callback to make the icon_state indicate invisibility is in lurker/update_icon + animate(xeno, alpha = alpha_amount, time = 0.1 SECONDS, easing = QUAD_EASING) + xeno.speed_modifier -= speed_buff xeno.recalculate_speed() @@ -53,31 +61,44 @@ // if we go off early, this also works fine. invis_timer_id = addtimer(CALLBACK(src, PROC_REF(invisibility_off)), duration, TIMER_STOPPABLE) - // Only resets when invisibility ends - apply_cooldown_override(1000000000) return ..() -/datum/action/xeno_action/onclick/lurker_invisibility/proc/invisibility_off() - if(!owner || owner.alpha == initial(owner.alpha)) +/// Implementation for disabling invisibility. +/// (refund_multiplier) indicates how much cooldown to refund based on time remaining +/// 0 indicates full cooldown; 0.5 indicates 50% of remaining time is refunded +/datum/action/xeno_action/onclick/lurker_invisibility/proc/invisibility_off(refund_multiplier = 0.0) + var/mob/living/carbon/xenomorph/xeno = owner + + if(!istype(xeno)) + return + if(owner.alpha == initial(owner.alpha) && !xeno.stealth) return - if (invis_timer_id != TIMER_ID_NULL) + if(invis_timer_id != TIMER_ID_NULL) deltimer(invis_timer_id) invis_timer_id = TIMER_ID_NULL - var/mob/living/carbon/xenomorph/xeno = owner - if (istype(xeno)) - animate(xeno, alpha = initial(xeno.alpha), time = 0.1 SECONDS, easing = QUAD_EASING) - to_chat(xeno, SPAN_XENOHIGHDANGER("We feel our invisibility end!")) + animate(xeno, alpha = initial(xeno.alpha), time = 0.1 SECONDS, easing = QUAD_EASING) + to_chat(xeno, SPAN_XENOHIGHDANGER("We feel our invisibility end!")) - xeno.update_icons() + button.icon_state = "template" + xeno.update_icons() + + xeno.speed_modifier += speed_buff + xeno.recalculate_speed() + + var/datum/behavior_delegate/lurker_base/behavior = xeno.behavior_delegate + if(!istype(behavior)) + CRASH("lurker_base behavior_delegate missing/invalid for [xeno]!") - xeno.speed_modifier += speed_buff - xeno.recalculate_speed() + var/recharge_time = behavior.invis_recharge_time + if(behavior.invis_start_time > 0) // Sanity + refund_multiplier = clamp(refund_multiplier, 0, 1) + var/remaining = 1 - (world.time - behavior.invis_start_time) / behavior.invis_duration + recharge_time = behavior.invis_recharge_time - remaining * refund_multiplier * behavior.invis_recharge_time + apply_cooldown_override(recharge_time) - var/datum/behavior_delegate/lurker_base/behavior = xeno.behavior_delegate - if (istype(behavior)) - behavior.on_invisibility_off() + behavior.on_invisibility_off() /datum/action/xeno_action/onclick/lurker_invisibility/ability_cooldown_over() to_chat(owner, SPAN_XENOHIGHDANGER("We are ready to use our invisibility again!")) diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm b/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm index b9bde4c78992..5196be26f3d7 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm @@ -67,15 +67,14 @@ name = "Base Lurker Behavior Delegate" // Config - var/invis_recharge_time = 150 // 15 seconds to recharge invisibility. + var/invis_recharge_time = 20 SECONDS var/invis_start_time = -1 // Special value for when we're not invisible - var/invis_duration = 300 // so we can display how long the lurker is invisible to it + var/invis_duration = 30 SECONDS // so we can display how long the lurker is invisible to it var/buffed_slash_damage_ratio = 1.2 var/slash_slow_duration = 35 // State var/next_slash_buffed = FALSE - var/can_go_invisible = TRUE /datum/behavior_delegate/lurker_base/melee_attack_modify_damage(original_damage, mob/living/carbon/target_carbon) if (!isxeno_human(target_carbon)) @@ -116,55 +115,57 @@ var/datum/action/xeno_action/onclick/lurker_invisibility/lurker_invis_action = get_xeno_action_by_type(bound_xeno, /datum/action/xeno_action/onclick/lurker_invisibility) if (lurker_invis_action) - lurker_invis_action.invisibility_off() + lurker_invis_action.invisibility_off() // Full cooldown /datum/behavior_delegate/lurker_base/proc/decloak_handler(mob/source) SIGNAL_HANDLER var/datum/action/xeno_action/onclick/lurker_invisibility/lurker_invis_action = get_xeno_action_by_type(bound_xeno, /datum/action/xeno_action/onclick/lurker_invisibility) if(istype(lurker_invis_action)) - lurker_invis_action.invisibility_off() + lurker_invis_action.invisibility_off(0.5) // Partial refund of remaining time -// What to do when we go invisible +/// Implementation for enabling invisibility. /datum/behavior_delegate/lurker_base/proc/on_invisibility() var/datum/action/xeno_action/activable/pounce/lurker/lurker_pounce_action = get_xeno_action_by_type(bound_xeno, /datum/action/xeno_action/activable/pounce/lurker) - if (lurker_pounce_action) + if(lurker_pounce_action) lurker_pounce_action.knockdown = TRUE // pounce knocks down lurker_pounce_action.freeze_self = TRUE ADD_TRAIT(bound_xeno, TRAIT_CLOAKED, TRAIT_SOURCE_ABILITY("cloak")) RegisterSignal(bound_xeno, COMSIG_MOB_EFFECT_CLOAK_CANCEL, PROC_REF(decloak_handler)) bound_xeno.stealth = TRUE - can_go_invisible = FALSE invis_start_time = world.time +/// Implementation for disabling invisibility. /datum/behavior_delegate/lurker_base/proc/on_invisibility_off() var/datum/action/xeno_action/activable/pounce/lurker/lurker_pounce_action = get_xeno_action_by_type(bound_xeno, /datum/action/xeno_action/activable/pounce/lurker) - if (lurker_pounce_action) + if(lurker_pounce_action) lurker_pounce_action.knockdown = FALSE // pounce no longer knocks down lurker_pounce_action.freeze_self = FALSE bound_xeno.stealth = FALSE REMOVE_TRAIT(bound_xeno, TRAIT_CLOAKED, TRAIT_SOURCE_ABILITY("cloak")) UnregisterSignal(bound_xeno, COMSIG_MOB_EFFECT_CLOAK_CANCEL) + invis_start_time = -1 - // SLIGHTLY hacky because we need to maintain lots of other state on the lurker - // whenever invisibility is on/off CD and when it's active. - addtimer(CALLBACK(src, PROC_REF(regen_invisibility)), invis_recharge_time) +/datum/behavior_delegate/lurker_base/append_to_stat() + . = list() - invis_start_time = -1 + // Invisible + if(invis_start_time != -1) + var/time_left = (invis_duration-(world.time - invis_start_time)) / 10 + . += "Invisibility Remaining: [time_left] second\s." + return -/datum/behavior_delegate/lurker_base/proc/regen_invisibility() - if (can_go_invisible) + var/datum/action/xeno_action/onclick/lurker_invisibility/lurker_invisibility_action = get_xeno_action_by_type(bound_xeno, /datum/action/xeno_action/onclick/lurker_invisibility) + if(!lurker_invisibility_action) return - can_go_invisible = TRUE - if(bound_xeno) - var/datum/action/xeno_action/onclick/lurker_invisibility/lurker_invisibility_action = get_xeno_action_by_type(bound_xeno, /datum/action/xeno_action/onclick/lurker_invisibility) - if(lurker_invisibility_action) - lurker_invisibility_action.end_cooldown() + // Recharged + if(lurker_invisibility_action.cooldown_timer_id == TIMER_ID_NULL) + . += "Invisibility Recharge: Ready." + return -/datum/behavior_delegate/lurker_base/append_to_stat() - . = list() - var/invis_message = (invis_start_time == -1) ? "N/A" : "[(invis_duration-(world.time - invis_start_time))/10] seconds." - . += "Invisibility Time Left: [invis_message]" + // Recharging + var/time_left = timeleft(lurker_invisibility_action.cooldown_timer_id) / 10 + . += "Invisibility Recharge: [time_left] second\s." /datum/behavior_delegate/lurker_base/on_collide(atom/movable/movable_atom) . = ..() @@ -184,4 +185,4 @@ return to_chat(bound_xeno, SPAN_XENOHIGHDANGER("We bumped into someone and lost our invisibility!")) - lurker_invisibility_action.invisibility_off() + lurker_invisibility_action.invisibility_off(0.5) // partial refund of remaining time diff --git a/code/modules/mob/mob_grab.dm b/code/modules/mob/mob_grab.dm index bd7370f4806f..b6f2871e0370 100644 --- a/code/modules/mob/mob_grab.dm +++ b/code/modules/mob/mob_grab.dm @@ -132,6 +132,7 @@ if(user.action_busy) to_chat(xeno, SPAN_WARNING("We are already busy with something.")) return + SEND_SIGNAL(xeno, COMSIG_MOB_EFFECT_CLOAK_CANCEL) xeno.visible_message(SPAN_DANGER("[xeno] starts to devour [pulled]!"), \ SPAN_DANGER("We start to devour [pulled]!"), null, 5) if(HAS_TRAIT(xeno, TRAIT_CLOAKED)) //cloaked don't show the visible message, so we gotta work around