diff --git a/code/__DEFINES/dynamic.dm b/code/__DEFINES/dynamic.dm index 6a6e734cba7..f07b7e20b0f 100644 --- a/code/__DEFINES/dynamic.dm +++ b/code/__DEFINES/dynamic.dm @@ -27,3 +27,8 @@ /// Max number of teams we can have for the abductor ruleset #define ABDUCTOR_MAX_TEAMS 4 + +// Ruletype defines +#define ROUNDSTART_RULESET "Roundstart" +#define LATEJOIN_RULESET "Latejoin" +#define MIDROUND_RULESET "Midround" diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index 96ab26883d9..818a2ef72b4 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -262,7 +262,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) return forced_latejoin_rule = added_rule log_admin("[key_name(usr)] set [added_rule] to proc on the next latejoin.") - message_admins("[key_name(usr)] set [added_rule] to proc on the next latejoin.") + message_admins("[key_name(usr)] set [added_rule] to proc on the next valid latejoin.") else if(href_list["clear_forced_latejoin"]) forced_latejoin_rule = null log_admin("[key_name(usr)] cleared the forced latejoin ruleset.") @@ -680,38 +680,63 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) return if (forced_latejoin_rule) - forced_latejoin_rule.candidates = list(newPlayer) - forced_latejoin_rule.trim_candidates() - forced_latejoin_rule.load_templates() - log_dynamic("Forcing ruleset [forced_latejoin_rule]") - if (forced_latejoin_rule.ready(TRUE)) - if (!forced_latejoin_rule.repeatable) - latejoin_rules = remove_from_list(latejoin_rules, forced_latejoin_rule.type) - addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/game_mode/dynamic/, execute_midround_latejoin_rule), forced_latejoin_rule), forced_latejoin_rule.delay) - forced_latejoin_rule = null + log_dynamic("Forcing specific [forced_latejoin_rule.ruletype] ruleset [forced_latejoin_rule].") + if(!handle_executing_latejoin(forced_latejoin_rule, newPlayer, forced = TRUE)) + message_admins("The forced latejoin ruleset [forced_latejoin_rule.name] couldn't be executed \ + as the most recent latejoin did not fulfill the ruleset's requirements.") + return - else if (latejoin_injection_cooldown < world.time && (forced_injection || prob(latejoin_roll_chance))) - forced_injection = FALSE + if(latejoin_injection_cooldown >= world.time && !forced_injection && !prob(latejoin_roll_chance)) + return - var/list/drafted_rules = list() - for (var/datum/dynamic_ruleset/latejoin/rule in latejoin_rules) - if (!rule.weight) - continue - if (rule.acceptable(GLOB.alive_player_list.len, threat_level) && mid_round_budget >= rule.cost) - // No stacking : only one round-ender, unless threat level > stacking_limit. - if (threat_level < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking) - if(rule.flags & HIGH_IMPACT_RULESET && high_impact_ruleset_executed) - continue + var/was_forced = forced_injection + forced_injection = FALSE + var/list/possible_latejoin_rules = list() + for (var/datum/dynamic_ruleset/latejoin/rule in latejoin_rules) + if(!rule.weight) + continue + if(mid_round_budget < rule.cost) + continue + if(!rule.acceptable(GLOB.alive_player_list.len, threat_level)) + continue + possible_latejoin_rules[rule] = rule.get_weight() - rule.candidates = list(newPlayer) - rule.trim_candidates() - rule.load_templates() - if (rule.ready()) - drafted_rules[rule] = rule.get_weight() + if(!length(possible_latejoin_rules)) + log_dynamic("FAIL: [newPlayer] was selected to roll for a latejoin ruleset, but there were no valid rulesets.") + return - if (drafted_rules.len > 0 && pick_latejoin_rule(drafted_rules)) - var/latejoin_injection_cooldown_middle = 0.5*(latejoin_delay_max + latejoin_delay_min) - latejoin_injection_cooldown = round(clamp(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), latejoin_delay_min, latejoin_delay_max)) + world.time + log_dynamic("[newPlayer] was selected to roll for a latejoin ruleset from the following list: [english_list(possible_latejoin_rules)].") + // You get one shot at becoming a latejoin antag, if it fails the next guy will try. + var/datum/dynamic_ruleset/latejoin/picked_rule = pick_ruleset(possible_latejoin_rules, max_allowed_attempts = 1) + if(isnull(picked_rule)) + log_dynamic("FAIL: No valid rulset was selected for [newPlayer]'s latejoin[was_forced ? "" : ", the next player will be checked instead"].") + return + if(was_forced) + log_dynamic("Forcing random [picked_rule.ruletype] ruleset [picked_rule].") + handle_executing_latejoin(picked_rule, newPlayer, forced = was_forced) + +/** + * This proc handles the execution of a latejoin ruleset, including removing it from latejoin rulesets if not repeatable, + * upping the injection cooldown, and starting a timer to execute the ruleset on delay. + */ +/datum/game_mode/dynamic/proc/handle_executing_latejoin(datum/dynamic_ruleset/ruleset, mob/living/carbon/human/only_candidate, forced = FALSE) + ruleset.candidates = list(only_candidate) + ruleset.trim_candidates() + ruleset.load_templates() + if (!ruleset.ready(forced)) + log_dynamic("FAIL: [only_candidate] was selected to latejoin with the [ruleset] ruleset, \ + but the ruleset failed to execute[length(ruleset.candidates) ? "":" as they were not a valid candiate"].") + return FALSE + if (!ruleset.repeatable) + latejoin_rules = remove_from_list(latejoin_rules, ruleset.type) + addtimer(CALLBACK(src, PROC_REF(execute_midround_latejoin_rule), ruleset), ruleset.delay) + + if(!forced) + var/latejoin_injection_cooldown_middle = 0.5 * (latejoin_delay_max + latejoin_delay_min) + latejoin_injection_cooldown = round(clamp(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), latejoin_delay_min, latejoin_delay_max)) + world.time + log_dynamic("A latejoin rulset triggered successfully, the next latejoin injection will happen at [latejoin_injection_cooldown] round time.") + + return TRUE /// Apply configurations to rule. /datum/game_mode/dynamic/proc/configure_ruleset(datum/dynamic_ruleset/ruleset) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm index 0e6ea0e7d0c..87147e6cd35 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm @@ -96,11 +96,11 @@ ..() /datum/dynamic_ruleset/roundstart // One or more of those drafted at roundstart - ruletype = "Roundstart" + ruletype = ROUNDSTART_RULESET // Can be drafted when a player joins the server /datum/dynamic_ruleset/latejoin - ruletype = "Latejoin" + ruletype = LATEJOIN_RULESET /// By default, a rule is acceptable if it satisfies the threat level/population requirements. /// If your rule has extra checks, such as counting security officers, do that in ready() instead diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index 902d38b3404..4321b6f83dd 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -5,7 +5,7 @@ /// Midround Rulesets /datum/dynamic_ruleset/midround // Can be drafted once in a while during a round - ruletype = "Midround" + ruletype = MIDROUND_RULESET var/midround_ruleset_style /// If the ruleset should be restricted from ghost roles. var/restrict_ghost_roles = TRUE diff --git a/code/game/gamemodes/dynamic/ruleset_picking.dm b/code/game/gamemodes/dynamic/ruleset_picking.dm index 4eae6fd993b..5d907e6580c 100644 --- a/code/game/gamemodes/dynamic/ruleset_picking.dm +++ b/code/game/gamemodes/dynamic/ruleset_picking.dm @@ -1,13 +1,25 @@ #define ADMIN_CANCEL_MIDROUND_TIME (10 SECONDS) -/// From a list of rulesets, returns one based on weight and availability. -/// Mutates the list that is passed into it to remove invalid rules. -/datum/game_mode/dynamic/proc/pick_ruleset(list/drafted_rules) +/// +/// +/** + * From a list of rulesets, returns one based on weight and availability. + * Mutates the list that is passed into it to remove invalid rules. + * + * * max_allowed_attempts - Allows you to configure how many times the proc will attempt to pick a ruleset before giving up. + */ +/datum/game_mode/dynamic/proc/pick_ruleset(list/drafted_rules, max_allowed_attempts = INFINITY) if (only_ruleset_executed) log_dynamic("FAIL: only_ruleset_executed") return null - while (TRUE) + if(!length(drafted_rules)) + log_dynamic("FAIL: pick ruleset supplied with an empty list of drafted rules.") + return null + + var/attempts = 0 + while (attempts < max_allowed_attempts) + attempts++ var/datum/dynamic_ruleset/rule = pick_weight(drafted_rules) if (!rule) var/list/leftover_rules = list() @@ -15,10 +27,10 @@ leftover_rules += "[leftover_rule]" log_dynamic("FAIL: No rulesets left to pick. Leftover rules: [leftover_rules.Join(", ")]") - return null if (check_blocking(rule.blocking_rules, executed_rules)) + log_dynamic("FAIL: [rule] can't execute as another rulset is blocking it.") drafted_rules -= rule if(drafted_rules.len <= 0) return null @@ -37,6 +49,8 @@ return rule + return null + /// Executes a random midround ruleset from the list of drafted rules. /datum/game_mode/dynamic/proc/pick_midround_rule(list/drafted_rules, description) log_dynamic("Rolling [drafted_rules.len] [description]") @@ -68,28 +82,18 @@ midround_rules = remove_from_list(midround_rules, rule.type) addtimer(CALLBACK(src, PROC_REF(execute_midround_latejoin_rule), rule), rule.delay) -/// Executes a random latejoin ruleset from the list of drafted rules. -/datum/game_mode/dynamic/proc/pick_latejoin_rule(list/drafted_rules) - var/datum/dynamic_ruleset/rule = pick_ruleset(drafted_rules) - if (isnull(rule)) - return - if (!rule.repeatable) - latejoin_rules = remove_from_list(latejoin_rules, rule.type) - addtimer(CALLBACK(src, PROC_REF(execute_midround_latejoin_rule), rule), rule.delay) - return TRUE - /// Mainly here to facilitate delayed rulesets. All midround/latejoin rulesets are executed with a timered callback to this proc. /datum/game_mode/dynamic/proc/execute_midround_latejoin_rule(sent_rule) var/datum/dynamic_ruleset/rule = sent_rule spend_midround_budget(rule.cost, threat_log, "[worldtime2text()]: [rule.ruletype] [rule.name]") rule.pre_execute(GLOB.alive_player_list.len) if (rule.execute()) - log_dynamic("Injected a [rule.ruletype == "latejoin" ? "latejoin" : "midround"] ruleset [rule.name].") + log_dynamic("Injected a [rule.ruletype] ruleset [rule.name].") if(rule.flags & HIGH_IMPACT_RULESET) high_impact_ruleset_executed = TRUE else if(rule.flags & ONLY_RULESET) only_ruleset_executed = TRUE - if(rule.ruletype == "Latejoin") + if(rule.ruletype == LATEJOIN_RULESET) var/mob/M = pick(rule.candidates) message_admins("[key_name(M)] joined the station, and was selected by the [rule.name] ruleset.") log_dynamic("[key_name(M)] joined the station, and was selected by the [rule.name] ruleset.")