Remove /datum/game_mode, we SSdynamic now [again] (#79965)

I don't remember what was hard about this last time it took me like 20
minutes this time so I'm scared.

Removes dynamic simulations, only I have used them and it's a lot more
complicated now with this. I plan on making Dynamic simulations a part
of moth.fans anyway
This commit is contained in:
Mothblocks
2023-12-02 10:25:48 +13:00
committed by GitHub
parent ba9958d854
commit 714ff3ec54
31 changed files with 340 additions and 541 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
/// Log to dynamic and message admins
/datum/game_mode/dynamic/proc/log_dynamic_and_announce(text)
/datum/controller/subsystem/dynamic/proc/log_dynamic_and_announce(text)
message_admins("DYNAMIC: [text]")
log_dynamic("[text]")
+12 -12
View File
@@ -242,7 +242,7 @@ GLOBAL_LIST_INIT(achievements_unlocked, list())
CHECK_TICK
//Set news report and mode result
mode.set_round_result()
SSdynamic.set_round_result()
to_chat(world, span_infoplain(span_big(span_bold("<BR><BR><BR>The round has ended."))))
log_game("The round has ended.")
@@ -353,17 +353,17 @@ GLOBAL_LIST_INIT(achievements_unlocked, list())
//ignore this comment, it fixes the broken sytax parsing caused by the " above
else
parts += "[FOURSPACES]<i>Nobody died this shift!</i>"
if(istype(SSticker.mode, /datum/game_mode/dynamic))
var/datum/game_mode/dynamic/mode = SSticker.mode
parts += "[FOURSPACES]Threat level: [mode.threat_level]"
parts += "[FOURSPACES]Threat left: [mode.mid_round_budget]"
if(mode.roundend_threat_log.len)
parts += "[FOURSPACES]Threat edits:"
for(var/entry as anything in mode.roundend_threat_log)
parts += "[FOURSPACES][FOURSPACES][entry]<BR>"
parts += "[FOURSPACES]Executed rules:"
for(var/datum/dynamic_ruleset/rule in mode.executed_rules)
parts += "[FOURSPACES][FOURSPACES][rule.ruletype] - <b>[rule.name]</b>: -[rule.cost + rule.scaled_times * rule.scaling_cost] threat"
parts += "[FOURSPACES]Threat level: [SSdynamic.threat_level]"
parts += "[FOURSPACES]Threat left: [SSdynamic.mid_round_budget]"
if(SSdynamic.roundend_threat_log.len)
parts += "[FOURSPACES]Threat edits:"
for(var/entry as anything in SSdynamic.roundend_threat_log)
parts += "[FOURSPACES][FOURSPACES][entry]<BR>"
parts += "[FOURSPACES]Executed rules:"
for(var/datum/dynamic_ruleset/rule in SSdynamic.executed_rules)
parts += "[FOURSPACES][FOURSPACES][rule.ruletype] - <b>[rule.name]</b>: -[rule.cost + rule.scaled_times * rule.scaling_cost] threat"
return parts.Join("<br>")
/client/proc/roundend_report_file()
@@ -20,7 +20,11 @@ GLOBAL_LIST_EMPTY(dynamic_station_traits)
/// Rulesets which have been forcibly enabled or disabled
GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
/datum/game_mode/dynamic
SUBSYSTEM_DEF(dynamic)
name = "Dynamic"
flags = SS_NO_INIT
wait = 1 SECONDS
// Threat logging vars
/// The "threat cap", threat shouldn't normally go above this and is used in ruleset calculations
var/threat_level = 0
@@ -193,7 +197,7 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
VAR_PRIVATE/next_midround_injection
/datum/game_mode/dynamic/admin_panel()
/datum/controller/subsystem/dynamic/proc/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>")
dat += "Dynamic Mode <a href='?_src_=vars;[HrefToken()];Vars=[REF(src)]'>\[VV\]</a> <a href='?src=[text_ref(src)];[HrefToken()]'>\[Refresh\]</a><BR>"
dat += "Threat Level: <b>[threat_level]</b><br/>"
@@ -231,7 +235,7 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
usr << browse(dat.Join(), "window=gamemode_panel;size=500x500")
/datum/game_mode/dynamic/Topic(href, href_list)
/datum/controller/subsystem/dynamic/Topic(href, href_list)
if (..()) // Sanity, maybe ?
return
if(!check_rights(R_ADMIN))
@@ -289,8 +293,8 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
admin_panel() // Refreshes the window
// Checks if there are HIGH_IMPACT_RULESETs and calls the rule's round_result() proc
/datum/game_mode/dynamic/set_round_result()
// Set result and news report here
/datum/controller/subsystem/dynamic/proc/set_round_result()
// If it got to this part, just pick one high impact ruleset if it exists
for(var/datum/dynamic_ruleset/rule in executed_rules)
if(rule.flags & HIGH_IMPACT_RULESET)
@@ -299,9 +303,20 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
if(SSticker.news_report)
return
return ..()
SSticker.mode_result = "undefined"
/datum/game_mode/dynamic/proc/send_intercept()
// Something nuked the station - it wasn't nuke ops (they set their own via their rulset)
if(GLOB.station_was_nuked)
SSticker.news_report = STATION_NUKED
if(SSsupermatter_cascade.cascade_initiated)
SSticker.news_report = SUPERMATTER_CASCADE
// Only show this one if we have nothing better to show
if(EMERGENCY_ESCAPED_OR_ENDGAMED && !SSticker.news_report)
SSticker.news_report = SSshuttle.emergency?.is_hijacked() ? SHUTTLE_HIJACK : STATION_EVACUATED
/datum/controller/subsystem/dynamic/proc/send_intercept()
if(SScommunications.block_command_report) //If we don't want the report to be printed just yet, we put it off until it's ready
addtimer(CALLBACK(src, PROC_REF(send_intercept)), 10 SECONDS)
return
@@ -316,11 +331,33 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
min_threat = min(ruleset.cost, min_threat)
var/greenshift = GLOB.dynamic_forced_extended || (threat_level < min_threat && shown_threat < min_threat) //if both shown and real threat are below any ruleset, its extended time
generate_station_goals(greenshift)
. += generate_station_goal_report()
. += generate_station_trait_report()
generate_station_goals(greenshift ? INFINITY : CONFIG_GET(number/station_goal_budget))
if (GLOB.station_goals.len > 0)
var/list/texts = list("<hr><b>Special Orders for [station_name()]:</b><BR>")
for(var/datum/station_goal/station_goal as anything in GLOB.station_goals)
station_goal.on_report()
texts += station_goal.get_report()
. += texts.Join("<hr>")
var/list/trait_list_strings = list()
for(var/datum/station_trait/station_trait as anything in SSstation.station_traits)
if(!station_trait.show_in_report)
continue
trait_list_strings += "[station_trait.get_report()]<BR>"
if(trait_list_strings.len > 0)
. += "<hr><b>Identified shift divergencies:</b><BR>" + trait_list_strings.Join()
if(length(SScommunications.command_report_footnotes))
. += generate_report_footnote()
var/footnote_pile = ""
for(var/datum/command_footnote/footnote in SScommunications.command_report_footnotes)
footnote_pile += "[footnote.message]<BR>"
footnote_pile += "<i>[footnote.signature]</i><BR>"
footnote_pile += "<BR>"
. += "<hr><b>Additional Notes: </b><BR><BR>" + footnote_pile
print_command_report(., "[command_name()] Status Summary", announce=FALSE)
if(greenshift)
@@ -330,8 +367,10 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
if(SSsecurity_level.get_current_level_as_number() < SEC_LEVEL_BLUE)
SSsecurity_level.set_level(SEC_LEVEL_BLUE)
return .
/// Generate the advisory level depending on the shown threat level.
/datum/game_mode/dynamic/proc/generate_advisory_level()
/datum/controller/subsystem/dynamic/proc/generate_advisory_level()
var/advisory_string = ""
if (prob(PULSAR_REPORT_CHANCE))
if(HAS_TRAIT(SSstation, STATION_TRAIT_BANANIUM_SHIPMENTS))
@@ -376,7 +415,7 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
return advisory_string
/datum/game_mode/dynamic/proc/show_threatlog(mob/admin)
/datum/controller/subsystem/dynamic/proc/show_threatlog(mob/admin)
if(!SSticker.HasRoundStarted())
tgui_alert(usr, "The round hasn't started yet!")
return
@@ -395,7 +434,7 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
usr << browse(out.Join(), "window=threatlog;size=700x500")
/// Generates the threat level using lorentz distribution and assigns peaceful_percentage.
/datum/game_mode/dynamic/proc/generate_threat()
/datum/controller/subsystem/dynamic/proc/generate_threat()
// At lower pop levels we run a Liner Interpolation against the max threat based proportionally on the number
// of players ready. This creates a balanced lorentz curve within a smaller range than 0 to max_threat_level.
var/calculated_max_threat = (SSticker.totalPlayersReady < low_pop_player_threshold) ? LERP(low_pop_maximum_threat, max_threat_level, SSticker.totalPlayersReady / low_pop_player_threshold) : max_threat_level
@@ -410,12 +449,12 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
peaceful_percentage = (threat_level/max_threat_level)*100
/// Generates the midround and roundstart budgets
/datum/game_mode/dynamic/proc/generate_budgets()
/datum/controller/subsystem/dynamic/proc/generate_budgets()
round_start_budget = lorentz_to_amount(roundstart_split_curve_centre, roundstart_split_curve_width, threat_level, 0.1)
initial_round_start_budget = round_start_budget
mid_round_budget = threat_level - round_start_budget
/datum/game_mode/dynamic/proc/setup_parameters()
/datum/controller/subsystem/dynamic/proc/setup_parameters()
log_dynamic("Dynamic mode parameters for the round:")
log_dynamic("Centre is [threat_curve_centre], Width is [threat_curve_width], Forced extended is [GLOB.dynamic_forced_extended ? "Enabled" : "Disabled"], No stacking is [GLOB.dynamic_no_stacking ? "Enabled" : "Disabled"].")
log_dynamic("Stacking limit is [GLOB.dynamic_stacking_limit].")
@@ -448,17 +487,18 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
)
return TRUE
/datum/game_mode/dynamic/proc/setup_shown_threat()
/datum/controller/subsystem/dynamic/proc/setup_shown_threat()
if (prob(FAKE_REPORT_CHANCE))
shown_threat = rand(1, 100)
else
shown_threat = clamp(threat_level + rand(REPORT_NEG_DIVERGENCE, REPORT_POS_DIVERGENCE), 0, 100)
/datum/game_mode/dynamic/proc/set_cooldowns()
/datum/controller/subsystem/dynamic/proc/set_cooldowns()
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
/datum/game_mode/dynamic/pre_setup()
// Called BEFORE everyone is equipped with their job
/datum/controller/subsystem/dynamic/proc/pre_setup()
if(CONFIG_GET(flag/dynamic_config_enabled))
var/json_file = file("[global.config.directory]/dynamic.json")
if(fexists(json_file))
@@ -517,24 +557,99 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
candidates.Cut()
return TRUE
/datum/game_mode/dynamic/post_setup(report)
// Called AFTER everyone is equipped with their job
/datum/controller/subsystem/dynamic/proc/post_setup(report)
for(var/datum/dynamic_ruleset/roundstart/rule in executed_rules)
rule.candidates.Cut() // The rule should not use candidates at this point as they all are null.
addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/game_mode/dynamic/, execute_roundstart_rule), rule), rule.delay)
addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/controller/subsystem/dynamic/, execute_roundstart_rule), rule), rule.delay)
if (!CONFIG_GET(flag/no_intercept_report))
addtimer(CALLBACK(src, PROC_REF(send_intercept)), rand(waittime_l, waittime_h))
..()
addtimer(CALLBACK(src, PROC_REF(display_roundstart_logout_report)), ROUNDSTART_LOGOUT_REPORT_TIME)
if(CONFIG_GET(flag/reopen_roundstart_suicide_roles))
var/delay = CONFIG_GET(number/reopen_roundstart_suicide_roles_delay)
if(delay)
delay *= (1 SECONDS)
else
delay = (4 MINUTES) //default to 4 minutes if the delay isn't defined.
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(reopen_roundstart_suicide_roles)), delay)
if(SSdbcore.Connect())
var/list/to_set = list()
var/arguments = list()
if(GLOB.revdata.originmastercommit)
to_set += "commit_hash = :commit_hash"
arguments["commit_hash"] = GLOB.revdata.originmastercommit
if(to_set.len)
arguments["round_id"] = GLOB.round_id
var/datum/db_query/query_round_game_mode = SSdbcore.NewQuery(
"UPDATE [format_table_name("round")] SET [to_set.Join(", ")] WHERE id = :round_id",
arguments
)
query_round_game_mode.Execute()
qdel(query_round_game_mode)
return TRUE
/datum/controller/subsystem/dynamic/proc/display_roundstart_logout_report()
var/list/msg = list("[span_boldnotice("Roundstart logout report")]\n\n")
for(var/i in GLOB.mob_living_list)
var/mob/living/L = i
var/mob/living/carbon/C = L
if (istype(C) && !C.last_mind)
continue // never had a client
if(L.ckey && !GLOB.directory[L.ckey])
msg += "<b>[L.name]</b> ([L.key]), the [L.job] (<font color='#ffcc00'><b>Disconnected</b></font>)\n"
if(L.ckey && L.client)
var/failed = FALSE
if(L.client.inactivity >= ROUNDSTART_LOGOUT_AFK_THRESHOLD) //Connected, but inactive (alt+tabbed or something)
msg += "<b>[L.name]</b> ([L.key]), the [L.job] (<font color='#ffcc00'><b>Connected, Inactive</b></font>)\n"
failed = TRUE //AFK client
if(!failed && L.stat)
if(HAS_TRAIT(L, TRAIT_SUICIDED)) //Suicider
msg += "<b>[L.name]</b> ([L.key]), the [L.job] ([span_boldannounce("Suicide")])\n"
failed = TRUE //Disconnected client
if(!failed && (L.stat == UNCONSCIOUS || L.stat == HARD_CRIT))
msg += "<b>[L.name]</b> ([L.key]), the [L.job] (Dying)\n"
failed = TRUE //Unconscious
if(!failed && L.stat == DEAD)
msg += "<b>[L.name]</b> ([L.key]), the [L.job] (Dead)\n"
failed = TRUE //Dead
continue //Happy connected client
for(var/mob/dead/observer/D in GLOB.dead_mob_list)
if(D.mind && D.mind.current == L)
if(L.stat == DEAD)
if(HAS_TRAIT(L, TRAIT_SUICIDED)) //Suicider
msg += "<b>[L.name]</b> ([ckey(D.mind.key)]), the [L.job] ([span_boldannounce("Suicide")])\n"
continue //Disconnected client
else
msg += "<b>[L.name]</b> ([ckey(D.mind.key)]), the [L.job] (Dead)\n"
continue //Dead mob, ghost abandoned
else
if(D.can_reenter_corpse)
continue //Adminghost, or cult/wizard ghost
else
msg += "<b>[L.name]</b> ([ckey(D.mind.key)]), the [L.job] ([span_boldannounce("Ghosted")])\n"
continue //Ghosted while alive
var/concatenated_message = msg.Join()
log_admin(concatenated_message)
to_chat(GLOB.admins, concatenated_message)
/// Initializes the internal ruleset variables
/datum/game_mode/dynamic/proc/setup_rulesets()
/datum/controller/subsystem/dynamic/proc/setup_rulesets()
midround_rules = init_rulesets(/datum/dynamic_ruleset/midround)
latejoin_rules = init_rulesets(/datum/dynamic_ruleset/latejoin)
/// Returns a list of the provided rulesets.
/// Configures their variables to match config.
/datum/game_mode/dynamic/proc/init_rulesets(ruleset_subtype)
/datum/controller/subsystem/dynamic/proc/init_rulesets(ruleset_subtype)
var/list/rulesets = list()
for (var/datum/dynamic_ruleset/ruleset_type as anything in subtypesof(ruleset_subtype))
@@ -551,14 +666,13 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
return rulesets
/// A simple roundstart proc used when dynamic_forced_roundstart_ruleset has rules in it.
/datum/game_mode/dynamic/proc/rigged_roundstart()
/datum/controller/subsystem/dynamic/proc/rigged_roundstart()
message_admins("[GLOB.dynamic_forced_roundstart_ruleset.len] rulesets being forced. Will now attempt to draft players for them.")
log_dynamic("[GLOB.dynamic_forced_roundstart_ruleset.len] rulesets being forced. Will now attempt to draft players for them.")
for (var/datum/dynamic_ruleset/roundstart/rule in GLOB.dynamic_forced_roundstart_ruleset)
configure_ruleset(rule)
message_admins("Drafting players for forced ruleset [rule.name].")
log_dynamic("Drafting players for forced ruleset [rule.name].")
rule.mode = src
rule.acceptable(roundstart_pop_ready, threat_level) // Assigns some vars in the modes, running it here for consistency
rule.candidates = candidates.Copy()
rule.trim_candidates()
@@ -572,7 +686,7 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
spend_roundstart_budget(picking_roundstart_rule(rule, scaled_times, forced = TRUE))
/datum/game_mode/dynamic/proc/roundstart(list/roundstart_rules)
/datum/controller/subsystem/dynamic/proc/roundstart(list/roundstart_rules)
if (GLOB.dynamic_forced_extended)
log_dynamic("Starting a round of forced extended.")
return TRUE
@@ -631,7 +745,7 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
update_log()
/// Initializes the round start ruleset provided to it. Returns how much threat to spend.
/datum/game_mode/dynamic/proc/picking_roundstart_rule(datum/dynamic_ruleset/roundstart/ruleset, scaled_times = 0, forced = FALSE)
/datum/controller/subsystem/dynamic/proc/picking_roundstart_rule(datum/dynamic_ruleset/roundstart/ruleset, scaled_times = 0, forced = FALSE)
log_dynamic("Picked a ruleset: [ruleset.name], scaled [scaled_times] times")
ruleset.trim_candidates()
@@ -650,7 +764,7 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
return 0
/// Mainly here to facilitate delayed rulesets. All roundstart rulesets are executed with a timered callback to this proc.
/datum/game_mode/dynamic/proc/execute_roundstart_rule(sent_rule)
/datum/controller/subsystem/dynamic/proc/execute_roundstart_rule(sent_rule)
var/datum/dynamic_ruleset/rule = sent_rule
if(rule.execute())
if(rule.persistent)
@@ -665,7 +779,7 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
return FALSE
/// An experimental proc to allow admins to call rules on the fly or have rules call other rules.
/datum/game_mode/dynamic/proc/picking_specific_rule(ruletype, forced = FALSE, ignore_cost = FALSE)
/datum/controller/subsystem/dynamic/proc/picking_specific_rule(ruletype, forced = FALSE, ignore_cost = FALSE)
var/datum/dynamic_ruleset/midround/new_rule
if(ispath(ruletype))
new_rule = new ruletype() // You should only use it to call midround rules though.
@@ -714,7 +828,7 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
new_rule.forget_startup()
return FALSE
/datum/game_mode/dynamic/process()
/datum/controller/subsystem/dynamic/fire()
for (var/datum/dynamic_ruleset/rule in current_rules)
if(rule.rule_process() == RULESET_STOP_PROCESSING) // If rule_process() returns 1 (RULESET_STOP_PROCESSING), stop processing.
current_rules -= rule
@@ -722,14 +836,14 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
try_midround_roll()
/// Removes type from the list
/datum/game_mode/dynamic/proc/remove_from_list(list/type_list, type)
/datum/controller/subsystem/dynamic/proc/remove_from_list(list/type_list, type)
for(var/I in type_list)
if(istype(I, type))
type_list -= I
return type_list
/// Checks if a type in blocking_list is in rule_list.
/datum/game_mode/dynamic/proc/check_blocking(list/blocking_list, list/rule_list)
/datum/controller/subsystem/dynamic/proc/check_blocking(list/blocking_list, list/rule_list)
if(blocking_list.len > 0)
for(var/blocking in blocking_list)
for(var/_executed in rule_list)
@@ -739,7 +853,8 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
return TRUE
return FALSE
/datum/game_mode/dynamic/make_antag_chance(mob/living/carbon/human/newPlayer)
/// Handles late-join antag assignments
/datum/controller/subsystem/dynamic/proc/make_antag_chance(mob/living/carbon/human/newPlayer)
if (GLOB.dynamic_forced_extended)
return
if(EMERGENCY_ESCAPED_OR_ENDGAMED) // No more rules after the shuttle has left
@@ -789,7 +904,7 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
* This proc handles the execution of a latejoin ruleset, including removing it from latejoin rulesets if not repeatable,
* upping the injection cooldown, and starting a timer to execute the ruleset on delay.
*/
/datum/game_mode/dynamic/proc/handle_executing_latejoin(datum/dynamic_ruleset/ruleset, mob/living/carbon/human/only_candidate, forced = FALSE)
/datum/controller/subsystem/dynamic/proc/handle_executing_latejoin(datum/dynamic_ruleset/ruleset, mob/living/carbon/human/only_candidate, forced = FALSE)
ruleset.candidates = list(only_candidate)
ruleset.trim_candidates()
ruleset.load_templates()
@@ -809,7 +924,7 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
return TRUE
/// Apply configurations to rule.
/datum/game_mode/dynamic/proc/configure_ruleset(datum/dynamic_ruleset/ruleset)
/datum/controller/subsystem/dynamic/proc/configure_ruleset(datum/dynamic_ruleset/ruleset)
var/rule_conf = LAZYACCESSASSOC(configuration, ruleset.ruletype, ruleset.name)
for(var/variable in rule_conf)
if(!(variable in ruleset.vars))
@@ -822,14 +937,14 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
ruleset.restricted_roles |= JOB_ASSISTANT
/// Get station traits and call for their config
/datum/game_mode/dynamic/proc/configure_station_trait_costs()
/datum/controller/subsystem/dynamic/proc/configure_station_trait_costs()
if(!CONFIG_GET(flag/dynamic_config_enabled))
return
for(var/datum/station_trait/station_trait as anything in GLOB.dynamic_station_traits)
configure_station_trait(station_trait)
/// Apply configuration for station trait costs
/datum/game_mode/dynamic/proc/configure_station_trait(datum/station_trait/station_trait)
/datum/controller/subsystem/dynamic/proc/configure_station_trait(datum/station_trait/station_trait)
var/list/station_trait_config = LAZYACCESSASSOC(configuration, "Station", station_trait.dynamic_threat_id)
var/cost = station_trait_config["cost"]
@@ -842,31 +957,31 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
GLOB.dynamic_station_traits[station_trait] = cost
/// Refund threat, but no more than threat_level.
/datum/game_mode/dynamic/proc/refund_threat(regain)
/datum/controller/subsystem/dynamic/proc/refund_threat(regain)
mid_round_budget = min(threat_level, mid_round_budget + regain)
/// Generate threat and increase the threat_level if it goes beyond, capped at 100
/datum/game_mode/dynamic/proc/create_threat(gain, list/threat_log, reason)
/datum/controller/subsystem/dynamic/proc/create_threat(gain, list/threat_log, reason)
mid_round_budget = min(100, mid_round_budget + gain)
if(mid_round_budget > threat_level)
threat_level = mid_round_budget
for(var/list/logs in threat_log)
log_threat(gain, logs, reason)
/datum/game_mode/dynamic/proc/log_threat(threat_change, list/threat_log, reason)
/datum/controller/subsystem/dynamic/proc/log_threat(threat_change, list/threat_log, reason)
var/gain_or_loss = "+"
if(threat_change < 0)
gain_or_loss = "-"
threat_log += "Threat [gain_or_loss][abs(threat_change)] - [reason]."
/// Expend round start threat, can't fall under 0.
/datum/game_mode/dynamic/proc/spend_roundstart_budget(cost, list/threat_log, reason)
/datum/controller/subsystem/dynamic/proc/spend_roundstart_budget(cost, list/threat_log, reason)
round_start_budget = max(round_start_budget - cost,0)
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)
/datum/controller/subsystem/dynamic/proc/spend_midround_budget(cost, list/threat_log, reason)
mid_round_budget = max(mid_round_budget - cost,0)
if (!isnull(threat_log))
log_threat(-cost, threat_log, reason)
@@ -879,7 +994,7 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
* threat and then rounded to the nearest interval.
* rand() calls without arguments returns a value between 0 and 1, allowing for smaller intervals.
*/
/datum/game_mode/dynamic/proc/lorentz_to_amount(centre = 0, scale = 1.8, max_threat = 100, interval = 1)
/datum/controller/subsystem/dynamic/proc/lorentz_to_amount(centre = 0, scale = 1.8, max_threat = 100, interval = 1)
var/location = RANDOM_DECIMAL(-MAXIMUM_DYN_DISTANCE, MAXIMUM_DYN_DISTANCE) * rand()
var/lorentz_result = LORENTZ_CUMULATIVE_DISTRIBUTION(centre, location, scale)
var/std_threat = lorentz_result * max_threat
@@ -888,6 +1003,44 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
var/upper_deviation = max((max_threat - std_threat) * (centre-location)/MAXIMUM_DYN_DISTANCE, 0)
return clamp(round(std_threat + upper_deviation - lower_deviation, interval), 0, 100)
/proc/reopen_roundstart_suicide_roles()
var/include_command = CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_positions)
var/list/reopened_jobs = list()
for(var/mob/living/quitter in GLOB.suicided_mob_list)
var/datum/job/job = SSjob.GetJob(quitter.job)
if(!job || !(job.job_flags & JOB_REOPEN_ON_ROUNDSTART_LOSS))
continue
if(!include_command && job.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND)
continue
job.current_positions = max(job.current_positions - 1, 0)
reopened_jobs += quitter.job
if(CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_report))
if(reopened_jobs.len)
var/reopened_job_report_positions
for(var/dead_dudes_job in reopened_jobs)
reopened_job_report_positions = "[reopened_job_report_positions ? "[reopened_job_report_positions]\n":""][dead_dudes_job]"
var/suicide_command_report = {"
<font size = 3><b>[command_name()] Human Resources Board</b><br>
Notice of Personnel Change</font><hr>
To personnel management staff aboard [station_name()]:<br><br>
Our medical staff have detected a series of anomalies in the vital sensors
of some of the staff aboard your station.<br><br>
Further investigation into the situation on our end resulted in us discovering
a series of rather... unforturnate decisions that were made on the part of said staff.<br><br>
As such, we have taken the liberty to automatically reopen employment opportunities for the positions of the crew members
who have decided not to partake in our research. We will be forwarding their cases to our employment review board
to determine their eligibility for continued service with the company (and of course the
continued storage of cloning records within the central medical backup server.)<br><br>
<i>The following positions have been reopened on our behalf:<br><br>
[reopened_job_report_positions]</i>
"}
print_command_report(suicide_command_report, "Central Command Personnel Update")
#undef MAXIMUM_DYN_DISTANCE
#undef FAKE_REPORT_CHANCE
@@ -1,7 +1,7 @@
/datum/game_mode/dynamic/proc/setup_hijacking()
/datum/controller/subsystem/dynamic/proc/setup_hijacking()
RegisterSignal(SSdcs, COMSIG_GLOB_PRE_RANDOM_EVENT, PROC_REF(on_pre_random_event))
/datum/game_mode/dynamic/proc/on_pre_random_event(datum/source, datum/round_event_control/round_event_control)
/datum/controller/subsystem/dynamic/proc/on_pre_random_event(datum/source, datum/round_event_control/round_event_control)
SIGNAL_HANDLER
if (!round_event_control.dynamic_should_hijack)
return
@@ -69,7 +69,7 @@
return serialization
/// Updates the log for the current snapshots.
/datum/game_mode/dynamic/proc/update_log()
/datum/controller/subsystem/dynamic/proc/update_log()
var/list/serialized = list()
serialized["threat_level"] = threat_level
serialized["round_start_budget"] = initial_round_start_budget
@@ -84,7 +84,7 @@
rustg_file_write(json_encode(serialized), "[GLOB.log_directory]/dynamic.json")
/// Creates a new snapshot with the given rulesets chosen, and writes to the JSON output.
/datum/game_mode/dynamic/proc/new_snapshot(datum/dynamic_ruleset/ruleset_chosen)
/datum/controller/subsystem/dynamic/proc/new_snapshot(datum/dynamic_ruleset/ruleset_chosen)
var/datum/dynamic_snapshot/new_snapshot = new
new_snapshot.remaining_threat = mid_round_budget
@@ -1,7 +1,7 @@
/// 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()
/datum/controller/subsystem/dynamic/proc/next_midround_injection()
if (!isnull(next_midround_injection))
return next_midround_injection
@@ -16,7 +16,7 @@
return last_midround_injection_attempt + distance
/datum/game_mode/dynamic/proc/try_midround_roll()
/datum/controller/subsystem/dynamic/proc/try_midround_roll()
if (!forced_injection && next_midround_injection() > world.time)
return
@@ -90,7 +90,7 @@
log_dynamic_and_announce("No midround rulesets could be drafted. ([heavy_light_log_count])")
/// 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)
/datum/controller/subsystem/dynamic/proc/get_heavy_midround_injection_chance(dry_run)
var/chance_modifier = 1
var/next_midround_roll = next_midround_injection() - SSticker.round_start_time
@@ -7,7 +7,7 @@
var/ruletype = ""
/// If set to TRUE, the rule won't be discarded after being executed, and dynamic will call rule_process() every time it ticks.
var/persistent = FALSE
/// If set to TRUE, dynamic mode will be able to draft this ruleset again later on. (doesn't apply for roundstart rules)
/// If set to TRUE, dynamic will be able to draft this ruleset again later on. (doesn't apply for roundstart rules)
var/repeatable = FALSE
/// If set higher than 0 decreases weight by itself causing the ruleset to appear less often the more it is repeated.
var/repeatable_weight_decrease = 2
@@ -41,7 +41,7 @@
var/required_candidates = 0
/// 0 -> 9, probability for this rule to be picked against other rules. If zero this will effectively disable the rule.
var/weight = 5
/// Threat cost for this rule, this is decreased from the mode's threat when the rule is executed.
/// Threat cost for this rule, this is decreased from the threat level when the rule is executed.
var/cost = 0
/// Cost per level the rule scales up.
var/scaling_cost = 0
@@ -51,13 +51,11 @@
var/total_cost = 0
/// A flag that determines how the ruleset is handled. Check __DEFINES/dynamic.dm for an explanation of the accepted values.
var/flags = NONE
/// Pop range per requirement. If zero defaults to mode's pop_per_requirement.
/// Pop range per requirement. If zero defaults to dynamic's pop_per_requirement.
var/pop_per_requirement = 0
/// Requirements are the threat level requirements per pop range.
/// With the default values, The rule will never get drafted below 10 threat level (aka: "peaceful extended"), and it requires a higher threat level at lower pops.
var/list/requirements = list(40,30,20,10,10,10,10,10,10,10)
/// Reference to the mode, use this instead of SSticker.mode.
var/datum/game_mode/dynamic/mode = null
/// If a role is to be considered another for the purpose of banning.
var/antag_flag_override = null
/// If set, will check this preference instead of antag_flag.
@@ -92,8 +90,6 @@
// side effects here. Dynamic rulesets should be stateless anyway.
SHOULD_NOT_OVERRIDE(TRUE)
mode = SSticker.mode
..()
/datum/dynamic_ruleset/roundstart // One or more of those drafted at roundstart
@@ -135,7 +131,7 @@
/// Sets the current threat indices and returns true if we're inside of them
/datum/dynamic_ruleset/proc/is_valid_threat(population, threat_level)
pop_per_requirement = pop_per_requirement > 0 ? pop_per_requirement : mode.pop_per_requirement
pop_per_requirement = pop_per_requirement > 0 ? pop_per_requirement : SSdynamic.pop_per_requirement
indice_pop = min(requirements.len,round(population/pop_per_requirement)+1)
return threat_level >= requirements[indice_pop]
@@ -148,14 +144,14 @@
return 0
var/antag_fraction = 0
for(var/_ruleset in (mode.executed_rules + list(src))) // we care about the antags we *will* assign, too
for(var/_ruleset in (SSdynamic.executed_rules + list(src))) // we care about the antags we *will* assign, too
var/datum/dynamic_ruleset/ruleset = _ruleset
antag_fraction += ((1 + ruleset.scaled_times) * ruleset.get_antag_cap(population)) / mode.roundstart_pop_ready
antag_fraction += ((1 + ruleset.scaled_times) * ruleset.get_antag_cap(population)) / SSdynamic.roundstart_pop_ready
for(var/i in 1 to max_scale)
if(antag_fraction < 0.25)
scaled_times += 1
antag_fraction += get_antag_cap(population) / mode.roundstart_pop_ready // we added new antags, gotta update the %
antag_fraction += get_antag_cap(population) / SSdynamic.roundstart_pop_ready // we added new antags, gotta update the %
return scaled_times * scaling_cost
@@ -170,7 +166,7 @@
/datum/dynamic_ruleset/proc/rule_process()
return
/// Called on game mode pre_setup for roundstart rulesets.
/// Called on pre_setup for roundstart rulesets.
/// Do everything you need to do before job is assigned here.
/// IMPORTANT: ASSIGN special_role HERE
/datum/dynamic_ruleset/proc/pre_execute()
@@ -206,15 +202,15 @@
/// Runs from gamemode process() if ruleset fails to start, like delayed rulesets not getting valid candidates.
/// This one only handles refunding the threat, override in ruleset to clean up the rest.
/datum/dynamic_ruleset/proc/clean_up()
mode.refund_threat(cost + (scaled_times * scaling_cost))
mode.threat_log += "[worldtime2text()]: [ruletype] [name] refunded [cost + (scaled_times * scaling_cost)]. Failed to execute."
SSdynamic.refund_threat(cost + (scaled_times * scaling_cost))
SSdynamic.threat_log += "[worldtime2text()]: [ruletype] [name] refunded [cost + (scaled_times * scaling_cost)]. Failed to execute."
/// Gets weight of the ruleset
/// Note that this decreases weight if repeatable is TRUE and repeatable_weight_decrease is higher than 0
/// Note: If you don't want repeatable rulesets to decrease their weight use the weight variable directly
/datum/dynamic_ruleset/proc/get_weight()
if(repeatable && weight > 1 && repeatable_weight_decrease > 0)
for(var/datum/dynamic_ruleset/DR in mode.executed_rules)
for(var/datum/dynamic_ruleset/DR in SSdynamic.executed_rules)
if(istype(DR, type))
weight = max(weight-repeatable_weight_decrease,1)
return weight
@@ -233,7 +229,7 @@
/datum/dynamic_ruleset/proc/trim_candidates()
return
/// Set mode result and news report here.
/// Set mode_result and news report here.
/// Only called if ruleset is flagged as HIGH_IMPACT_RULESET
/datum/dynamic_ruleset/proc/round_result()
@@ -29,7 +29,7 @@
if (M.mind && (M.mind.assigned_role.title in enemy_roles) && (!(M in candidates) || (M.mind.assigned_role.title in restricted_roles)))
job_check++ // Checking for "enemies" (such as sec officers). To be counters, they must either not be candidates to that rule, or have a job that restricts them from it
var/threat = round(mode.threat_level/10)
var/threat = round(SSdynamic.threat_level/10)
var/ruleset_forced = (GLOB.dynamic_forced_rulesets[type] || RULESET_NOT_FORCED) == RULESET_FORCE_ENABLED
if (!ruleset_forced && job_check < required_enemies[threat])
log_dynamic("FAIL: [src] is not ready, because there are not enough enemies: [required_enemies[threat]] needed, [job_check] found")
@@ -108,7 +108,7 @@
if (M.mind && (M.mind.assigned_role.title in enemy_roles) && (!(M in candidates) || (M.mind.assigned_role.title in restricted_roles)))
job_check++ // Checking for "enemies" (such as sec officers). To be counters, they must either not be candidates to that rule, or have a job that restricts them from it
var/threat = round(mode.threat_level/10)
var/threat = round(SSdynamic.threat_level/10)
var/ruleset_forced = (GLOB.dynamic_forced_rulesets[type] || RULESET_NOT_FORCED) == RULESET_FORCE_ENABLED
if (!ruleset_forced && job_check < required_enemies[threat])
log_dynamic("FAIL: [src] is not ready, because there are not enough enemies: [required_enemies[threat]] needed, [job_check] found")
@@ -132,23 +132,23 @@
message_admins("Possible volunteers was 0. This shouldn't appear, because of ready(), unless you forced it!")
return
mode.log_dynamic_and_announce("Polling [possible_volunteers.len] players to apply for the [name] ruleset.")
candidates = poll_ghost_candidates("The mode is looking for volunteers to become [antag_flag] for [name]", antag_flag_override, antag_flag || antag_flag_override, poll_time = 300)
SSdynamic.log_dynamic_and_announce("Polling [possible_volunteers.len] players to apply for the [name] ruleset.")
candidates = poll_ghost_candidates("Looking for volunteers to become [antag_flag] for [name]", antag_flag_override, antag_flag || antag_flag_override, poll_time = 300)
if(!candidates || candidates.len <= 0)
mode.log_dynamic_and_announce("The ruleset [name] received no applications.")
mode.executed_rules -= src
SSdynamic.log_dynamic_and_announce("The ruleset [name] received no applications.")
SSdynamic.executed_rules -= src
attempt_replacement()
return
mode.log_dynamic_and_announce("[candidates.len] players volunteered for [name].")
SSdynamic.log_dynamic_and_announce("[candidates.len] players volunteered for [name].")
review_applications()
/// Here is where you can check if your ghost applicants are valid for the ruleset.
/// Called by send_applications().
/datum/dynamic_ruleset/midround/from_ghosts/proc/review_applications()
if(candidates.len < required_applicants)
mode.executed_rules -= src
SSdynamic.executed_rules -= src
return
for (var/i = 1, i <= required_candidates, i++)
if(candidates.len <= 0)
@@ -200,12 +200,12 @@
/datum/dynamic_ruleset/midround/from_ghosts/proc/attempt_replacement()
var/datum/dynamic_ruleset/midround/from_living/autotraitor/sleeper_agent = new
mode.configure_ruleset(sleeper_agent)
SSdynamic.configure_ruleset(sleeper_agent)
if (!mode.picking_specific_rule(sleeper_agent))
if (!SSdynamic.picking_specific_rule(sleeper_agent))
return
mode.picking_specific_rule(/datum/dynamic_ruleset/latejoin/infiltrator)
SSdynamic.picking_specific_rule(/datum/dynamic_ruleset/latejoin/infiltrator)
///subtype to handle checking players
/datum/dynamic_ruleset/midround/from_living
@@ -378,7 +378,7 @@
var/list/operative_cap = list(2,2,3,3,4,5,5,5,5,5)
/datum/dynamic_ruleset/midround/from_ghosts/nuclear/acceptable(population=0, threat_level=0)
if (locate(/datum/dynamic_ruleset/roundstart/nuclear) in mode.executed_rules)
if (locate(/datum/dynamic_ruleset/roundstart/nuclear) in SSdynamic.executed_rules)
return FALSE // Unavailable if nuke ops were already sent at roundstart
indice_pop = min(operative_cap.len, round(living_players.len/5)+1)
required_candidates = operative_cap[indice_pop]
@@ -604,9 +604,9 @@ GLOBAL_VAR_INIT(revolutionary_win, FALSE)
. = ..()
message_admins("Starting a round of extended.")
log_game("Starting a round of extended.")
mode.spend_roundstart_budget(mode.round_start_budget)
mode.spend_midround_budget(mode.mid_round_budget)
mode.threat_log += "[worldtime2text()]: Extended ruleset set threat to 0."
SSdynamic.spend_roundstart_budget(SSdynamic.round_start_budget)
SSdynamic.spend_midround_budget(SSdynamic.mid_round_budget)
SSdynamic.threat_log += "[worldtime2text()]: Extended ruleset set threat to 0."
return TRUE
//////////////////////////////////////////////
@@ -2,12 +2,12 @@
/// This is used for impactful events like traitors hacking and creating more threat, or a revolutions victory.
/// It tries to spawn a heavy midround if possible, otherwise it will trigger a "bad" random event after a short period.
/// Calling this function will not use up any threat.
/datum/game_mode/dynamic/proc/unfavorable_situation()
/datum/controller/subsystem/dynamic/proc/unfavorable_situation()
SHOULD_NOT_SLEEP(TRUE)
INVOKE_ASYNC(src, PROC_REF(_unfavorable_situation))
/datum/game_mode/dynamic/proc/_unfavorable_situation()
/datum/controller/subsystem/dynamic/proc/_unfavorable_situation()
var/static/list/unfavorable_random_events = list()
if (!length(unfavorable_random_events))
unfavorable_random_events = generate_unfavourable_events()
@@ -24,7 +24,7 @@
picking_specific_rule(heavy_ruleset, forced = TRUE, ignore_cost = TRUE)
/// Return a valid heavy dynamic ruleset, or an empty list if there's no time to run any rulesets
/datum/game_mode/dynamic/proc/generate_unfavourable_heavy_rulesets()
/datum/controller/subsystem/dynamic/proc/generate_unfavourable_heavy_rulesets()
if (EMERGENCY_PAST_POINT_OF_NO_RETURN)
return list()
@@ -58,7 +58,7 @@
return possible_heavies
/// Filter the below list by which events can actually run on this map
/datum/game_mode/dynamic/proc/generate_unfavourable_events()
/datum/controller/subsystem/dynamic/proc/generate_unfavourable_events()
var/static/list/unfavorable_random_events = list(
/datum/round_event_control/earthquake,
/datum/round_event_control/immovable_rod,
@@ -12,10 +12,10 @@ This threat is split into two separate budgets--`round_start_budget` and `mid_ro
This split is done with a similar method, known as the ["lorentz distribution"](https://en.wikipedia.org/wiki/Cauchy_distribution), exists to create a bell curve that ensures that while most rounds will have a threat level around ~50, chaotic and tame rounds still exist for variety.
The process of creating these numbers occurs in `/datum/game_mode/dynamic/proc/generate_threat` (for creating the threat level) and `/datum/game_mode/dynamic/proc/generate_budgets` (for splitting the threat level into budgets).
The process of creating these numbers occurs in `/datum/controller/subsystem/dynamic/proc/generate_threat` (for creating the threat level) and `/datum/controller/subsystem/dynamic/proc/generate_budgets` (for splitting the threat level into budgets).
## Deciding roundstart threats
In `/datum/game_mode/dynamic/proc/roundstart()` (called when no admin chooses the rulesets explicitly), Dynamic uses the available roundstart budget to pick threats. This is done through the following system:
In `/datum/controller/subsystem/dynamic/proc/roundstart()` (called when no admin chooses the rulesets explicitly), Dynamic uses the available roundstart budget to pick threats. This is done through the following system:
- All roundstart rulesets (remember, `/datum/dynamic_ruleset/roundstart`) are put into an associative list with their weight as the values (`drafted_rules`).
- Until there is either no roundstart budget left, or until there is no ruleset we can choose from with the available threat, a `pickweight` is done based on the drafted_rules. If the same threat is picked twice, it will "scale up". The meaning of this depends on the ruleset itself, using the `scaled_times` variable; traitors for instance will create more the higher they scale.
@@ -24,7 +24,7 @@ In `/datum/game_mode/dynamic/proc/roundstart()` (called when no admin chooses th
- After all roundstart threats are chosen, `/datum/dynamic_ruleset/proc/picking_roundstart_rule` is called for each, passing in the ruleset and the number of times it is scaled.
- In this stage, `pre_execute` is called, which is the function that will determine what players get what antagonists. If this function returns FALSE for whatever reason (in the case of an error), then its threat is refunded.
After this process is done, any leftover roundstart threat will be given to the existing midround budget (done in `/datum/game_mode/dynamic/pre_setup()`).
After this process is done, any leftover roundstart threat will be given to the existing midround budget (done in `/datum/controller/subsystem/dynamic/pre_setup()`).
## Deciding midround threats
@@ -105,13 +105,13 @@ Midround - Rulesets have additional types
## Configuration and variables
### Configuration
Configuration can be done through a `config/dynamic.json` file. One is provided as example in the codebase. This config file, loaded in `/datum/game_mode/dynamic/pre_setup()`, directly overrides the values in the codebase, and so is perfect for making some rulesets harder/easier to get, turning them off completely, changing how much they cost, etc.
Configuration can be done through a `config/dynamic.json` file. One is provided as example in the codebase. This config file, loaded in `/datum/controller/subsystem/dynamic/pre_setup()`, directly overrides the values in the codebase, and so is perfect for making some rulesets harder/easier to get, turning them off completely, changing how much they cost, etc.
The format of this file is:
```json
{
"Dynamic": {
/* Configuration in here will directly override `/datum/game_mode/dynamic` itself. */
/* Configuration in here will directly override `/datum/controller/subsystem/dynamic` itself. */
/* Keys are variable names, values are their new values. */
},
@@ -172,7 +172,7 @@ This is not a complete list--search "configurable" in this README to learn more.
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.
- 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/controller/subsystem/dynamic/proc/get_injection_chance()`). Regardless of its decision, a new timer will be started within the range of 1 to 3 minutes, repeatedly.
- `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.
@@ -187,7 +187,7 @@ Random events have the potential to be hijacked by Dynamic to keep the pace of m
`/datum/round_event_control/dynamic_should_hijack` is a variable to random events to allow Dynamic to hijack them, and defaults to FALSE. This is set to TRUE for random events that spawn antagonists.
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:
In `/datum/controller/subsystem/dynamic/on_pre_random_event` (in `dynamic_hijacking.dm`), Dynamic hooks to random events. If the `dynamic_should_hijack` variable is TRUE, the following sequence of events occurs:
![Flow chart to describe the chain of events for Dynamic 2021 to take](https://github.com/tgstation/documentation-assets/blob/main/dynamic/random_event_hijacking.png)
@@ -8,7 +8,7 @@
*
* * max_allowed_attempts - Allows you to configure how many times the proc will attempt to pick a ruleset before giving up.
*/
/datum/game_mode/dynamic/proc/pick_ruleset(list/drafted_rules, max_allowed_attempts = INFINITY)
/datum/controller/subsystem/dynamic/proc/pick_ruleset(list/drafted_rules, max_allowed_attempts = INFINITY)
if (only_ruleset_executed)
log_dynamic("FAIL: only_ruleset_executed")
return null
@@ -52,7 +52,7 @@
return null
/// Executes a random midround ruleset from the list of drafted rules.
/datum/game_mode/dynamic/proc/pick_midround_rule(list/drafted_rules, description)
/datum/controller/subsystem/dynamic/proc/pick_midround_rule(list/drafted_rules, description)
log_dynamic("Rolling [drafted_rules.len] [description]")
var/datum/dynamic_ruleset/rule = pick_ruleset(drafted_rules)
@@ -75,7 +75,7 @@
return rule
/// Fired after admins do not cancel a midround injection.
/datum/game_mode/dynamic/proc/execute_midround_rule(datum/dynamic_ruleset/rule)
/datum/controller/subsystem/dynamic/proc/execute_midround_rule(datum/dynamic_ruleset/rule)
current_midround_rulesets = null
midround_injection_timer_id = null
if (!rule.repeatable)
@@ -83,7 +83,7 @@
addtimer(CALLBACK(src, PROC_REF(execute_midround_latejoin_rule), rule), rule.delay)
/// Mainly here to facilitate delayed rulesets. All midround/latejoin rulesets are executed with a timered callback to this proc.
/datum/game_mode/dynamic/proc/execute_midround_latejoin_rule(sent_rule)
/datum/controller/subsystem/dynamic/proc/execute_midround_latejoin_rule(sent_rule)
var/datum/dynamic_ruleset/rule = sent_rule
spend_midround_budget(rule.cost, threat_log, "[worldtime2text()]: [rule.ruletype] [rule.name]")
rule.pre_execute(GLOB.alive_player_list.len)
@@ -109,7 +109,7 @@
return FALSE
/// Fired when an admin cancels the current midround injection.
/datum/game_mode/dynamic/proc/admin_cancel_midround(mob/user, timer_id)
/datum/controller/subsystem/dynamic/proc/admin_cancel_midround(mob/user, timer_id)
if (midround_injection_timer_id != timer_id || !deltimer(midround_injection_timer_id))
to_chat(user, span_notice("Too late!"))
return
@@ -120,7 +120,7 @@
current_midround_rulesets = null
/// Fired when an admin requests a different midround injection.
/datum/game_mode/dynamic/proc/admin_different_midround(mob/user, timer_id)
/datum/controller/subsystem/dynamic/proc/admin_different_midround(mob/user, timer_id)
if (midround_injection_timer_id != timer_id || !deltimer(midround_injection_timer_id))
to_chat(user, span_notice("Too late!"))
return
+17 -12
View File
@@ -20,8 +20,6 @@ SUBSYSTEM_DEF(ticker)
/// Boolean to track and check if our subsystem setup is done.
var/setup_done = FALSE
var/datum/game_mode/mode = null
var/login_music //music played in pregame lobby
var/round_end_sound //music/jingle played when the world reboots
var/round_end_sound_sent = TRUE //If all clients have loaded it
@@ -206,10 +204,9 @@ SUBSYSTEM_DEF(ticker)
SEND_SIGNAL(src, COMSIG_TICKER_ERROR_SETTING_UP)
if(GAME_STATE_PLAYING)
mode.process(wait * 0.1)
check_queue()
if(!roundend_check_paused && (mode.check_finished() || force_ending))
if(!roundend_check_paused && (check_finished() || force_ending))
current_state = GAME_STATE_FINISHED
toggle_ooc(TRUE) // Turn it on
toggle_dooc(TRUE)
@@ -217,17 +214,27 @@ SUBSYSTEM_DEF(ticker)
check_maprotate()
Master.SetRunLevel(RUNLEVEL_POSTGAME)
/// Checks if the round should be ending, called every ticker tick
/datum/controller/subsystem/ticker/proc/check_finished()
if(!setup_done)
return FALSE
if(SSshuttle.emergency && (SSshuttle.emergency.mode == SHUTTLE_ENDGAME))
return TRUE
if(GLOB.station_was_nuked)
return TRUE
if(GLOB.revolutionary_win)
return TRUE
return FALSE
/datum/controller/subsystem/ticker/proc/setup()
to_chat(world, span_boldannounce("Starting game..."))
var/init_start = world.timeofday
mode = new /datum/game_mode/dynamic
CHECK_TICK
//Configure mode and assign player to special mode stuff
var/can_continue = 0
can_continue = src.mode.pre_setup() //Choose antagonists
//Configure mode and assign player to antagonists
var/can_continue = FALSE
can_continue = SSdynamic.pre_setup() //Choose antagonists
CHECK_TICK
can_continue = can_continue && SSjob.DivideOccupations() //Distribute jobs
CHECK_TICK
@@ -235,7 +242,6 @@ SUBSYSTEM_DEF(ticker)
if(!GLOB.Debug2)
if(!can_continue)
log_game("Game failed pre_setup")
QDEL_NULL(mode)
to_chat(world, "<B>Error setting up game.</B> Reverting to pre-game lobby.")
SSjob.ResetOccupations()
return FALSE
@@ -293,7 +299,7 @@ SUBSYSTEM_DEF(ticker)
/datum/controller/subsystem/ticker/proc/PostSetup()
set waitfor = FALSE
mode.post_setup()
SSdynamic.post_setup()
GLOB.start_state = new /datum/station_state()
GLOB.start_state.count()
@@ -525,7 +531,6 @@ SUBSYSTEM_DEF(ticker)
/datum/controller/subsystem/ticker/Recover()
current_state = SSticker.current_state
force_ending = SSticker.force_ending
mode = SSticker.mode
login_music = SSticker.login_music
round_end_sound = SSticker.round_end_sound
@@ -1,122 +0,0 @@
#ifdef TESTING
/datum/dynamic_simulation
var/datum/game_mode/dynamic/gamemode
var/datum/dynamic_simulation_config/config
var/list/mock_candidates = list()
/datum/dynamic_simulation/proc/initialize_gamemode(forced_threat)
gamemode = new
if (forced_threat)
gamemode.create_threat(forced_threat)
else
gamemode.generate_threat()
gamemode.generate_budgets()
gamemode.set_cooldowns()
/datum/dynamic_simulation/proc/create_candidates(players)
GLOB.new_player_list.Cut()
for (var/_ in 1 to players)
var/mob/dead/new_player/mock_new_player = new
mock_new_player.ready = PLAYER_READY_TO_PLAY
var/datum/mind/mock_mind = new
mock_new_player.mind = mock_mind
var/datum/client_interface/mock_client = new
var/datum/preferences/prefs = new
var/list/be_special = list()
for (var/special_role in GLOB.special_roles)
be_special += special_role
prefs.be_special = be_special
mock_client.prefs = prefs
mock_new_player.mock_client = mock_client
mock_candidates += mock_new_player
/datum/dynamic_simulation/proc/simulate(datum/dynamic_simulation_config/config)
src.config = config
initialize_gamemode(config.forced_threat_level)
create_candidates(config.roundstart_players)
gamemode.pre_setup()
var/total_antags = 0
for (var/_ruleset in gamemode.executed_rules)
var/datum/dynamic_ruleset/ruleset = _ruleset
total_antags += ruleset.assigned.len
return list(
"roundstart_players" = config.roundstart_players,
"threat_level" = gamemode.threat_level,
"snapshot" = list(
"antag_percent" = total_antags / config.roundstart_players,
"remaining_threat" = gamemode.mid_round_budget,
"rulesets" = gamemode.executed_rules.Copy(),
),
)
/datum/dynamic_simulation_config
/// How many players round start should there be?
var/roundstart_players
/// Optional, force this threat level instead of picking randomly through the lorentz distribution
var/forced_threat_level
/client/proc/run_dynamic_simulations()
set name = "Run Dynamic Simulations"
set category = "Debug"
var/simulations = input(usr, "Enter number of simulations") as num
var/roundstart_players = input(usr, "Enter number of round start players") as num
var/forced_threat_level = input(usr, "Enter forced threat level, if you want one") as num | null
SSticker.mode = new /datum/game_mode/dynamic
message_admins("Running dynamic simulations...")
var/list/outputs = list()
var/datum/dynamic_simulation_config/dynamic_config = new
if (roundstart_players)
dynamic_config.roundstart_players = roundstart_players
if (forced_threat_level)
dynamic_config.forced_threat_level = forced_threat_level
for (var/count in 1 to simulations)
var/datum/dynamic_simulation/simulator = new
var/output = simulator.simulate(dynamic_config)
outputs += list(output)
if (CHECK_TICK)
log_world("[count]/[simulations]")
message_admins("Writing file...")
WRITE_FILE(file("[GLOB.log_directory]/dynamic_simulations.json"), json_encode(outputs))
message_admins("Writing complete.")
/proc/export_dynamic_json_of(ruleset_list)
var/list/export = list()
for (var/_ruleset in ruleset_list)
var/datum/dynamic_ruleset/ruleset = _ruleset
export[ruleset.name] = list(
"repeatable_weight_decrease" = ruleset.repeatable_weight_decrease,
"weight" = ruleset.weight,
"cost" = ruleset.cost,
"scaling_cost" = ruleset.scaling_cost,
"antag_cap" = ruleset.antag_cap,
"pop_per_requirement" = ruleset.pop_per_requirement,
"requirements" = ruleset.requirements,
"base_prob" = ruleset.base_prob,
)
return export
#endif
-228
View File
@@ -1,228 +0,0 @@
/*
* GAMEMODES (by Rastaf0)
*
* In the new mode system all special roles are fully supported.
* You can have proper wizards/traitors/changelings/cultists during any mode.
* Only two things really depends on gamemode:
* 1. Starting roles, equipment and preparations
* 2. Conditions of finishing the round.
*
*/
/datum/game_mode
///Attempts to select players for special roles the mode might have.
/datum/game_mode/proc/pre_setup()
return TRUE
///Everyone should now be on the station and have their normal gear. This is the place to give the special roles extra things
/datum/game_mode/proc/post_setup(report) //Gamemodes can override the intercept report. Passing TRUE as the argument will force a report.
if(!report)
report = !CONFIG_GET(flag/no_intercept_report)
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(display_roundstart_logout_report)), ROUNDSTART_LOGOUT_REPORT_TIME)
if(CONFIG_GET(flag/reopen_roundstart_suicide_roles))
var/delay = CONFIG_GET(number/reopen_roundstart_suicide_roles_delay)
if(delay)
delay = (delay SECONDS)
else
delay = (4 MINUTES) //default to 4 minutes if the delay isn't defined.
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(reopen_roundstart_suicide_roles)), delay)
if(SSdbcore.Connect())
var/list/to_set = list()
var/arguments = list()
if(SSticker.mode)
to_set += "game_mode = :game_mode"
arguments["game_mode"] = SSticker.mode
if(GLOB.revdata.originmastercommit)
to_set += "commit_hash = :commit_hash"
arguments["commit_hash"] = GLOB.revdata.originmastercommit
if(to_set.len)
arguments["round_id"] = GLOB.round_id
var/datum/db_query/query_round_game_mode = SSdbcore.NewQuery(
"UPDATE [format_table_name("round")] SET [to_set.Join(", ")] WHERE id = :round_id",
arguments
)
query_round_game_mode.Execute()
qdel(query_round_game_mode)
return TRUE
///Handles late-join antag assignments
/datum/game_mode/proc/make_antag_chance(mob/living/carbon/human/character)
return
/// Checks if the round should be ending, called every ticker tick
/datum/game_mode/proc/check_finished()
if(!SSticker.setup_done)
return FALSE
if(SSshuttle.emergency && (SSshuttle.emergency.mode == SHUTTLE_ENDGAME))
return TRUE
if(GLOB.station_was_nuked)
return TRUE
if(GLOB.revolutionary_win)
return TRUE
return FALSE
/*
* Generate a list of station goals available to purchase to report to the crew.
*
* Returns a formatted string all station goals that are available to the station.
*/
/datum/game_mode/proc/generate_station_goal_report()
if(!GLOB.station_goals.len)
return
. = "<hr><b>Special Orders for [station_name()]:</b><BR>"
var/list/goal_reports = list()
for(var/datum/station_goal/station_goal as anything in GLOB.station_goals)
station_goal.on_report()
goal_reports += station_goal.get_report()
. += goal_reports.Join("<hr>")
return
/*
* Generate a list of active station traits to report to the crew.
*
* Returns a formatted string of all station traits (that are shown) affecting the station.
*/
/datum/game_mode/proc/generate_station_trait_report()
var/trait_list_string = ""
for(var/datum/station_trait/station_trait as anything in SSstation.station_traits)
if(!station_trait.show_in_report)
continue
trait_list_string += "[station_trait.get_report()]<BR>"
if(trait_list_string != "")
return "<hr><b>Identified shift divergencies:</b><BR>" + trait_list_string
return
/datum/game_mode/proc/generate_report_footnote()
var/footnote_pile = ""
for(var/datum/command_footnote/footnote in SScommunications.command_report_footnotes)
footnote_pile += "[footnote.message]<BR>"
footnote_pile += "<i>[footnote.signature]</i><BR>"
footnote_pile += "<BR>"
return "<hr><b>Additional Notes: </b><BR><BR>" + footnote_pile
/proc/reopen_roundstart_suicide_roles()
var/include_command = CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_positions)
var/list/reopened_jobs = list()
for(var/mob/living/quitter in GLOB.suicided_mob_list)
var/datum/job/job = SSjob.GetJob(quitter.job)
if(!job || !(job.job_flags & JOB_REOPEN_ON_ROUNDSTART_LOSS))
continue
if(!include_command && job.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND)
continue
job.current_positions = max(job.current_positions - 1, 0)
reopened_jobs += quitter.job
if(CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_report))
if(reopened_jobs.len)
var/reopened_job_report_positions
for(var/dead_dudes_job in reopened_jobs)
reopened_job_report_positions = "[reopened_job_report_positions ? "[reopened_job_report_positions]\n":""][dead_dudes_job]"
var/suicide_command_report = "<font size = 3><b>[command_name()] Human Resources Board</b><br>\
Notice of Personnel Change</font><hr>\
To personnel management staff aboard [station_name()]:<br><br>\
Our medical staff have detected a series of anomalies in the vital sensors \
of some of the staff aboard your station.<br><br>\
Further investigation into the situation on our end resulted in us discovering \
a series of rather... unforturnate decisions that were made on the part of said staff.<br><br>\
As such, we have taken the liberty to automatically reopen employment opportunities for the positions of the crew members \
who have decided not to partake in our research. We will be forwarding their cases to our employment review board \
to determine their eligibility for continued service with the company (and of course the \
continued storage of cloning records within the central medical backup server.)<br><br>\
<i>The following positions have been reopened on our behalf:<br><br>\
[reopened_job_report_positions]</i>"
print_command_report(suicide_command_report, "Central Command Personnel Update")
//////////////////////////
//Reports player logouts//
//////////////////////////
/proc/display_roundstart_logout_report()
var/list/msg = list("[span_boldnotice("Roundstart logout report")]\n\n")
for(var/i in GLOB.mob_living_list)
var/mob/living/L = i
var/mob/living/carbon/C = L
if (istype(C) && !C.last_mind)
continue // never had a client
if(L.ckey && !GLOB.directory[L.ckey])
msg += "<b>[L.name]</b> ([L.key]), the [L.job] (<font color='#ffcc00'><b>Disconnected</b></font>)\n"
if(L.ckey && L.client)
var/failed = FALSE
if(L.client.inactivity >= ROUNDSTART_LOGOUT_AFK_THRESHOLD) //Connected, but inactive (alt+tabbed or something)
msg += "<b>[L.name]</b> ([L.key]), the [L.job] (<font color='#ffcc00'><b>Connected, Inactive</b></font>)\n"
failed = TRUE //AFK client
if(!failed && L.stat)
if(HAS_TRAIT(L, TRAIT_SUICIDED)) //Suicider
msg += "<b>[L.name]</b> ([L.key]), the [L.job] ([span_boldannounce("Suicide")])\n"
failed = TRUE //Disconnected client
if(!failed && (L.stat == UNCONSCIOUS || L.stat == HARD_CRIT))
msg += "<b>[L.name]</b> ([L.key]), the [L.job] (Dying)\n"
failed = TRUE //Unconscious
if(!failed && L.stat == DEAD)
msg += "<b>[L.name]</b> ([L.key]), the [L.job] (Dead)\n"
failed = TRUE //Dead
continue //Happy connected client
for(var/mob/dead/observer/D in GLOB.dead_mob_list)
if(D.mind && D.mind.current == L)
if(L.stat == DEAD)
if(HAS_TRAIT(L, TRAIT_SUICIDED)) //Suicider
msg += "<b>[L.name]</b> ([ckey(D.mind.key)]), the [L.job] ([span_boldannounce("Suicide")])\n"
continue //Disconnected client
else
msg += "<b>[L.name]</b> ([ckey(D.mind.key)]), the [L.job] (Dead)\n"
continue //Dead mob, ghost abandoned
else
if(D.can_reenter_corpse)
continue //Adminghost, or cult/wizard ghost
else
msg += "<b>[L.name]</b> ([ckey(D.mind.key)]), the [L.job] ([span_boldannounce("Ghosted")])\n"
continue //Ghosted while alive
var/concatenated_message = msg.Join()
log_admin(concatenated_message)
to_chat(GLOB.admins, concatenated_message)
/datum/game_mode/proc/generate_station_goals(greenshift)
var/goal_budget = greenshift ? INFINITY : CONFIG_GET(number/station_goal_budget)
var/list/possible = subtypesof(/datum/station_goal)
// Remove all goals that require space if space is not present
if(SSmapping.is_planetary())
for(var/datum/station_goal/goal as anything in possible)
if(initial(goal.requires_space))
possible -= goal
var/goal_weights = 0
while(possible.len && goal_weights < goal_budget)
var/datum/station_goal/picked = pick_n_take(possible)
goal_weights += initial(picked.weight)
GLOB.station_goals += new picked
//Set result and news report here
/datum/game_mode/proc/set_round_result()
SSticker.mode_result = "undefined"
// Something nuked the station - it wasn't nuke ops (they set their own via their rulset)
if(GLOB.station_was_nuked)
SSticker.news_report = STATION_NUKED
if(SSsupermatter_cascade.cascade_initiated)
SSticker.news_report = SUPERMATTER_CASCADE
// Only show this one if we have nothing better to show
if(EMERGENCY_ESCAPED_OR_ENDGAMED && !SSticker.news_report)
SSticker.news_report = SSshuttle.emergency?.is_hijacked() ? SHUTTLE_HIJACK : STATION_EVACUATED
/// Mode specific admin panel.
/datum/game_mode/proc/admin_panel()
return
@@ -861,7 +861,6 @@
hacker.log_message("hacked a communications console, resulting in: [picked_option].", LOG_GAME, log_globally = TRUE)
switch(picked_option)
if(HACK_PIRATE) // Triggers pirates, which the crew may be able to pay off to prevent
var/datum/game_mode/dynamic/dynamic = SSticker.mode
var/list/pirate_rulesets = list(
/datum/dynamic_ruleset/midround/pirates,
/datum/dynamic_ruleset/midround/dangerous_pirates,
@@ -870,7 +869,7 @@
"Attention crew: sector monitoring reports a massive jump-trace from an enemy vessel destined for your system. Prepare for imminent hostile contact.",
"[command_name()] High-Priority Update",
)
dynamic.picking_specific_rule(pick(pirate_rulesets), forced = TRUE, ignore_cost = TRUE)
SSdynamic.picking_specific_rule(pick(pirate_rulesets), forced = TRUE, ignore_cost = TRUE)
if(HACK_FUGITIVES) // Triggers fugitives, which can cause confusion / chaos as the crew decides which side help
priority_announce(
@@ -891,22 +890,20 @@
continue
shake_camera(crew_member, 15, 1)
var/datum/game_mode/dynamic/dynamic = SSticker.mode
dynamic.unfavorable_situation()
SSdynamic.unfavorable_situation()
if(HACK_SLEEPER) // Trigger one or multiple sleeper agents with the crew (or for latejoining crew)
var/datum/dynamic_ruleset/midround/sleeper_agent_type = /datum/dynamic_ruleset/midround/from_living/autotraitor
var/datum/game_mode/dynamic/dynamic = SSticker.mode
var/max_number_of_sleepers = clamp(round(length(GLOB.alive_player_list) / 20), 1, 3)
var/num_agents_created = 0
for(var/num_agents in 1 to rand(1, max_number_of_sleepers))
if(!dynamic.picking_specific_rule(sleeper_agent_type, forced = TRUE, ignore_cost = TRUE))
if(!SSdynamic.picking_specific_rule(sleeper_agent_type, forced = TRUE, ignore_cost = TRUE))
break
num_agents_created++
if(num_agents_created <= 0)
// We failed to run any midround sleeper agents, so let's be patient and run latejoin traitor
dynamic.picking_specific_rule(/datum/dynamic_ruleset/latejoin/infiltrator, forced = TRUE, ignore_cost = TRUE)
SSdynamic.picking_specific_rule(/datum/dynamic_ruleset/latejoin/infiltrator, forced = TRUE, ignore_cost = TRUE)
else
// We spawned some sleeper agents, nice - give them a report to kickstart the paranoia
+4 -9
View File
@@ -31,8 +31,7 @@
dat += "<hr/>"
if(SSticker.IsRoundInProgress())
dat += "<a href='?src=[REF(src)];[HrefToken()];gamemode_panel=1'>(Game Mode Panel)</a><BR>"
if(istype(SSticker.mode, /datum/game_mode/dynamic))
dat += "<A href='?src=[REF(src)];[HrefToken()];f_dynamic_ruleset_manage=1'>(Manage Dynamic Rulesets)</A><br>"
dat += "<A href='?src=[REF(src)];[HrefToken()];f_dynamic_ruleset_manage=1'>(Manage Dynamic Rulesets)</A><br>"
dat += {"
<BR>
<A href='?src=[REF(src)];[HrefToken()];create_object=1'>Create Object</A><br>
@@ -147,9 +146,6 @@
user << browse(dat, "window=dyn_mode_options;size=900x650")
/datum/admins/proc/dynamic_ruleset_manager(mob/user)
if (SSticker.current_state > GAME_STATE_PREGAME && !istype(SSticker.mode, /datum/game_mode/dynamic))
return // Not running dynamic
var/dat = "<center><B><h2>Dynamic Ruleset Management</h2></B></center><hr>\
Change these options to forcibly enable or disable dynamic rulesets.<br/>\
Disabled rulesets will never run, even if they would otherwise be valid.<br/>\
@@ -172,11 +168,10 @@
user << browse(dat, "window=dyn_mode_options;size=900x650")
return
var/datum/game_mode/dynamic/current_mode = SSticker.mode
var/pop_count = length(GLOB.alive_player_list)
var/threat_level = current_mode.threat_level
dat += dynamic_ruleset_category_during_round_display("Latejoin", current_mode.latejoin_rules, pop_count, threat_level)
dat += dynamic_ruleset_category_during_round_display("Midround", current_mode.midround_rules, pop_count, threat_level)
var/threat_level = SSdynamic.threat_level
dat += dynamic_ruleset_category_during_round_display("Latejoin", SSdynamic.latejoin_rules, pop_count, threat_level)
dat += dynamic_ruleset_category_during_round_display("Midround", SSdynamic.midround_rules, pop_count, threat_level)
user << browse(dat, "window=dyn_mode_options;size=900x650")
/datum/admins/proc/dynamic_ruleset_category_pre_start_display(title, list/rules)
-1
View File
@@ -169,7 +169,6 @@ GLOBAL_PROTECT(admin_verbs_debug)
return list(
#ifdef TESTING /* Keep these at the top to not make the list look fugly */
/client/proc/check_missing_sprites,
/client/proc/run_dynamic_simulations,
#endif
/proc/machine_upgrade,
/datum/admins/proc/create_or_modify_area,
+1 -2
View File
@@ -95,8 +95,7 @@
tgui_alert(usr, "The game hasn't started yet!")
return
var/list/dat = list("<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'><title>Round Status</title></head><body><h1><B>Round Status</B></h1>")
if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Currently only used by dynamic. If more start using this, find a better way.
dat += "<a href='?_src_=holder;[HrefToken()];gamemode_panel=1'>Game Mode Panel</a><br>"
dat += "<a href='?_src_=holder;[HrefToken()];gamemode_panel=1'>Game Mode Panel</a><br>"
dat += "Round Duration: <B>[DisplayTimeText(world.time - SSticker.round_start_time)]</B><BR>"
dat += "<B>Emergency shuttle</B><BR>"
if(EMERGENCY_IDLE_OR_RECALLED)
+4 -4
View File
@@ -69,7 +69,7 @@
else if(href_list["gamemode_panel"])
if(!check_rights(R_ADMIN))
return
SSticker.mode.admin_panel()
SSdynamic.admin_panel()
else if(href_list["call_shuttle"])
if(!check_rights(R_ADMIN))
@@ -400,7 +400,7 @@
else if(href_list["f_dynamic_roundstart"])
if(!check_rights(R_ADMIN))
return
if(SSticker?.mode)
if(SSticker.HasRoundStarted())
return tgui_alert(usr, "The game has already started.")
var/roundstart_rules = list()
for (var/rule in subtypesof(/datum/dynamic_ruleset/roundstart))
@@ -467,7 +467,7 @@
if(!check_rights(R_ADMIN))
return
if(SSticker?.mode)
if(SSticker.HasRoundStarted())
return tgui_alert(usr, "The game has already started.")
dynamic_mode_options(usr)
@@ -501,7 +501,7 @@
if(!check_rights(R_ADMIN))
return
if(SSticker?.mode)
if(SSticker.HasRoundStarted())
return tgui_alert(usr, "The game has already started.")
var/new_value = input(usr, "Enter the forced threat level for dynamic mode.", "Forced threat level") as num
@@ -523,7 +523,7 @@ GLOBAL_VAR(station_nuke_source)
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NUKE_DEVICE_DETONATING, src)
if(SSticker?.mode)
if(SSticker.HasRoundStarted())
SSticker.roundend_check_paused = TRUE
addtimer(CALLBACK(src, PROC_REF(actually_explode)), 10 SECONDS)
return TRUE
@@ -498,7 +498,7 @@
SM.Grant(newstruct)
newstruct.key = target.key
var/atom/movable/screen/alert/bloodsense/BS
if(newstruct.mind && ((stoner && IS_CULTIST(stoner)) || cultoverride) && SSticker?.mode)
if(newstruct.mind && ((stoner && IS_CULTIST(stoner)) || cultoverride) && SSticker.HasRoundStarted())
newstruct.mind.add_antag_datum(/datum/antagonist/cult/construct)
if(IS_CULTIST(stoner) || cultoverride)
to_chat(newstruct, "<b>You are still bound to serve the cult[stoner ? " and [stoner]":""], follow [stoner ? stoner.p_their() : "their"] orders and help [stoner ? stoner.p_them() : "them"] complete [stoner ? stoner.p_their() : "their"] goals at all costs.</b>")
@@ -21,8 +21,7 @@
/datum/spellbook_entry/summon/guns/can_be_purchased()
// Summon Guns requires 98 threat.
var/datum/game_mode/dynamic/mode = SSticker.mode
if(mode.threat_level < MINIMUM_THREAT_FOR_RITUALS)
if(SSdynamic.threat_level < MINIMUM_THREAT_FOR_RITUALS)
return FALSE
// Also must be config enabled
return !CONFIG_GET(flag/no_summon_guns)
@@ -39,8 +38,7 @@
/datum/spellbook_entry/summon/magic/can_be_purchased()
// Summon Magic requires 98 threat.
var/datum/game_mode/dynamic/mode = SSticker.mode
if(mode.threat_level < MINIMUM_THREAT_FOR_RITUALS)
if(SSdynamic.threat_level < MINIMUM_THREAT_FOR_RITUALS)
return FALSE
// Also must be config enabled
return !CONFIG_GET(flag/no_summon_magic)
@@ -60,8 +58,7 @@
/datum/spellbook_entry/summon/events/can_be_purchased()
// Summon Events requires 98 threat.
var/datum/game_mode/dynamic/mode = SSticker.mode
if(mode.threat_level < MINIMUM_THREAT_FOR_RITUALS)
if(SSdynamic.threat_level < MINIMUM_THREAT_FOR_RITUALS)
return FALSE
// Also, must be config enabled
return !CONFIG_GET(flag/no_summon_events)
@@ -138,8 +135,7 @@
return ..()
/datum/spellbook_entry/summon/specific_spell/can_be_purchased()
var/datum/game_mode/dynamic/mode = SSticker.mode
if(mode.threat_level < MINIMUM_THREAT_FOR_RITUALS)
if(SSdynamic.threat_level < MINIMUM_THREAT_FOR_RITUALS)
return FALSE
if(GLOB.mass_teaching)
return FALSE
@@ -51,8 +51,7 @@
if (DOOM_METEORS)
var/datum/dynamic_ruleset/roundstart/meteor/meteors = new()
meteors.meteordelay = 0
var/datum/game_mode/dynamic/mode = SSticker.mode
mode.execute_roundstart_rule(meteors) // Meteors will continue until morale is crushed.
SSdynamic.execute_roundstart_rule(meteors) // Meteors will continue until morale is crushed.
priority_announce("Meteors have been detected on collision course with the station.", "Meteor Alert", ANNOUNCER_METEORS)
#undef DOOM_SINGULARITY
+1 -2
View File
@@ -87,8 +87,7 @@
if(ispath(typepath, /datum/round_event/ghost_role) && !(GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT))
return FALSE
var/datum/game_mode/dynamic/dynamic = SSticker.mode
if (istype(dynamic) && dynamic_should_hijack && dynamic.random_event_hijacked != HIJACKED_NOTHING)
if (dynamic_should_hijack && SSdynamic.random_event_hijacked != HIJACKED_NOTHING)
return FALSE
return TRUE
@@ -235,10 +235,10 @@
if(SSshuttle.emergency)
switch(SSshuttle.emergency.mode)
if(SHUTTLE_RECALL, SHUTTLE_IDLE)
SSticker.mode.make_antag_chance(humanc)
SSdynamic.make_antag_chance(humanc)
if(SHUTTLE_CALL)
if(SSshuttle.emergency.timeLeft(1) > initial(SSshuttle.emergency_call_time)*0.5)
SSticker.mode.make_antag_chance(humanc)
SSdynamic.make_antag_chance(humanc)
if((job.job_flags & JOB_ASSIGN_QUIRKS) && humanc && CONFIG_GET(flag/roundstart_traits))
SSquirks.AssignQuirks(humanc, humanc.client)
+10 -10
View File
@@ -53,7 +53,7 @@
GLOB.keyloop_list |= src
else if(stat != DEAD || !SSlag_switch?.measures[DISABLE_DEAD_KEYLOOP])
GLOB.keyloop_list |= src
if(!SSticker?.mode)
if(!SSticker.HasRoundStarted())
return
if(stat == DEAD)
add_to_current_dead_players()
@@ -65,7 +65,7 @@
SHOULD_CALL_PARENT(TRUE)
GLOB.player_list -= src
GLOB.keyloop_list -= src
if(!SSticker?.mode)
if(!SSticker.HasRoundStarted())
return
if(stat == DEAD)
remove_from_current_dead_players()
@@ -75,12 +75,12 @@
///Adds the cliented mob reference to either the list of dead player-mobs or to the list of observers, depending on how they joined the game.
/mob/proc/add_to_current_dead_players()
if(!SSticker?.mode)
if(!SSticker.HasRoundStarted())
return
GLOB.dead_player_list |= src
/mob/dead/observer/add_to_current_dead_players()
if(!SSticker?.mode)
if(!SSticker.HasRoundStarted())
return
if(started_as_observer)
GLOB.current_observers_list |= src
@@ -92,12 +92,12 @@
///Removes the mob reference from either the list of dead player-mobs or from the list of observers, depending on how they joined the game.
/mob/proc/remove_from_current_dead_players()
if(!SSticker?.mode)
if(!SSticker.HasRoundStarted())
return
GLOB.dead_player_list -= src
/mob/dead/observer/remove_from_current_dead_players()
if(!SSticker?.mode)
if(!SSticker.HasRoundStarted())
return
if(started_as_observer)
GLOB.current_observers_list -= src
@@ -107,7 +107,7 @@
///Adds the cliented mob reference to the list of living player-mobs. If the mob is an antag, it adds it to the list of living antag player-mobs.
/mob/proc/add_to_current_living_players()
if(!SSticker?.mode)
if(!SSticker.HasRoundStarted())
return
GLOB.alive_player_list |= src
if(mind && (mind.special_role || length(mind.antag_datums)))
@@ -115,7 +115,7 @@
///Removes the mob reference from the list of living player-mobs. If the mob is an antag, it removes it from the list of living antag player-mobs.
/mob/proc/remove_from_current_living_players()
if(!SSticker?.mode)
if(!SSticker.HasRoundStarted())
return
GLOB.alive_player_list -= src
if(LAZYLEN(mind?.antag_datums))
@@ -124,7 +124,7 @@
///Adds the cliented mob reference to the list of living antag player-mobs.
/mob/proc/add_to_current_living_antags()
if(!SSticker?.mode)
if(!SSticker.HasRoundStarted())
return
if (length(mind.antag_datums) == 0)
@@ -137,6 +137,6 @@
///Removes the mob reference from the list of living antag player-mobs.
/mob/proc/remove_from_current_living_antags()
if(!SSticker?.mode)
if(!SSticker.HasRoundStarted())
return
GLOB.current_living_antags -= src
+5 -6
View File
@@ -101,12 +101,11 @@
F.parent = src
fillers += F
if(SSticker.mode)
var/datum/station_goal/dna_vault/dna_vault_goal = locate() in GLOB.station_goals
if (!isnull(dna_vault_goal))
animals_max = dna_vault_goal.animal_count
plants_max = dna_vault_goal.plant_count
dna_max = dna_vault_goal.human_count
var/datum/station_goal/dna_vault/dna_vault_goal = locate() in GLOB.station_goals
if (!isnull(dna_vault_goal))
animals_max = dna_vault_goal.animal_count
plants_max = dna_vault_goal.plant_count
dna_max = dna_vault_goal.human_count
return ..()
@@ -0,0 +1,13 @@
/// Creates the initial station goals.
/proc/generate_station_goals(goal_budget)
var/list/possible = subtypesof(/datum/station_goal)
// Remove all goals that require space if space is not present
if(SSmapping.is_planetary())
for(var/datum/station_goal/goal as anything in possible)
if(initial(goal.requires_space))
possible -= goal
var/goal_weights = 0
while(possible.len && goal_weights < goal_budget)
var/datum/station_goal/picked = pick_n_take(possible)
goal_weights += initial(picked.weight)
GLOB.station_goals += new picked
+11 -12
View File
@@ -687,6 +687,16 @@
#include "code\controllers\subsystem\wardrobe.dm"
#include "code\controllers\subsystem\weather.dm"
#include "code\controllers\subsystem\wiremod_composite.dm"
#include "code\controllers\subsystem\dynamic\dynamic.dm"
#include "code\controllers\subsystem\dynamic\dynamic_hijacking.dm"
#include "code\controllers\subsystem\dynamic\dynamic_logging.dm"
#include "code\controllers\subsystem\dynamic\dynamic_midround_rolling.dm"
#include "code\controllers\subsystem\dynamic\dynamic_rulesets.dm"
#include "code\controllers\subsystem\dynamic\dynamic_rulesets_latejoin.dm"
#include "code\controllers\subsystem\dynamic\dynamic_rulesets_midround.dm"
#include "code\controllers\subsystem\dynamic\dynamic_rulesets_roundstart.dm"
#include "code\controllers\subsystem\dynamic\dynamic_unfavorable_situation.dm"
#include "code\controllers\subsystem\dynamic\ruleset_picking.dm"
#include "code\controllers\subsystem\movement\ai_movement.dm"
#include "code\controllers\subsystem\movement\cliff_falling.dm"
#include "code\controllers\subsystem\movement\hyperspace_drift.dm"
@@ -1870,20 +1880,8 @@
#include "code\game\atom\atom_vv.dm"
#include "code\game\atom\atoms_initializing_EXPENSIVE.dm"
#include "code\game\gamemodes\events.dm"
#include "code\game\gamemodes\game_mode.dm"
#include "code\game\gamemodes\objective.dm"
#include "code\game\gamemodes\objective_items.dm"
#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"
#include "code\game\gamemodes\dynamic\dynamic_rulesets_roundstart.dm"
#include "code\game\gamemodes\dynamic\dynamic_simulations.dm"
#include "code\game\gamemodes\dynamic\dynamic_unfavorable_situation.dm"
#include "code\game\gamemodes\dynamic\ruleset_picking.dm"
#include "code\game\machinery\_machinery.dm"
#include "code\game\machinery\ai_slipper.dm"
#include "code\game\machinery\airlock_control.dm"
@@ -5585,6 +5583,7 @@
#include "code\modules\spells\spell_types\touch\smite.dm"
#include "code\modules\station_goals\bsa.dm"
#include "code\modules\station_goals\dna_vault.dm"
#include "code\modules\station_goals\generate_goals.dm"
#include "code\modules\station_goals\meteor_shield.dm"
#include "code\modules\station_goals\station_goal.dm"
#include "code\modules\station_goals\vault_mutation.dm"