From 1cbab9a6f6f70deacd950f8a04987691b49a10f4 Mon Sep 17 00:00:00 2001 From: Drathek <76988376+Drulikar@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:09:31 -0600 Subject: [PATCH] Hugging respawn delay and join as xeno refactoring (#7679) # 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
Screenshots & Videos Hugger testing: https://youtu.be/fjcy8L32w1U (prior to 1 minute respawn change for lesser) Join as xeno refactoring: https://youtu.be/HnsVlBq8DDc
# 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: --- code/__DEFINES/xeno.dm | 2 +- code/__HELPERS/game.dm | 2 +- code/_onclick/observer.dm | 42 ++++++----- code/game/gamemodes/cm_initialize.dm | 69 +++++++++++-------- code/modules/mob/dead/observer/observer.dm | 4 +- .../carbon/xenomorph/castes/Facehugger.dm | 19 +++-- .../living/carbon/xenomorph/hive_status.dm | 2 +- 7 files changed, 86 insertions(+), 54 deletions(-) diff --git a/code/__DEFINES/xeno.dm b/code/__DEFINES/xeno.dm index 8e7fb2af647b..612d9f1f392b 100644 --- a/code/__DEFINES/xeno.dm +++ b/code/__DEFINES/xeno.dm @@ -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 diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index 644d5df363c1..aef434cf99d0 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -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 diff --git a/code/_onclick/observer.dm b/code/_onclick/observer.dm index 04c70bbe1112..62026324c594 100644 --- a/code/_onclick/observer.dm +++ b/code/_onclick/observer.dm @@ -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 @@ -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 diff --git a/code/game/gamemodes/cm_initialize.dm b/code/game/gamemodes/cm_initialize.dm index cd2cdd18fe88..9c6a804862f5 100644 --- a/code/game/gamemodes/cm_initialize.dm +++ b/code/game/gamemodes/cm_initialize.dm @@ -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 @@ -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 @@ -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. @@ -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)) @@ -606,7 +611,7 @@ 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 @@ -614,30 +619,34 @@ Additional game mode variables. 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() @@ -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") diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index eae5af870b27..34435d9f8318 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -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 diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm b/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm index 43c5b78514af..6b69211d5bb1 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm @@ -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, @@ -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 . = ..() @@ -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 diff --git a/code/modules/mob/living/carbon/xenomorph/hive_status.dm b/code/modules/mob/living/carbon/xenomorph/hive_status.dm index d7aaf7ca30b3..f254f3d6ca39 100644 --- a/code/modules/mob/living/carbon/xenomorph/hive_status.dm +++ b/code/modules/mob/living/carbon/xenomorph/hive_status.dm @@ -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