Skip to content

Commit

Permalink
Hugging respawn delay and join as xeno refactoring (#7679)
Browse files Browse the repository at this point in the history
# About the pull request

This PR sets a time of death on a successful hug. However, that time of
death will not affect their position in larva queue. It also updates the
join as facehugger verb to mention that eggs are also a way to join as a
facehugger.

This has been revised to only prevent immediate respawning as a lesser
drone on successful hug (huggers can still try to become a hugger again
immediately). I also bumped the lesser drone respawn time from 30s to 1
minute, and fixed a situation where the join as xeno button wasn't
checking the bypass_time_of_death_checks variable set for lessers &
huggers.

In addition, there were various inconsistencies between joining as a
xeno between larva queue, ctrl click, and join as xeno verb that have
been rectified.

To recap:
- Dying as a hugger or lesser drone will not alter your larva queue
position, and now the join as xeno button being used to take over an afk
xeno will not be prevented for recent death (larva queue already had
this exception).
- Dying as a lesser will require you to wait 1 minute to respawn as a
lesser again, or 3 minutes to respawn as a hugger.
- Dying as a hugger will require you to wait 1 minute to respawn as a
lesser, or 3 minutes to respawn as a hugger again.
- Successfully hugging as a hugger will require you to wait 1 minute to
respawn as a lesser, or 0 minutes to respawn as a hugger again.

# Explain why it's good for the game

A lone player hugging someone, then immediately spawning as a lesser to
go rush to that cap borders on breaking the metagaming rule even though
the entire hive should have the knowledge of the hug (they *were* part
of the hive but became a ghost). At the moment another ghost could just
then immediately spawn and go cap that new hug, but that will be later
mitigated with https://hackmd.io/@Drathek/rJ9zXhzSC

# Testing Photographs and Procedure
<details>
<summary>Screenshots & Videos</summary>

Hugger testing:
https://youtu.be/fjcy8L32w1U (prior to 1 minute respawn change for
lesser)

Join as xeno refactoring:
https://youtu.be/HnsVlBq8DDc

</details>

# Changelog
:cl: Drathek
balance: Player huggers now have to wait 1 minute to respawn as a lesser
on successful hug (same as death). They can still immediately respawn as
a hugger on successful hug.
balance: Lesser drones respawn time has been increased from 30s to 1
minute.
fix: Join as xeno button/verb (much like larva queue) no longer
considers a recent hugger/lesser death as a reason to deny it.
fix: Fixed join as xeno button/verb allowing 0 health afk xenos to be
controlled.
fix: Fixed the xeno_bypass_timer gamemode flag skipping inactivity
checks.
refactor: Refactored some of the join as xeno code inconsistencies and
changed prompts to tgui_alert
spellcheck: Mentioned eggs when using join as facehugger verb.
/:cl:
  • Loading branch information
Drulikar authored Nov 27, 2024
1 parent 2456b2f commit 1cbab9a
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 54 deletions.
2 changes: 1 addition & 1 deletion code/__DEFINES/xeno.dm
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,7 @@
#define FRENZY_DAMAGE_MULTIPLIER 2

#define JOIN_AS_FACEHUGGER_DELAY (3 MINUTES)
#define JOIN_AS_LESSER_DRONE_DELAY (30 SECONDS)
#define JOIN_AS_LESSER_DRONE_DELAY (1 MINUTES)

// larva states
#define LARVA_STATE_BLOODY 0
Expand Down
2 changes: 1 addition & 1 deletion code/__HELPERS/game.dm
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@

// copied from join as xeno
var/deathtime = world.time - cur_obs.timeofdeath
if(deathtime < XENO_JOIN_DEAD_TIME && ( !cur_obs.client.admin_holder || !(cur_obs.client.admin_holder.rights & R_ADMIN)) && !cur_obs.bypass_time_of_death_checks)
if(deathtime < XENO_JOIN_DEAD_TIME && !cur_obs.bypass_time_of_death_checks && !check_client_rights(cur_obs.client, R_ADMIN, FALSE))
continue

