Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Xeno Candidate Queue #3636

Merged
merged 10 commits into from
Jun 23, 2023
2 changes: 0 additions & 2 deletions code/__DEFINES/mobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
#define OVEREAT_TIME 200

//=================================================
#define ALIEN_SELECT_AFK_BUFFER 1 // How many minutes that a person can be AFK before not being allowed to be an alien.

#define HEAT_DAMAGE_LEVEL_1 2 //Amount of damage applied when your body temperature just passes the 360.15k safety point
#define HEAT_DAMAGE_LEVEL_2 4 //Amount of damage applied when your body temperature passes the 400K point
#define HEAT_DAMAGE_LEVEL_3 8 //Amount of damage applied when your body temperature passes the 1000K point
Expand Down
16 changes: 16 additions & 0 deletions code/__DEFINES/xeno.dm
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,22 @@
#define WEED_BASE_GROW_SPEED (5 SECONDS)
#define WEED_BASE_DECAY_SPEED (10 SECONDS)

/// The time you must be dead to join as a xeno larva
#define XENO_JOIN_DEAD_LARVA_TIME (2.5 MINUTES)
/// The time you must be dead to join as xeno (not larva)
#define XENO_JOIN_DEAD_TIME (5 MINUTES)
/// The time of inactivity you cannot exceed to join as a xeno
#define XENO_JOIN_AFK_TIME_LIMIT (5 MINUTES)
/// The amount of time after round start before buried larva spawns are disallowed
#define XENO_BURIED_LARVA_TIME_LIMIT (30 MINUTES)

/// The time against away_timer when an AFK xeno larva can be replaced
#define XENO_LEAVE_TIMER_LARVA 80 //80 seconds
/// The time against away_timer when an AFK xeno (not larva) can be replaced
#define XENO_LEAVE_TIMER 300 //300 seconds
/// The time against away_timer when an AFK xeno gets listed in the available list so ghosts can get ready
#define XENO_AVAILABLE_TIMER 60 //60 seconds

/// Between 2% to 10% of explosion severity
#define WEED_EXPLOSION_DAMAGEMULT rand(2, 10)*0.01

Expand Down
4 changes: 0 additions & 4 deletions code/__HELPERS/_time.dm
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@

#define DECISECONDS_TO_HOURS /36000

#define XENO_LEAVE_TIMER_LARVA 80 //80 seconds
#define XENO_LEAVE_TIMER 300 //300 seconds
#define XENO_AVAILABLE_TIMER 60 //60 seconds, when to add a xeno to the avaliable list so ghosts can get ready

var/midnight_rollovers = 0
var/rollovercheck_last_timeofday = 0

Expand Down
9 changes: 9 additions & 0 deletions code/__HELPERS/cmp.dm
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,12 @@ var/atom/cmp_dist_origin=null

/proc/cmp_typepaths_asc(A, B)
return sorttext("[B]","[A]")

/// Compares mobs based on their timeofdeath value in ascending order
/proc/cmp_mob_deathtime_asc(mob/A, mob/B)
return A.timeofdeath - B.timeofdeath

/// Compares observers based on their larva_queue_time value in ascending order
/// Assumes the client on the observer is not null
/proc/cmp_obs_larvaqueuetime_asc(mob/dead/observer/A, mob/dead/observer/B)
return A.client.larva_queue_time - B.client.larva_queue_time
61 changes: 48 additions & 13 deletions code/__HELPERS/game.dm
Original file line number Diff line number Diff line change
Expand Up @@ -241,35 +241,70 @@
else
return get_step(start, EAST)

// Same as above but for alien candidates.
/proc/get_alien_candidates()
/// Get a list of observers that can be alien candidates, optionally sorted by larva_queue_time
/proc/get_alien_candidates(sorted = TRUE)
var/list/candidates = list()

for(var/i in GLOB.observer_list)
var/mob/dead/observer/O = i
for(var/mob/dead/observer/cur_obs as anything in GLOB.observer_list)
// Preference check
if(!cur_obs.client || !cur_obs.client.prefs || !(cur_obs.client.prefs.be_special & BE_ALIEN_AFTER_DEATH))
continue

// Jobban check
if(!O.client || !O.client.prefs || !(O.client.prefs.be_special & BE_ALIEN_AFTER_DEATH) || jobban_isbanned(O, JOB_XENOMORPH))
if(jobban_isbanned(cur_obs, JOB_XENOMORPH))
continue

