From ea6f252e810bea0d52be32a5b8a3fa6daf0d5b90 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Mon, 23 May 2022 17:06:51 +0200 Subject: [PATCH] [MIRROR] Dynamic 2022, Part 1 - Redesigning midround rolls (Midrounds happen far more frequently now, and scale their power over time) [MDB IGNORE] (#13718) * Dynamic 2022, Part 1 - Redesigning midround rolls (Midrounds happen far more frequently now, and scale their power over time) * Update dynamic_rulesets_midround.dm * wew Co-authored-by: Mothblocks <35135081+Mothblocks@users.noreply.github.com> Co-authored-by: Zonespace <41448081+Zonespace27@users.noreply.github.com> Co-authored-by: Gandalf <9026500+Gandalf2k15@users.noreply.github.com> --- code/__DEFINES/dynamic.dm | 9 +- code/game/gamemodes/dynamic/dynamic.dm | 191 +++++++----------- .../gamemodes/dynamic/dynamic_hijacking.dm | 8 +- .../dynamic/dynamic_midround_rolling.dm | 90 +++++++++ .../dynamic/dynamic_rulesets_latejoin.dm | 4 +- .../dynamic/dynamic_rulesets_midround.dm | 97 ++++----- .../dynamic/dynamic_rulesets_roundstart.dm | 9 - code/game/gamemodes/dynamic/readme.md | 38 ++-- .../game/gamemodes/dynamic/ruleset_picking.dm | 4 +- .../antagonists/_common/antag_datum.dm | 10 +- .../antagonists/ashwalker/ashwalker.dm | 1 + .../antagonists/brainwashing/brainwashing.dm | 1 + .../antagonists/changeling/changeling.dm | 2 +- code/modules/antagonists/creep/creep.dm | 1 + code/modules/antagonists/ert/ert.dm | 1 + code/modules/antagonists/fugitive/fugitive.dm | 1 + code/modules/antagonists/fugitive/hunter.dm | 1 + .../antagonists/greentext/greentext.dm | 1 + .../antagonists/highlander/highlander.dm | 1 + .../antagonists/hypnotized/hypnotized.dm | 1 + code/modules/antagonists/thief/thief.dm | 1 + .../antagonists/valentines/valentine.dm | 2 +- code/modules/mob/mob_lists.dm | 9 +- .../unit_tests/dynamic_ruleset_sanity.dm | 9 +- .../modules/Midroundtraitor/code/event.dm | 1 + .../code/cortical_borer_antag.dm | 1 + tgstation.dme | 1 + 27 files changed, 286 insertions(+), 209 deletions(-) create mode 100644 code/game/gamemodes/dynamic/dynamic_midround_rolling.dm diff --git a/code/__DEFINES/dynamic.dm b/code/__DEFINES/dynamic.dm index dedf05396c5..ae32d40a930 100644 --- a/code/__DEFINES/dynamic.dm +++ b/code/__DEFINES/dynamic.dm @@ -7,11 +7,14 @@ /// This ruleset can only be picked once. Anything that does not have a scaling_cost MUST have this. #define LONE_RULESET (1 << 2) +/// This is a "heavy" midround ruleset, and should be run later into the round +#define MIDROUND_RULESET_STYLE_HEAVY "Heavy" + +/// This is a "light" midround ruleset, and should be run early into the round +#define MIDROUND_RULESET_STYLE_LIGHT "Light" + /// No round event was hijacked this cycle #define HIJACKED_NOTHING "HIJACKED_NOTHING" /// This cycle, a round event was hijacked when the last midround event was too recent. #define HIJACKED_TOO_RECENT "HIJACKED_TOO_RECENT" - -/// This cycle, a round event was hijacked when the next midround event is too soon. -#define HIJACKED_TOO_SOON "HIJACKED_TOO_SOON" diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index f19871ccbe3..c5641bcb749 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -78,26 +78,29 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) /// The maximum time the recurring latejoin ruleset timer is allowed to be. var/latejoin_delay_max = (25 MINUTES) - /// When world.time is over this number the mode tries to inject a midround ruleset. - var/midround_injection_cooldown = 0 + /// The low bound for the midround roll time splits. + /// This number influences where to place midround rolls, making this smaller + /// will make midround rolls more frequent, and vice versa. + /// A midround will never be able to roll before this. + var/midround_lower_bound = 10 MINUTES - /// The minimum time the recurring midround ruleset timer is allowed to be. - var/midround_delay_min = (15 MINUTES) + /// The upper bound for the midround roll time splits. + /// This number influences where to place midround rolls, making this larger + /// will make midround rolls less frequent, and vice versa. + /// A midround will never be able to roll farther than this. + var/midround_upper_bound = 100 MINUTES - /// The maximum time the recurring midround ruleset timer is allowed to be. - var/midround_delay_max = (35 MINUTES) + /// The distance between the chosen midround roll point (which is deterministic), + /// and when it can actually roll. + /// Basically, if this is set to 5 minutes, and a midround roll point is decided to be at 20 minutes, + /// then it can roll anywhere between 15 and 25 minutes. + var/midround_roll_distance = 3 MINUTES - /// If above this threat, increase the chance of injection - var/higher_injection_chance_minimum_threat = 70 - - /// The chance of injection increase when above higher_injection_chance_minimum_threat - var/higher_injection_chance = 15 - - /// If below this threat, decrease the chance of injection - var/lower_injection_chance_minimum_threat = 10 - - /// The chance of injection decrease when above lower_injection_chance_minimum_threat - var/lower_injection_chance = 15 + /// The amount of threat per midround roll. + /// Basically, if this is set to 5, then for every 5 threat, one midround roll will be added. + /// The equation this is used in rounds up, meaning that if this is set to 5, and you have 6 + /// threat, then you will get 2 midround rolls. + var/threat_per_midround_roll = 6.5 /// A number between -5 and +5. /// A negative value will give a more peaceful round and @@ -127,15 +130,49 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) /// The maximum amount of time for antag random events to be hijacked. var/random_event_hijack_maximum = 18 MINUTES + /// What is the lower bound of when the roundstart annoucement is sent out? + var/waittime_l = 600 + + /// What is the higher bound of when the roundstart annoucement is sent out? + var/waittime_h = 1800 + + /// Maximum amount of threat allowed to generate. + var/max_threat_level = 100 + + /// The extra chance multiplier that a heavy impact midround ruleset will run next time. + /// For example, if this is set to 50, then the next heavy roll will be about 50% more likely to happen. + var/hijacked_random_event_injection_chance_modifier = 50 + + /// Any midround before this point is guaranteed to be light + var/midround_light_upper_bound = 25 MINUTES + + /// Any midround after this point is guaranteed to be heavy + var/midround_heavy_lower_bound = 55 MINUTES + + /// If there are less than this many players readied, threat level will be lowered. + /// This number should be kept fairly low, as there are other measures that population + /// impacts Dynamic, such as the requirements variable on rulesets. + var/low_pop_player_threshold = 20 + + /// The maximum threat that can roll with *zero* players. + /// As the number of players approaches `low_pop_player_threshold`, the maximum + /// threat level will increase. + /// For example, if `low_pop_minimum_threat` is 50, `low_pop_player_threshold` is 20, + /// and the number of readied players is 10, then the highest threat that can roll is + /// lerp(50, 100, 10 / 20), AKA 75. + var/low_pop_minimum_threat = 50 + + /// The chance for latejoins to roll when ready + var/latejoin_roll_chance = 50 + + // == EVERYTHING BELOW THIS POINT SHOULD NOT BE CONFIGURED == + /// A list of recorded "snapshots" of the round, stored in the dynamic.json log var/list/datum/dynamic_snapshot/snapshots /// The time when the last midround injection was attempted, whether or not it was successful var/last_midround_injection_attempt = 0 - /// The amount to inject when a round event is hijacked - var/hijacked_random_event_injection_chance = 50 - /// Whether or not a random event has been hijacked this midround cycle var/random_event_hijacked = HIJACKED_NOTHING @@ -150,14 +187,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) /// Can differ from the actual threat amount. var/shown_threat - /// What is the lower bound of when the roundstart annoucement is sent out? - var/waittime_l = 600 - /// What is the higher bound of when the roundstart annoucement is sent out? - var/waittime_h = 1800 - - /// Maximum amount of threat allowed to generate. - var/max_threat_level = 100 - + VAR_PRIVATE/next_midround_injection /datum/game_mode/dynamic/admin_panel() var/list/dat = list("Game Mode Panel

Game Mode Panel

") @@ -186,9 +216,15 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) dat += "[DR.ruletype] - [DR.name]
" else dat += "none.
" - dat += "
Injection Timers: ([get_injection_chance(dry_run = TRUE)]% latejoin chance, [get_midround_injection_chance(dry_run = TRUE)]% midround chance)
" + dat += "
Injection Timers: ([get_heavy_midround_injection_chance(dry_run = TRUE)]% heavy midround chance)
" dat += "Latejoin: [DisplayTimeText(latejoin_injection_cooldown-world.time)] \[Now!\]
" - dat += "Midround: [DisplayTimeText(midround_injection_cooldown-world.time)] \[Now!\]
" + + var/next_injection = next_midround_injection() + if (next_injection == INFINITY) + dat += "All midrounds have been exhausted." + else + dat += "Midround: [DisplayTimeText(next_injection - world.time)] \[Now!\]
" + usr << browse(dat.Join(), "window=gamemode_panel;size=500x500") /datum/game_mode/dynamic/Topic(href, href_list) @@ -215,9 +251,9 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) forced_injection = TRUE message_admins("[key_name(usr)] forced a latejoin injection.") else if (href_list["injectmid"]) - midround_injection_cooldown = 0 forced_injection = TRUE message_admins("[key_name(usr)] forced a midround injection.") + try_midround_roll() else if (href_list["threatlog"]) show_threatlog(usr) else if (href_list["stacking_limit"]) @@ -330,6 +366,9 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) var/relative_threat = LORENTZ_DISTRIBUTION(threat_curve_centre, threat_curve_width) threat_level = clamp(round(lorentz_to_amount(relative_threat), 0.1), 0, max_threat_level) + if (SSticker.totalPlayersReady < low_pop_player_threshold) + threat_level = min(threat_level, LERP(low_pop_minimum_threat, max_threat_level, SSticker.totalPlayersReady / low_pop_player_threshold)) + peaceful_percentage = round(LORENTZ_CUMULATIVE_DISTRIBUTION(relative_threat, threat_curve_centre, threat_curve_width), 0.01)*100 /// Generates the midround and roundstart budgets @@ -362,9 +401,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) 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 - var/midround_injection_cooldown_middle = 0.5*(midround_delay_max + midround_delay_min) - midround_injection_cooldown = round(clamp(EXP_DISTRIBUTION(midround_injection_cooldown_middle), midround_delay_min, midround_delay_max)) + world.time - /datum/game_mode/dynamic/pre_setup() if(CONFIG_GET(flag/dynamic_config_enabled)) var/json_file = file("[global.config.directory]/dynamic.json") @@ -611,80 +647,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if(rule.rule_process() == RULESET_STOP_PROCESSING) // If rule_process() returns 1 (RULESET_STOP_PROCESSING), stop processing. current_rules -= rule - if (midround_injection_cooldown < world.time) - if (GLOB.dynamic_forced_extended) - return - - // Somehow it managed to trigger midround multiple times so this was moved here. - // There is no way this should be able to trigger an injection twice now. - var/midround_injection_cooldown_middle = 0.5*(midround_delay_max + midround_delay_min) - midround_injection_cooldown = (round(clamp(EXP_DISTRIBUTION(midround_injection_cooldown_middle), midround_delay_min, midround_delay_max)) + world.time) - - // Time to inject some threat into the round - if(EMERGENCY_PAST_POINT_OF_NO_RETURN) // Unless the shuttle is past the point of no return - return - - message_admins("DYNAMIC: Checking for midround injection.") - log_game("DYNAMIC: Checking for midround injection.") - - last_midround_injection_attempt = world.time - - if (prob(get_midround_injection_chance())) - var/list/drafted_rules = list() - for (var/datum/dynamic_ruleset/midround/rule in midround_rules) - if (!rule.weight) - continue - if (rule.acceptable(GLOB.alive_player_list.len, threat_level) && mid_round_budget >= rule.cost) - // If admins have disabled dynamic from picking from the ghost pool - if(rule.ruletype == "Latejoin" && !(GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT)) - continue - rule.trim_candidates() - if (rule.ready()) - drafted_rules[rule] = rule.get_weight() - if (drafted_rules.len > 0) - pick_midround_rule(drafted_rules) - else if (random_event_hijacked == HIJACKED_TOO_SOON) - log_game("DYNAMIC: Midround injection failed when random event was hijacked. Spawning another random event in its place.") - - // A random event antag would have rolled had this injection check passed. - // As a refund, spawn a non-ghost-role random event. - SSevents.spawnEvent() - SSevents.reschedule() - - random_event_hijacked = HIJACKED_NOTHING - -/// Gets the chance for latejoin injection, the dry_run argument is only used for forced injection. -/datum/game_mode/dynamic/proc/get_injection_chance(dry_run = FALSE) - if(forced_injection) - forced_injection = dry_run - return 100 - var/chance = 0 - var/max_pop_per_antag = max(5,15 - round(threat_level/10) - round(GLOB.alive_player_list.len/5)) - if (!GLOB.current_living_antags.len) - chance += 50 // No antags at all? let's boost those odds! - else - var/current_pop_per_antag = GLOB.alive_player_list.len / GLOB.current_living_antags.len - if (current_pop_per_antag > max_pop_per_antag) - chance += min(50, 25+10*(current_pop_per_antag-max_pop_per_antag)) - else - chance += 25-10*(max_pop_per_antag-current_pop_per_antag) - if (GLOB.dead_player_list.len > GLOB.alive_player_list.len) - chance -= 30 // More than half the crew died? ew, let's calm down on antags - if (mid_round_budget > higher_injection_chance_minimum_threat) - chance += higher_injection_chance - if (mid_round_budget < lower_injection_chance_minimum_threat) - chance -= lower_injection_chance - return round(max(0,chance)) - -/// Gets the chance for midround injection, the dry_run argument is only used for forced injection. -/// Usually defers to the latejoin injection chance. -/datum/game_mode/dynamic/proc/get_midround_injection_chance(dry_run) - var/chance = get_injection_chance(dry_run) - - if (random_event_hijacked != HIJACKED_NOTHING) - chance += hijacked_random_event_injection_chance - - return chance + try_midround_roll() /// Removes type from the list /datum/game_mode/dynamic/proc/remove_from_list(list/type_list, type) @@ -719,7 +682,9 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) addtimer(CALLBACK(src, /datum/game_mode/dynamic/.proc/execute_midround_latejoin_rule, forced_latejoin_rule), forced_latejoin_rule.delay) forced_latejoin_rule = null - else if (latejoin_injection_cooldown < world.time && prob(get_injection_chance())) + else if (latejoin_injection_cooldown < world.time && (forced_injection || prob(latejoin_roll_chance))) + forced_injection = FALSE + var/list/drafted_rules = list() for (var/datum/dynamic_ruleset/latejoin/rule in latejoin_rules) if (!rule.weight) @@ -785,14 +750,14 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) /// Expend round start threat, can't fall under 0. /datum/game_mode/dynamic/proc/spend_roundstart_budget(cost, list/threat_log, reason) round_start_budget = max(round_start_budget - cost,0) - for(var/list/logs in threat_log) - log_threat(cost, logs, reason) + if (!isnull(threat_log)) + log_threat(-cost, threat_log, reason) /// Expend midround threat, can't fall under 0. /datum/game_mode/dynamic/proc/spend_midround_budget(cost, list/threat_log, reason) mid_round_budget = max(mid_round_budget - cost,0) - for(var/list/logs in threat_log) - log_threat(cost, logs, reason) + if (!isnull(threat_log)) + log_threat(-cost, threat_log, reason) /// Turns the value generated by lorentz distribution to number between 0 and 100. /// Used for threat level and splitting the budgets. diff --git a/code/game/gamemodes/dynamic/dynamic_hijacking.dm b/code/game/gamemodes/dynamic/dynamic_hijacking.dm index 04892ad1530..335a4246a2b 100644 --- a/code/game/gamemodes/dynamic/dynamic_hijacking.dm +++ b/code/game/gamemodes/dynamic/dynamic_hijacking.dm @@ -17,11 +17,9 @@ if (world.time - last_midround_injection_attempt < time_range) random_event_hijacked = HIJACKED_TOO_RECENT dynamic_log("Random event [round_event_control.name] tried to roll, but the last midround injection \ - was too recent. Injection chance has been raised to [get_midround_injection_chance(dry_run = TRUE)]%.") + was too recent. Heavy injection chance has been raised to [get_heavy_midround_injection_chance(dry_run = TRUE)]%.") return CANCEL_PRE_RANDOM_EVENT - if (midround_injection_cooldown - world.time < time_range) - random_event_hijacked = HIJACKED_TOO_SOON - dynamic_log("Random event [round_event_control.name] tried to roll, but the next midround injection \ - is too soon. Injection chance has been raised to [get_midround_injection_chance(dry_run = TRUE)]%.") + if (next_midround_injection() - world.time < time_range) + dynamic_log("Random event [round_event_control.name] tried to roll, but the next midround injection is too soon.") return CANCEL_PRE_RANDOM_EVENT diff --git a/code/game/gamemodes/dynamic/dynamic_midround_rolling.dm b/code/game/gamemodes/dynamic/dynamic_midround_rolling.dm new file mode 100644 index 00000000000..96fb0d68413 --- /dev/null +++ b/code/game/gamemodes/dynamic/dynamic_midround_rolling.dm @@ -0,0 +1,90 @@ +/// Returns the world.time of the next midround injection. +/// Will return a cached result from `next_midround_injection`, the variable. +/// If that variable is null, will generate a new one. +/datum/game_mode/dynamic/proc/next_midround_injection() + if (!isnull(next_midround_injection)) + return next_midround_injection + + // Admins can futz around with the midround threat, and we want to be able to react to that + var/midround_threat = threat_level - round_start_budget + + var/rolls = CEILING(midround_threat / threat_per_midround_roll, 1) + var/distance = ((1 / (rolls + 1)) * midround_upper_bound) + midround_lower_bound + + if (last_midround_injection_attempt == 0) + last_midround_injection_attempt = SSticker.round_start_time + + return last_midround_injection_attempt + distance + +/datum/game_mode/dynamic/proc/try_midround_roll() + if (!forced_injection && next_midround_injection() > world.time) + return + + if (GLOB.dynamic_forced_extended) + return + + if (EMERGENCY_PAST_POINT_OF_NO_RETURN) + return + + last_midround_injection_attempt = world.time + next_midround_injection = null + forced_injection = FALSE + + var/spawn_heavy = prob(get_heavy_midround_injection_chance()) + dynamic_log("A midround ruleset is rolling, and will be [spawn_heavy ? "HEAVY" : "LIGHT"].") + + random_event_hijacked = HIJACKED_NOTHING + + var/list/drafted_heavies = list() + var/list/drafted_lights = list() + + for (var/datum/dynamic_ruleset/midround/ruleset in midround_rules) + if (ruleset.weight == 0) + continue + + if (!ruleset.acceptable(GLOB.alive_player_list.len, threat_level)) + continue + + if (mid_round_budget < ruleset.cost) + continue + + if (ruleset.minimum_round_time > world.time - SSticker.round_start_time) + continue + + // If admins have disabled dynamic from picking from the ghost pool + if(istype(ruleset, /datum/dynamic_ruleset/midround/from_ghosts) && !(GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT)) + continue + + ruleset.trim_candidates() + if (ruleset.ready()) + var/ruleset_is_heavy = (ruleset.midround_ruleset_style == MIDROUND_RULESET_STYLE_HEAVY) + if (ruleset_is_heavy) + drafted_heavies[ruleset] = ruleset.get_weight() + else + drafted_lights[ruleset] = ruleset.get_weight() + + if (spawn_heavy && drafted_heavies.len > 0 && pick_midround_rule(drafted_heavies)) + return + else if (drafted_lights.len > 0 && pick_midround_rule(drafted_lights)) + if (spawn_heavy) + dynamic_log("A heavy ruleset was intended to roll, but there weren't any available.") + else + dynamic_log("No midround rulesets could be drafted.") + +/// Gets the chance for a heavy ruleset midround injection, the dry_run argument is only used for forced injection. +/datum/game_mode/dynamic/proc/get_heavy_midround_injection_chance(dry_run) + var/chance_modifier = 1 + var/next_midround_roll = next_midround_injection() - SSticker.round_start_time + + if (random_event_hijacked != HIJACKED_NOTHING) + chance_modifier += (hijacked_random_event_injection_chance_modifier / 100) + + if (GLOB.current_living_antags.len == 0) + chance_modifier += 0.5 + + if (GLOB.dead_player_list.len > GLOB.alive_player_list.len) + chance_modifier -= 0.3 + + var/heavy_coefficient = CLAMP01((next_midround_roll - midround_light_upper_bound) / (midround_heavy_lower_bound - midround_light_upper_bound)) + + return 100 * (heavy_coefficient * max(1, chance_modifier)) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index 7083c1171f6..6030b0c96de 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -115,7 +115,7 @@ required_candidates = 1 weight = 2 delay = 1 MINUTES // Prevents rule start while head is offstation. - cost = 20 + cost = 10 requirements = list(101,101,70,40,30,20,20,20,20,20) flags = HIGH_IMPACT_RULESET blocking_rules = list(/datum/dynamic_ruleset/roundstart/revs) @@ -200,7 +200,7 @@ ) required_candidates = 1 weight = 4 - cost = 10 + cost = 7 requirements = list(101,101,101,10,10,10,10,10,10,10) repeatable = TRUE diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index c7923a1c9ba..c8a4c8f9749 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -11,6 +11,7 @@ /datum/dynamic_ruleset/midround // Can be drafted once in a while during a round ruletype = "Midround" + var/midround_ruleset_style /// If the ruleset should be restricted from ghost roles. var/restrict_ghost_roles = TRUE /// What mob type the ruleset is restricted to. @@ -20,6 +21,9 @@ var/list/dead_players = list() var/list/list_observers = list() + /// The minimum round time before this ruleset will show up + var/minimum_round_time = 0 + /datum/dynamic_ruleset/midround/from_ghosts weight = 0 required_type = /mob/dead/observer @@ -171,9 +175,6 @@ /datum/dynamic_ruleset/midround/from_ghosts/proc/attempt_replacement() var/datum/dynamic_ruleset/midround/autotraitor/sleeper_agent = new - // Otherwise, it has a chance to fail. We don't want that, since this is already pretty unlikely. - sleeper_agent.has_failure_chance = FALSE - mode.configure_ruleset(sleeper_agent) if (!mode.picking_specific_rule(sleeper_agent)) @@ -189,6 +190,7 @@ /datum/dynamic_ruleset/midround/autotraitor name = "Syndicate Sleeper Agent" + midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT antag_datum = /datum/antagonist/traitor antag_flag = ROLE_SLEEPER_AGENT antag_flag_override = ROLE_TRAITOR @@ -207,32 +209,11 @@ ROLE_POSITRONIC_BRAIN, ) required_candidates = 1 - weight = 7 - cost = 10 - requirements = list(10,10,10,10,10,10,10,10,10,10) + weight = 35 + cost = 3 + requirements = list(3,3,3,3,3,3,3,3,3,3) repeatable = TRUE - /// Whether or not this instance of sleeper agent should be randomly acceptable. - /// If TRUE, then this has a threat level% chance to succeed. - var/has_failure_chance = TRUE - -/datum/dynamic_ruleset/midround/autotraitor/acceptable(population = 0, threat = 0) - var/player_count = GLOB.alive_player_list.len - var/antag_count = GLOB.current_living_antags.len - var/max_traitors = round(player_count / 10) + 1 - - // adding traitors if the antag population is getting low - var/too_little_antags = antag_count < max_traitors - if (!too_little_antags) - log_game("DYNAMIC: Too many living antags compared to living players ([antag_count] living antags, [player_count] living players, [max_traitors] max traitors)") - return FALSE - - if (has_failure_chance && !prob(mode.threat_level)) - log_game("DYNAMIC: Random chance to roll autotraitor failed, it was a [mode.threat_level]% chance.") - return FALSE - - return ..() - /datum/dynamic_ruleset/midround/autotraitor/trim_candidates() ..() for(var/mob/living/player in living_players) @@ -266,6 +247,7 @@ /datum/dynamic_ruleset/midround/families name = "Family Head Aspirants" + midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY persistent = TRUE antag_datum = /datum/antagonist/gang antag_flag = ROLE_FAMILY_HEAD_ASPIRANT @@ -343,6 +325,7 @@ /datum/dynamic_ruleset/midround/malf name = "Malfunctioning AI" + midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY antag_datum = /datum/antagonist/malf_ai antag_flag = ROLE_MALF_MIDROUND antag_flag_override = ROLE_MALF @@ -359,7 +342,7 @@ required_enemies = list(4,4,4,4,4,4,2,2,2,0) required_candidates = 1 weight = 3 - cost = 22 + cost = 10 requirements = list(101,101,101,80,60,50,30,20,10,10) required_type = /mob/living/silicon/ai blocking_rules = list(/datum/dynamic_ruleset/roundstart/malf_ai) @@ -403,6 +386,7 @@ /datum/dynamic_ruleset/midround/from_ghosts/wizard name = "Wizard" + midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY antag_datum = /datum/antagonist/wizard antag_flag = ROLE_WIZARD_MIDROUND antag_flag_override = ROLE_WIZARD @@ -415,8 +399,8 @@ required_enemies = list(2,2,1,1,1,1,1,0,0,0) required_candidates = 1 weight = 1 - cost = 20 - requirements = list(90,90,90,80,60,40,30,20,10,10) + cost = 10 + requirements = list(90,90,90,80,60,50,40,40,40,40) flags = HIGH_IMPACT_RULESET /datum/dynamic_ruleset/midround/from_ghosts/wizard/ready(forced = FALSE) @@ -440,6 +424,7 @@ /datum/dynamic_ruleset/midround/from_ghosts/nuclear name = "Nuclear Assault" + midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY antag_flag = ROLE_OPERATIVE_MIDROUND antag_flag_override = ROLE_OPERATIVE antag_datum = /datum/antagonist/nukeop @@ -454,7 +439,8 @@ required_enemies = list(3,3,3,3,3,2,1,1,0,0) required_candidates = 5 weight = 5 - cost = 35 + cost = 7 + minimum_round_time = 70 MINUTES requirements = list(90,90,90,80,60,40,30,20,10,10) var/list/operative_cap = list(2,2,3,3,4,5,5,5,5,5) var/datum/team/nuclear/nuke_team @@ -490,6 +476,7 @@ /datum/dynamic_ruleset/midround/from_ghosts/blob name = "Blob" + midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY antag_datum = /datum/antagonist/blob antag_flag = ROLE_BLOB enemy_roles = list( @@ -500,8 +487,9 @@ ) required_enemies = list(2,2,1,1,1,1,1,0,0,0) required_candidates = 1 - weight = 2 - cost = 10 + minimum_round_time = 35 MINUTES + weight = 3 + cost = 8 requirements = list(101,101,101,80,60,50,30,20,10,10) repeatable = TRUE @@ -512,6 +500,7 @@ /// Infects a random player, making them explode into a blob. /datum/dynamic_ruleset/midround/blob_infection name = "Blob Infection" + midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY antag_datum = /datum/antagonist/blob/infection antag_flag = ROLE_BLOB_INFECTION antag_flag_override = ROLE_BLOB @@ -536,7 +525,8 @@ ) required_enemies = list(2,2,1,1,1,1,1,0,0,0) required_candidates = 1 - weight = 2 + minimum_round_time = 35 MINUTES + weight = 3 cost = 10 requirements = list(101,101,101,80,60,50,30,20,10,10) repeatable = TRUE @@ -569,6 +559,7 @@ /datum/dynamic_ruleset/midround/from_ghosts/xenomorph name = "Alien Infestation" + midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY antag_datum = /datum/antagonist/xeno antag_flag = ROLE_ALIEN enemy_roles = list( @@ -579,7 +570,8 @@ ) required_enemies = list(2,2,1,1,1,1,1,0,0,0) required_candidates = 1 - weight = 0 //SKYRAT EDIT CHANGE + minimum_round_time = 40 MINUTES + weight = 5 cost = 10 requirements = list(101,101,101,70,50,40,20,15,10,10) repeatable = TRUE @@ -620,6 +612,7 @@ /datum/dynamic_ruleset/midround/from_ghosts/nightmare name = "Nightmare" + midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT antag_datum = /datum/antagonist/nightmare antag_flag = ROLE_NIGHTMARE antag_flag_override = ROLE_ALIEN @@ -632,7 +625,7 @@ required_enemies = list(2,2,1,1,1,1,1,0,0,0) required_candidates = 1 weight = 3 - cost = 10 + cost = 5 requirements = list(101,101,101,70,50,40,20,15,10,10) repeatable = TRUE var/list/spawn_locs = list() @@ -671,6 +664,7 @@ /datum/dynamic_ruleset/midround/from_ghosts/space_dragon name = "Space Dragon" + midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY antag_datum = /datum/antagonist/space_dragon antag_flag = ROLE_SPACE_DRAGON antag_flag_override = ROLE_SPACE_DRAGON @@ -683,7 +677,7 @@ required_enemies = list(2,2,1,1,1,1,1,0,0,0) required_candidates = 1 weight = 4 - cost = 10 + cost = 7 requirements = list(101,101,101,80,60,50,30,20,10,10) repeatable = TRUE var/list/spawn_locs = list() @@ -721,6 +715,7 @@ /datum/dynamic_ruleset/midround/from_ghosts/abductors name = "Abductors" + midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY antag_datum = /datum/antagonist/abductor antag_flag = ROLE_ABDUCTOR enemy_roles = list( @@ -733,7 +728,7 @@ required_candidates = 2 required_applicants = 2 weight = 4 - cost = 10 + cost = 7 requirements = list(101,101,101,80,60,50,30,20,10,10) repeatable = TRUE var/datum/team/abductor_team/new_team @@ -764,6 +759,7 @@ /datum/dynamic_ruleset/midround/from_ghosts/space_ninja name = "Space Ninja" + midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY antag_datum = /datum/antagonist/ninja antag_flag = ROLE_NINJA enemy_roles = list( @@ -775,11 +771,10 @@ required_enemies = list(2,2,1,1,1,1,1,0,0,0) required_candidates = 1 weight = 4 - cost = 10 + cost = 8 requirements = list(101,101,101,80,60,50,30,20,10,10) repeatable = TRUE var/list/spawn_locs = list() - minimum_players = 999 //SKYRAT EDIT ADDITION - EVENTS /datum/dynamic_ruleset/midround/from_ghosts/space_ninja/execute() for(var/obj/effect/landmark/carpspawn/carp_spawn in GLOB.landmarks_list) @@ -809,6 +804,7 @@ /datum/dynamic_ruleset/midround/spiders name = "Spiders" + midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY antag_flag = ROLE_SPIDER required_type = /mob/dead/observer enemy_roles = list( @@ -820,7 +816,7 @@ required_enemies = list(2,2,1,1,1,1,1,0,0,0) required_candidates = 0 weight = 3 - cost = 10 + cost = 8 requirements = list(101,101,101,80,60,50,30,20,10,10) repeatable = TRUE var/spawncount = 2 @@ -832,6 +828,7 @@ /// Revenant ruleset /datum/dynamic_ruleset/midround/from_ghosts/revenant name = "Revenant" + midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT antag_datum = /datum/antagonist/revenant antag_flag = ROLE_REVENANT enemy_roles = list( @@ -843,7 +840,7 @@ required_enemies = list(2,2,1,1,1,1,1,0,0,0) required_candidates = 1 weight = 4 - cost = 10 + cost = 5 requirements = list(101,101,101,70,50,40,20,15,10,10) repeatable = TRUE var/dead_mobs_required = 20 @@ -883,11 +880,12 @@ /// Sentient Disease ruleset /datum/dynamic_ruleset/midround/from_ghosts/sentient_disease name = "Sentient Disease" + midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY antag_datum = /datum/antagonist/disease antag_flag = ROLE_SENTIENT_DISEASE required_candidates = 1 weight = 4 - cost = 10 + cost = 8 requirements = list(101,101,101,80,60,50,30,20,10,10) repeatable = TRUE @@ -900,8 +898,9 @@ return virus /// Space Pirates ruleset -/datum/dynamic_ruleset/midround/from_ghosts/pirates +/datum/dynamic_ruleset/midround/pirates name = "Space Pirates" + midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY antag_flag = "Space Pirates" required_type = /mob/dead/observer enemy_roles = list( @@ -911,13 +910,13 @@ JOB_SECURITY_OFFICER, ) required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 3 + required_candidates = 0 weight = 4 - cost = 10 + cost = 8 requirements = list(101,101,101,80,60,50,30,20,10,10) repeatable = TRUE -/datum/dynamic_ruleset/midround/from_ghosts/pirates/acceptable(population=0, threat=0) +/datum/dynamic_ruleset/midround/pirates/acceptable(population=0, threat=0) if (!SSmapping.empty_space) return FALSE return ..() @@ -929,6 +928,7 @@ /// Obsessed ruleset /datum/dynamic_ruleset/midround/obsessed name = "Obsessed" + midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT antag_datum = /datum/antagonist/obsessed antag_flag = ROLE_OBSESSED restricted_roles = list( @@ -974,6 +974,7 @@ /// Thief ruleset /datum/dynamic_ruleset/midround/opportunist name = "Opportunist" + midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT antag_datum = /datum/antagonist/thief antag_flag = ROLE_OPPORTUNIST antag_flag_override = ROLE_THIEF @@ -991,7 +992,7 @@ ROLE_POSITRONIC_BRAIN, ) required_candidates = 1 - weight = 0 // Disabled until Dynamic midround rolling handles minor threats better + weight = 2 cost = 3 //Worth less than obsessed, but there's more of them. requirements = list(10,10,10,10,10,10,10,10,10,10) repeatable = TRUE diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index 283ef8644c1..d632a1b56bc 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -7,7 +7,6 @@ /datum/dynamic_ruleset/roundstart/traitor name = "Traitors" - persistent = TRUE antag_flag = ROLE_TRAITOR antag_datum = /datum/antagonist/traitor minimum_required_age = 0 @@ -30,11 +29,9 @@ requirements = list(8,8,8,8,8,8,8,8,8,8) antag_cap = list("denominator" = 24) var/autotraitor_cooldown = (15 MINUTES) - COOLDOWN_DECLARE(autotraitor_cooldown_check) /datum/dynamic_ruleset/roundstart/traitor/pre_execute(population) . = ..() - COOLDOWN_START(src, autotraitor_cooldown_check, autotraitor_cooldown) var/num_traitors = get_antag_cap(population) * (scaled_times + 1) for (var/i = 1 to num_traitors) if(candidates.len <= 0) @@ -46,12 +43,6 @@ GLOB.pre_setup_antags += M.mind return TRUE -/datum/dynamic_ruleset/roundstart/traitor/rule_process() - if (COOLDOWN_FINISHED(src, autotraitor_cooldown_check)) - COOLDOWN_START(src, autotraitor_cooldown_check, autotraitor_cooldown) - log_game("DYNAMIC: Checking if we can turn someone into a traitor.") - mode.picking_specific_rule(/datum/dynamic_ruleset/midround/autotraitor) - ////////////////////////////////////////////// // // // MALFUNCTIONING AI // diff --git a/code/game/gamemodes/dynamic/readme.md b/code/game/gamemodes/dynamic/readme.md index afc7ea5c81f..b897596fb29 100644 --- a/code/game/gamemodes/dynamic/readme.md +++ b/code/game/gamemodes/dynamic/readme.md @@ -28,20 +28,27 @@ After this process is done, any leftover roundstart threat will be given to the ## Deciding midround threats -Latejoin and midround injection cooldowns are set using exponential distribution between +### Frequency -- 5 minutes and 25 for latejoin (configurable as latejoin_delay_min and latejoin_delay_max) -- 15 minutes and 35 for midround (configurable as midround_delay_min and midround_delay_max) +The frequency of midround threats is based on the midround threat of the round. The number of midround threats that will roll is `threat_level` / `threat_per_midround_roll` (configurable), rounded up. For example, if `threat_per_midround_roll` is set to 5, then for every 5 threat, one midround roll will be added. If you have 6 threat, with this configuration, you will get 2 midround rolls. -this value is then added to `world.time` and assigned to the injection cooldown variables. +These midround roll points are then equidistantly spaced across the round, starting from `midround_lower_bound` (configurable) to `midround_upper_bound` (configurable), with a +/- of `midround_roll_distance` (configurable). -[rigged_roundstart][/datum/game_mode/dynamic/proc/rigged_roundstart] is called instead if there are forced rules (an admin set the mode) +For example, if: +1. `midround_lower_bound` is `10 MINUTES` +2. `midround_upper_bound` is `100 MINUTES` +3. `midround_roll_distance` is `3 MINUTES` +4. You have 5 midround rolls for the round -1. [setup_parameters][/datum/game_mode/proc/setup_parameters]\() -2. [pre_setup][/datum/game_mode/proc/pre_setup]\() -3. [roundstart][/datum/game_mode/dynamic/proc/roundstart]\() OR [rigged_roundstart][/datum/game_mode/dynamic/proc/rigged_roundstart]\() -4. [picking_roundstart_rule][/datum/game_mode/dynamic/proc/picking_roundstart_rule]\(drafted_rules) -5. [post_setup][/datum/game_mode/proc/post_setup]\() +...then those 5 midround rolls will be placed equidistantly (meaning equally apart) across the first 10-100 minutes of the round. Every individual roll will then be adjusted to either be 3 minutes earlier, or 3 minutes later. + +### Threat variety + +Threats are split between **heavy** rulesets and **light** rulesets. A heavy ruleset includes major threats like space dragons or blobs, while light rulesets are ones that don't often cause shuttle calls when rolled, such as revenants or traitors (sleeper agents). + +When a midround roll occurs, the decision to choose between light or heavy depends on the current round time. If it is less than `midround_light_upper_bound` (configurable), then it is guaranteed to be a light ruleset. If it is more than `midround_heavy_lower_bound`, then it is guaranteed to be a heavy ruleset. If it is any point in between, it will interpolate the value between those. This means that the longer the round goes on, the more likely you are to get a heavy ruleset. + +If no heavy ruleset can run, such as not having enough threat, then a light ruleset is guaranteed to run. ## Rule Processing @@ -153,17 +160,14 @@ Rulesets have the following variables notable to developers and those interested - `protected_roles` - Serves the same purpose of `restricted_roles`, except it can be turned off through configuration (`protect_roles_from_antagonist`). For example, security officers *shouldn't* be made traitor, so they are in Traitor's `protected_roles`. - When considering putting a role in `protected_roles` or `restricted_roles`, the rule of thumb is if it is *technically infeasible* to support that job in that role. There's no *technical* reason a security officer can't be a traitor, and so they are simply in `protected_roles`. There *are* technical reasons a cyborg can't be a changeling, so they are in `restricted_roles` instead. +This is not a complete list--search "configurable" in this README to learn more. + ### Dynamic The "Dynamic" key has the following configurable values: - `pop_per_requirement` - The default value of `pop_per_requirement` for any ruleset that does not explicitly set it. Defaults to 6. - `latejoin_delay_min`, `latejoin_delay_max` - The time range, in deciseconds (take your seconds, and multiply by 10), for a latejoin to attempt rolling. Once this timer is finished, a new one will be created within the same range. - Suppose you have a `latejoin_delay_min` of 600 (60 seconds, 1 minute) and a `latejoin_delay_max` of 1800 (180 seconds, 3 minutes). Once the round starts, a random number in this range will be picked--let's suppose 1.5 minutes. After 1.5 minutes, Dynamic will decide if a latejoin threat should be created (a probability of `/datum/game_mode/dynamic/proc/get_injection_chance()`). Regardless of its decision, a new timer will be started within the range of 1 to 3 minutes, repeatedly. -- `midround_delay_min`, `midround_delay_max` - Same as `latejoin_delay_min` and `latejoin_delay_max`, except for midround threats instead of latejoin ones. -- `higher_injection_chance`, `higher_injection_chance_minimum_threat` - Manipulates the injection chance (`/datum/game_mode/dynamic/proc/get_injection_chance()`). If the *current midround budget* is above `higher_injection_chance_minimum_threat`, then this chance will be increased by `higher_injection_chance`. - - For example: suppose you have a `higher_injection_chance_minimum_threat` of 70, and a `higher_injection_chance` of 15. This means that, if when a midround threat is trying to roll, there is 75 midround budget left, then the injection chance will go up 15%. -- `lower_injection_chance`, `lower_injection_chance_minimum_threat` - The inverse of the `higher_injection_chance` variables. If the *current midround budget* is *below* `lower_injection_chance`, then the chance is lowered by `lower_injection_chance_minimum_threat`. - - For example: suppose you have a `lower_injection_chance_minimum_threat` of 30, and a `lower_injection_chance` of 15. This means if there is 20 midround budget left, then the chance will lower by 15%. - `threat_curve_centre` - A number between -5 and +5. A negative value will give a more peaceful round and a positive value will give a round with higher threat. - `threat_curve_width` - A number between 0.5 and 4. Higher value will favour extreme rounds and lower value rounds closer to the average. - `roundstart_split_curve_centre` - A number between -5 and +5. Equivalent to threat_curve_centre, but for the budget split. A negative value will weigh towards midround rulesets, and a positive value will weight towards roundstart ones. @@ -180,6 +184,6 @@ Random events have the potential to be hijacked by Dynamic to keep the pace of m In `/datum/game_mode/dynamic/on_pre_random_event` (in `dynamic_hijacking.dm`), Dynamic hooks to random events. If the `dynamic_should_hijack` variable is TRUE, the following sequence of events occurs: -![Flow chart to describe the chain of events for Dynamic 2021 to take](https://user-images.githubusercontent.com/35135081/109071468-9cab7e00-76a8-11eb-8f9f-2b920c602ef4.png) +![Flow chart to describe the chain of events for Dynamic 2021 to take](https://github.com/tgstation/documentation-assets/blob/main/dynamic/random_event_hijacking.png) -`n` is a random value between `random_event_hijack_minimum` and `random_event_hijack_maximum`. Injection chance, should it need to be raised, is increased by `hijacked_random_event_injection_chance`. +`n` is a random value between `random_event_hijack_minimum` and `random_event_hijack_maximum`. Heavy injection chance, should it need to be raised, is increased by `hijacked_random_event_injection_chance_modifier`. diff --git a/code/game/gamemodes/dynamic/ruleset_picking.dm b/code/game/gamemodes/dynamic/ruleset_picking.dm index 828d0cdda14..7778059394b 100644 --- a/code/game/gamemodes/dynamic/ruleset_picking.dm +++ b/code/game/gamemodes/dynamic/ruleset_picking.dm @@ -33,7 +33,7 @@ /datum/game_mode/dynamic/proc/pick_midround_rule(list/drafted_rules) var/datum/dynamic_ruleset/rule = pick_ruleset(drafted_rules) if (isnull(rule)) - return + return null current_midround_rulesets = drafted_rules - rule midround_injection_timer_id = addtimer( @@ -47,6 +47,8 @@ CANCEL | \ SOMETHING ELSE") + return rule + /// Fired after admins do not cancel a midround injection. /datum/game_mode/dynamic/proc/execute_midround_rule(datum/dynamic_ruleset/rule) current_midround_rulesets = null diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index 295d04db080..d07d95a2c5a 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -36,7 +36,7 @@ GLOBAL_LIST_EMPTY(antagonists) ///Name of the antag hud we provide to this mob. var/antag_hud_name /// If set to true, the antag will not be added to the living antag list. - var/soft_antag = FALSE + var/count_against_dynamic_roll_chance = TRUE /// The battlecry this antagonist shouts when suiciding with C4/X4. var/suicide_cry = "" //Antag panel properties @@ -92,14 +92,14 @@ GLOBAL_LIST_EMPTY(antagonists) /datum/antagonist/proc/on_body_transfer(mob/living/old_body, mob/living/new_body) SHOULD_CALL_PARENT(TRUE) remove_innate_effects(old_body) - if(!soft_antag && old_body && old_body.stat != DEAD && !LAZYLEN(old_body.mind?.antag_datums)) + if(old_body && old_body.stat != DEAD && !LAZYLEN(old_body.mind?.antag_datums)) old_body.remove_from_current_living_antags() var/datum/action/antag_info/info_button = info_button_ref?.resolve() if(info_button) info_button.Remove(old_body) info_button.Grant(new_body) apply_innate_effects(new_body) - if(!soft_antag && new_body.stat != DEAD) + if(count_against_dynamic_roll_chance && new_body.stat != DEAD) new_body.add_to_current_living_antags() //This handles the application of antag huds/special abilities @@ -162,7 +162,7 @@ GLOBAL_LIST_EMPTY(antagonists) replace_banned_player() else if(owner.current.client?.holder && (CONFIG_GET(flag/auto_deadmin_antagonists) || owner.current.client.prefs?.toggles & DEADMIN_ANTAGONIST)) owner.current.client.holder.auto_deadmin() - if(!soft_antag && owner.current.stat != DEAD && owner.current.client) + if(count_against_dynamic_roll_chance && owner.current.stat != DEAD && owner.current.client) owner.current.add_to_current_living_antags() SEND_SIGNAL(owner, COMSIG_ANTAGONIST_GAINED, src) @@ -203,7 +203,7 @@ GLOBAL_LIST_EMPTY(antagonists) remove_innate_effects() clear_antag_moodies() LAZYREMOVE(owner.antag_datums, src) - if(!LAZYLEN(owner.antag_datums) && !soft_antag) + if(!LAZYLEN(owner.antag_datums)) owner.current.remove_from_current_living_antags() if(info_button_ref) QDEL_NULL(info_button_ref) diff --git a/code/modules/antagonists/ashwalker/ashwalker.dm b/code/modules/antagonists/ashwalker/ashwalker.dm index f03454b021f..2bc799863d6 100644 --- a/code/modules/antagonists/ashwalker/ashwalker.dm +++ b/code/modules/antagonists/ashwalker/ashwalker.dm @@ -11,6 +11,7 @@ prevent_roundtype_conversion = FALSE antagpanel_category = "Ash Walkers" suicide_cry = "I HAVE NO IDEA WHAT THIS THING DOES!!" + count_against_dynamic_roll_chance = FALSE var/datum/team/ashwalkers/ashie_team //SKYRAT EDIT: Recipes for Tribals ///The list of recipes that will be learned on inheriting the antag datum diff --git a/code/modules/antagonists/brainwashing/brainwashing.dm b/code/modules/antagonists/brainwashing/brainwashing.dm index b56f276e8df..96d807207ea 100644 --- a/code/modules/antagonists/brainwashing/brainwashing.dm +++ b/code/modules/antagonists/brainwashing/brainwashing.dm @@ -33,6 +33,7 @@ antag_hud_name = "brainwashed" antagpanel_category = "Other" show_name_in_check_antagonists = TRUE + count_against_dynamic_roll_chance = FALSE ui_name = "AntagInfoBrainwashed" suicide_cry = "FOR... SOMEONE!!" diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm index 81a1dbc0271..4d42eda14dc 100644 --- a/code/modules/antagonists/changeling/changeling.dm +++ b/code/modules/antagonists/changeling/changeling.dm @@ -910,7 +910,7 @@ name = "\improper Headslug Changeling" show_in_antagpanel = FALSE give_objectives = FALSE - soft_antag = TRUE + count_against_dynamic_roll_chance = FALSE genetic_points = 5 total_genetic_points = 5 diff --git a/code/modules/antagonists/creep/creep.dm b/code/modules/antagonists/creep/creep.dm index 5c52106442b..07618c4239d 100644 --- a/code/modules/antagonists/creep/creep.dm +++ b/code/modules/antagonists/creep/creep.dm @@ -6,6 +6,7 @@ antag_hud_name = "obsessed" show_name_in_check_antagonists = TRUE roundend_category = "obsessed" + count_against_dynamic_roll_chance = FALSE silent = TRUE //not actually silent, because greet will be called by the trauma anyway. suicide_cry = "FOR MY LOVE!!" preview_outfit = /datum/outfit/obsessed diff --git a/code/modules/antagonists/ert/ert.dm b/code/modules/antagonists/ert/ert.dm index 6459c0e83fb..04bf5772075 100644 --- a/code/modules/antagonists/ert/ert.dm +++ b/code/modules/antagonists/ert/ert.dm @@ -10,6 +10,7 @@ show_to_ghosts = TRUE antag_moodlet = /datum/mood_event/focused suicide_cry = "FOR NANOTRASEN!!" + count_against_dynamic_roll_chance = FALSE var/datum/team/ert/ert_team var/leader = FALSE var/datum/outfit/outfit = /datum/outfit/centcom/ert/security diff --git a/code/modules/antagonists/fugitive/fugitive.dm b/code/modules/antagonists/fugitive/fugitive.dm index d3d18775667..6cbc7530481 100644 --- a/code/modules/antagonists/fugitive/fugitive.dm +++ b/code/modules/antagonists/fugitive/fugitive.dm @@ -9,6 +9,7 @@ antag_hud_name = "fugitive" suicide_cry = "FOR FREEDOM!!" preview_outfit = /datum/outfit/prisoner + count_against_dynamic_roll_chance = FALSE var/datum/team/fugitive/fugitive_team var/is_captured = FALSE var/backstory = "error" diff --git a/code/modules/antagonists/fugitive/hunter.dm b/code/modules/antagonists/fugitive/hunter.dm index 5e674df4957..59c8c415108 100644 --- a/code/modules/antagonists/fugitive/hunter.dm +++ b/code/modules/antagonists/fugitive/hunter.dm @@ -7,6 +7,7 @@ prevent_roundtype_conversion = FALSE antag_hud_name = "fugitive_hunter" suicide_cry = "FOR GLORY!!" + count_against_dynamic_roll_chance = FALSE var/datum/team/fugitive_hunters/hunter_team var/backstory = "error" diff --git a/code/modules/antagonists/greentext/greentext.dm b/code/modules/antagonists/greentext/greentext.dm index 97bd5c9e482..cfc87bdccc5 100644 --- a/code/modules/antagonists/greentext/greentext.dm +++ b/code/modules/antagonists/greentext/greentext.dm @@ -3,6 +3,7 @@ show_in_antagpanel = FALSE show_name_in_check_antagonists = TRUE //Not that it will be there for long suicide_cry = "FOR THE GREENTEXT!!" // This can never actually show up, but not including it is a missed opportunity + count_against_dynamic_roll_chance = FALSE /datum/antagonist/greentext/proc/forge_objectives() var/datum/objective/succeed_objective = new /datum/objective("Succeed") diff --git a/code/modules/antagonists/highlander/highlander.dm b/code/modules/antagonists/highlander/highlander.dm index 27f21456333..92a81d0775f 100644 --- a/code/modules/antagonists/highlander/highlander.dm +++ b/code/modules/antagonists/highlander/highlander.dm @@ -5,6 +5,7 @@ show_name_in_check_antagonists = TRUE can_elimination_hijack = ELIMINATION_ENABLED suicide_cry = "FOR SCOTLAND!!" // If they manage to lose their no-drop stuff somehow + count_against_dynamic_roll_chance = FALSE /datum/antagonist/highlander/apply_innate_effects(mob/living/mob_override) var/mob/living/L = owner.current || mob_override diff --git a/code/modules/antagonists/hypnotized/hypnotized.dm b/code/modules/antagonists/hypnotized/hypnotized.dm index 41b6559f5fd..2ee17b671aa 100644 --- a/code/modules/antagonists/hypnotized/hypnotized.dm +++ b/code/modules/antagonists/hypnotized/hypnotized.dm @@ -8,6 +8,7 @@ show_in_antagpanel = TRUE antagpanel_category = "Other" show_name_in_check_antagonists = TRUE + count_against_dynamic_roll_chance = FALSE silent = TRUE //not actually silent, because greet will be called by the trauma anyway. /// Brain trauma associated with this antag datum diff --git a/code/modules/antagonists/thief/thief.dm b/code/modules/antagonists/thief/thief.dm index 529bcab630b..43ee50bae70 100644 --- a/code/modules/antagonists/thief/thief.dm +++ b/code/modules/antagonists/thief/thief.dm @@ -9,6 +9,7 @@ preview_outfit = /datum/outfit/thief antag_hud_name = "thief" ui_name = "AntagInfoThief" + count_against_dynamic_roll_chance = FALSE ///assoc list of strings set up for the flavor of the thief. var/list/thief_flavor ///if added by an admin, they can choose a thief flavor diff --git a/code/modules/antagonists/valentines/valentine.dm b/code/modules/antagonists/valentines/valentine.dm index b760b618fe3..67f824e2077 100644 --- a/code/modules/antagonists/valentines/valentine.dm +++ b/code/modules/antagonists/valentines/valentine.dm @@ -5,7 +5,7 @@ prevent_roundtype_conversion = FALSE suicide_cry = "FOR MY LOVE!!" var/datum/mind/date - soft_antag = TRUE + count_against_dynamic_roll_chance = FALSE /datum/antagonist/valentine/proc/forge_objectives() var/datum/objective/protect/protect_objective = new /datum/objective/protect diff --git a/code/modules/mob/mob_lists.dm b/code/modules/mob/mob_lists.dm index aa0ce4028d2..f0a5275bf71 100644 --- a/code/modules/mob/mob_lists.dm +++ b/code/modules/mob/mob_lists.dm @@ -126,7 +126,14 @@ /mob/proc/add_to_current_living_antags() if(!SSticker?.mode) return - GLOB.current_living_antags |= src + + if (length(mind.antag_datums) == 0) + return + + for (var/datum/antagonist/antagonist in mind.antag_datums) + if (antagonist.count_against_dynamic_roll_chance) + GLOB.current_living_antags |= src + return ///Removes the mob reference from the list of living antag player-mobs. /mob/proc/remove_from_current_living_antags() diff --git a/code/modules/unit_tests/dynamic_ruleset_sanity.dm b/code/modules/unit_tests/dynamic_ruleset_sanity.dm index 8ec500e1a82..983a1026b53 100644 --- a/code/modules/unit_tests/dynamic_ruleset_sanity.dm +++ b/code/modules/unit_tests/dynamic_ruleset_sanity.dm @@ -2,9 +2,7 @@ /datum/unit_test/dynamic_roundstart_ruleset_sanity /datum/unit_test/dynamic_roundstart_ruleset_sanity/Run() - for (var/_ruleset in subtypesof(/datum/dynamic_ruleset/roundstart)) - var/datum/dynamic_ruleset/roundstart/ruleset = _ruleset - + for (var/datum/dynamic_ruleset/roundstart/ruleset as anything in subtypesof(/datum/dynamic_ruleset/roundstart)) var/has_scaling_cost = initial(ruleset.scaling_cost) var/is_lone = initial(ruleset.flags) & (LONE_RULESET | HIGH_IMPACT_RULESET) @@ -13,6 +11,11 @@ else if (!has_scaling_cost && !is_lone) TEST_FAIL("[ruleset] has no scaling cost, but is also not a lone/highlander ruleset.") + for (var/datum/dynamic_ruleset/midround/ruleset as anything in subtypesof(/datum/dynamic_ruleset/midround) - /datum/dynamic_ruleset/midround/from_ghosts) + var/midround_ruleset_style = initial(ruleset.midround_ruleset_style) + if (midround_ruleset_style != MIDROUND_RULESET_STYLE_HEAVY && midround_ruleset_style != MIDROUND_RULESET_STYLE_LIGHT) + TEST_FAIL("[ruleset] has an invalid midround_ruleset_style, it should be MIDROUND_RULESET_STYLE_HEAVY or MIDROUND_RULESET_STYLE_LIGHT") + /// Verifies that dynamic rulesets have unique antag_flag. /datum/unit_test/dynamic_unique_antag_flags diff --git a/modular_skyrat/modules/Midroundtraitor/code/event.dm b/modular_skyrat/modules/Midroundtraitor/code/event.dm index 66c24838962..e0152f1e3f8 100644 --- a/modular_skyrat/modules/Midroundtraitor/code/event.dm +++ b/modular_skyrat/modules/Midroundtraitor/code/event.dm @@ -1,6 +1,7 @@ /datum/dynamic_ruleset/midround/from_ghosts/lone_infiltrator name = "Lone Infiltrator" antag_datum = /datum/antagonist/traitor/infiltrator + midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY antag_flag = ROLE_LONE_INFILTRATOR restricted_roles = list(JOB_CYBORG, JOB_AI, JOB_SECURITY_OFFICER, JOB_WARDEN, JOB_DETECTIVE, JOB_HEAD_OF_SECURITY, JOB_CAPTAIN, JOB_CORRECTIONS_OFFICER, JOB_VANGUARD_OPERATIVE, JOB_NT_REP, JOB_BLUESHIELD, JOB_ORDERLY, JOB_BOUNCER, JOB_CUSTOMS_AGENT, JOB_ENGINEERING_GUARD, JOB_SCIENCE_GUARD) //SKYRAT EDIT - Sec_haul required_candidates = 1 diff --git a/modular_skyrat/modules/cortical_borer/code/cortical_borer_antag.dm b/modular_skyrat/modules/cortical_borer/code/cortical_borer_antag.dm index 58006466cfb..c3ddb2b0f85 100644 --- a/modular_skyrat/modules/cortical_borer/code/cortical_borer_antag.dm +++ b/modular_skyrat/modules/cortical_borer/code/cortical_borer_antag.dm @@ -135,6 +135,7 @@ /datum/dynamic_ruleset/midround/from_ghosts/cortical_borer name = "Cortical Borer Infestation" antag_datum = /datum/antagonist/cortical_borer + midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT antag_flag = ROLE_BORER enemy_roles = list( JOB_CAPTAIN, diff --git a/tgstation.dme b/tgstation.dme index b58f520db30..c9293a48a5a 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1291,6 +1291,7 @@ #include "code\game\gamemodes\dynamic\dynamic.dm" #include "code\game\gamemodes\dynamic\dynamic_hijacking.dm" #include "code\game\gamemodes\dynamic\dynamic_logging.dm" +#include "code\game\gamemodes\dynamic\dynamic_midround_rolling.dm" #include "code\game\gamemodes\dynamic\dynamic_rulesets.dm" #include "code\game\gamemodes\dynamic\dynamic_rulesets_latejoin.dm" #include "code\game\gamemodes\dynamic\dynamic_rulesets_midround.dm"