// AFK players cannot be drafted
Expand Down
42 changes: 26 additions & 16 deletions code/_onclick/observer.dm
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,29 @@
do_observe(xeno)
return FALSE

if(!SSticker.mode.xeno_bypass_timer)
if((!islarva(xeno) && xeno.away_timer < XENO_LEAVE_TIMER) || (islarva(xeno) && xeno.away_timer < XENO_LEAVE_TIMER_LARVA))
var/to_wait = XENO_LEAVE_TIMER - xeno.away_timer
if(islarva(xeno))
to_wait = XENO_LEAVE_TIMER_LARVA - xeno.away_timer
if(to_wait > 60 SECONDS) // don't spam for clearly non-AFK xenos
to_chat(src, SPAN_WARNING("That player hasn't been away long enough. Please wait [to_wait] second\s longer."))
do_observe(target)
return FALSE
if(xeno.health <= 0)
to_chat(src, SPAN_WARNING("You cannot join if the xenomorph is in critical condition or unconscious."))
do_observe(xeno)
return FALSE

var/required_leave_time = XENO_LEAVE_TIMER
var/required_dead_time = XENO_JOIN_DEAD_TIME
if(islarva(xeno))
required_leave_time = XENO_LEAVE_TIMER_LARVA
required_dead_time = XENO_JOIN_DEAD_LARVA_TIME

if(xeno.away_timer < required_leave_time)
var/to_wait = required_leave_time - xeno.away_timer
if(to_wait > 60 SECONDS) // don't spam for clearly non-AFK xenos
to_chat(src, SPAN_WARNING("That player hasn't been away long enough. Please wait [to_wait] second\s longer."))
do_observe(target)
return FALSE

if(!SSticker.mode.xeno_bypass_timer)
var/deathtime = world.time - timeofdeath
if(deathtime < XENO_JOIN_DEAD_LARVA_TIME)
var/message = "You have been dead for [DisplayTimeText(deathtime)]."
message = SPAN_WARNING("[message]")
to_chat(src, message)
to_chat(src, SPAN_WARNING("You must wait at least 2.5 minutes before rejoining the game!"))
if(deathtime < required_dead_time && !bypass_time_of_death_checks)
to_chat(src, SPAN_WARNING("You have been dead for [DisplayTimeText(deathtime)]."))
to_chat(src, SPAN_WARNING("You must wait at least [required_dead_time / 600] minute\s before rejoining the game!"))
do_observe(target)
return FALSE

Expand All @@ -60,13 +67,16 @@
do_observe(target)
return FALSE

if(alert(src, "Are you sure you want to transfer yourself into [xeno]?", "Confirm Transfer", "Yes", "No") != "Yes")
if(tgui_alert(src, "Are you sure you want to transfer yourself into [xeno]?", "Confirm Transfer", list("Yes", "No")) != "Yes")
return FALSE
if(((!islarva(xeno) && xeno.away_timer < XENO_LEAVE_TIMER) || (islarva(xeno) && xeno.away_timer < XENO_LEAVE_TIMER_LARVA)) || xeno.stat == DEAD) // Do it again, just in case

if(xeno.away_timer < required_leave_time || xeno.stat == DEAD || !(xeno in GLOB.living_xeno_list)) // Do it again, just in case
to_chat(src, SPAN_WARNING("That xenomorph can no longer be controlled. Please try another."))
return FALSE

SSticker.mode.transfer_xeno(src, xeno)
return TRUE

do_observe(target)
return TRUE

Expand Down
69 changes: 39 additions & 30 deletions code/game/gamemodes/cm_initialize.dm
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,10 @@ Additional game mode variables.
var/list/available_xenos = list()
var/list/available_xenos_non_ssd = list()

var/mob/dead/observer/candidate_observer = null
if(isobserver(xeno_candidate))
candidate_observer = xeno_candidate

