diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm
index 53fbd15651..32da3b5938 100644
--- a/code/controllers/configuration/configuration.dm
+++ b/code/controllers/configuration/configuration.dm
@@ -425,6 +425,7 @@ Example config:
var/list/min_pop = Get(/datum/config_entry/keyed_list/min_pop)
var/list/max_pop = Get(/datum/config_entry/keyed_list/max_pop)
var/list/repeated_mode_adjust = Get(/datum/config_entry/number_list/repeated_mode_adjust)
+ var/desired_chaos_level = 9 - SSpersistence.get_recent_chaos()
for(var/T in gamemode_cache)
var/datum/game_mode/M = new T()
if(!(M.config_tag in modes))
@@ -449,6 +450,17 @@ Example config:
adjustment += repeated_mode_adjust[recent_round]
recent_round = SSpersistence.saved_modes.Find(M.config_tag,recent_round+1,0)
final_weight *= max(0,((100-adjustment)/100))
+ if(Get(/datum/config_entry/flag/weigh_by_recent_chaos))
+ var/chaos_level = M.get_chaos()
+ var/exponent = Get(/datum/config_entry/number/chaos_exponent)
+ var/delta = chaos_level - desired_chaos_level
+ if(desired_chaos_level > 5)
+ delta = abs(min(delta, 0))
+ else if(desired_chaos_level < 5)
+ delta = max(delta, 0)
+ else
+ delta = abs(delta)
+ final_weight /= (delta + 1) ** exponent
runnable_modes[M] = final_weight
return runnable_modes
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index 4034722417..76a5f5060c 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -7,6 +7,13 @@
/datum/config_entry/keyed_list/probability/ValidateListEntry(key_name)
return key_name in config.modes
+/datum/config_entry/keyed_list/chaos_level
+ key_mode = KEY_MODE_TEXT
+ value_mode = VALUE_MODE_NUM
+
+/datum/config_entry/keyed_list/chaos_level/ValidateListEntry(key_name)
+ return key_name in config.modes
+
/datum/config_entry/keyed_list/max_pop
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_NUM
@@ -596,3 +603,8 @@
/// Dirtyness multiplier for making turfs dirty
/datum/config_entry/number/turf_dirty_multiplier
config_entry_value = 1
+
+/datum/config_entry/flag/weigh_by_recent_chaos
+
+/datum/config_entry/number/chaos_exponent
+ config_entry_value = 1
diff --git a/code/controllers/subsystem/persistence/recent_votes_etc.dm b/code/controllers/subsystem/persistence/recent_votes_etc.dm
index f1b902d6ab..3c36c7581e 100644
--- a/code/controllers/subsystem/persistence/recent_votes_etc.dm
+++ b/code/controllers/subsystem/persistence/recent_votes_etc.dm
@@ -3,6 +3,7 @@
*/
/datum/controller/subsystem/persistence
var/list/saved_modes = list(1,2,3)
+ var/list/saved_chaos = list(5,5,5)
var/list/saved_dynamic_rules = list(list(),list(),list())
var/list/saved_storytellers = list("foo","bar","baz")
var/list/average_dynamic_threat = 50
@@ -33,6 +34,14 @@
file_data["data"] = saved_modes
fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))
+ saved_chaos[3] = saved_chaos[2]
+ saved_chaos[2] = saved_chaos[1]
+ saved_chaos[1] = SSticker.mode.get_chaos()
+ json_file = file("data/RecentChaos.json")
+ file_data = list()
+ file_data["data"] = saved_chaos
+ fdel(json_file)
+ WRITE_FILE(json_file, json_encode(file_data))
/datum/controller/subsystem/persistence/proc/CollectStoryteller(var/datum/game_mode/dynamic/mode)
saved_storytellers.len = 3
@@ -105,3 +114,9 @@
if(!json)
return
saved_maps = json["maps"]
+
+/datum/controller/subsystem/persistence/proc/get_recent_chaos()
+ var/sum = 0
+ for(var/n in saved_chaos)
+ sum += n
+ return sum/length(saved_chaos)
diff --git a/code/game/gamemodes/bloodsucker/bloodsucker.dm b/code/game/gamemodes/bloodsucker/bloodsucker.dm
index a16e7eab59..65321f5820 100644
--- a/code/game/gamemodes/bloodsucker/bloodsucker.dm
+++ b/code/game/gamemodes/bloodsucker/bloodsucker.dm
@@ -24,6 +24,7 @@
traitor_name = "Bloodsucker"
antag_flag = ROLE_BLOODSUCKER
false_report_weight = 1
+ chaos = 4
restricted_jobs = list("AI","Cyborg")
protected_jobs = list("Chaplain", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_players = 20
diff --git a/code/game/gamemodes/brother/traitor_bro.dm b/code/game/gamemodes/brother/traitor_bro.dm
index 718ed2c103..eda6b5f9e2 100644
--- a/code/game/gamemodes/brother/traitor_bro.dm
+++ b/code/game/gamemodes/brother/traitor_bro.dm
@@ -6,6 +6,7 @@
name = "traitor+brothers"
config_tag = "traitorbro"
required_players = 25
+ chaos = 5
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm
index 8029685bf7..20ef83a54c 100644
--- a/code/game/gamemodes/changeling/changeling.dm
+++ b/code/game/gamemodes/changeling/changeling.dm
@@ -10,6 +10,7 @@ GLOBAL_VAR(changeling_team_objective_type) //If this is not null, we hand our th
config_tag = "changeling"
antag_flag = ROLE_CHANGELING
false_report_weight = 10
+ chaos = 5
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") //citadel change - adds HoP, CE, CMO, and RD to ling role blacklist
required_players = 15
diff --git a/code/game/gamemodes/changeling/traitor_chan.dm b/code/game/gamemodes/changeling/traitor_chan.dm
index b010b08bc3..88a1cde8ce 100644
--- a/code/game/gamemodes/changeling/traitor_chan.dm
+++ b/code/game/gamemodes/changeling/traitor_chan.dm
@@ -2,6 +2,7 @@
name = "traitor+changeling"
config_tag = "traitorchan"
false_report_weight = 10
+ chaos = 6
traitors_possible = 3 //hard limit on traitors if scaling is turned off
restricted_jobs = list("AI", "Cyborg")
required_players = 25
diff --git a/code/game/gamemodes/clock_cult/clock_cult.dm b/code/game/gamemodes/clock_cult/clock_cult.dm
index 29455afe56..d8ebf6f20c 100644
--- a/code/game/gamemodes/clock_cult/clock_cult.dm
+++ b/code/game/gamemodes/clock_cult/clock_cult.dm
@@ -134,6 +134,7 @@ Credit where due:
config_tag = "clockwork_cult"
antag_flag = ROLE_SERVANT_OF_RATVAR
false_report_weight = 10
+ chaos = 8
required_players = 24 //Fixing this directly for now since apparently config machine for forcing modes broke.
required_enemies = 3
recommended_enemies = 5
diff --git a/code/game/gamemodes/clown_ops/clown_ops.dm b/code/game/gamemodes/clown_ops/clown_ops.dm
index 108c67ad27..659d2de105 100644
--- a/code/game/gamemodes/clown_ops/clown_ops.dm
+++ b/code/game/gamemodes/clown_ops/clown_ops.dm
@@ -1,7 +1,7 @@
/datum/game_mode/nuclear/clown_ops
name = "clown ops"
config_tag = "clownops"
-
+ chaos = 8
announce_span = "danger"
announce_text = "Clown empire forces are approaching the station in an attempt to HONK it!\n\
Operatives: Secure the nuclear authentication disk and use your bananium fission explosive to HONK the station.\n\
diff --git a/code/game/gamemodes/cult/cult.dm b/code/game/gamemodes/cult/cult.dm
index 8ec4123201..ba9fad7a84 100644
--- a/code/game/gamemodes/cult/cult.dm
+++ b/code/game/gamemodes/cult/cult.dm
@@ -38,6 +38,7 @@
config_tag = "cult"
antag_flag = ROLE_CULTIST
false_report_weight = 10
+ chaos = 8
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_players = 30
diff --git a/code/game/gamemodes/devil/devil agent/devil_agent.dm b/code/game/gamemodes/devil/devil agent/devil_agent.dm
index 789cff5c8f..ccfd6f1dd5 100644
--- a/code/game/gamemodes/devil/devil agent/devil_agent.dm
+++ b/code/game/gamemodes/devil/devil agent/devil_agent.dm
@@ -1,6 +1,7 @@
/datum/game_mode/devil/devil_agents
name = "Devil Agents"
config_tag = "devil_agents"
+ chaos = 5
required_players = 25
required_enemies = 3
recommended_enemies = 8
diff --git a/code/game/gamemodes/devil/devil_game_mode.dm b/code/game/gamemodes/devil/devil_game_mode.dm
index 0f2e8f7858..9bf7fc0e82 100644
--- a/code/game/gamemodes/devil/devil_game_mode.dm
+++ b/code/game/gamemodes/devil/devil_game_mode.dm
@@ -3,6 +3,7 @@
config_tag = "devil"
antag_flag = ROLE_DEVIL
false_report_weight = 1
+ chaos = 3
protected_jobs = list("Lawyer", "Curator", "Chaplain", "Head of Security", "Captain", "AI")
required_players = 0
required_enemies = 1
diff --git a/code/game/gamemodes/eldritch_cult/eldritch_cult.dm b/code/game/gamemodes/eldritch_cult/eldritch_cult.dm
index a3e3c54dce..1693163fa2 100644
--- a/code/game/gamemodes/eldritch_cult/eldritch_cult.dm
+++ b/code/game/gamemodes/eldritch_cult/eldritch_cult.dm
@@ -3,6 +3,7 @@
config_tag = "heresy"
antag_flag = ROLE_HERETIC
false_report_weight = 5
+ chaos = 5
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") //citadel change - adds HoP, CE, CMO, and RD to heretic role blacklist
required_players = 15
diff --git a/code/game/gamemodes/extended/extended.dm b/code/game/gamemodes/extended/extended.dm
index 976f3e3eca..a6abcbefbd 100644
--- a/code/game/gamemodes/extended/extended.dm
+++ b/code/game/gamemodes/extended/extended.dm
@@ -3,6 +3,7 @@
config_tag = "secret_extended"
false_report_weight = 5
required_players = 0
+ chaos = 0
announce_span = "notice"
announce_text = "Just have fun and enjoy the game!"
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index 8bef236c5e..10c0154412 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -17,6 +17,7 @@
var/config_tag = null
var/votable = 1
var/probability = 0
+ var/chaos = 5 // 0-9, used for weighting round-to-round
var/false_report_weight = 0 //How often will this show up incorrectly in a centcom report?
var/station_was_nuked = 0 //see nuclearbomb.dm and malfunction.dm
var/nuke_off_station = 0 //Used for tracking where the nuke hit
@@ -623,3 +624,10 @@
/// Mode specific info for ghost game_info
/datum/game_mode/proc/ghost_info()
return
+
+/datum/game_mode/proc/get_chaos()
+ var/chaos_levels = CONFIG_GET(keyed_list/chaos_level)
+ if(config_tag in chaos_levels)
+ return chaos_levels[config_tag]
+ else
+ return chaos
diff --git a/code/game/gamemodes/gangs/gangs.dm b/code/game/gamemodes/gangs/gangs.dm
index 0dc4a520ef..100669c487 100644
--- a/code/game/gamemodes/gangs/gangs.dm
+++ b/code/game/gamemodes/gangs/gangs.dm
@@ -6,6 +6,7 @@ GLOBAL_LIST_EMPTY(gangs)
name = "gang war"
config_tag = "gang"
antag_flag = ROLE_GANG
+ chaos = 9
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_players = 15
diff --git a/code/game/gamemodes/meteor/meteor.dm b/code/game/gamemodes/meteor/meteor.dm
index afeebb770b..78fe6d9324 100644
--- a/code/game/gamemodes/meteor/meteor.dm
+++ b/code/game/gamemodes/meteor/meteor.dm
@@ -2,6 +2,7 @@
name = "meteor"
config_tag = "meteor"
false_report_weight = 1
+ chaos = 9
var/meteordelay = 2000
var/nometeors = 0
var/rampupdelta = 5
diff --git a/code/game/gamemodes/monkey/monkey.dm b/code/game/gamemodes/monkey/monkey.dm
index 76460ffbb8..c91252258a 100644
--- a/code/game/gamemodes/monkey/monkey.dm
+++ b/code/game/gamemodes/monkey/monkey.dm
@@ -11,6 +11,7 @@
required_players = 20
required_enemies = 1
recommended_enemies = 1
+ chaos = 9
restricted_jobs = list("Cyborg", "AI")
diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm
index 48a298984c..dcf84e84db 100644
--- a/code/game/gamemodes/nuclear/nuclear.dm
+++ b/code/game/gamemodes/nuclear/nuclear.dm
@@ -2,6 +2,7 @@
name = "nuclear emergency"
config_tag = "nuclear"
false_report_weight = 10
+ chaos = 9
required_players = 28 // 30 players - 3 players to be the nuke ops = 25 players remaining
required_enemies = 2
recommended_enemies = 5
diff --git a/code/game/gamemodes/overthrow/overthrow.dm b/code/game/gamemodes/overthrow/overthrow.dm
index dca0c1ade1..6a118567df 100644
--- a/code/game/gamemodes/overthrow/overthrow.dm
+++ b/code/game/gamemodes/overthrow/overthrow.dm
@@ -3,6 +3,7 @@
name = "overthrow"
config_tag = "overthrow"
antag_flag = ROLE_OVERTHROW
+ chaos = 5
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_players = 20 // the core idea is of a swift, bloodless coup, so it shouldn't be as chaotic as revs.
diff --git a/code/game/gamemodes/revolution/revolution.dm b/code/game/gamemodes/revolution/revolution.dm
index 9eed18d906..419a74d616 100644
--- a/code/game/gamemodes/revolution/revolution.dm
+++ b/code/game/gamemodes/revolution/revolution.dm
@@ -12,6 +12,7 @@
config_tag = "revolution"
antag_flag = ROLE_REV
false_report_weight = 10
+ chaos = 8
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_players = 20
diff --git a/code/game/gamemodes/traitor/double_agents.dm b/code/game/gamemodes/traitor/double_agents.dm
index fc669d4855..c64e508cef 100644
--- a/code/game/gamemodes/traitor/double_agents.dm
+++ b/code/game/gamemodes/traitor/double_agents.dm
@@ -10,6 +10,7 @@
required_enemies = 5
recommended_enemies = 8
reroll_friendly = 0
+ chaos = 7
traitor_name = "Nanotrasen Internal Affairs Agent"
antag_flag = ROLE_INTERNAL_AFFAIRS
diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm
index 8b1b18660a..d42fe615cd 100644
--- a/code/game/gamemodes/traitor/traitor.dm
+++ b/code/game/gamemodes/traitor/traitor.dm
@@ -17,6 +17,7 @@
recommended_enemies = 4
reroll_friendly = 1
enemy_minimum_age = 0
+ chaos = 2
announce_span = "danger"
announce_text = "There are Syndicate agents on the station!\n\
diff --git a/code/game/gamemodes/wizard/wizard.dm b/code/game/gamemodes/wizard/wizard.dm
index 23f065318e..d8cb851aad 100644
--- a/code/game/gamemodes/wizard/wizard.dm
+++ b/code/game/gamemodes/wizard/wizard.dm
@@ -12,6 +12,7 @@
recommended_enemies = 1
enemy_minimum_age = 7
round_ends_with_antag_death = 1
+ chaos = 9
announce_span = "danger"
announce_text = "There is a space wizard attacking the station!\n\
Wizard: Accomplish your objectives and cause mayhem on the station.\n\
diff --git a/config/game_options.txt b/config/game_options.txt
index 7776d87d4e..b9d763e5b0 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -72,6 +72,14 @@ ALERT_DELTA Destruction of the station is imminent. All crew are instructed to o
## Uncomment to not send a roundstart intercept report. Gamemodes may override this.
#NO_INTERCEPT_REPORT
+## Comment to disable weighting modes by how chaotic recent mode rolls were.
+WEIGH_BY_RECENT_CHAOS
+
+## The weight adjustment will be proportional to this power relative to the "ideal" weight range.
+## e.g. if we have a weight range of 0-5, and an exponent of 1, 6 will be weighted 1/2, 7 1/3 etc.
+## if exponent is 2, it'll be 1/4, 1/9 etc.
+CHAOS_EXPONENT 1
+
## Probablities for game modes chosen in 'secret' and 'random' modes.
## Default probablity is 1, increase to make that mode more likely to be picked.
## Set to 0 to disable that mode.
@@ -154,6 +162,11 @@ FORCE_ANTAG_COUNT CLOCKWORK_CULT
#FORCE_ANTAG_COUNT WIZARD
#FORCE_ANTAG_COUNT MONKEY
+## A config for how much each game mode's chaos level is.
+## All of them have reasonable defaults, but this can be used to adjust them.
+## 0-9, where 0 is lowest chaos (should only be extended) and 9 is highest (wizard? nukies?)
+#CHAOS_LEVEL EXTENDED 0
+
## Uncomment these for overrides of the minimum / maximum number of players in a round type.
## If you set any of these occasionally check to see if you still need them as the modes
## will still be actively rebalanced around the SUGGESTED populations, not your overrides.