//players that can still be revived are skipped
if(O.mind && O.mind.original && ishuman(O.mind.original))
var/mob/living/carbon/human/H = O.mind.original
if (H.check_tod() && H.is_revivable())
if(cur_obs.mind && cur_obs.mind.original && ishuman(cur_obs.mind.original))
var/mob/living/carbon/human/cur_human = cur_obs.mind.original
if(cur_human.check_tod() && cur_human.is_revivable())
continue

// copied from join as xeno
var/deathtime = world.time - O.timeofdeath
if(deathtime < 3000 && ( !O.client.admin_holder || !(O.client.admin_holder.rights & R_ADMIN)) )
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)) )
continue

// Admins and AFK players cannot be drafted
if(O.client.inactivity / 600 > ALIEN_SELECT_AFK_BUFFER + 5 || (O.client.admin_holder && (O.client.admin_holder.rights & R_MOD)) && O.adminlarva == 0)
// AFK players cannot be drafted
if(cur_obs.client.inactivity > XENO_JOIN_AFK_TIME_LIMIT)
continue

candidates += O
// Mods with larva protection cannot be drafted
if((cur_obs.client.admin_holder && (cur_obs.client.admin_holder.rights & R_MOD)) && !cur_obs.adminlarva)
continue

candidates += cur_obs

// Optionally sort by larva_queue_time
if(sorted && length(candidates))
candidates = sort_list(candidates, GLOBAL_PROC_REF(cmp_obs_larvaqueuetime_asc))

return candidates

/**
* Messages observers that are currently candidates an update on the queue.
*
* Arguments:
* * candidates - The list of observers from get_alien_candidates()
* * dequeued - How many candidates to skip messaging because they were dequeued
* * cache_only - Whether to not actually send a to_chat message and instead only update larva_queue_cached_message
*/
/proc/message_alien_candidates(list/candidates, dequeued, cache_only = FALSE)
var/new_players = 0
for(var/i in (1 + dequeued) to candidates.len)
var/mob/dead/observer/cur_obs = candidates[i]

// Generate the messages
var/cached_message = SPAN_XENONOTICE("You are currently [i-dequeued]\th in the larva queue. There are [new_players] ahead of you that have yet to play this round.")
cur_obs.larva_queue_cached_message = cached_message
if(!cache_only)
var/chat_message = dequeued ? replacetext(cached_message, "currently", "now") : cached_message
to_chat(candidates[i], chat_message)

// Count how many are prioritized
if(cur_obs.client.larva_queue_time < 2) // 0 and 1 because facehuggers/t-domers are slightly deprioritized
new_players++

/proc/convert_k2c(temp)
return ((temp - T0C))

Expand Down
4 changes: 2 additions & 2 deletions code/_onclick/observer.dm
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@
return FALSE

var/deathtime = world.time - timeofdeath
if(deathtime < 2.5 MINUTES)
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 2.5 minutes before rejoining the game!"))
to_chat(src, SPAN_WARNING("You must wait atleast 2.5 minutes before rejoining the game!"))
ManualFollow(target)
return FALSE

Expand Down
36 changes: 28 additions & 8 deletions code/game/gamemodes/cm_initialize.dm
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ Additional game mode variables.
var/datum/hive_status/hive
for(var/hivenumber in GLOB.hive_datum)
hive = GLOB.hive_datum[hivenumber]
if(!hive.hardcore && hive.stored_larva && (hive.hive_location || (world.time < 30 MINUTES + SSticker.round_start_time)))
if(!hive.hardcore && hive.stored_larva && (hive.hive_location || (world.time < XENO_BURIED_LARVA_TIME_LIMIT + SSticker.round_start_time)))
if(SSticker.mode && (SSticker.mode.flags_round_type & MODE_RANDOM_HIVE))
available_xenos |= "any buried larva"
LAZYADD(available_xenos["any buried larva"], hive)
Expand All @@ -364,7 +364,27 @@ Additional game mode variables.
available_xenos[larva_option] = list(hive)