for(var/mob/living/carbon/xenomorph/cur_xeno as anything in GLOB.living_xeno_list)
if(cur_xeno.aghosted)
continue //aghosted xenos don't count
Expand Down Expand Up @@ -520,11 +524,9 @@ Additional game mode variables.
to_chat(candidate_new_player, SPAN_XENONOTICE(candidate_new_player.larva_queue_cached_message))
return FALSE

if(!isobserver(xeno_candidate))
if(!candidate_observer)
return FALSE

var/mob/dead/observer/candidate_observer = xeno_candidate

// If an observing mod wants to join as a xeno, disable their larva protection so that they can enter the queue.
if(check_client_rights(candidate_observer.client, R_MOD, FALSE))
candidate_observer.admin_larva_protection = FALSE
Expand Down Expand Up @@ -567,7 +569,9 @@ Additional game mode variables.
return FALSE

var/mob/living/carbon/xenomorph/new_xeno
if(!instant_join)
if(instant_join)
new_xeno = pick(available_xenos_non_ssd) //Just picks something at random.
else
var/userInput = tgui_input_list(usr, "Available Xenomorphs", "Join as Xeno", available_xenos, theme="hive_status")

if(available_xenos[userInput]) //Free xeno mobs have no associated value and skip this. "Pooled larva" strings have a list of hives.
Expand All @@ -576,20 +580,21 @@ Additional game mode variables.
if(!xeno_bypass_timer)
var/deathtime = world.time - xeno_candidate.timeofdeath
if(isnewplayer(xeno_candidate))
deathtime = XENO_JOIN_DEAD_LARVA_TIME //so new players don't have to wait to latejoin as xeno in the round's first 5 mins.
if(deathtime < XENO_JOIN_DEAD_LARVA_TIME && !check_client_rights(xeno_candidate.client, R_ADMIN, FALSE))
var/message = SPAN_WARNING("You have been dead for [DisplayTimeText(deathtime)].")
to_chat(xeno_candidate, message)
to_chat(xeno_candidate, SPAN_WARNING("You must wait 2 minutes and 30 seconds before rejoining the game as a buried larva!"))
deathtime = INFINITY //so new players don't have to wait to latejoin as xeno in the round's first 2.5 mins.
if(deathtime < XENO_JOIN_DEAD_LARVA_TIME && !candidate_observer.bypass_time_of_death_checks && !check_client_rights(xeno_candidate.client, R_ADMIN, FALSE))
to_chat(xeno_candidate, SPAN_WARNING("You have been dead for [DisplayTimeText(deathtime)]."))
to_chat(xeno_candidate, SPAN_WARNING("You must wait at least [XENO_JOIN_DEAD_LARVA_TIME / 600] minute\s before rejoining the game as a buried larva!"))
return FALSE

for(var/mob_name in picked_hive.banished_ckeys)
if(picked_hive.banished_ckeys[mob_name] == xeno_candidate.ckey)
to_chat(xeno_candidate, SPAN_WARNING("You are banished from the [picked_hive], you may not rejoin unless the Queen re-admits you or dies."))
return FALSE

if(isnewplayer(xeno_candidate))
var/mob/new_player/noob = xeno_candidate
noob.close_spawn_windows()

if(picked_hive.hive_location)
picked_hive.hive_location.spawn_burrowed_larva(xeno_candidate)
else if((world.time < XENO_BURIED_LARVA_TIME_LIMIT + SSticker.round_start_time))
Expand All @@ -606,38 +611,42 @@ Additional game mode variables.
return FALSE
new_xeno = userInput

if(!(new_xeno in GLOB.living_xeno_list) || new_xeno.stat == DEAD)
if(new_xeno.stat == DEAD)
to_chat(xeno_candidate, SPAN_WARNING("You cannot join if the xenomorph is dead."))
return FALSE

if(new_xeno.health <= 0)
to_chat(xeno_candidate, SPAN_WARNING("You cannot join if the xenomorph is in critical condition or unconscious."))
return FALSE

