Skip to content

Commit

Permalink
Lurker invis toggling (#6148)
Browse files Browse the repository at this point in the history
# 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
<details>
<summary>Screenshots & Videos</summary>

https://youtu.be/ODEF7E6lDrI

</details>


# 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:
  • Loading branch information
Drulikar committed May 1, 2024
1 parent 36e390d commit be29b4d
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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
Expand All @@ -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()

Expand All @@ -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!"))
Expand Down
51 changes: 26 additions & 25 deletions code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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)
. = ..()
Expand All @@ -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
1 change: 1 addition & 0 deletions code/modules/mob/mob_grab.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit be29b4d

Please sign in to comment.