if(!available_xenos.len || (instant_join && !available_xenos_non_ssd.len))
to_chat(xeno_candidate, SPAN_WARNING("There aren't any available xenomorphs or burrowed larvae. You can try getting spawned as a chestburster larva by toggling your Xenomorph candidacy in Preferences -> Toggle SpecialRole Candidacy."))
if(!xeno_candidate.client || !xeno_candidate.client.prefs || !(xeno_candidate.client.prefs.be_special & BE_ALIEN_AFTER_DEATH))
to_chat(xeno_candidate, SPAN_WARNING("There aren't any available xenomorphs or burrowed larvae. You can try getting spawned as a chestburster larva by toggling your Xenomorph candidacy in Preferences -> Toggle SpecialRole Candidacy."))
return FALSE
to_chat(xeno_candidate, SPAN_WARNING("There aren't any available xenomorphs or burrowed larvae."))

// Give the player a cached message of their queue status if they are an observer
var/mob/dead/observer/candidate_observer = xeno_candidate
if(istype(candidate_observer))
if(candidate_observer.larva_queue_cached_message)
to_chat(xeno_candidate, candidate_observer.larva_queue_cached_message)
return FALSE

// No cache, lets check now then
message_alien_candidates(get_alien_candidates(), dequeued = 0, cache_only = TRUE)
if(candidate_observer.larva_queue_cached_message)
to_chat(xeno_candidate, candidate_observer.larva_queue_cached_message)
return FALSE

// We aren't in queue yet, lets teach them about the queue then
candidate_observer.larva_queue_cached_message = SPAN_XENONOTICE("You are currently still awaiting assignment in the larva queue. Priority is given to players who have yet to play in the round, but otherwise the ordering is based on your time of death. When you have been dead long enough and are not inactive, you will periodically receive messages where you are in the queue relative to other currently valid xeno candidates. Note: Playing as a facehugger or in the thunderdome will not alter your time of death. This means you won't lose your relative place in queue if you step away, disconnect, play as a facehugger, or play in the thunderdome.")
to_chat(xeno_candidate, candidate_observer.larva_queue_cached_message)
return FALSE

var/mob/living/carbon/xenomorph/new_xeno
Expand All @@ -377,11 +397,11 @@ Additional game mode variables.
if(!xeno_bypass_timer)
var/deathtime = world.time - xeno_candidate.timeofdeath
if(isnewplayer(xeno_candidate))
deathtime = 2.5 MINUTES //so new players don't have to wait to latejoin as xeno in the round's first 5 mins.
if(deathtime < 2.5 MINUTES && !check_client_rights(xeno_candidate.client, R_ADMIN, FALSE))
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.5 minutes before rejoining the game as a buried larva!"))
to_chat(xeno_candidate, SPAN_WARNING("You must wait 2 minutes and 30 seconds before rejoining the game as a buried larva!"))
return FALSE

for(var/mob_name in picked_hive.banished_ckeys)
Expand All @@ -393,7 +413,7 @@ Additional game mode variables.
noob.close_spawn_windows()
if(picked_hive.hive_location)
picked_hive.hive_location.spawn_burrowed_larva(xeno_candidate)
else if((world.time < 30 MINUTES + SSticker.round_start_time))
else if((world.time < XENO_BURIED_LARVA_TIME_LIMIT + SSticker.round_start_time))
picked_hive.do_buried_larva_spawn(xeno_candidate)
else
to_chat(xeno_candidate, SPAN_WARNING("Seems like something went wrong. Try again?"))
Expand All @@ -417,8 +437,8 @@ Additional game mode variables.
if(!xeno_bypass_timer)
var/deathtime = world.time - xeno_candidate.timeofdeath
if(istype(xeno_candidate, /mob/new_player))
deathtime = 5 MINUTES //so new players don't have to wait to latejoin as xeno in the round's first 5 mins.
if(deathtime < 5 MINUTES && !check_client_rights(xeno_candidate.client, R_ADMIN, FALSE))
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)
Expand Down
2 changes: 2 additions & 0 deletions code/modules/client/client_defines.dm
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
var/adminobs = null
var/area = null
var/time_died_as_mouse = null //when the client last died as a mouse
/// The descriminator for larva queue ordering: Generally set to timeofdeath except for facehuggers/admin z-level play
var/larva_queue_time