var/required_leave_time = XENO_LEAVE_TIMER
var/required_dead_time = XENO_JOIN_DEAD_TIME
if(islarva(new_xeno))
required_leave_time = XENO_LEAVE_TIMER_LARVA
required_dead_time = XENO_JOIN_DEAD_LARVA_TIME

if(new_xeno.away_timer < required_leave_time)
var/to_wait = required_leave_time - new_xeno.away_timer
to_chat(xeno_candidate, SPAN_WARNING("That player hasn't been away long enough. Please wait [to_wait] second\s longer."))
return FALSE

if(!xeno_bypass_timer)
var/deathtime = world.time - xeno_candidate.timeofdeath
if(istype(xeno_candidate, /mob/new_player))
deathtime = XENO_JOIN_DEAD_TIME //so new players don't have to wait to latejoin as xeno in the round's first 5 mins.
if(deathtime < XENO_JOIN_DEAD_TIME && !check_client_rights(xeno_candidate.client, R_ADMIN, FALSE))
var/message = "You have been dead for [DisplayTimeText(deathtime)]."
message = SPAN_WARNING("[message]")
to_chat(xeno_candidate, message)
to_chat(xeno_candidate, SPAN_WARNING("You must wait 5 minutes before rejoining the game!"))
return FALSE
if((!islarva(new_xeno) && new_xeno.away_timer < XENO_LEAVE_TIMER) || (islarva(new_xeno) && new_xeno.away_timer < XENO_LEAVE_TIMER_LARVA))
var/to_wait = XENO_LEAVE_TIMER - new_xeno.away_timer
if(islarva(new_xeno))
to_wait = XENO_LEAVE_TIMER_LARVA - new_xeno.away_timer
to_chat(xeno_candidate, SPAN_WARNING("That player hasn't been away long enough. Please wait [to_wait] second\s longer."))
deathtime = INFINITY //so new players don't have to wait to latejoin as xeno in the round's first 5 mins.
if(deathtime < required_dead_time && !candidate_observer.bypass_time_of_death_checks && !check_client_rights(xeno_candidate.client, R_ADMIN, FALSE))
to_chat(xeno_candidate, SPAN_WARNING("You have been dead for [DisplayTimeText(deathtime)]."))
to_chat(xeno_candidate, SPAN_WARNING("You must wait at least [required_dead_time / 600] minute\s before rejoining the game!"))
return FALSE

if(alert(xeno_candidate, "Everything checks out. Are you sure you want to transfer yourself into [new_xeno]?", "Confirm Transfer", "Yes", "No") == "Yes")
if(((!islarva(new_xeno) && new_xeno.away_timer < XENO_LEAVE_TIMER) || (islarva(new_xeno) && new_xeno.away_timer < XENO_LEAVE_TIMER_LARVA)) || !(new_xeno in GLOB.living_xeno_list) || new_xeno.stat == DEAD || !xeno_candidate) // Do it again, just in case
to_chat(xeno_candidate, SPAN_WARNING("That xenomorph can no longer be controlled. Please try another."))
return FALSE
else return FALSE
else new_xeno = pick(available_xenos_non_ssd) //Just picks something at random.
if(istype(new_xeno) && xeno_candidate && xeno_candidate.client)
if(tgui_alert(xeno_candidate, "Are you sure you want to transfer yourself into [new_xeno]?", "Confirm Transfer", list("Yes", "No")) != "Yes")
return FALSE

if(new_xeno.away_timer < required_leave_time || new_xeno.stat == DEAD || !(new_xeno in GLOB.living_xeno_list) || !xeno_candidate) // Do it again, just in case
to_chat(xeno_candidate, SPAN_WARNING("That xenomorph can no longer be controlled. Please try another."))
return FALSE

if(istype(new_xeno) && xeno_candidate?.client)
if(isnewplayer(xeno_candidate))
var/mob/new_player/noob = xeno_candidate
noob.close_spawn_windows()
Expand Down Expand Up @@ -691,7 +700,7 @@ Additional game mode variables.
available_facehugger_sources[descriptive_name] = morpher

