mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-17 19:14:15 +01:00
[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>
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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("<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'><title>Game Mode Panel</title></head><body><h1><B>Game Mode Panel</B></h1>")
|
||||
@@ -186,9 +216,15 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
|
||||
dat += "[DR.ruletype] - <b>[DR.name]</b><br>"
|
||||
else
|
||||
dat += "none.<br>"
|
||||
dat += "<br>Injection Timers: (<b>[get_injection_chance(dry_run = TRUE)]%</b> latejoin chance, <b>[get_midround_injection_chance(dry_run = TRUE)]%</b> midround chance)<BR>"
|
||||
dat += "<br>Injection Timers: (<b>[get_heavy_midround_injection_chance(dry_run = TRUE)]%</b> heavy midround chance)<BR>"
|
||||
dat += "Latejoin: [DisplayTimeText(latejoin_injection_cooldown-world.time)] <a href='?src=\ref[src];[HrefToken()];injectlate=1'>\[Now!\]</a><BR>"
|
||||
dat += "Midround: [DisplayTimeText(midround_injection_cooldown-world.time)] <a href='?src=\ref[src];[HrefToken()];injectmid=1'>\[Now!\]</a><BR>"
|
||||
|
||||
var/next_injection = next_midround_injection()
|
||||
if (next_injection == INFINITY)
|
||||
dat += "All midrounds have been exhausted."
|
||||
else
|
||||
dat += "Midround: [DisplayTimeText(next_injection - world.time)] <a href='?src=\ref[src];[HrefToken()];injectmid=1'>\[Now!\]</a><BR>"
|
||||
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 //
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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 @@
|
||||
<a href='?src=[REF(src)];cancelmidround=[midround_injection_timer_id]'>CANCEL</a> | \
|
||||
<a href='?src=[REF(src)];differentmidround=[midround_injection_timer_id]'>SOMETHING ELSE</a>")
|
||||
|
||||
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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!!"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user