var/donator = 0
var/adminhelped = 0
Expand Down
15 changes: 12 additions & 3 deletions code/modules/cm_aliens/structures/special/pylon_core.dm
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
var/last_healed = 0
var/last_attempt = 0 // logs time of last attempt to prevent spam. if you want to destroy it, you must commit.
var/last_larva_time = 0
var/last_larva_queue_time = 0
var/last_surge_time = 0
var/spawn_cooldown = 30 SECONDS
var/surge_cooldown = 90 SECONDS
Expand Down Expand Up @@ -142,11 +143,19 @@
linked_hive.hive_ui.update_burrowed_larva()
qdel(L)

if((last_larva_time + spawn_cooldown) < world.time && can_spawn_larva()) // every minute
var/spawning_larva = can_spawn_larva() && (last_larva_time + spawn_cooldown) < world.time
if(spawning_larva)
last_larva_time = world.time
if(spawning_larva || (last_larva_queue_time + spawn_cooldown * 4) < world.time)
last_larva_queue_time = world.time
var/list/players_with_xeno_pref = get_alien_candidates()
if(players_with_xeno_pref && players_with_xeno_pref.len && can_spawn_larva())
spawn_burrowed_larva(pick(players_with_xeno_pref))
if(players_with_xeno_pref && players_with_xeno_pref.len)
if(spawning_larva && spawn_burrowed_larva(players_with_xeno_pref[1]))
// We were in spawning_larva mode and successfully spawned someone
message_alien_candidates(players_with_xeno_pref, dequeued = 1)
else
// Just time to update everyone their queue status (or the spawn failed)
message_alien_candidates(players_with_xeno_pref, dequeued = 0)

if(linked_hive.hijack_burrowed_surge && (last_surge_time + surge_cooldown) < world.time)
last_surge_time = world.time
Expand Down
19 changes: 14 additions & 5 deletions code/modules/mob/dead/observer/observer.dm
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
plane = GHOST_PLANE
layer = ABOVE_FLY_LAYER
stat = DEAD
var/adminlarva = 0
var/ghostvision = 1
var/adminlarva = FALSE
var/ghostvision = TRUE
var/can_reenter_corpse
var/started_as_observer //This variable is set to 1 when you enter the game as an observer.
//If you died in the game and are a ghost - this will remain as null.
Expand All @@ -45,7 +45,7 @@
"Squad HUD" = FALSE,
"Xeno Status HUD" = FALSE
)
universal_speak = 1
universal_speak = TRUE
var/updatedir = TRUE //Do we have to update our dir as the ghost moves around?
var/atom/movable/following = null
var/datum/orbit_menu/orbit_menu
Expand All @@ -55,6 +55,8 @@
var/own_orbit_size = 0
var/observer_actions = list(/datum/action/observer_action/join_xeno)
var/datum/action/minimap/observer/minimap
var/larva_queue_cached_message

alpha = 127

/mob/dead/observer/verb/toggle_ghostsee()
Expand Down Expand Up @@ -327,8 +329,8 @@ Works together with spawning an observer, noted above.
ghost.langchat_make_image()

SStgui.on_transfer(src, ghost)
if(is_admin_level(z))
ghost.timeofdeath = 0 // Bypass respawn limit if you die on the admin zlevel
if(is_admin_level((get_turf(src))?.z)) // Gibbed humans ghostize the brain in their head which itself is z 0
ghost.timeofdeath = 1 // Bypass respawn limit if you die on the admin zlevel

ghost.key = key
ghost.mind = mind
Expand Down Expand Up @@ -363,6 +365,12 @@ Works together with spawning an observer, noted above.
if(ghost.client.player_data)
ghost.client.player_data.load_timestat_data()

// Larva queue: We use the larger of their existing queue time or the new timeofdeath except for facehuggers
// We don't change facehugger timeofdeath because they are still on cooldown if they died as a hugger
// Facehuggers are atleast 1 because they did get some action compared to those at 0 timeofdeath
var/new_tod = isfacehugger(src) ? 1 : ghost.timeofdeath
ghost.client.larva_queue_time = max(ghost.client.larva_queue_time, new_tod)

ghost.set_huds_from_prefs()