if(length(available_facehugger_sources) <= 0)
to_chat(xeno_candidate, SPAN_WARNING("There aren't any Carriers or Egg Morphers with available Facehuggers for you to join. Please try again later!"))
to_chat(xeno_candidate, SPAN_WARNING("There aren't any Carriers or Egg Morphers with available Facehuggers for you to join. Find an egg or try again later!"))
return FALSE

var/source_picked = tgui_input_list(xeno_candidate, "Select a Facehugger source.", "Facehugger Source Choice", available_facehugger_sources, theme="hive_status")
Expand Down
4 changes: 3 additions & 1 deletion code/modules/mob/dead/observer/observer.dm
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,10 @@
var/datum/action/minimap/observer/minimap
///The last message for this player with their larva queue information
var/larva_queue_cached_message
///Used to bypass time of death checks such as when being selected for larva.
///Used to bypass time of death checks such as when being selected for larva
var/bypass_time_of_death_checks = FALSE
///Used to bypass time of death checks for a successful hug
var/bypass_time_of_death_checks_hugger = FALSE

alpha = 127

Expand Down
19 changes: 15 additions & 4 deletions code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,7 @@
counts_for_roundend = FALSE
refunds_larva_if_banished = FALSE
can_hivemind_speak = FALSE
/// The lifetime hugs from this hugger
var/total_facehugs = 0
/// How many hugs the hugger needs to age
var/next_facehug_goal = FACEHUG_TIER_1

base_actions = list(
/datum/action/xeno_action/onclick/xeno_resting,
/datum/action/xeno_action/watch_xeno,
Expand All @@ -68,6 +65,13 @@
weed_food_states = list("Facehugger_1","Facehugger_2","Facehugger_3")
weed_food_states_flipped = list("Facehugger_1","Facehugger_2","Facehugger_3")

/// The lifetime hugs from this hugger
var/total_facehugs = 0
/// How many hugs the hugger needs to age
var/next_facehug_goal = FACEHUG_TIER_1
/// Whether a hug was performed successfully
var/hug_successful = FALSE

/mob/living/carbon/xenomorph/facehugger/Login()
var/last_ckey_inhabited = persistent_ckey
. = ..()
Expand Down Expand Up @@ -163,9 +167,16 @@
return
if(client)
client.player_data?.adjust_stat(PLAYER_STAT_FACEHUGS, STAT_CATEGORY_XENO, 1)
hug_successful = TRUE
timeofdeath = world.time
qdel(src)
return did_hug

/mob/living/carbon/xenomorph/facehugger/ghostize(can_reenter_corpse, aghosted)
var/mob/dead/observer/ghost = ..()
ghost?.bypass_time_of_death_checks_hugger = hug_successful
return ghost

/mob/living/carbon/xenomorph/facehugger/age_xeno()
if(stat == DEAD || !caste || QDELETED(src) || !client)
return
Expand Down
2 changes: 1 addition & 1 deletion code/modules/mob/living/carbon/xenomorph/hive_status.dm
Original file line number Diff line number Diff line change
Expand Up @@ -869,7 +869,7 @@
if(world.time < hugger_timelock)
to_chat(user, SPAN_WARNING("The hive cannot support facehuggers yet..."))
return FALSE
if(world.time - user.timeofdeath < JOIN_AS_FACEHUGGER_DELAY)
if(!user.bypass_time_of_death_checks_hugger && world.time - user.timeofdeath < JOIN_AS_FACEHUGGER_DELAY)
var/time_left = floor((user.timeofdeath + JOIN_AS_FACEHUGGER_DELAY - world.time) / 10)
to_chat(user, SPAN_WARNING("You ghosted too recently. You cannot become a facehugger until 3 minutes have passed ([time_left] seconds remaining)."))
return FALSE
Expand Down

0 comments on commit 1cbab9a

Please sign in to comment.