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 PanelGame 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:
-
+
-`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"