return ghost
Expand Down Expand Up @@ -405,6 +413,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
var/mob/dead/observer/ghost = ghostize((is_nested && nest && !QDELETED(nest))) //FALSE parameter is so we can never re-enter our body, "Charlie, you can never come baaaack~" :3
if(ghost && !is_admin_level(z))
ghost.timeofdeath = world.time
ghost.client?.larva_queue_time = world.time
if(is_nested && nest && !QDELETED(nest))
ghost.can_reenter_corpse = FALSE

Expand Down
3 changes: 2 additions & 1 deletion code/modules/mob/living/carbon/xenomorph/Embryo.dm
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@
var/list/candidates = get_alien_candidates()

if(candidates && candidates.len)
picked = pick(candidates)
picked = candidates[1]
message_alien_candidates(candidates, dequeued = 1)

// Spawn the larva
var/mob/living/carbon/xenomorph/larva/new_xeno
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@
for(var/mob/dead/observer/observer as anything in GLOB.observer_list)
to_chat(observer, SPAN_DEADSAY("<b>[human]</b> has been facehugged by <b>[src]</b>" + " [OBSERVER_JMP(observer, human)]"))
to_chat(src, SPAN_DEADSAY("<b>[human]</b> has been facehugged by <b>[src]</b>"))
timeofdeath = 1 // Ever so slightly deprioritized for larva queue
qdel(src)
if(hug_area)
xeno_message(SPAN_XENOMINORWARNING("You sense that [src] has facehugged a host at \the [hug_area]!"), 1, src.hivenumber)
Expand Down
18 changes: 11 additions & 7 deletions code/modules/mob/living/carbon/xenomorph/death.dm
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,28 @@

if(GLOB.hive_datum[hivenumber].stored_larva)
GLOB.hive_datum[hivenumber].stored_larva = round(GLOB.hive_datum[hivenumber].stored_larva * 0.5) //Lose half on dead queen
var/turf/larva_spawn

var/list/players_with_xeno_pref = get_alien_candidates()
while(GLOB.hive_datum[hivenumber].stored_larva > 0 && istype(GLOB.hive_datum[hivenumber].hive_location, /obj/effect/alien/resin/special/pylon/core)) // stil some left
larva_spawn = get_turf(GLOB.hive_datum[hivenumber].hive_location)
if(players_with_xeno_pref && players_with_xeno_pref.len)
var/mob/xeno_candidate = pick(players_with_xeno_pref)
if(players_with_xeno_pref && istype(GLOB.hive_datum[hivenumber].hive_location, /obj/effect/alien/resin/special/pylon/core))
var/turf/larva_spawn = get_turf(GLOB.hive_datum[hivenumber].hive_location)
var/count = 0
while(GLOB.hive_datum[hivenumber].stored_larva > 0 && count < length(players_with_xeno_pref)) // still some left
var/mob/xeno_candidate = players_with_xeno_pref[++count]
var/mob/living/carbon/xenomorph/larva/new_xeno = new /mob/living/carbon/xenomorph/larva(larva_spawn)
new_xeno.set_hive_and_update(hivenumber)

new_xeno.generate_name()
if(!SSticker.mode.transfer_xeno(xeno_candidate, new_xeno))
qdel(new_xeno)
return

new_xeno.visible_message(SPAN_XENODANGER("A larva suddenly burrows out of the ground!"),
SPAN_XENODANGER("You burrow out of the ground after feeling an immense tremor through the hive, which quickly fades into complete silence..."))

GLOB.hive_datum[hivenumber].stored_larva--
GLOB.hive_datum[hivenumber].hive_ui.update_burrowed_larva()
GLOB.hive_datum[hivenumber].stored_larva--
GLOB.hive_datum[hivenumber].hive_ui.update_burrowed_larva()
if(count)
message_alien_candidates(players_with_xeno_pref, dequeued = count)

if(hive && hive.living_xeno_queen == src)
xeno_message(SPAN_XENOANNOUNCE("A sudden tremor ripples through the hive... the Queen has been slain! Vengeance!"),3, hivenumber)
Expand Down
3 changes: 2 additions & 1 deletion code/modules/shuttle/helpers.dm
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

/datum/door_controller/aggregate/Destroy(force, ...)
. = ..()
QDEL_NULL_LIST(door_controllers)
QDEL_LIST_ASSOC_VAL(door_controllers)
door_controllers = null

/datum/door_controller/aggregate/proc/set_label(label)
for(var/datum/door_controller/single/cont in door_controllers)
Expand Down
Loading