diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index 1b9be7b3c5b..a9e9b459116 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -384,6 +384,24 @@ return active_players +/// Get living players who are playing in the round (no ghosts) +/proc/get_living_players_count() + var/count = 0 + for(var/mob/living/carbon/human/player in GLOB.human_list) + if(player.mind && player.stat != DEAD && player.client) + count++ + return count + +/// Same as above but only count Sec +/datum/antagonist/traitor/proc/get_living_security_players_count() + var/count = 0 + for(var/mob/living/carbon/human/player in GLOB.human_list) + if(!player.mind || player.stat == DEAD || !player.client) + continue + if(player.mind.assigned_role in GLOB.active_security_positions) + count++ + return count + /proc/mobs_in_area(area/the_area, client_needed=0, moblist=GLOB.mob_list) var/list/mobs_found[0] for(var/mob/M in moblist) diff --git a/code/_globalvars/game_modes.dm b/code/_globalvars/game_modes.dm index 7981c71a1bd..9ceadf9e7c0 100644 --- a/code/_globalvars/game_modes.dm +++ b/code/_globalvars/game_modes.dm @@ -6,5 +6,7 @@ GLOBAL_DATUM(start_state, /datum/station_state) // Used in round-end report. Don GLOBAL_VAR(custom_event_msg) +GLOBAL_VAR(roundstart_ready_players) // Number of players who were readied up at roundstart + /// We want anomalous_particulate_tracker to exist only once and be accessible from anywhere. GLOBAL_DATUM_INIT(anomaly_smash_track, /datum/anomalous_particulate_tracker, new) diff --git a/code/controllers/configuration/sections/gamemode_configuration.dm b/code/controllers/configuration/sections/gamemode_configuration.dm index ef8bfc3c84d..50d18eafaa2 100644 --- a/code/controllers/configuration/sections/gamemode_configuration.dm +++ b/code/controllers/configuration/sections/gamemode_configuration.dm @@ -22,6 +22,12 @@ var/enable_gamemode_player_limit = TRUE /// Should we generate random station traits at game start? var/add_random_station_traits = TRUE + /// Minimum readied up players required for roundstart hijack objectives + var/min_players_hijack_roundstart = 30 + /// Minimum alive crew required for midround hijack objectives + var/min_players_hijack_midround = 20 + /// Minimum living security required for midround hijack objectives + var/min_security_hijack_midround = 2 // Dynamically setup a list of all gamemodes /datum/configuration_section/gamemode_configuration/New() @@ -57,6 +63,10 @@ CONFIG_LOAD_NUM(traitor_objectives_amount, data["traitor_objective_amount"]) + CONFIG_LOAD_NUM(min_players_hijack_roundstart, data["min_players_hijack_roundstart"]) + CONFIG_LOAD_NUM(min_players_hijack_midround, data["min_players_hijack_midround"]) + CONFIG_LOAD_NUM(min_security_hijack_midround, data["min_security_hijack_midround"]) + // Load gamemode probabilities if(islist(data["gamemode_probabilities"])) for(var/list/assocset in data["gamemode_probabilities"]) diff --git a/code/controllers/subsystem/SSticker.dm b/code/controllers/subsystem/SSticker.dm index e605af697d8..efb19d3d207 100644 --- a/code/controllers/subsystem/SSticker.dm +++ b/code/controllers/subsystem/SSticker.dm @@ -276,6 +276,13 @@ SUBSYSTEM_DEF(ticker) // Behold, a rough way of figuring out what takes 10 years var/watch = start_watch() + + // Count ready players before we spawn them for hijack objective requirements + GLOB.roundstart_ready_players = 0 + for(var/mob/new_player/player in GLOB.new_player_mobs) + if(player.ready && player.client) + GLOB.roundstart_ready_players++ + create_characters() // Create player characters and transfer clients log_debug("Creating characters took [stop_watch(watch)]s") diff --git a/code/game/gamemodes/dynamic/antag_rulesets.dm b/code/game/gamemodes/dynamic/antag_rulesets.dm index 99bdebf18f1..db76e6fc850 100644 --- a/code/game/gamemodes/dynamic/antag_rulesets.dm +++ b/code/game/gamemodes/dynamic/antag_rulesets.dm @@ -200,6 +200,7 @@ if(ishuman(antag.current)) traitor_datum.delayed_objectives = TRUE traitor_datum.addtimer(CALLBACK(traitor_datum, TYPE_PROC_REF(/datum/antagonist/traitor, reveal_delayed_objectives)), latespawn_time, TIMER_DELETE_ME) + traitor_datum.is_roundstart = TRUE antag.add_antag_datum(traitor_datum) SSblackbox.record_feedback("nested tally", "dynamic_selections", 1, list("roundstart", "[antagonist_type]")) diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm index c7064e98534..e821838c4b5 100644 --- a/code/game/gamemodes/traitor/traitor.dm +++ b/code/game/gamemodes/traitor/traitor.dm @@ -58,6 +58,7 @@ traitor_datum.delayed_objectives = TRUE traitor_datum.addtimer(CALLBACK(traitor_datum, TYPE_PROC_REF(/datum/antagonist/traitor, reveal_delayed_objectives)), random_time, TIMER_DELETE_ME) + traitor_datum.is_roundstart = TRUE traitor.add_antag_datum(traitor_datum) /datum/game_mode/traitor/traitors_to_add() diff --git a/code/game/gamemodes/trifecta/trifecta.dm b/code/game/gamemodes/trifecta/trifecta.dm index 3b640f487ca..610ab33299c 100644 --- a/code/game/gamemodes/trifecta/trifecta.dm +++ b/code/game/gamemodes/trifecta/trifecta.dm @@ -123,6 +123,7 @@ traitor_datum.delayed_objectives = TRUE traitor_datum.addtimer(CALLBACK(traitor_datum, TYPE_PROC_REF(/datum/antagonist/traitor, reveal_delayed_objectives)), random_time, TIMER_DELETE_ME) + traitor_datum.is_roundstart = TRUE traitor.add_antag_datum(traitor_datum) ..() diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index ef4e102313d..e2119f596de 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -405,6 +405,12 @@ GLOBAL_LIST_EMPTY(antagonists) /datum/antagonist/proc/finalize_antag() return +/** + * Check if this antag can be assigned hijack. + */ +/datum/antagonist/proc/can_assign_hijack_objective() + return FALSE + /** * Create and assign a full set of randomized, basic human traitor objectives. * can_hijack - If you want the 5% chance for the antagonist to be able to roll hijack, only true for traitors @@ -412,11 +418,13 @@ GLOBAL_LIST_EMPTY(antagonists) /datum/antagonist/proc/forge_basic_objectives(can_hijack = FALSE, number_of_objectives = GLOB.configuration.gamemode.traitor_objectives_amount) // Hijack objective. if(can_hijack && prob(5) && !(locate(/datum/objective/hijack) in owner.get_all_objectives())) - if(prob(50)) // 50% chance you have to detonate the nuke instead - add_antag_objective(/datum/objective/nuke) - return - add_antag_objective(/datum/objective/hijack) - return // Hijack should be their only objective (normally), so return. + // Check if hijack is allowed based on player count and number of sec + if(can_assign_hijack_objective()) + if(prob(50)) // 50% chance you have to detonate the nuke instead + add_antag_objective(/datum/objective/nuke) + return + add_antag_objective(/datum/objective/hijack) + return // Hijack should be their only objective (normally), so return. // Will give normal steal/kill/etc. type objectives. for(var/i in 1 to number_of_objectives) diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm index 5308e5d3100..22c68740fa9 100644 --- a/code/modules/antagonists/traitor/datum_traitor.dm +++ b/code/modules/antagonists/traitor/datum_traitor.dm @@ -21,6 +21,8 @@ RESTRICT_TYPE(/datum/antagonist/traitor) /// Have we / are we sending a backstab message at this time. If we are, do not send another. var/sending_backstab = FALSE + /// Whether this traitor was assigned during round start + var/is_roundstart = FALSE /datum/antagonist/traitor/on_gain() // Create this in case the traitor wants to mindslaves someone. @@ -90,7 +92,14 @@ RESTRICT_TYPE(/datum/antagonist/traitor) /datum/antagonist/traitor/select_organization() if(is_ai(owner.current)) return - var/chaos = pickweight(list(ORG_CHAOS_HUNTER = ORG_PROB_HUNTER, ORG_CHAOS_MILD = ORG_PROB_MILD, ORG_CHAOS_AVERAGE = ORG_PROB_AVERAGE, ORG_CHAOS_HIJACK = ORG_PROB_HIJACK)) + var/list/chaos_weights = list( + ORG_CHAOS_HUNTER = ORG_PROB_HUNTER, + ORG_CHAOS_MILD = ORG_PROB_MILD, + ORG_CHAOS_AVERAGE = ORG_PROB_AVERAGE + ) + if(can_assign_hijack_objective()) + chaos_weights[ORG_CHAOS_HIJACK] = ORG_PROB_HIJACK + var/chaos = pickweight(chaos_weights) for(var/org_type in shuffle(subtypesof(/datum/antag_org/syndicate))) var/datum/antag_org/org = org_type if(initial(org.chaos_level) == chaos) @@ -344,3 +353,21 @@ RESTRICT_TYPE(/datum/antagonist/traitor) var/list/messages = owner.prepare_announce_objectives() to_chat(owner.current, chat_box_red(messages.Join("
"))) SEND_SOUND(owner.current, sound('sound/ambience/alarm4.ogg')) + +/// Helper functions for hijack pop checks + +/datum/antagonist/traitor/can_assign_hijack_objective() + var/total_players + if(is_roundstart) + total_players = GLOB.roundstart_ready_players + if(total_players < GLOB.configuration.gamemode.min_players_hijack_roundstart) + return FALSE + return TRUE + total_players = get_living_players_count() + if(total_players < GLOB.configuration.gamemode.min_players_hijack_midround) + return FALSE + var/security_count = get_living_security_players_count() + if(security_count < GLOB.configuration.gamemode.min_security_hijack_midround) + return FALSE + return TRUE + diff --git a/config/example/config.toml b/config/example/config.toml index c8b28564a7b..3ac5921d3a3 100644 --- a/config/example/config.toml +++ b/config/example/config.toml @@ -285,6 +285,13 @@ enable_gamemode_player_limit = true # Enable to generate zero or more random station traits on game start. add_random_station_traits = false +# Minimum readied up players required for roundstart hijack objectives +min_players_hijack_roundstart = 30 +# Minimum alive crew required for midround hijack objectives +min_players_hijack_midround = 20 +# Minimum security required for midround hijack objectives +min_security_hijack_midround = 2 + ################################################################ [general_configuration]