diff --git a/code/__DEFINES/preferences.dm b/code/__DEFINES/preferences.dm
index 0992b2e586..7a284ff1a0 100644
--- a/code/__DEFINES/preferences.dm
+++ b/code/__DEFINES/preferences.dm
@@ -75,3 +75,10 @@
#define JP_LOW 1
#define JP_MEDIUM 2
#define JP_HIGH 3
+
+//Chaos levels for dynamic voting
+#define CHAOS_NONE "None (Extended)"
+#define CHAOS_LOW "Low"
+#define CHAOS_MED "Medium"
+#define CHAOS_HIGH "High"
+#define CHAOS_MAX "Maximum"
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index fe7cf57cb3..2382e0e444 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -213,3 +213,4 @@
#define NINJA_SUIT_TRAIT "ninja-suit"
#define ANTI_DROP_IMPLANT_TRAIT "anti-drop-implant"
#define ABDUCTOR_ANTAGONIST "abductor-antagonist"
+#define MADE_UNCLONEABLE "made-uncloneable"
diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm
index fb7a29c907..2c92816264 100644
--- a/code/__HELPERS/roundend.dm
+++ b/code/__HELPERS/roundend.dm
@@ -313,8 +313,14 @@
parts += "[FOURSPACES]Threat level: [mode.threat_level]"
parts += "[FOURSPACES]Threat left: [mode.threat]"
parts += "[FOURSPACES]Executed rules:"
+ for(var/str in mode.threat_log)
+ parts += "[FOURSPACES][FOURSPACES][str]"
+ for(var/entry in mode.threat_tallies)
+ parts += "[FOURSPACES][FOURSPACES][entry] added [mode.threat_tallies[entry]]"
+ /*
for(var/datum/dynamic_ruleset/rule in mode.executed_rules)
- parts += "[FOURSPACES][FOURSPACES][rule.ruletype] - [rule.name]: -[rule.cost] threat"
+ parts += "[FOURSPACES][FOURSPACES][rule.ruletype] - [rule.name]: -[rule.cost + rule.scaled_times * rule.scaling_cost] threat"
+ */
return parts.Join("
")
/client/proc/roundend_report_file()
diff --git a/code/controllers/configuration/entries/dynamic.dm b/code/controllers/configuration/entries/dynamic.dm
new file mode 100644
index 0000000000..df57bd5aa8
--- /dev/null
+++ b/code/controllers/configuration/entries/dynamic.dm
@@ -0,0 +1,126 @@
+/datum/config_entry/flag/dynamic_voting
+
+/datum/config_entry/number/dynamic_high_pop_limit
+ config_entry_value = 55
+ min_val = 1
+
+/datum/config_entry/number/dynamic_pop_per_requirement
+ config_entry_value = 6
+ min_val = 1
+
+/datum/config_entry/number/dynamic_midround_delay_min
+ config_entry_value = 15
+ min_val = 1
+
+/datum/config_entry/number/dynamic_midround_delay_max
+ config_entry_value = 35
+ min_val = 1
+
+/datum/config_entry/number/dynamic_latejoin_delay_min
+ config_entry_value = 5
+ min_val = 1
+
+/datum/config_entry/number/dynamic_latejoin_delay_max
+ config_entry_value = 25
+ min_val = 1
+
+/datum/config_entry/number/dynamic_first_midround_delay_min
+ config_entry_value = 20
+ min_val = 1
+
+/datum/config_entry/number/dynamic_first_midround_delay_max
+ config_entry_value = 40
+ min_val = 1
+
+/datum/config_entry/number/dynamic_first_latejoin_delay_min
+ config_entry_value = 10
+ min_val = 1
+
+/datum/config_entry/number/dynamic_first_latejoin_delay_max
+ config_entry_value = 30
+ min_val = 1
+
+
+/datum/config_entry/keyed_list/dynamic_cost
+ key_mode = KEY_MODE_TEXT
+ value_mode = VALUE_MODE_NUM
+
+/datum/config_entry/keyed_list/dynamic_weight
+ key_mode = KEY_MODE_TEXT
+ value_mode = VALUE_MODE_NUM
+
+/datum/config_entry/keyed_list/dynamic_requirements
+ key_mode = KEY_MODE_TEXT
+ value_mode = VALUE_MODE_NUM_LIST
+
+/datum/config_entry/keyed_list/dynamic_high_population_requirement
+ key_mode = KEY_MODE_TEXT
+ value_mode = VALUE_MODE_NUM
+
+/datum/config_entry/number_list/dynamic_second_rule_requirements
+
+/datum/config_entry/number_list/dynamic_third_rule_requirements
+
+/datum/config_entry/number/dynamic_second_rule_high_pop_requirement
+ config_entry_value = 50
+
+/datum/config_entry/number/dynamic_third_rule_high_pop_requirement
+ config_entry_value = 70
+
+/datum/config_entry/number_list/dynamic_hijack_requirements
+
+/datum/config_entry/number/dynamic_hijack_high_population_requirement
+ config_entry_value = 25
+
+/datum/config_entry/number/dynamic_hijack_cost
+ config_entry_value = 5
+
+/datum/config_entry/number/dynamic_glorious_death_cost
+ config_entry_value = 5
+
+/datum/config_entry/number/dynamic_assassinate_cost
+ config_entry_value = 2
+
+/datum/config_entry/number/dynamic_summon_guns_requirement
+ config_entry_value = 10
+ min_val = 0
+
+/datum/config_entry/number/dynamic_summon_guns_cost
+ config_entry_value = 5
+ min_val = 0
+
+/datum/config_entry/number/dynamic_summon_magic_requirement
+ config_entry_value = 10
+ min_val = 0
+
+/datum/config_entry/number/dynamic_summon_magic_cost
+ config_entry_value = 5
+ min_val = 0
+
+/datum/config_entry/number/dynamic_summon_events_requirement
+ config_entry_value = 20
+ min_val = 0
+
+/datum/config_entry/number/dynamic_summon_events_cost
+ config_entry_value = 10
+ min_val = 0
+
+/datum/config_entry/number/dynamic_staff_of_change_requirement
+ config_entry_value = 20
+ min_val = 0
+
+/datum/config_entry/number/dynamic_staff_of_change_cost
+ config_entry_value = 10
+ min_val = 0
+
+/datum/config_entry/number/dynamic_apprentice_cost
+ config_entry_value = 10
+ min_val = 0
+
+/datum/config_entry/number/dynamic_warops_requirement
+ config_entry_value = 60
+ min_val = 0
+
+/datum/config_entry/number/dynamic_warops_cost
+ config_entry_value = 10
+ min_val = 0
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index ed3cca587d..fdddbda344 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -379,43 +379,3 @@
/datum/config_entry/number/auto_transfer_delay
config_entry_value = 72000
min_val = 0
-
-/datum/config_entry/number/dynamic_high_pop_limit
- config_entry_value = 55
- min_val = 1
-
-/datum/config_entry/number/dynamic_pop_per_requirement
- config_entry_value = 6
- min_val = 1
-
-/datum/config_entry/number/dynamic_midround_delay_min
- config_entry_value = 15
- min_val = 1
-
-/datum/config_entry/number/dynamic_midround_delay_max
- config_entry_value = 35
- min_val = 1
-
-/datum/config_entry/number/dynamic_latejoin_delay_min
- config_entry_value = 5
- min_val = 1
-
-/datum/config_entry/number/dynamic_latejoin_delay_max
- config_entry_value = 25
- min_val = 1
-
-/datum/config_entry/keyed_list/dynamic_cost
- key_mode = KEY_MODE_TEXT
- value_mode = VALUE_MODE_NUM
-
-/datum/config_entry/keyed_list/dynamic_weight
- key_mode = KEY_MODE_TEXT
- value_mode = VALUE_MODE_NUM
-
-/datum/config_entry/keyed_list/dynamic_requirements
- key_mode = KEY_MODE_TEXT
- value_mode = VALUE_MODE_NUM_LIST
-
-/datum/config_entry/keyed_list/dynamic_high_population_requirement
- key_mode = KEY_MODE_TEXT
- value_mode = VALUE_MODE_NUM
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 2ab06f4d59..6f619fef0b 100755
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -479,7 +479,8 @@ SUBSYSTEM_DEF(ticker)
if(SSticker.timeLeft < 900)
SSticker.timeLeft = 900
SSticker.modevoted = TRUE
- SSvote.initiate_vote("roundtype","server",TRUE)
+ var/dynamic = CONFIG_GET(flag/dynamic_voting)
+ SSvote.initiate_vote(dynamic ? "dynamic" : "roundtype","server",TRUE)
/datum/controller/subsystem/ticker/Recover()
current_state = SSticker.current_state
diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm
index e29382377f..50be61f91d 100644
--- a/code/controllers/subsystem/vote.dm
+++ b/code/controllers/subsystem/vote.dm
@@ -87,7 +87,7 @@ SUBSYSTEM_DEF(vote)
/datum/controller/subsystem/vote/proc/announce_result()
var/list/winners = get_result()
var/text
- var/was_roundtype_vote = mode == "roundtype"
+ var/was_roundtype_vote = mode == "roundtype" || mode == "dynamic"
if(winners.len > 0)
if(question)
text += "[question]"
@@ -124,6 +124,9 @@ SUBSYSTEM_DEF(vote)
message_admins(admintext)
return .
+#define PEACE "calm"
+#define CHAOS "chaotic"
+
/datum/controller/subsystem/vote/proc/result()
. = announce_result()
var/restart = 0
@@ -146,6 +149,32 @@ SUBSYSTEM_DEF(vote)
restart = 1
else
GLOB.master_mode = .
+ if("dynamic")
+ if(SSticker.current_state > GAME_STATE_PREGAME)//Don't change the mode if the round already started.
+ return message_admins("A vote has tried to change the gamemode, but the game has already started. Aborting.")
+ GLOB.master_mode = "dynamic"
+ var/mean = 0
+ var/voters = 0
+ for(var/client/c in GLOB.clients)
+ var/vote = c.prefs.preferred_chaos
+ if(vote)
+ voters += 1
+ switch(vote)
+ if(CHAOS_NONE)
+ mean -= 0.1
+ if(CHAOS_LOW)
+ mean -= 0.05
+ if(CHAOS_HIGH)
+ mean += 0.05
+ if(CHAOS_MAX)
+ mean += 0.1
+ mean/=voters
+ if(voted.len != 0)
+ mean += (choices[PEACE]*-1+choices[CHAOS])/voted.len
+ GLOB.dynamic_curve_centre = mean*20
+ GLOB.dynamic_curve_width = CLAMP(2-abs(mean*5),0.5,4)
+ to_chat(world,"Dynamic curve centre set to [GLOB.dynamic_curve_centre] and width set to [GLOB.dynamic_curve_width].")
+ log_admin("Dynamic curve centre set to [GLOB.dynamic_curve_centre] and width set to [GLOB.dynamic_curve_width]")
if("map")
var/datum/map_config/VM = config.maplist[.]
message_admins("The map has been voted for and will change to: [VM.map_name]")
@@ -223,6 +252,8 @@ SUBSYSTEM_DEF(vote)
choices |= M
if("roundtype") //CIT CHANGE - adds the roundstart secret/extended vote
choices.Add("secret", "extended")
+ if("dynamic")
+ choices.Add(PEACE,CHAOS)
if("custom")
question = stripped_input(usr,"What is the vote for?")
if(!question)
@@ -379,4 +410,7 @@ SUBSYSTEM_DEF(vote)
else if(owner.ckey)
var/datum/player_details/P = GLOB.player_details[owner.ckey]
if(P)
- P.player_actions -= src
\ No newline at end of file
+ P.player_actions -= src
+
+#undef PEACE
+#undef CHAOS
diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm
index ca009f76ad..83a3debfdc 100644
--- a/code/game/gamemodes/dynamic/dynamic.dm
+++ b/code/game/gamemodes/dynamic/dynamic.dm
@@ -11,15 +11,26 @@
#define RULESET_STOP_PROCESSING 1
// -- Injection delays
-GLOBAL_VAR_INIT(dynamic_latejoin_delay_min, (5 MINUTES))
-GLOBAL_VAR_INIT(dynamic_latejoin_delay_max, (25 MINUTES))
+GLOBAL_VAR_INIT(dynamic_latejoin_delay_min, (10 MINUTES))
+GLOBAL_VAR_INIT(dynamic_latejoin_delay_max, (30 MINUTES))
-GLOBAL_VAR_INIT(dynamic_midround_delay_min, (15 MINUTES))
-GLOBAL_VAR_INIT(dynamic_midround_delay_max, (35 MINUTES))
+GLOBAL_VAR_INIT(dynamic_midround_delay_min, (10 MINUTES))
+GLOBAL_VAR_INIT(dynamic_midround_delay_max, (30 MINUTES))
+
+GLOBAL_VAR_INIT(dynamic_event_delay_min, (10 MINUTES))
+GLOBAL_VAR_INIT(dynamic_event_delay_max, (30 MINUTES)) // this is on top of regular events, so can't be quite as often
+
+
+// -- Roundstart injection delays
+GLOBAL_VAR_INIT(dynamic_first_latejoin_delay_min, (2 MINUTES))
+GLOBAL_VAR_INIT(dynamic_first_latejoin_delay_max, (30 MINUTES))
+
+GLOBAL_VAR_INIT(dynamic_first_midround_delay_min, (20 MINUTES))
+GLOBAL_VAR_INIT(dynamic_first_midround_delay_max, (30 MINUTES))
// Are HIGHLANDER_RULESETs allowed to stack?
GLOBAL_VAR_INIT(dynamic_no_stacking, TRUE)
-// A number between -5 and +5.
+// 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.
GLOBAL_VAR_INIT(dynamic_curve_centre, 0)
@@ -27,7 +38,7 @@ GLOBAL_VAR_INIT(dynamic_curve_centre, 0)
// Higher value will favour extreme rounds and
// lower value rounds closer to the average.
GLOBAL_VAR_INIT(dynamic_curve_width, 1.8)
-// If enabled only picks a single starting rule and executes only autotraitor midround ruleset.
+// If enabled only picks a single starting rule and executes only autotraitor midround ruleset.
GLOBAL_VAR_INIT(dynamic_classic_secret, FALSE)
// How many roundstart players required for high population override to take effect.
GLOBAL_VAR_INIT(dynamic_high_pop_limit, 55)
@@ -38,7 +49,7 @@ GLOBAL_VAR_INIT(dynamic_forced_extended, FALSE)
GLOBAL_VAR_INIT(dynamic_stacking_limit, 90)
// List of forced roundstart rulesets.
GLOBAL_LIST_EMPTY(dynamic_forced_roundstart_ruleset)
-// Forced threat level, setting this to zero or higher forces the roundstart threat to the value.
+// Forced threat level, setting this to zero or higher forces the roundstart threat to the value.
GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
/datum/game_mode/dynamic
@@ -49,20 +60,28 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
announce_text = "Dynamic mode!" // This needs to be changed maybe
reroll_friendly = FALSE;
-
+
// Threat logging vars
/// The "threat cap", threat shouldn't normally go above this and is used in ruleset calculations
- var/threat_level = 0
+ var/threat_level = 0
/// Set at the beginning of the round. Spent by the mode to "purchase" rules.
var/threat = 0
+ /// Starting threat level, for things that increase it but can bring it back down.
+ var/initial_threat_level = 0
+ /// Things that cause a rolling threat adjustment to be displayed at roundend.
+ var/list/threat_tallies = list()
/// Running information about the threat. Can store text or datum entries.
var/list/threat_log = list()
+ /// As above, but with info such as refunds.
+ var/list/threat_log_verbose = list()
/// List of roundstart rules used for selecting the rules.
var/list/roundstart_rules = list()
/// List of latejoin rules used for selecting the rules.
var/list/latejoin_rules = list()
/// List of midround rules used for selecting the rules.
var/list/midround_rules = list()
+ /// List of events used for reducing threat without causing antag injection (necessarily).
+ var/list/events = list()
/** # Pop range per requirement.
* If the value is five the range is:
* 0-4, 5-9, 10-14, 15-19, 20-24, 25-29, 30-34, 35-39, 40-54, 45+
@@ -71,15 +90,21 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
* If it is seven the range is:
* 0-6, 7-13, 14-20, 21-27, 28-34, 35-41, 42-48, 49-55, 56-62, 63+
*/
- var/pop_per_requirement = 6
- /// The requirement used for checking if a second rule should be selected.
+ var/pop_per_requirement = 9
+ /// The requirement used for checking if a second rule should be selected. Index based on pop_per_requirement.
var/list/second_rule_req = list(100, 100, 80, 70, 60, 50, 30, 20, 10, 0)
- /// The requirement used for checking if a third rule should be selected.
+ /// The probability for a second ruleset with index being every ten threat.
+ var/list/second_rule_prob = list(0,0,60,80,80,80,100,100,100,100)
+ /// The requirement used for checking if a third rule should be selected. Index based on pop_per_requirement.
var/list/third_rule_req = list(100, 100, 100, 90, 80, 70, 60, 50, 40, 30)
- /// Threat requirement for a second ruleset when high pop override is in effect.
+ /// The probability for a third ruleset with index being every ten threat.
+ var/list/third_rule_prob = list(0,0,0,0,60,60,80,90,100,100)
+ /// Threat requirement for a second ruleset when high pop override is in effect.
var/high_pop_second_rule_req = 40
- /// Threat requirement for a third ruleset when high pop override is in effect.
+ /// Threat requirement for a third ruleset when high pop override is in effect.
var/high_pop_third_rule_req = 60
+ /// The amount of additional rulesets waiting to be picked.
+ var/extra_rulesets_amount = 0
/// Number of players who were ready on roundstart.
var/roundstart_pop_ready = 0
/// List of candidates used on roundstart rulesets.
@@ -94,6 +119,8 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
var/latejoin_injection_cooldown = 0
/// When world.time is over this number the mode tries to inject a midround ruleset.
var/midround_injection_cooldown = 0
+ /// When wor.dtime is over this number the mode tries to do an event.
+ var/event_injection_cooldown = 0
/// When TRUE GetInjectionChance returns 100.
var/forced_injection = FALSE
/// Forced ruleset to be executed for the next latejoin.
@@ -106,19 +133,33 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
var/highlander_executed = FALSE
/// If a only ruleset has been executed.
var/only_ruleset_executed = FALSE
+ /// Antags rolled by rules so far, to keep track of and discourage scaling past a certain ratio of crew/antags especially on lowpop.
+ var/antags_rolled = 0
/datum/game_mode/dynamic/New() // i have NO IDEA if this is the proper way to do this.
..()
pop_per_requirement = CONFIG_GET(number/dynamic_pop_per_requirement)
+ second_rule_req = CONFIG_GET(number_list/dynamic_second_rule_requirements)
+ third_rule_req = CONFIG_GET(number_list/dynamic_third_rule_requirements)
+ if(second_rule_req.len<10)
+ second_rule_req = list(101, 101, 101, 101, 100, 90, 80, 70, 60, 50)
+ if(third_rule_req.len<10)
+ third_rule_req = list(101, 101, 101, 101, 101, 100, 90, 80, 70, 60)
+ high_pop_second_rule_req = CONFIG_GET(number/dynamic_second_rule_high_pop_requirement)
+ high_pop_third_rule_req = CONFIG_GET(number/dynamic_third_rule_high_pop_requirement)
GLOB.dynamic_high_pop_limit = CONFIG_GET(number/dynamic_high_pop_limit)
GLOB.dynamic_latejoin_delay_min = CONFIG_GET(number/dynamic_latejoin_delay_min)*600
GLOB.dynamic_latejoin_delay_max = CONFIG_GET(number/dynamic_latejoin_delay_max)*600
GLOB.dynamic_midround_delay_min = CONFIG_GET(number/dynamic_midround_delay_min)*600
GLOB.dynamic_midround_delay_max = CONFIG_GET(number/dynamic_midround_delay_max)*600
+ GLOB.dynamic_first_latejoin_delay_min = CONFIG_GET(number/dynamic_first_latejoin_delay_min)*600
+ GLOB.dynamic_first_latejoin_delay_max = CONFIG_GET(number/dynamic_first_latejoin_delay_max)*600
+ GLOB.dynamic_first_midround_delay_min = CONFIG_GET(number/dynamic_first_midround_delay_min)*600
+ GLOB.dynamic_first_midround_delay_max = CONFIG_GET(number/dynamic_first_midround_delay_max)*600
/datum/game_mode/dynamic/admin_panel()
var/list/dat = list("
Game Mode PanelGame Mode Panel
")
- dat += "Dynamic Mode \[VV\]
"
+ dat += "Dynamic Mode \[VV\]\[Refresh\]
"
dat += "Threat Level: [threat_level]
"
dat += "Threat to Spend: [threat] \[Adjust\] \[View Log\]
"
@@ -140,6 +181,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
dat += "
Injection Timers: ([get_injection_chance(TRUE)]% chance)
"
dat += "Latejoin: [(latejoin_injection_cooldown-world.time)>60*10 ? "[round((latejoin_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(latejoin_injection_cooldown-world.time)] seconds"] \[Now!\]
"
dat += "Midround: [(midround_injection_cooldown-world.time)>60*10 ? "[round((midround_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(midround_injection_cooldown-world.time)] seconds"] \[Now!\]
"
+ dat += "Event: [(event_injection_cooldown-world.time)>60*10 ? "[round((event_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(event_injection_cooldown-world.time)] seconds"] \[Now!\]
"
usr << browse(dat.Join(), "window=gamemode_panel;size=500x500")
/datum/game_mode/dynamic/Topic(href, href_list)
@@ -171,11 +213,15 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
midround_injection_cooldown = 0
forced_injection = TRUE
message_admins("[key_name(usr)] forced a midround injection.", 1)
+ else if (href_list["forceevent"])
+ event_injection_cooldown = 0
+ // events always happen anyway
+ message_admins("[key_name(usr)] forced an event.", 1)
else if (href_list["threatlog"])
show_threatlog(usr)
else if (href_list["stacking_limit"])
GLOB.dynamic_stacking_limit = input(usr,"Change the threat limit at which round-endings rulesets will start to stack.", "Change stacking limit", null) as num
-
+
admin_panel() // Refreshes the window
// Checks if there are HIGHLANDER_RULESETs and calls the rule's round_result() proc
@@ -193,30 +239,33 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
/datum/game_mode/dynamic/send_intercept()
. = "Central Command Status Summary
"
switch(round(threat_level))
- if(0 to 19)
- update_playercounts()
- if(!current_players[CURRENT_LIVING_ANTAGS].len)
- . += "Peaceful Waypoint
"
- . += "Your station orbits deep within controlled, core-sector systems and serves as a waypoint for routine traffic through Nanotrasen's trade empire. Due to the combination of high security, interstellar traffic, and low strategic value, it makes any direct threat of violence unlikely. Your primary enemies will be incompetence and bored crewmen: try to organize team-building events to keep staffers interested and productive."
- else
+ if(0 to 20)
+ . += "Peaceful Waypoint
"
+ . += "Your station orbits deep within controlled, core-sector systems and serves as a waypoint for routine traffic through Nanotrasen's trade empire. Due to the combination of high security, interstellar traffic, and low strategic value, it makes any direct threat of violence unlikely. Your primary enemies will be incompetence and bored crewmen: try to organize team-building events to keep staffers interested and productive. However, even deep in our territory there may be subversive elements, especially for such a high-value target as your station. Keep an eye out, but don't expect much trouble."
+ set_security_level(SEC_LEVEL_GREEN)
+ if(21 to 79)
+ var/perc_green = 100-round(100*((threat_level-21)/(79-21)))
+ if(prob(perc_green))
. += "Core Territory
"
. += "Your station orbits within reliably mundane, secure space. Although Nanotrasen has a firm grip on security in your region, the valuable resources and strategic position aboard your station make it a potential target for infiltrations. Monitor crew for non-loyal behavior, but expect a relatively tame shift free of large-scale destruction. We expect great things from your station."
- if(20 to 39)
- . += "Anomalous Exogeology
"
- . += "Although your station lies within what is generally considered Nanotrasen-controlled space, the course of its orbit has caused it to cross unusually close to exogeological features with anomalous readings. Although these features offer opportunities for our research department, it is known that these little understood readings are often correlated with increased activity from competing interstellar organizations and individuals, among them the Wizard Federation and Cult of the Geometer of Blood - all known competitors for Anomaly Type B sites. Exercise elevated caution."
- if(40 to 65)
- . += "Contested System
"
- . += "Your station's orbit passes along the edge of Nanotrasen's sphere of influence. While subversive elements remain the most likely threat against your station, hostile organizations are bolder here, where our grip is weaker. Exercise increased caution against elite Syndicate strike forces, or Executives forbid, some kind of ill-conceived unionizing attempt."
- if(66 to 79)
- . += "Uncharted Space
"
- . += "Congratulations and thank you for participating in the NT 'Frontier' space program! Your station is actively orbiting a high value system far from the nearest support stations. Little is known about your region of space, and the opportunity to encounter the unknown invites greater glory. You are encouraged to elevate security as necessary to protect Nanotrasen assets."
+ set_security_level(SEC_LEVEL_GREEN)
+ else if(prob(perc_green))
+ . += "Contested System
"
+ . += "Your station's orbit passes along the edge of Nanotrasen's sphere of influence. While subversive elements remain the most likely threat against your station, hostile organizations are bolder here, where our grip is weaker. Exercise increased caution against elite Syndicate strike forces, or Executives forbid, some kind of ill-conceived unionizing attempt."
+ set_security_level(SEC_LEVEL_BLUE)
+ else
+ . += "Uncharted Space
"
+ . += "Congratulations and thank you for participating in the NT 'Frontier' space program! Your station is actively orbiting a high value system far from the nearest support stations. Little is known about your region of space, and the opportunity to encounter the unknown invites greater glory. You are encouraged to elevate security as necessary to protect Nanotrasen assets."
+ set_security_level(SEC_LEVEL_BLUE)
if(80 to 99)
. += "Black Orbit
"
. += "As part of a mandatory security protocol, we are required to inform you that as a result of your orbital pattern directly behind an astrological body (oriented from our nearest observatory), your station will be under decreased monitoring and support. It is anticipated that your extreme location and decreased surveillance could pose security risks. Avoid unnecessary risks and attempt to keep your station in one piece."
+ set_security_level(SEC_LEVEL_AMBER)
if(100)
. += "Impending Doom
"
. += "Your station is somehow in the middle of hostile territory, in clear view of any enemy of the corporation. Your likelihood to survive is low, and station destruction is expected and almost inevitable. Secure any sensitive material and neutralize any enemy you will come across. It is important that you at least try to maintain the station.
"
. += "Good luck."
+ set_security_level(SEC_LEVEL_RED)
if(station_goals.len)
. += "
Special Orders for [station_name()]:"
@@ -225,9 +274,10 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
. += G.get_report()
print_command_report(., "Central Command Status Summary", announce=FALSE)
- priority_announce("A summary has been copied and printed to all communications consoles.", "Security level elevated.", "intercept")
- if(GLOB.security_level < SEC_LEVEL_BLUE)
- set_security_level(SEC_LEVEL_BLUE)
+ if(GLOB.security_level >= SEC_LEVEL_BLUE)
+ priority_announce("A summary has been copied and printed to all communications consoles.", "Security level elevated.", "intercept")
+ else
+ priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no likely threats to [station_name()]. Have a secure shift!", "Security Report", "commandreport")
// Yes, this is copy pasted from game_mode
/datum/game_mode/dynamic/check_finished(force_ending)
@@ -245,6 +295,11 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if(rule.flags & HIGHLANDER_RULESET)
return rule.check_finished()
+/datum/game_mode/dynamic/proc/log_threat(var/log_str,var/verbose = FALSE)
+ threat_log_verbose += ("[worldtime2text()]: "+log_str)
+ if(!verbose)
+ threat_log += log_str
+
/datum/game_mode/dynamic/proc/show_threatlog(mob/admin)
if(!SSticker.HasRoundStarted())
alert("The round hasn't started yet!")
@@ -253,9 +308,9 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if(!check_rights(R_ADMIN))
return
- var/list/out = list("Threat LogThreat Log
Starting Threat: [threat_level]
")
+ var/list/out = list("Threat LogThreat Log
Starting Threat: [initial_threat_level]
")
- for(var/entry in threat_log)
+ for(var/entry in threat_log_verbose)
if(istext(entry))
out += "[entry]
"
@@ -273,15 +328,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
threat = threat_level
/datum/game_mode/dynamic/can_start()
- /* Disabled for now, had some changes that need to be tested and this might interfere with that.
- if(GLOB.dynamic_curve_centre == 0)
- // 10 is when the centre starts to decrease
- // 6 is just 1 + 5 (from the maximum value and the one decreased)
- // 1 just makes the curve look better, I don't know.
- // Limited between 1 and 5 then inverted and rounded
- // With this you get a centre curve that stays at -5 until 10 then first rapidly decreases but slows down at the end
- GLOB.dynamic_curve_centre = round(-CLAMP((10*6/GLOB.player_list.len)-1, 0, 5), 0.5)
- */
message_admins("Dynamic mode parameters for the round:")
message_admins("Centre is [GLOB.dynamic_curve_centre], Width is [GLOB.dynamic_curve_width], Forced extended is [GLOB.dynamic_forced_extended ? "Enabled" : "Disabled"], No stacking is [GLOB.dynamic_no_stacking ? "Enabled" : "Disabled"].")
message_admins("Stacking limit is [GLOB.dynamic_stacking_limit], Classic secret is [GLOB.dynamic_classic_secret ? "Enabled" : "Disabled"], High population limit is [GLOB.dynamic_high_pop_limit].")
@@ -294,12 +340,17 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
else
generate_threat()
- var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_latejoin_delay_max + GLOB.dynamic_latejoin_delay_min)
- latejoin_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_latejoin_delay_min, GLOB.dynamic_latejoin_delay_max)) + world.time
+ var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_first_latejoin_delay_max + GLOB.dynamic_first_latejoin_delay_min)
+ latejoin_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_first_latejoin_delay_min, GLOB.dynamic_first_latejoin_delay_max)) + world.time
- var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_midround_delay_max + GLOB.dynamic_midround_delay_min)
- midround_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_midround_delay_min, GLOB.dynamic_midround_delay_max)) + world.time
+ var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_first_midround_delay_min + GLOB.dynamic_first_midround_delay_max)
+ midround_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_first_midround_delay_min, GLOB.dynamic_first_midround_delay_max)) + world.time
+
+ var/event_injection_cooldown_middle = 0.5*(GLOB.dynamic_event_delay_max + GLOB.dynamic_event_delay_min)
+ event_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(event_injection_cooldown_middle), GLOB.dynamic_event_delay_min, GLOB.dynamic_event_delay_max)) + world.time)
+
log_game("DYNAMIC: Dynamic Mode initialized with a Threat Level of... [threat_level]!")
+ initial_threat_level = threat_level
return TRUE
/datum/game_mode/dynamic/pre_setup()
@@ -316,24 +367,30 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if ("Midround")
if (ruleset.weight)
midround_rules += ruleset
+ if("Event")
+ if(ruleset.weight)
+ events += ruleset
for(var/mob/dead/new_player/player in GLOB.player_list)
if(player.ready == PLAYER_READY_TO_PLAY && player.mind)
roundstart_pop_ready++
candidates.Add(player)
log_game("DYNAMIC: Listing [roundstart_rules.len] round start rulesets, and [candidates.len] players ready.")
if (candidates.len <= 0)
+ log_game("DYNAMIC: [candidates.len] candidates.")
return TRUE
if (roundstart_rules.len <= 0)
+ log_game("DYNAMIC: [roundstart_rules.len] rules.")
return TRUE
-
+
if(GLOB.dynamic_forced_roundstart_ruleset.len > 0)
rigged_roundstart()
- else
+ else
roundstart()
var/starting_rulesets = ""
for (var/datum/dynamic_ruleset/roundstart/DR in executed_rules)
starting_rulesets += "[DR.name], "
+ log_game("DYNAMIC: Picked the following roundstart rules: [starting_rulesets]")
candidates.Cut()
return TRUE
@@ -341,9 +398,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
update_playercounts()
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.
- if(!rule.execute())
- stack_trace("The starting rule \"[rule.name]\" failed to execute.")
+ addtimer(CALLBACK(src, /datum/game_mode/dynamic/.proc/execute_roundstart_rule, rule), rule.delay)
..()
/// A simple roundstart proc used when dynamic_forced_roundstart_ruleset has rules in it.
@@ -354,6 +409,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
message_admins("Drafting players for forced ruleset [rule.name].")
log_game("DYNAMIC: Drafting players for forced ruleset [rule.name].")
rule.mode = src
+ rule.acceptable(GLOB.player_list.len, threat_level) // Assigns some vars in the modes, running it here for consistency
rule.candidates = candidates.Copy()
rule.trim_candidates()
if (rule.ready(TRUE))
@@ -365,18 +421,20 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
return TRUE
var/list/drafted_rules = list()
for (var/datum/dynamic_ruleset/roundstart/rule in roundstart_rules)
- if (rule.acceptable(roundstart_pop_ready, threat_level) && threat >= rule.cost) // If we got the population and threat required
+ if (rule.acceptable(GLOB.player_list.len, threat_level) && threat >= rule.cost) // If we got the population and threat required
rule.candidates = candidates.Copy()
rule.trim_candidates()
if (rule.ready() && rule.candidates.len > 0)
drafted_rules[rule] = rule.weight
-
- var/indice_pop = min(10,round(roundstart_pop_ready/pop_per_requirement)+1)
- var/extra_rulesets_amount = 0
+ if(!drafted_rules.len)
+ message_admins("Not enough threat level for roundstart antags!")
+ log_game("DYNAMIC: Not enough threat level for roundstart antags!")
+ var/indice_pop = min(10,round(GLOB.player_list.len/pop_per_requirement)+1)
+ extra_rulesets_amount = 0
if (GLOB.dynamic_classic_secret)
extra_rulesets_amount = 0
else
- if (roundstart_pop_ready > GLOB.dynamic_high_pop_limit)
+ if (GLOB.player_list.len > GLOB.dynamic_high_pop_limit)
message_admins("High Population Override is in effect! Threat Level will have more impact on which roles will appear, and player population less.")
log_game("DYNAMIC: High Population Override is in effect! Threat Level will have more impact on which roles will appear, and player population less.")
if (threat_level > high_pop_second_rule_req)
@@ -384,23 +442,28 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if (threat_level > high_pop_third_rule_req)
extra_rulesets_amount++
else
- if (threat_level >= second_rule_req[indice_pop])
+ var/threat_indice = min(10, max(round(threat_level ? threat_level/10 : 1), 1)) // 0-9 threat = 1, 10-19 threat = 2 ...
+ if (threat_level >= second_rule_req[indice_pop] && prob(second_rule_prob[threat_indice]))
extra_rulesets_amount++
- if (threat_level >= third_rule_req[indice_pop])
+ if (threat_level >= third_rule_req[indice_pop] && prob(third_rule_prob[threat_indice]))
extra_rulesets_amount++
+ log_game("DYNAMIC: Trying to roll [extra_rulesets_amount + 1] roundstart rulesets. Picking from [drafted_rules.len] eligible rulesets.")
if (drafted_rules.len > 0 && picking_roundstart_rule(drafted_rules))
- if (extra_rulesets_amount > 0) // We've got enough population and threat for a second rulestart rule
+ log_game("DYNAMIC: First ruleset picked successfully. [extra_rulesets_amount] remaining.")
+ while(extra_rulesets_amount > 0 && drafted_rules.len > 0) // We had enough threat for one or two more rulesets
for (var/datum/dynamic_ruleset/roundstart/rule in drafted_rules)
if (rule.cost > threat)
drafted_rules -= rule
- if (drafted_rules.len > 0 && picking_roundstart_rule(drafted_rules))
- if (extra_rulesets_amount > 1) // We've got enough population and threat for a third rulestart rule
- for (var/datum/dynamic_ruleset/roundstart/rule in drafted_rules)
- if (rule.cost > threat)
- drafted_rules -= rule
- picking_roundstart_rule(drafted_rules)
+ if(drafted_rules.len)
+ picking_roundstart_rule(drafted_rules)
+ extra_rulesets_amount--
+ log_game("DYNAMIC: Additional ruleset picked successfully, now [executed_rules.len] picked. [extra_rulesets_amount] remaining.")
else
+
+ if(threat >= 50)
+ message_admins("DYNAMIC: Picking first roundstart ruleset failed. You should report this.")
+ log_game("DYNAMIC: Picking first roundstart ruleset failed. drafted_rules.len = [drafted_rules.len] and threat = [threat]/[threat_level]")
return FALSE
return TRUE
@@ -408,68 +471,74 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
/datum/game_mode/dynamic/proc/picking_roundstart_rule(list/drafted_rules = list(), forced = FALSE)
var/datum/dynamic_ruleset/roundstart/starting_rule = pickweight(drafted_rules)
if(!starting_rule)
+ log_game("DYNAMIC: Couldn't pick a starting ruleset. No rulesets available")
return FALSE
if(!forced)
if(only_ruleset_executed)
return FALSE
// Check if a blocking ruleset has been executed.
- else if(check_blocking(starting_rule.blocking_rules, executed_rules))
+ else if(check_blocking(starting_rule.blocking_rules, executed_rules)) // Should already be filtered out, but making sure. Check filtering at end of proc if reported.
drafted_rules -= starting_rule
if(drafted_rules.len <= 0)
+ log_game("DYNAMIC: Picking [starting_rule.name] failed due to blocking_rules and no more rulesets available. Report this.")
return FALSE
starting_rule = pickweight(drafted_rules)
// Check if the ruleset is highlander and if a highlander ruleset has been executed
- else if(starting_rule.flags & HIGHLANDER_RULESET)
- if(threat < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
+ else if(starting_rule.flags & HIGHLANDER_RULESET) // Should already be filtered out, but making sure. Check filtering at end of proc if reported.
+ if(threat_level > GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
if(highlander_executed)
drafted_rules -= starting_rule
if(drafted_rules.len <= 0)
+ log_game("DYNAMIC: Picking [starting_rule.name] failed due to no highlander stacking and no more rulesets available. Report this.")
return FALSE
starting_rule = pickweight(drafted_rules)
+ // With low pop and high threat there might be rulesets that get executed with no valid candidates.
+ else if(!starting_rule.ready()) // Should already be filtered out, but making sure. Check filtering at end of proc if reported.
+ drafted_rules -= starting_rule
+ if(drafted_rules.len <= 0)
+ log_game("DYNAMIC: Picking [starting_rule.name] failed because there were not enough candidates and no more rulesets available. Report this.")
+ return FALSE
+ starting_rule = pickweight(drafted_rules)
- log_game("DYNAMIC: Picking a [istype(starting_rule, /datum/dynamic_ruleset/roundstart/delayed/) ? " delayed " : ""] ruleset [starting_rule.name]")
+ log_game("DYNAMIC: Picked a ruleset: [starting_rule.name]")
roundstart_rules -= starting_rule
drafted_rules -= starting_rule
- if (istype(starting_rule, /datum/dynamic_ruleset/roundstart/delayed/))
- var/datum/dynamic_ruleset/roundstart/delayed/rule = starting_rule
- addtimer(CALLBACK(src, .proc/execute_delayed, rule), rule.delay)
-
starting_rule.trim_candidates()
+ var/added_threat = starting_rule.scale_up(extra_rulesets_amount, threat)
if (starting_rule.pre_execute())
- spend_threat(starting_rule.cost)
- threat_log += "[worldtime2text()]: Roundstart [starting_rule.name] spent [starting_rule.cost]"
+ spend_threat(starting_rule.cost + added_threat)
+ log_threat("[starting_rule.ruletype] - [starting_rule.name] -[starting_rule.cost + starting_rule.scaled_times * starting_rule.scaling_cost] threat")
if(starting_rule.flags & HIGHLANDER_RULESET)
highlander_executed = TRUE
else if(starting_rule.flags & ONLY_RULESET)
only_ruleset_executed = TRUE
executed_rules += starting_rule
- if (starting_rule.persistent)
- current_rules += starting_rule
- for(var/mob/M in starting_rule.assigned)
- for (var/datum/dynamic_ruleset/roundstart/rule in roundstart_rules)
- if (!rule.ready())
- drafted_rules -= rule // And removing rules that are no longer elligible
+ for(var/datum/dynamic_ruleset/roundstart/rule in drafted_rules)
+ if(check_blocking(rule.blocking_rules, executed_rules))
+ drafted_rules -= rule
+ if(highlander_executed && rule.flags & HIGHLANDER_RULESET)
+ drafted_rules -= rule
+ if(!rule.ready())
+ drafted_rules -= rule // And removing rules that are no longer eligible
return TRUE
else
stack_trace("The starting rule \"[starting_rule.name]\" failed to pre_execute.")
return FALSE
-/// Executes delayed roundstart rules and has a hack in it.
-/datum/game_mode/dynamic/proc/execute_delayed(datum/dynamic_ruleset/roundstart/delayed/rule)
- update_playercounts()
- rule.candidates = current_players[CURRENT_LIVING_PLAYERS].Copy()
- rule.trim_candidates()
+/// 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)
+ var/datum/dynamic_ruleset/rule = sent_rule
if(rule.execute())
- executed_rules += rule
- if (rule.persistent)
+ if(rule.persistent)
current_rules += rule
return TRUE
- else
- stack_trace("The delayed roundstart rule \"[rule.name]\" failed to execute.")
- return FALSE
+ rule.clean_up() // Refund threat, delete teams and so on.
+ executed_rules -= rule
+ stack_trace("The starting rule \"[rule.name]\" failed to execute.")
+ return FALSE
/// Picks a random midround OR latejoin rule from the list given as an argument and executes it.
/// Also this could be named better.
@@ -477,7 +546,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
var/datum/dynamic_ruleset/rule = pickweight(drafted_rules)
if(!rule)
return FALSE
-
+
if(!forced)
if(only_ruleset_executed)
return FALSE
@@ -489,23 +558,75 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
rule = pickweight(drafted_rules)
// Check if the ruleset is highlander and if a highlander ruleset has been executed
else if(rule.flags & HIGHLANDER_RULESET)
- if(threat < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
+ if(threat_level > GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
if(highlander_executed)
drafted_rules -= rule
if(drafted_rules.len <= 0)
return FALSE
rule = pickweight(drafted_rules)
-
+
if(!rule.repeatable)
if(rule.ruletype == "Latejoin")
latejoin_rules = remove_from_list(latejoin_rules, rule.type)
- else if(rule.type == "Midround")
+ else if(rule.ruletype == "Midround")
midround_rules = remove_from_list(midround_rules, rule.type)
-
+ else if(rule.ruletype == "Event")
+ events = remove_from_list(events,rule.type)
+ addtimer(CALLBACK(src, /datum/game_mode/dynamic/.proc/execute_midround_latejoin_rule, rule), rule.delay)
+ return TRUE
+
+/// 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)
+ var/datum/dynamic_ruleset/midround/new_rule
+ if(ispath(ruletype))
+ new_rule = new ruletype() // You should only use it to call midround rules though.
+ else if(istype(ruletype, /datum/dynamic_ruleset))
+ new_rule = ruletype
+ else
+ return FALSE
+
+ if(!new_rule)
+ return FALSE
+
+ if(!forced)
+ if(only_ruleset_executed)
+ return FALSE
+ // Check if a blocking ruleset has been executed.
+ else if(check_blocking(new_rule.blocking_rules, executed_rules))
+ return FALSE
+ // Check if the ruleset is highlander and if a highlander ruleset has been executed
+ else if(new_rule.flags & HIGHLANDER_RULESET)
+ if(threat_level > GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
+ if(highlander_executed)
+ return FALSE
+
+ update_playercounts()
+ if ((forced || (new_rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && new_rule.cost <= threat)))
+ new_rule.trim_candidates()
+ if (new_rule.ready(forced))
+ spend_threat(new_rule.cost)
+ log_threat("[new_rule.ruletype] - [new_rule.name] -[new_rule.cost] threat")
+ if (new_rule.execute()) // This should never fail since ready() returned 1
+ if(new_rule.flags & HIGHLANDER_RULESET)
+ highlander_executed = TRUE
+ else if(new_rule.flags & ONLY_RULESET)
+ only_ruleset_executed = TRUE
+ log_game("DYNAMIC: Making a call to a specific ruleset...[new_rule.name]!")
+ executed_rules += new_rule
+ if (new_rule.persistent)
+ current_rules += new_rule
+ return TRUE
+ else if (forced)
+ log_game("DYNAMIC: The ruleset [new_rule.name] couldn't be executed due to lack of eligible players.")
+ return FALSE
+
+/// 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)
+ var/datum/dynamic_ruleset/rule = sent_rule
if (rule.execute())
log_game("DYNAMIC: Injected a [rule.ruletype == "latejoin" ? "latejoin" : "midround"] ruleset [rule.name].")
spend_threat(rule.cost)
- threat_log += "[worldtime2text()]: [rule.ruletype] [rule.name] spent [rule.cost]"
+ log_threat("[rule.ruletype] [rule.name] spent [rule.cost]")
if(rule.flags & HIGHLANDER_RULESET)
highlander_executed = TRUE
else if(rule.flags & ONLY_RULESET)
@@ -519,54 +640,8 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if (rule.persistent)
current_rules += rule
return TRUE
- else
- stack_trace("The [rule.ruletype] rule \"[rule.name]\" failed to execute.")
- 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)
- var/datum/dynamic_ruleset/midround/new_rule
- if(ispath(ruletype))
- new_rule = new ruletype() // You should only use it to call midround rules though.
- else if(istype(ruletype, /datum/dynamic_ruleset))
- new_rule = ruletype
- else
- return FALSE
-
- if(!new_rule)
- return FALSE
-
- if(!forced)
- if(only_ruleset_executed)
- return FALSE
- // Check if a blocking ruleset has been executed.
- else if(check_blocking(new_rule.blocking_rules, executed_rules))
- return FALSE
- // Check if the ruleset is highlander and if a highlander ruleset has been executed
- else if(new_rule.flags & HIGHLANDER_RULESET)
- if(threat < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
- if(highlander_executed)
- return FALSE
-
- update_playercounts()
- if ((forced || (new_rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && new_rule.cost <= threat)))
- new_rule.candidates = current_players.Copy()
- new_rule.trim_candidates()
- if (new_rule.ready(forced))
- spend_threat(new_rule.cost)
- threat_log += "[worldtime2text()]: Forced rule [new_rule.name] spent [new_rule.cost]"
- if (new_rule.execute()) // This should never fail since ready() returned 1
- if(new_rule.flags & HIGHLANDER_RULESET)
- highlander_executed = TRUE
- else if(new_rule.flags & ONLY_RULESET)
- only_ruleset_executed = TRUE
- log_game("DYNAMIC: Making a call to a specific ruleset...[new_rule.name]!")
- executed_rules += new_rule
- if (new_rule.persistent)
- current_rules += new_rule
- return TRUE
- else if (forced)
- log_game("DYNAMIC: The ruleset [new_rule.name] couldn't be executed due to lack of elligible players.")
+ rule.clean_up()
+ stack_trace("The [rule.ruletype] rule \"[rule.name]\" failed to execute.")
return FALSE
/datum/game_mode/dynamic/process()
@@ -581,35 +656,54 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if (midround_injection_cooldown < world.time)
if (GLOB.dynamic_forced_extended)
return
-
- // Somehow it manages to trigger midround multiple times so this was moved here.
+
+ // Somehow it managed to trigger midround multiple times so this was moved here.
// There is no way this should be able to trigger an injection twice now.
var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_midround_delay_max + GLOB.dynamic_midround_delay_min)
midround_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_midround_delay_min, GLOB.dynamic_midround_delay_max)) + world.time)
-
+
// Time to inject some threat into the round
if(EMERGENCY_ESCAPED_OR_ENDGAMED) // Unless the shuttle is gone
return
+ message_admins("DYNAMIC: Checking for midround injection.")
+ log_game("DYNAMIC: Checking for midround injection.")
- log_game("DYNAMIC: Checking state of the round.")
-
update_playercounts()
-
- if (prob(get_injection_chance()))
+ if (get_injection_chance())
+ var/cur_threat_frac = threat/threat_level
var/list/drafted_rules = list()
+ var/antag_num = current_players[CURRENT_LIVING_ANTAGS].len
for (var/datum/dynamic_ruleset/midround/rule in midround_rules)
+ // if there are antags OR the rule is an antag rule, antag_acceptable will be true.
if (rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost)
// Classic secret : only autotraitor/minor roles
if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
continue
- rule.candidates = list()
- rule.candidates = current_players.Copy()
rule.trim_candidates()
- if (rule.ready() && rule.candidates.len > 0)
- drafted_rules[rule] = rule.get_weight()
+ if (rule.ready())
+ if(!antag_num)
+ drafted_rules[rule] = round(rule.get_weight() + (rule.cost * cur_threat_frac))
+ else
+ drafted_rules[rule] = rule.get_weight()
if (drafted_rules.len > 0)
picking_midround_latejoin_rule(drafted_rules)
-
+ else
+ midround_injection_cooldown = (midround_injection_cooldown + world.time)/2
+
+ if(event_injection_cooldown < world.time)
+ var/event_injection_cooldown_middle = 0.5*(GLOB.dynamic_event_delay_max + GLOB.dynamic_event_delay_min)
+ event_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(event_injection_cooldown_middle), GLOB.dynamic_event_delay_min, GLOB.dynamic_event_delay_max)) + world.time)
+ message_admins("DYNAMIC: Doing event injection.")
+ log_game("DYNAMIC: Doing event injection.")
+ update_playercounts()
+ var/list/drafted_rules = list()
+ for(var/datum/dynamic_ruleset/event/rule in events)
+ if(rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost)
+ if(rule.ready())
+ drafted_rules[rule] = rule.get_weight()
+ if(drafted_rules.len > 0)
+ picking_midround_latejoin_rule(drafted_rules)
+
/// Updates current_players.
/datum/game_mode/dynamic/proc/update_playercounts()
current_players[CURRENT_LIVING_PLAYERS] = list()
@@ -641,7 +735,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
var/high_pop_factor = (current_players[CURRENT_LIVING_PLAYERS].len >= GLOB.dynamic_high_pop_limit)
var/max_pop_per_antag = max(5,15 - round(threat_level/10) - round(current_players[CURRENT_LIVING_PLAYERS].len/(high_pop_factor ? 10 : 5)))
if (!current_players[CURRENT_LIVING_ANTAGS].len)
- chance += 50 // No antags at all? let's boost those odds!
+ chance += 80 // No antags at all? let's boost those odds!
else
var/current_pop_per_antag = current_players[CURRENT_LIVING_PLAYERS].len / current_players[CURRENT_LIVING_ANTAGS].len
if (current_pop_per_antag > max_pop_per_antag)
@@ -704,11 +798,11 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
// Classic secret : only autotraitor/minor roles
if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
continue
- // No stacking : only one round-enter, unless > stacking_limit threat.
- if (threat < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
+ // No stacking : only one round-ender, unless threat level > stacking_limit.
+ if (threat_level > GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
if(rule.flags & HIGHLANDER_RULESET && highlander_executed)
continue
-
+
rule.candidates = list(newPlayer)
rule.trim_candidates()
if (rule.ready())
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm
index 011b6ec251..0c8ec0a2b8 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm
@@ -1,22 +1,28 @@
+#define EXTRA_RULESET_PENALTY 20 // Changes how likely a gamemode is to scale based on how many other roundstart rulesets are waiting to be rolled.
+#define POP_SCALING_PENALTY 50 // Discourages scaling up rulesets if ratio of antags to crew is high.
+
+#define REVOLUTION_VICTORY 1
+#define STATION_VICTORY 2
+
/datum/dynamic_ruleset
/// For admin logging and round end screen.
var/name = ""
/// For admin logging and round end screen, do not change this unless making a new rule type.
- var/ruletype = ""
+ var/ruletype = ""
/// For config purposes, similar to config_tag for secret game modes.
var/config_tag = null
/// 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
+ 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)
- var/repeatable = FALSE
+ 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
+ var/repeatable_weight_decrease = 2
/// List of players that are being drafted for this rule
- var/list/mob/candidates = list()
+ var/list/mob/candidates = list()
/// List of players that were selected for this rule
- var/list/datum/mind/assigned = list()
+ var/list/datum/mind/assigned = list()
/// Preferences flag such as ROLE_WIZARD that need to be turned on for players to be antag
- var/antag_flag = null
+ var/antag_flag = null
/// The antagonist datum that is assigned to the mobs mind on ruleset execution.
var/datum/antagonist/antag_datum = null
/// The required minimum account age for this ruleset.
@@ -24,19 +30,25 @@
/// If set, and config flag protect_roles_from_antagonist is false, then the rule will not pick players from these roles.
var/list/protected_roles = list()
/// If set, rule will deny candidates from those roles always.
- var/list/restricted_roles = list()
+ var/list/restricted_roles = list()
/// If set, rule will only accept candidates from those roles, IMPORTANT: DOES NOT WORK ON ROUNDSTART RULESETS.
- var/list/exclusive_roles = list()
+ var/list/exclusive_roles = list()
/// If set, there needs to be a certain amount of players doing those roles (among the players who won't be drafted) for the rule to be drafted IMPORTANT: DOES NOT WORK ON ROUNDSTART RULESETS.
- var/list/enemy_roles = list()
+ var/list/enemy_roles = list()
/// If enemy_roles was set, this is the amount of enemy job workers needed per threat_level range (0-10,10-20,etc) IMPORTANT: DOES NOT WORK ON ROUNDSTART RULESETS.
- var/required_enemies = list(1,1,0,0,0,0,0,0,0,0)
+ var/required_enemies = list(1,1,0,0,0,0,0,0,0,0)
/// The rule needs this many candidates (post-trimming) to be executed (example: Cult needs 4 players at round start)
- var/required_candidates = 0
+ var/required_candidates = 0
/// 1 -> 9, probability for this rule to be picked against other rules
- var/weight = 5
+ var/weight = 5
/// Threat cost for this rule, this is decreased from the mode's threat when the rule is executed.
- var/cost = 0
+ var/cost = 0
+ /// Cost per level the rule scales up.
+ var/scaling_cost = 0
+ /// How many times a rule has scaled up upon getting picked.
+ var/scaled_times = 0
+ /// Used for the roundend report
+ var/total_cost = 0
/// A flag that determines how the ruleset is handled
/// HIGHLANDER_RULESET are rulesets can end the round.
/// TRAITOR_RULESET and MINOR_RULESET can't end the round and have no difference right now.
@@ -46,20 +58,28 @@
/// 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)
- /// An alternative, static requirement used instead when pop is over mode's high_pop_limit.
+ /// An alternative, static requirement used instead when pop is over mode's high_pop_limit.
var/high_population_requirement = 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
+ var/antag_flag_override = null
/// If a ruleset type which is in this list has been executed, then the ruleset will not be executed.
var/list/blocking_rules = list()
- /// The minimum amount of players required for the rule to be considered.
+ /// The minimum amount of players required for the rule to be considered.
var/minimum_players = 0
/// The maximum amount of players required for the rule to be considered.
- /// Anything below zero or exactly zero is ignored.
+ /// Anything below zero or exactly zero is ignored.
var/maximum_players = 0
-
+ /// Calculated during acceptable(), used in scaling and team sizes.
+ var/indice_pop = 0
+ /// Population scaling. Used by team antags and scaling for solo antags.
+ var/list/antag_cap = list()
+ /// Base probability used in scaling. The higher it is, the more likely to scale. Kept as a var to allow for config editing._SendSignal(sigtype, list/arguments)
+ var/base_prob = 60
+ /// Delay for when execute will get called from the time of post_setup (roundstart) or process (midround/latejoin).
+ /// Make sure your ruleset works with execute being called during the game when using this, and that the clean_up proc reverts it properly in case of faliure.
+ var/delay = 0
/datum/dynamic_ruleset/New()
..()
@@ -87,10 +107,6 @@
/datum/dynamic_ruleset/roundstart // One or more of those drafted at roundstart
ruletype = "Roundstart"
-/datum/dynamic_ruleset/roundstart/delayed/ // Executed with a 30 seconds delay
- var/delay = 30 SECONDS
- var/required_type = /mob/living/carbon/human // No ghosts, new players or silicons allowed.
-
// Can be drafted when a player joins the server
/datum/dynamic_ruleset/latejoin
ruletype = "Latejoin"
@@ -103,17 +119,48 @@
if(maximum_players > 0 && population > maximum_players)
return FALSE
if (population >= GLOB.dynamic_high_pop_limit)
- return (threat_level >= high_population_requirement)
+ indice_pop = 10
+ if(threat_level < high_population_requirement)
+ log_game("DYNAMIC: [name] did not reach threat level threshold: [threat_level]/[high_population_requirement]")
+ return FALSE
+ else
+ return TRUE
else
pop_per_requirement = pop_per_requirement > 0 ? pop_per_requirement : mode.pop_per_requirement
- var/indice_pop = min(10,round(population/pop_per_requirement)+1)
- return (threat_level >= requirements[indice_pop])
+ if(antag_cap.len && requirements.len != antag_cap.len)
+ message_admins("DYNAMIC: requirements and antag_cap lists have different lengths in ruleset [name]. Likely config issue, report this.")
+ log_game("DYNAMIC: requirements and antag_cap lists have different lengths in ruleset [name]. Likely config issue, report this.")
+ indice_pop = min(requirements.len,round(population/pop_per_requirement)+1)
+ if(threat_level < requirements[indice_pop])
+ log_game("DYNAMIC: [name] did not reach threat level threshold: [threat_level]/[requirements[indice_pop]]")
+ return FALSE
+ else
+ return TRUE
+
+/// Called when a suitable rule is picked during roundstart(). Will some times attempt to scale a rule up when there is threat remaining. Returns the amount of scaled steps.
+/datum/dynamic_ruleset/proc/scale_up(extra_rulesets = 0, remaining_threat_level = 0)
+ remaining_threat_level -= cost
+ if(scaling_cost && scaling_cost <= remaining_threat_level) // Only attempts to scale the modes with a scaling cost explicitly set.
+ var/new_prob
+ var/pop_to_antags = (mode.antags_rolled + (antag_cap[indice_pop] * (scaled_times + 1))) / mode.roundstart_pop_ready
+ log_game("DYNAMIC: [name] roundstart ruleset attempting to scale up with [extra_rulesets] rulesets waiting and [remaining_threat_level] threat remaining.")
+ for(var/i in 1 to 3) //Can scale a max of 3 times
+ if(remaining_threat_level >= scaling_cost && pop_to_antags < 0.25)
+ new_prob = base_prob + (remaining_threat_level) - (scaled_times * scaling_cost) - (extra_rulesets * EXTRA_RULESET_PENALTY) - (pop_to_antags * POP_SCALING_PENALTY)
+ if (!prob(new_prob))
+ break
+ remaining_threat_level -= scaling_cost
+ scaled_times++
+ pop_to_antags = (mode.antags_rolled + (antag_cap[indice_pop] * (scaled_times + 1))) / mode.roundstart_pop_ready
+ log_game("DYNAMIC: [name] roundstart ruleset failed scaling up at [new_prob ? new_prob : 0]% chance after [scaled_times]/3 successful scaleups. [remaining_threat_level] threat remaining, antag to crew ratio: [pop_to_antags*100]%.")
+ mode.antags_rolled += (1 + scaled_times) * antag_cap[indice_pop]
+ return scaled_times * scaling_cost
/// This is called if persistent variable is true everytime SSTicker ticks.
/datum/dynamic_ruleset/proc/rule_process()
return
-/// Called on game mode pre_setup, used for non-delayed roundstart rulesets only.
+/// Called on game mode 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()
@@ -126,20 +173,20 @@
M.add_antag_datum(antag_datum)
return TRUE
-/// Called after delay set in ruleset.
-/// Give your candidates or assignees equipment and antag datum here.
-/datum/dynamic_ruleset/roundstart/delayed/execute()
- if (SSticker && SSticker.current_state < GAME_STATE_PLAYING)
- CRASH("The delayed ruleset [name] executed before the round started.")
-
/// Here you can perform any additional checks you want. (such as checking the map etc)
/// Remember that on roundstart no one knows what their job is at this point.
/// IMPORTANT: If ready() returns TRUE, that means pre_execute() or execute() should never fail!
-/datum/dynamic_ruleset/proc/ready(forced = 0)
- if (required_candidates > candidates.len)
+/datum/dynamic_ruleset/proc/ready(forced = 0)
+ if (required_candidates > candidates.len)
return FALSE
return TRUE
+/// 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.log_threat("[ruletype] [name] refunded [cost + (scaled_times * scaling_cost)]",verbose=TRUE)
+
/// 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
@@ -156,14 +203,6 @@
/datum/dynamic_ruleset/proc/trim_candidates()
return
-/// Counts how many players are ready at roundstart.
-/// Used only by non-delayed roundstart rulesets.
-/datum/dynamic_ruleset/proc/num_players()
- . = 0
- for(var/mob/dead/new_player/P in GLOB.player_list)
- if(P.client && P.ready == PLAYER_READY_TO_PLAY)
- . ++
-
/// Set mode result and news report here.
/// Only called if ruleset is flagged as HIGHLANDER_RULESET
/datum/dynamic_ruleset/proc/round_result()
@@ -191,32 +230,14 @@
if(P.mind.special_role) // We really don't want to give antag to an antag.
candidates.Remove(P)
continue
- if (!(antag_flag in P.client.prefs.be_special) || jobban_isbanned(P.ckey, list(antag_flag, ROLE_SYNDICATE)) || (antag_flag_override && jobban_isbanned(P.ckey, list(antag_flag_override, ROLE_SYNDICATE))))//are they willing and not antag-banned?
- candidates.Remove(P)
- continue
-
-/// Checks if candidates are required mob type, connected, banned and if the job is exclusive to the role.
-/datum/dynamic_ruleset/roundstart/delayed/trim_candidates()
- . = ..()
- for (var/mob/P in candidates)
- if (!istype(P, required_type))
- candidates.Remove(P) // Can be a new_player, etc.
- continue
- if(!mode.check_age(P.client, minimum_required_age))
- candidates.Remove(P)
- continue
- if (!P.client || !P.mind || !P.mind.assigned_role) // Are they connected?
- candidates.Remove(P)
- continue
- if(P.mind.special_role || P.mind.antag_datums?.len > 0) // Are they an antag already?
- candidates.Remove(P)
- continue
- if (!(antag_flag in P.client.prefs.be_special) || jobban_isbanned(P.ckey, list(antag_flag, ROLE_SYNDICATE)) || (antag_flag_override && jobban_isbanned(P.ckey, list(antag_flag_override, ROLE_SYNDICATE))))//are they willing and not antag-banned?
- candidates.Remove(P)
- continue
- if ((exclusive_roles.len > 0) && !(P.mind.assigned_role in exclusive_roles)) // Is the rule exclusive to their job?
- candidates.Remove(P)
- continue
+ if(antag_flag_override)
+ if(!(antag_flag_override in P.client.prefs.be_special) || jobban_isbanned(P.ckey, antag_flag_override))
+ candidates.Remove(P)
+ continue
+ else
+ if(!(antag_flag in P.client.prefs.be_special) || jobban_isbanned(P.ckey, antag_flag))
+ candidates.Remove(P)
+ continue
/// Do your checks if the ruleset is ready to be executed here.
/// Should ignore certain checks if forced is TRUE
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm
new file mode 100644
index 0000000000..a20022cb71
--- /dev/null
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm
@@ -0,0 +1,346 @@
+/datum/dynamic_ruleset/event
+ ruletype = "Event"
+ var/typepath // typepath of the event
+ var/triggering
+
+/datum/dynamic_ruleset/event/execute()
+ var/datum/round_event/E = new typepath()
+ E.current_players = get_active_player_count(alive_check = 1, afk_check = 1, human_check = 1)
+ // E.control = src // can't be done! we just don't use events that require these, those can be from_ghost almost always
+
+ testing("[time2text(world.time, "hh:mm:ss")] [E.type]")
+ deadchat_broadcast("[name] has just been triggered by dynamic!")
+ log_game("Random Event triggering: [name] ([typepath])")
+
+ return E
+
+/datum/dynamic_ruleset/event/ready(forced = FALSE) // same as midround cause we're still using enemy system
+ if (!forced)
+ var/job_check = 0
+ if (enemy_roles.len > 0)
+ for (var/mob/M in mode.current_players[CURRENT_LIVING_PLAYERS])
+ if (M.stat == DEAD)
+ continue // Dead players cannot count as opponents
+ if (M.mind && M.mind.assigned_role && (M.mind.assigned_role in enemy_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)
+ if (job_check < required_enemies[threat])
+ return FALSE
+ return TRUE
+
+//////////////////////////////////////////////
+// //
+// PIRATES //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/event/pirates
+ name = "Space Pirates"
+ config_tag = "pirates"
+ typepath = /datum/round_event/pirates
+ antag_flag = ROLE_TRAITOR
+ enemy_roles = list("AI","Security Officer","Head of Security","Captain")
+ required_enemies = list(2,2,1,1,0,0,0,0,0,0)
+ weight = 5
+ cost = 10
+ blocking_rules = list(/datum/dynamic_ruleset/roundstart/nuclear,/datum/dynamic_ruleset/midround/from_ghosts/nuclear)
+ requirements = list(70,60,50,50,40,40,40,30,20,15)
+ high_population_requirement = 15
+
+/datum/dynamic_ruleset/event/pirates/ready(forced = FALSE)
+ if (!SSmapping.empty_space)
+ return FALSE
+ return ..()
+
+//////////////////////////////////////////////
+// //
+// SPIDERS //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/event/spiders
+ name = "Spider Infestation"
+ config_tag = "spiders"
+ typepath = /datum/round_event/spider_infestation
+ enemy_roles = list("AI","Security Officer","Head of Security","Captain")
+ required_enemies = list(2,2,1,1,0,0,0,0,0,0)
+ weight = 5
+ cost = 10
+ requirements = list(70,60,50,50,40,40,40,30,20,15)
+ high_population_requirement = 15
+
+//////////////////////////////////////////////
+// //
+// CLOGGED VENTS //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/event/ventclog
+ name = "Clogged Vents"
+ config_tag = "ventclog"
+ typepath = /datum/round_event/vent_clog
+ enemy_roles = list("Chemist","Medical Doctor","Chief Medical Officer")
+ required_enemies = list(1,1,1,0,0,0,0,0,0,0)
+ cost = 2
+ weight = 4
+ repeatable_weight_decrease = 3
+ requirements = list(5,5,5,5,5,5,5,5,5,5) // yes, can happen on fake-extended
+ high_population_requirement = 5
+ repeatable = TRUE
+
+/datum/dynamic_ruleset/event/ventclog/ready()
+ if(mode.threat_level > 30 && mode.threat >= 5 && prob(20))
+ name = "Clogged Vents: Threatening"
+ cost = 5
+ required_enemies = list(3,3,3,2,2,2,1,1,1,1)
+ typepath = /datum/round_event/vent_clog/threatening
+ else if(mode.threat_level > 15 && mode.threat > 15 && prob(30))
+ name = "Clogged Vents: Catastrophic"
+ cost = 15
+ required_enemies = list(2,2,1,1,1,1,0,0,0,0)
+ typepath = /datum/round_event/vent_clog/catastrophic
+ else
+ cost = 2
+ name = "Clogged Vents: Normal"
+ required_enemies = list(1,1,1,0,0,0,0,0,0,0)
+ typepath = /datum/round_event/vent_clog
+ return ..()
+
+//////////////////////////////////////////////
+// //
+// ION STORM //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/event/ion_storm
+ name = "Ion Storm"
+ config_tag = "ion_storm"
+ typepath = /datum/round_event/ion_storm
+ enemy_roles = list("Research Director","Captain","Chief Engineer")
+ required_enemies = list(1,1,0,0,0,0,0,0,0,0)
+ weight = 4
+ // no repeatable weight decrease. too variable to be unfun multiple times in one round
+ cost = 3
+ requirements = list(5,5,5,5,5,5,5,5,5,5)
+ high_population_requirement = 5
+ repeatable = TRUE
+
+//////////////////////////////////////////////
+// //
+// METEORS //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/event/meteor_wave
+ name = "Meteor Wave"
+ config_tag = "meteor_wave"
+ typepath = /datum/round_event/meteor_wave
+ enemy_roles = list("Chief Engineer","Station Engineer","Atmospheric Technician","Captain","Cyborg")
+ required_enemies = list(3,3,3,3,3,3,3,3,3,3)
+ cost = 15
+ weight = 3
+ repeatable_weight_decrease = 2
+ requirements = list(60,50,40,30,30,30,30,30,30,30)
+ high_population_requirement = 30
+ repeatable = TRUE
+
+/datum/dynamic_ruleset/event/meteor_wave/ready()
+ if(mode.threat_level > 40 && mode.threat >= 25 && prob(20))
+ cost = 25
+ typepath = /datum/round_event/meteor_wave/threatening
+ else if(mode.threat_level > 50 && mode.threat >= 40 && prob(30))
+ cost = 40
+ typepath = /datum/round_event/meteor_wave/catastrophic
+ else
+ cost = 15
+ typepath = /datum/round_event/meteor_wave
+ return ..()
+
+//////////////////////////////////////////////
+// //
+// ANOMALIES //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/event/anomaly_bluespace
+ name = "Anomaly: Bluespace"
+ config_tag = "anomaly_bluespace"
+ typepath = /datum/round_event/anomaly/anomaly_bluespace
+ enemy_roles = list("Chief Engineer","Station Engineer","Atmospheric Technician","Research Director","Scientist","Captain")
+ required_enemies = list(1,1,1,0,0,0,0,0,0,0)
+ weight = 2
+ repeatable_weight_decrease = 1
+ cost = 3
+ requirements = list(5,5,5,5,5,5,5,5,5,5)
+ high_population_requirement = 5
+ repeatable = TRUE
+
+/datum/dynamic_ruleset/event/anomaly_flux
+ name = "Anomaly: Hyper-Energetic Flux"
+ config_tag = "anomaly_flux"
+ typepath = /datum/round_event/anomaly/anomaly_flux
+ enemy_roles = list("Chief Engineer","Station Engineer","Atmospheric Technician","Research Director","Scientist","Captain")
+ required_enemies = list(1,1,1,0,0,0,0,0,0,0)
+ weight = 2
+ repeatable_weight_decrease = 1
+ cost = 5
+ requirements = list(5,5,5,5,5,5,5,5,5,5)
+ high_population_requirement = 10
+ repeatable = TRUE
+
+/datum/dynamic_ruleset/event/anomaly_gravitational
+ name = "Anomaly: Gravitational"
+ config_tag = "anomaly_gravitational"
+ typepath = /datum/round_event/anomaly/anomaly_grav
+ weight = 2
+ repeatable_weight_decrease = 1
+ cost = 3
+ requirements = list(5,5,5,5,5,5,5,5,5,5)
+ high_population_requirement = 5
+ repeatable = TRUE
+
+/datum/dynamic_ruleset/event/anomaly_pyroclastic
+ name = "Anomaly: Pyroclastic"
+ config_tag = "anomaly_pyroclastic"
+ typepath = /datum/round_event/anomaly/anomaly_pyro
+ weight = 2
+ repeatable_weight_decrease = 1
+ cost = 5
+ enemy_roles = list("Chief Engineer","Station Engineer","Atmospheric Technician","Research Director","Scientist","Captain","Cyborg")
+ required_enemies = list(1,1,1,1,1,1,1,1,1,1)
+ requirements = list(10,10,10,10,10,10,10,10,10,10)
+ high_population_requirement = 10
+ repeatable = TRUE
+
+/datum/dynamic_ruleset/event/anomaly_vortex
+ name = "Anomaly: Vortex"
+ config_tag = "anomaly_vortex"
+ typepath = /datum/round_event/anomaly/anomaly_vortex
+ weight = 2
+ repeatable_weight_decrease = 1
+ cost = 5
+ enemy_roles = list("Chief Engineer","Station Engineer","Atmospheric Technician","Research Director","Scientist","Captain","Cyborg")
+ required_enemies = list(1,1,1,1,1,1,1,1,1,1)
+ requirements = list(10,10,10,10,10,10,10,10,10,10)
+ high_population_requirement = 10
+ repeatable = TRUE
+
+//////////////////////////////////////////////
+// //
+// WOW THAT'S A LOT OF EVENTS //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/event/brand_intelligence
+ name = "Brand Intelligence"
+ config_tag = "brand_intelligence"
+ typepath = /datum/round_event/brand_intelligence
+ weight = 1
+ repeatable_weight_decrease = 1
+ cost = 2
+ enemy_roles = list("Chief Engineer","Station Engineer","Atmospheric Technician","Research Director","Scientist","Captain","Cyborg")
+ required_enemies = list(1,1,1,1,0,0,0,0,0,0)
+ requirements = list(10,10,10,10,10,10,10,10,10,10)
+ high_population_requirement = 10
+ repeatable = TRUE
+
+/datum/dynamic_ruleset/event/carp_migration
+ name = "Carp Migration"
+ config_tag = "carp_migration"
+ typepath = /datum/round_event/carp_migration
+ weight = 7
+ repeatable_weight_decrease = 3
+ cost = 4
+ requirements = list(10,10,10,10,10,10,10,10,10,10)
+ high_population_requirement = 10
+ repeatable = TRUE
+
+/datum/dynamic_ruleset/event/communications_blackout
+ name = "Communications Blackout"
+ config_tag = "communications_blackout"
+ typepath = /datum/round_event/communications_blackout
+ cost = 4
+ weight = 2
+ repeatable_weight_decrease = 3
+ enemy_roles = list("Chief Engineer","Station Engineer")
+ required_enemies = list(1,1,1,0,0,0,0,0,0,0)
+ requirements = list(5,5,5,5,5,5,5,5,5,5)
+ high_population_requirement = 5
+ repeatable = TRUE
+
+/datum/dynamic_ruleset/event/processor_overload
+ name = "Processer Overload"
+ config_tag = "processor_overload"
+ typepath = /datum/round_event/processor_overload
+ cost = 4
+ weight = 2
+ repeatable_weight_decrease = 3
+ enemy_roles = list("Chief Engineer","Station Engineer")
+ required_enemies = list(1,1,1,0,0,0,0,0,0,0)
+ requirements = list(5,5,5,5,5,5,5,5,5,5)
+ high_population_requirement = 5
+ repeatable = TRUE
+
+/datum/dynamic_ruleset/event/space_dust
+ name = "Minor Space Dust"
+ config_tag = "space_dust"
+ typepath = /datum/round_event/space_dust
+ cost = 2
+ weight = 2
+ repeatable_weight_decrease = 1
+ enemy_roles = list("Chief Engineer","Station Engineer")
+ required_enemies = list(1,1,1,0,0,0,0,0,0,0)
+ requirements = list(5,5,5,5,5,5,5,5,5,5)
+ high_population_requirement = 5
+ repeatable = TRUE
+
+/datum/dynamic_ruleset/event/major_dust
+ name = "Major Space Dust"
+ config_tag = "major_dust"
+ typepath = /datum/round_event/meteor_wave/major_dust
+ cost = 4
+ weight = 2
+ repeatable_weight_decrease = 1
+ enemy_roles = list("Chief Engineer","Station Engineer")
+ required_enemies = list(2,2,2,2,2,2,2,2,2,2)
+ requirements = list(10,10,10,10,10,10,10,10,10,10)
+ high_population_requirement = 10
+ repeatable = TRUE
+
+/datum/dynamic_ruleset/event/electrical_storm
+ name = "Electrical Storm"
+ config_tag = "electrical_storm"
+ typepath = /datum/round_event/electrical_storm
+ cost = 1
+ weight = 2
+ repeatable_weight_decrease = 1
+ enemy_roles = list("Chief Engineer","Station Engineer")
+ required_enemies = list(1,1,1,0,0,0,0,0,0,0)
+ requirements = list(5,5,5,5,5,5,5,5,5,5)
+ high_population_requirement = 5
+ repeatable = TRUE
+
+/datum/dynamic_ruleset/event/heart_attack
+ name = "Random Heart Attack"
+ config_tag = "heart_attack"
+ typepath = /datum/round_event/heart_attack
+ cost = 3
+ weight = 2
+ repeatable_weight_decrease = 1
+ enemy_roles = list("Medical Doctor","Chief Medical Officer")
+ required_enemies = list(2,2,2,2,2,2,2,2,2,2)
+ requirements = list(101,101,101,5,5,5,5,5,5,5)
+ high_population_requirement = 5
+ repeatable = TRUE
+
+/datum/dynamic_ruleset/event/radiation_storm
+ name = "Radiation Storm"
+ config_tag = "radiation_storm"
+ typepath = /datum/round_event/radiation_storm
+ cost = 3
+ weight = 1
+ enemy_roles = list("Chemist","Chief Medical Officer","Geneticist","Medical Doctor","AI","Captain")
+ required_enemies = list(1,1,1,1,1,1,1,1,1,1)
+ requirements = list(5,5,5,5,5,5,5,5,5,5)
+ high_population_requirement = 5
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
index fde020a3ae..5810fd0ae0 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
@@ -12,9 +12,14 @@
if(!mode.check_age(P.client, minimum_required_age))
candidates.Remove(P)
continue
- if (!(antag_flag in P.client.prefs.be_special) || jobban_isbanned(P.ckey, list(antag_flag, ROLE_SYNDICATE)) || (antag_flag_override && jobban_isbanned(P.ckey, list(antag_flag_override))))//are they willing and not antag-banned?
- candidates.Remove(P)
- continue
+ if(antag_flag_override)
+ if(!(antag_flag_override in P.client.prefs.be_special) || jobban_isbanned(P.ckey, list(antag_flag_override)))
+ candidates.Remove(P)
+ continue
+ else
+ if(!(antag_flag in P.client.prefs.be_special) || jobban_isbanned(P.ckey, list(antag_flag, ROLE_SYNDICATE)))
+ candidates.Remove(P)
+ continue
if (P.mind.assigned_role in restricted_roles) // Does their job allow for it?
candidates.Remove(P)
continue
@@ -60,8 +65,8 @@
required_candidates = 1
weight = 7
cost = 5
- requirements = list(40,30,20,10,10,10,10,10,10,10)
- high_population_requirement = 10
+ requirements = list(40,30,20,15,15,15,15,15,15,15)
+ high_population_requirement = 15
repeatable = TRUE
flags = TRAITOR_RULESET
@@ -73,40 +78,138 @@
/datum/dynamic_ruleset/latejoin/provocateur
name = "Provocateur"
+ persistent = TRUE
config_tag = "latejoin_revolution"
antag_datum = /datum/antagonist/rev/head
antag_flag = ROLE_REV_HEAD
antag_flag_override = ROLE_REV
restricted_roles = list("AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
enemy_roles = list("AI", "Cyborg", "Security Officer","Detective","Head of Security", "Captain", "Warden")
- required_enemies = list(2,2,1,1,1,1,1,0,0,0)
+ required_enemies = list(4,4,3,3,3,3,3,2,2,1)
required_candidates = 1
weight = 2
+ delay = 1 MINUTES // Prevents rule start while head is offstation.
cost = 20
- requirements = list(101,101,70,40,30,20,20,20,20,20)
- high_population_requirement = 50
+ requirements = list(101,101,70,40,40,40,40,40,40,40)
+ high_population_requirement = 40
flags = HIGHLANDER_RULESET
- var/required_heads = 3
+ var/required_heads_of_staff = 3
+ var/finished = FALSE
+ var/datum/team/revolution/revolution
/datum/dynamic_ruleset/latejoin/provocateur/ready(forced=FALSE)
if (forced)
- required_heads = 1
+ required_heads_of_staff = 1
if(!..())
return FALSE
var/head_check = 0
for(var/mob/player in mode.current_players[CURRENT_LIVING_PLAYERS])
if (player.mind.assigned_role in GLOB.command_positions)
head_check++
- return (head_check >= required_heads)
+ return (head_check >= required_heads_of_staff)
/datum/dynamic_ruleset/latejoin/provocateur/execute()
- var/mob/M = pick(candidates)
- assigned += M.mind
- M.mind.special_role = antag_flag
- var/datum/antagonist/rev/head/new_head = new()
- new_head.give_flash = TRUE
- new_head.give_hud = TRUE
- new_head.remove_clumsy = TRUE
- new_head = M.mind.add_antag_datum(new_head)
- new_head.rev_team.max_headrevs = 1 // Only one revhead if it is latejoin.
+ var/mob/M = pick(candidates) // This should contain a single player, but in case.
+ if(check_eligible(M.mind)) // Didnt die/run off z-level/get implanted since leaving shuttle.
+ assigned += M.mind
+ M.mind.special_role = antag_flag
+ revolution = new()
+ var/datum/antagonist/rev/head/new_head = new()
+ new_head.give_flash = TRUE
+ new_head.give_hud = TRUE
+ new_head.remove_clumsy = TRUE
+ new_head = M.mind.add_antag_datum(new_head, revolution)
+ revolution.update_objectives()
+ revolution.update_heads()
+ SSshuttle.registerHostileEnvironment(src)
+ return TRUE
+ else
+ log_game("DYNAMIC: [ruletype] [name] discarded [M.name] from head revolutionary due to ineligibility.")
+ log_game("DYNAMIC: [ruletype] [name] failed to get any eligible headrevs. Refunding [cost] threat.")
+ return FALSE
+
+/datum/dynamic_ruleset/latejoin/provocateur/rule_process()
+ if(check_rev_victory())
+ finished = REVOLUTION_VICTORY
+ return RULESET_STOP_PROCESSING
+ else if (check_heads_victory())
+ finished = STATION_VICTORY
+ SSshuttle.clearHostileEnvironment(src)
+ priority_announce("It appears the mutiny has been quelled. Please return yourself and your colleagues to work. \
+ We have remotely blacklisted the head revolutionaries from your cloning software to prevent accidental cloning.", null, "attention", null, "Central Command Loyalty Monitoring Division")
+ for(var/datum/mind/M in revolution.members) // Remove antag datums and prevent headrev cloning then restarting rebellions.
+ if(M.has_antag_datum(/datum/antagonist/rev/head))
+ var/datum/antagonist/rev/head/R = M.has_antag_datum(/datum/antagonist/rev/head)
+ R.remove_revolutionary(FALSE, "gamemode")
+ var/mob/living/carbon/C = M.current
+ if(C.stat == DEAD)
+ C.makeUncloneable()
+ if(M.has_antag_datum(/datum/antagonist/rev))
+ var/datum/antagonist/rev/R = M.has_antag_datum(/datum/antagonist/rev)
+ R.remove_revolutionary(FALSE, "gamemode")
+ return RULESET_STOP_PROCESSING
+
+/// Checks for revhead loss conditions and other antag datums.
+/datum/dynamic_ruleset/latejoin/provocateur/proc/check_eligible(var/datum/mind/M)
+ var/turf/T = get_turf(M.current)
+ if(!considered_afk(M) && considered_alive(M) && is_station_level(T.z) && !M.antag_datums?.len && !HAS_TRAIT(M, TRAIT_MINDSHIELD))
+ return TRUE
+ return FALSE
+
+/datum/dynamic_ruleset/latejoin/provocateur/check_finished()
+ if(finished == REVOLUTION_VICTORY)
+ return TRUE
+ else
+ return ..()
+
+/datum/dynamic_ruleset/latejoin/provocateur/proc/check_rev_victory()
+ for(var/datum/objective/mutiny/objective in revolution.objectives)
+ if(!(objective.check_completion()))
+ return FALSE
return TRUE
+
+/datum/dynamic_ruleset/latejoin/provocateur/proc/check_heads_victory()
+ for(var/datum/mind/rev_mind in revolution.head_revolutionaries())
+ var/turf/T = get_turf(rev_mind.current)
+ if(!considered_afk(rev_mind) && considered_alive(rev_mind) && is_station_level(T.z))
+ if(ishuman(rev_mind.current) || ismonkey(rev_mind.current))
+ return FALSE
+ return TRUE
+
+/datum/dynamic_ruleset/latejoin/provocateur/round_result()
+ if(finished == REVOLUTION_VICTORY)
+ SSticker.mode_result = "win - heads killed"
+ SSticker.news_report = REVS_WIN
+ else if(finished == STATION_VICTORY)
+ SSticker.mode_result = "loss - rev heads killed"
+ SSticker.news_report = REVS_LOSE
+
+//////////////////////////////////////////////
+// //
+// VAMPIRE //
+// //
+//////////////////////////////////////////////
+
+/*
+/datum/dynamic_ruleset/latejoin/vampire
+ name = "vampire"
+ config_tag = "vampire_latejoin"
+ antag_flag = ROLE_VAMPIRE
+ antag_datum = ANTAG_DATUM_VAMPIRE
+ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain")
+ restricted_roles = list("AI", "Cyborg")
+ required_candidates = 1
+ weight = 5
+ cost = 15
+ requirements = list(80,70,60,50,40,20,20,15,15,15)
+ repeatable = TRUE
+ high_population_requirement = 15
+
+/datum/dynamic_ruleset/latejoin/vampire/pre_execute()
+ var/mob/M = pick(candidates)
+ candidates -= M
+ assigned += M.mind
+ M.mind.restricted_roles = restricted_roles
+ M.mind.special_role = ROLE_VAMPIRE
+ return TRUE
+*/
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
index cc90821618..9d4960858d 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
@@ -1,3 +1,6 @@
+#define REVENANT_SPAWN_THRESHOLD 20
+#define ABDUCTOR_MAX_TEAMS 4 // blame TG for not using the defines files
+
//////////////////////////////////////////////
// //
// MIDROUND RULESETS //
@@ -8,7 +11,7 @@
ruletype = "Midround"
/// If the ruleset should be restricted from ghost roles.
var/restrict_ghost_roles = TRUE
- /// What type the ruleset is restricted to.
+ /// What mob type the ruleset is restricted to.
var/required_type = /mob/living/carbon/human
var/list/living_players = list()
var/list/living_antags = list()
@@ -17,12 +20,11 @@
/datum/dynamic_ruleset/midround/from_ghosts
weight = 0
+ required_type = /mob/dead/observer
/// Whether the ruleset should call generate_ruleset_body or not.
var/makeBody = TRUE
/datum/dynamic_ruleset/midround/trim_candidates()
- // Unlike the previous two types, these rulesets are not meant for /mob/dead/new_player
- // And since I want those rulesets to be as flexible as possible, I'm not gonna put much here,
//
// All you need to know is that here, the candidates list contains 4 lists itself, indexed with the following defines:
// Candidates = list(CURRENT_LIVING_PLAYERS, CURRENT_LIVING_ANTAGS, CURRENT_DEAD_PLAYERS, CURRENT_OBSERVERS)
@@ -31,12 +33,10 @@
// We're still gonna trim the obvious (mobs without clients, jobbanned players, etc)
living_players = trim_list(mode.current_players[CURRENT_LIVING_PLAYERS])
living_antags = trim_list(mode.current_players[CURRENT_LIVING_ANTAGS])
- dead_players = trim_list(mode.current_players[CURRENT_DEAD_PLAYERS])
list_observers = trim_list(mode.current_players[CURRENT_OBSERVERS])
/datum/dynamic_ruleset/midround/proc/trim_list(list/L = list())
var/list/trimmed_list = L.Copy()
- var/antag_name = initial(antag_flag)
for(var/mob/M in trimmed_list)
if (!istype(M, required_type))
trimmed_list.Remove(M)
@@ -47,14 +47,19 @@
if(!mode.check_age(M.client, minimum_required_age))
trimmed_list.Remove(M)
continue
- if (!(antag_name in M.client.prefs.be_special) || jobban_isbanned(M.ckey, list(antag_name, ROLE_SYNDICATE)))//are they willing and not antag-banned?
- trimmed_list.Remove(M)
- continue
+ if(antag_flag_override)
+ if(!(antag_flag_override in M.client.prefs.be_special) || jobban_isbanned(M.ckey, antag_flag_override))
+ trimmed_list.Remove(M)
+ continue
+ else
+ if(!(antag_flag in M.client.prefs.be_special) || jobban_isbanned(M.ckey, antag_flag))
+ trimmed_list.Remove(M)
+ continue
if (M.mind)
if (restrict_ghost_roles && M.mind.assigned_role in GLOB.exp_specialmap[EXP_TYPE_SPECIAL]) // Are they playing a ghost role?
trimmed_list.Remove(M)
continue
- if (M.mind.assigned_role in restricted_roles || HAS_TRAIT(M, TRAIT_MINDSHIELD)) // Does their job allow it or are they mindshielded?
+ if (M.mind.assigned_role in restricted_roles) // Does their job allow it?
trimmed_list.Remove(M)
continue
if ((exclusive_roles.len > 0) && !(M.mind.assigned_role in exclusive_roles)) // Is the rule exclusive to their job?
@@ -71,7 +76,7 @@
if (!forced)
var/job_check = 0
if (enemy_roles.len > 0)
- for (var/mob/M in living_players)
+ for (var/mob/M in mode.current_players[CURRENT_LIVING_PLAYERS])
if (M.stat == DEAD)
continue // Dead players cannot count as opponents
if (M.mind && M.mind.assigned_role && (M.mind.assigned_role in enemy_roles) && (!(M in candidates) || (M.mind.assigned_role in restricted_roles)))
@@ -100,13 +105,13 @@
message_admins("Polling [possible_volunteers.len] players to apply for the [name] ruleset.")
log_game("DYNAMIC: Polling [possible_volunteers.len] players to apply for the [name] ruleset.")
- candidates = pollGhostCandidates("The mode is looking for volunteers to become [antag_flag] for [name]", antag_flag, SSticker.mode, antag_flag, poll_time = 300)
+ candidates = pollGhostCandidates("The mode is looking for volunteers to become a [name]", antag_flag, SSticker.mode, antag_flag, poll_time = 300)
if(!candidates || candidates.len <= 0)
message_admins("The ruleset [name] received no applications.")
log_game("DYNAMIC: The ruleset [name] received no applications.")
mode.refund_threat(cost)
- mode.threat_log += "[worldtime2text()]: Rule [name] refunded [cost] (no applications)"
+ mode.log_threat("Rule [name] refunded [cost] (no applications)",verbose=TRUE)
mode.executed_rules -= src
return
@@ -122,7 +127,7 @@
if(i == 1)
// We have found no candidates so far and we are out of applicants.
mode.refund_threat(cost)
- mode.threat_log += "[worldtime2text()]: Rule [name] refunded [cost] (all applications invalid)"
+ mode.log_threat("Rule [name] refunded [cost] (all applications invalid)",verbose=TRUE)
mode.executed_rules -= src
break
var/mob/applicant = pick(candidates)
@@ -177,9 +182,9 @@
required_candidates = 1
weight = 7
cost = 10
- requirements = list(50,40,30,20,10,10,10,10,10,10)
+ requirements = list(30,25,20,15,15,15,15,15,15,15)
repeatable = TRUE
- high_population_requirement = 10
+ high_population_requirement = 15
flags = TRAITOR_RULESET
/datum/dynamic_ruleset/midround/autotraitor/acceptable(population = 0, threat = 0)
@@ -225,24 +230,24 @@
/datum/dynamic_ruleset/midround/malf
name = "Malfunctioning AI"
- config_tag = "malf_ai"
+ config_tag = "midround_malf_ai"
antag_datum = /datum/antagonist/traitor
antag_flag = ROLE_MALF
enemy_roles = list("Security Officer", "Warden","Detective","Head of Security", "Captain", "Scientist", "Chemist", "Research Director", "Chief Engineer")
exclusive_roles = list("AI")
- required_enemies = list(4,4,4,4,4,4,2,2,2,0)
+ required_enemies = list(6,6,6,4,4,4,2,2,2,1)
required_candidates = 1
- weight = 3
+ weight = 2
cost = 35
- requirements = list(101,101,80,70,60,60,50,50,40,40)
- high_population_requirement = 35
+ requirements = list(101,101,70,50,50,50,40,30,30,30)
+ high_population_requirement = 30
required_type = /mob/living/silicon/ai
var/ion_announce = 33
var/removeDontImproveChance = 10
/datum/dynamic_ruleset/midround/malf/trim_candidates()
..()
- candidates = candidates[CURRENT_LIVING_PLAYERS]
+ living_players = candidates[CURRENT_LIVING_PLAYERS]
for(var/mob/living/player in candidates)
if(!isAI(player))
candidates -= player
@@ -256,8 +261,7 @@
/datum/dynamic_ruleset/midround/malf/execute()
if(!candidates || !candidates.len)
return FALSE
- var/mob/living/silicon/ai/M = pick(candidates)
- candidates -= M
+ var/mob/living/silicon/ai/M = pick_n_take(candidates)
assigned += M.mind
var/datum/antagonist/traitor/AI = new
M.mind.special_role = antag_flag
@@ -282,12 +286,12 @@
antag_datum = /datum/antagonist/wizard
antag_flag = ROLE_WIZARD
enemy_roles = list("Security Officer","Detective","Head of Security", "Captain")
- required_enemies = list(2,2,1,1,1,1,1,0,0,0)
+ required_enemies = list(4,4,3,2,2,1,1,0,0,0)
required_candidates = 1
weight = 1
cost = 20
- requirements = list(90,90,70,40,30,20,10,10,10,10)
- high_population_requirement = 50
+ requirements = list(90,90,70,50,50,50,50,40,30,30)
+ high_population_requirement = 30
repeatable = TRUE
/datum/dynamic_ruleset/midround/from_ghosts/wizard/ready(forced = FALSE)
@@ -315,12 +319,12 @@
antag_flag = ROLE_OPERATIVE
antag_datum = /datum/antagonist/nukeop
enemy_roles = list("AI", "Cyborg", "Security Officer", "Warden","Detective","Head of Security", "Captain")
- required_enemies = list(3,3,3,3,3,2,1,1,0,0)
+ required_enemies = list(5,5,4,3,3,2,2,2,1,1)
required_candidates = 5
weight = 5
cost = 35
- requirements = list(90,90,90,80,60,40,30,20,10,10)
- high_population_requirement = 10
+ requirements = list(90,90,90,80,70,60,50,40,40,40)
+ high_population_requirement = 40
var/operative_cap = list(2,2,3,3,4,5,5,5,5,5)
var/datum/team/nuclear/nuke_team
flags = HIGHLANDER_RULESET
@@ -328,7 +332,13 @@
/datum/dynamic_ruleset/midround/from_ghosts/nuclear/acceptable(population=0, threat=0)
if (locate(/datum/dynamic_ruleset/roundstart/nuclear) in mode.executed_rules)
return FALSE // Unavailable if nuke ops were already sent at roundstart
- var/indice_pop = min(10,round(living_players.len/5)+1)
+ indice_pop = min(10, round(living_players.len/5)+1)
+ /* NOTE: The above line's magic value of "10" is a hack due to the fact that byond was
+ not recognizing operative_cap as a defined variable. It should be operative_cap.len--
+ and yes, this means that if the len is changed, this variable must be changed along with it.
+ One day, once the mystery of why this issue was occuring is figured out,
+ we may change it back, but until this day comes, we must make it simply 10.
+ */
required_candidates = operative_cap[indice_pop]
return ..()
@@ -359,14 +369,20 @@
antag_datum = /datum/antagonist/blob
antag_flag = ROLE_BLOB
enemy_roles = list("Security Officer", "Detective", "Head of Security", "Captain")
- required_enemies = list(2,2,1,1,1,1,1,0,0,0)
+ required_enemies = list(3,3,2,2,2,1,1,1,1,0)
required_candidates = 1
+ blocking_rules = list(/datum/dynamic_ruleset/roundstart/clockcult)
weight = 4
cost = 10
- requirements = list(101,101,101,80,60,50,30,20,10,10)
+ requirements = list(101,101,101,80,60,50,50,50,50,50)
high_population_requirement = 50
repeatable = TRUE
+/datum/dynamic_ruleset/midround/from_ghosts/blob/ready(forced = FALSE)
+ if (required_candidates > (dead_players.len + list_observers.len))
+ return FALSE
+ return ..()
+
/datum/dynamic_ruleset/midround/from_ghosts/blob/generate_ruleset_body(mob/applicant)
var/body = applicant.become_overmind()
return body
@@ -383,15 +399,21 @@
antag_datum = /datum/antagonist/xeno
antag_flag = ROLE_ALIEN
enemy_roles = list("Security Officer", "Detective", "Head of Security", "Captain")
- required_enemies = list(2,2,1,1,1,1,1,0,0,0)
+ required_enemies = list(3,3,2,2,1,1,1,1,1,0)
required_candidates = 1
weight = 3
cost = 10
- requirements = list(101,101,101,70,50,40,20,15,10,10)
+ requirements = list(101,101,101,70,50,50,50,50,50,50)
high_population_requirement = 50
+ repeatable_weight_decrease = 2
repeatable = TRUE
var/list/vents = list()
+/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/ready(forced = FALSE)
+ if (required_candidates > (dead_players.len + list_observers.len))
+ return FALSE
+ return ..()
+
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/execute()
// 50% chance of being incremented by one
required_candidates += prob(50)
@@ -435,8 +457,9 @@
required_candidates = 1
weight = 3
cost = 10
- requirements = list(101,101,101,70,50,40,20,15,10,10)
+ requirements = list(101,101,101,70,50,40,20,15,15,15)
high_population_requirement = 50
+ repeatable_weight_decrease = 2
repeatable = TRUE
var/list/spawn_locs = list()
@@ -465,3 +488,240 @@
message_admins("[ADMIN_LOOKUPFLW(S)] has been made into a Nightmare by the midround ruleset.")
log_game("DYNAMIC: [key_name(S)] was spawned as a Nightmare by the midround ruleset.")
return S
+
+//////////////////////////////////////////////
+// //
+// SENTIENT DISEASE //
+// //
+//////////////////////////////////////////////
+
+
+/datum/dynamic_ruleset/midround/from_ghosts/sentient_disease
+ name = "Sentient Disease"
+ config_tag = "sentient_disease"
+ antag_flag = ROLE_ALIEN
+ enemy_roles = list("Virologist","Chief Medical Officer","Captain","Chemist")
+ required_enemies = list(2,1,1,1,0,0,0,0,0,0)
+ required_candidates = 1
+ weight = 4
+ cost = 5
+ requirements = list(30,30,20,20,15,10,10,10,10,5) // yes, it can even happen in "extended"!
+ high_population_requirement = 5
+
+/datum/dynamic_ruleset/midround/from_ghosts/sentient_disease/ready(forced = FALSE)
+ if (required_candidates > (dead_players.len + list_observers.len))
+ return FALSE
+ return ..()
+
+/datum/dynamic_ruleset/midround/from_ghosts/sentient_disease/generate_ruleset_body(mob/applicant)
+ var/mob/camera/disease/virus = new /mob/camera/disease(SSmapping.get_station_center())
+ applicant.transfer_ckey(virus, FALSE)
+ INVOKE_ASYNC(virus, /mob/camera/disease/proc/pick_name)
+ message_admins("[ADMIN_LOOKUPFLW(virus)] has been made into a sentient disease by the midround ruleset.")
+ log_game("[key_name(virus)] was spawned as a sentient disease by the midround ruleset.")
+ return virus
+
+//////////////////////////////////////////////
+// //
+// REVENANT //
+// //
+//////////////////////////////////////////////
+
+
+/datum/dynamic_ruleset/midround/from_ghosts/revenant
+ name = "Revenant"
+ config_tag = "revenant"
+ antag_flag = ROLE_REVENANT
+ enemy_roles = list("Chief Engineer","Station Engineer","Captain","Chaplain","AI")
+ required_enemies = list(2,1,1,1,0,0,0,0,0,0)
+ required_candidates = 1
+ weight = 4
+ cost = 5
+ requirements = list(30,30,30,30,20,15,15,15,15,15)
+ high_population_requirement = 15
+ var/list/spawn_locs = list()
+
+/datum/dynamic_ruleset/midround/from_ghosts/revenant/ready(forced = FALSE)
+ var/deadMobs = 0
+ for(var/mob/M in GLOB.dead_mob_list)
+ deadMobs++
+ if(deadMobs < REVENANT_SPAWN_THRESHOLD)
+ return FALSE
+ if(required_candidates > (dead_players.len + list_observers.len))
+ return FALSE
+ for(var/mob/living/L in GLOB.dead_mob_list) //look for any dead bodies
+ var/turf/T = get_turf(L)
+ if(T && is_station_level(T.z))
+ spawn_locs += T
+ if(!spawn_locs.len || spawn_locs.len < 15) //look for any morgue trays, crematoriums, ect if there weren't alot of dead bodies on the station to pick from
+ for(var/obj/structure/bodycontainer/bc in GLOB.bodycontainers)
+ var/turf/T = get_turf(bc)
+ if(T && is_station_level(T.z))
+ spawn_locs += T
+ if(!spawn_locs.len) //If we can't find any valid spawnpoints, try the carp spawns
+ for(var/obj/effect/landmark/carpspawn/L in GLOB.landmarks_list)
+ if(isturf(L.loc))
+ spawn_locs += L.loc
+ if(!spawn_locs.len) //If we can't find THAT, then just give up and cry
+ return FALSE
+ return ..()
+
+/datum/dynamic_ruleset/midround/from_ghosts/revenant/generate_ruleset_body(mob/applicant)
+ var/mob/living/simple_animal/revenant/revvie = new(pick(spawn_locs))
+ applicant.transfer_ckey(revvie, FALSE)
+ message_admins("[ADMIN_LOOKUPFLW(revvie)] has been made into a revenant by the midround ruleset.")
+ log_game("[key_name(revvie)] was spawned as a revenant by the midround ruleset.")
+ return revvie
+
+//////////////////////////////////////////////
+// //
+// SLAUGHTER DEMON //
+// //
+//////////////////////////////////////////////
+
+
+/datum/dynamic_ruleset/midround/from_ghosts/slaughter_demon
+ name = "Slaughter Demon"
+ config_tag = "slaughter_demon"
+ antag_flag = ROLE_ALIEN
+ enemy_roles = list("Security Officer","Shaft Miner","Head of Security","Captain","Janitor","AI","Cyborg")
+ required_enemies = list(3,2,2,2,2,1,1,1,1,0)
+ required_candidates = 1
+ weight = 4
+ cost = 15
+ requirements = list(101,101,101,90,80,70,60,50,40,30)
+ high_population_requirement = 30
+ var/list/spawn_locs = list()
+
+/datum/dynamic_ruleset/midround/from_ghosts/slaughter_demon/ready(forced = FALSE)
+ if(required_candidates > (dead_players.len + list_observers.len))
+ return FALSE
+ for(var/obj/effect/landmark/carpspawn/L in GLOB.landmarks_list)
+ if(isturf(L.loc))
+ spawn_locs += L.loc
+
+ if(!spawn_locs.len)
+ return FALSE
+ return ..()
+
+/datum/dynamic_ruleset/midround/from_ghosts/slaughter_demon/generate_ruleset_body(mob/applicant)
+ var/datum/mind/player_mind = new /datum/mind(applicant.key)
+ player_mind.active = 1
+ var/obj/effect/dummy/phased_mob/slaughter/holder = new /obj/effect/dummy/phased_mob/slaughter((pick(spawn_locs)))
+ var/mob/living/simple_animal/slaughter/S = new (holder)
+ S.holder = holder
+ player_mind.transfer_to(S)
+ player_mind.assigned_role = "Slaughter Demon"
+ player_mind.special_role = "Slaughter Demon"
+ player_mind.add_antag_datum(/datum/antagonist/slaughter)
+ to_chat(S, S.playstyle_string)
+ to_chat(S, "You are currently not currently in the same plane of existence as the station. Blood Crawl near a blood pool to manifest.")
+ SEND_SOUND(S, 'sound/magic/demon_dies.ogg')
+ message_admins("[ADMIN_LOOKUPFLW(S)] has been made into a slaughter demon by dynamic.")
+ log_game("[key_name(S)] was spawned as a slaughter demon by dynamic.")
+ return S
+
+//////////////////////////////////////////////
+// //
+// ABDUCTORS //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/midround/from_ghosts/abductors
+ name = "Abductors"
+ config_tag = "abductors"
+ antag_flag = ROLE_ABDUCTOR
+ // Has two antagonist flags, in fact
+ enemy_roles = list("AI", "Cyborg", "Security Officer", "Warden","Detective","Head of Security", "Captain")
+ required_enemies = list(3,3,2,2,1,1,0,0,0,0)
+ required_candidates = 2
+ weight = 8
+ cost = 10
+ requirements = list(101,101,70,50,40,30,30,20,15,15)
+ blocking_rules = list(/datum/dynamic_ruleset/roundstart/nuclear,/datum/dynamic_ruleset/midround/from_ghosts/nuclear)
+ high_population_requirement = 15
+ var/datum/team/abductor_team/team
+ repeatable_weight_decrease = 4
+ repeatable = TRUE
+
+/datum/dynamic_ruleset/midround/from_ghosts/abductors/ready(forced = FALSE)
+ if(required_candidates > (dead_players.len + list_observers.len))
+ return FALSE
+ team = new /datum/team/abductor_team
+ if(team.team_number > ABDUCTOR_MAX_TEAMS)
+ return FALSE
+ return ..()
+
+/datum/dynamic_ruleset/midround/from_ghosts/abductors/finish_setup(mob/new_character, index)
+ switch(index)
+ if(1) // yeah this seems like a baffling anti-pattern but it's actually the best way to do this, shit you not
+ var/mob/living/carbon/human/agent = new_character
+ agent.mind.add_antag_datum(/datum/antagonist/abductor/agent, team)
+ log_game("[key_name(agent)] has been selected as [team.name] abductor agent.")
+ if(2)
+ var/mob/living/carbon/human/scientist = new_character
+ scientist.mind.add_antag_datum(/datum/antagonist/abductor/scientist, team)
+ log_game("[key_name(scientist)] has been selected as [team.name] abductor scientist.")
+
+//////////////////////////////////////////////
+// //
+// SPACE NINJA //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/midround/from_ghosts/ninja
+ name = "Space Ninja"
+ config_tag = "ninja"
+ antag_flag = ROLE_NINJA
+ enemy_roles = list("Security Officer","Head of Security","Captain","AI","Cyborg")
+ required_enemies = list(3,2,2,2,2,1,1,1,1,0)
+ required_candidates = 1
+ weight = 4
+ cost = 15
+ requirements = list(101,101,101,90,80,70,60,50,40,30)
+ high_population_requirement = 30
+ var/list/spawn_locs = list()
+ var/spawn_loc
+
+/datum/dynamic_ruleset/midround/from_ghosts/ninja/ready(forced = FALSE)
+ if(required_candidates > (dead_players.len + list_observers.len))
+ return FALSE
+ if(!spawn_loc)
+ var/list/spawn_locs = list()
+ for(var/obj/effect/landmark/carpspawn/L in GLOB.landmarks_list)
+ if(isturf(L.loc))
+ spawn_locs += L.loc
+ if(!spawn_locs.len)
+ return FALSE
+ spawn_loc = pick(spawn_locs)
+ if(!spawn_loc)
+ return FALSE
+ return ..()
+
+/datum/dynamic_ruleset/midround/from_ghosts/ninja/generate_ruleset_body(mob/applicant)
+ var/key = applicant.key
+
+ //Prepare ninja player mind
+ var/datum/mind/Mind = new /datum/mind(key)
+ Mind.assigned_role = ROLE_NINJA
+ Mind.special_role = ROLE_NINJA
+ Mind.active = 1
+
+ //spawn the ninja and assign the candidate
+ var/mob/living/carbon/human/Ninja = create_space_ninja(spawn_loc)
+ Mind.transfer_to(Ninja)
+ var/datum/antagonist/ninja/ninjadatum = new
+ ninjadatum.helping_station = pick(TRUE,FALSE)
+ if(ninjadatum.helping_station)
+ mode.refund_threat(5)
+ Mind.add_antag_datum(ninjadatum)
+
+ if(Ninja.mind != Mind) //something has gone wrong!
+ throw EXCEPTION("Ninja created with incorrect mind")
+
+ message_admins("[ADMIN_LOOKUPFLW(Ninja)] has been made into a ninja by dynamic.")
+ log_game("[key_name(Ninja)] was spawned as a ninja by dynamic.")
+ return Ninja
+
+#undef ABDUCTOR_MAX_TEAMS
+#undef REVENANT_SPAWN_THRESHOLD
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
index abca269eeb..9eb06884c1 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
@@ -13,20 +13,20 @@
antag_datum = /datum/antagonist/traitor/
minimum_required_age = 0
protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster", "Cyborg")
- restricted_roles = list("Cyborg")
+ restricted_roles = list("Cyborg", "AI")
required_candidates = 1
weight = 5
- cost = 10
- requirements = list(10,10,10,10,10,10,10,10,10,10)
- high_population_requirement = 10
+ cost = 10 // Avoid raising traitor threat above 10, as it is the default low cost ruleset.
+ scaling_cost = 10
+ requirements = list(50,50,50,50,50,50,50,50,50,50)
+ high_population_requirement = 40
+ antag_cap = list(1,1,1,1,2,2,2,2,3,3)
var/autotraitor_cooldown = 450 // 15 minutes (ticks once per 2 sec)
/datum/dynamic_ruleset/roundstart/traitor/pre_execute()
- var/traitor_scaling_coeff = 10 - max(0,round(mode.threat_level/10)-5) // Above 50 threat level, coeff goes down by 1 for every 10 levels
- var/num_traitors = min(round(mode.candidates.len / traitor_scaling_coeff) + 1, candidates.len)
+ var/num_traitors = antag_cap[indice_pop] * (scaled_times + 1)
for (var/i = 1 to num_traitors)
- var/mob/M = pick(candidates)
- candidates -= M
+ var/mob/M = pick_n_take(candidates)
assigned += M.mind
M.mind.special_role = ROLE_TRAITOR
M.mind.restricted_roles = restricted_roles
@@ -51,32 +51,27 @@
name = "Blood Brothers"
config_tag = "traitorbro"
antag_flag = ROLE_BROTHER
- antag_datum = /datum/antagonist/brother/
+ antag_datum = /datum/antagonist/brother
restricted_roles = list("AI", "Cyborg")
protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_candidates = 2
weight = 4
cost = 10
- requirements = list(40,30,30,20,20,15,15,15,10,10)
- high_population_requirement = 15
+ requirements = list(101,101,101,101,101,101,101,101,101,101)
+ high_population_requirement = 101
+ antag_cap = list(2,2,2,2,2,2,2,2,2,2) // Can pick 3 per team, but rare enough it doesn't matter.
var/list/datum/team/brother_team/pre_brother_teams = list()
- var/const/team_amount = 2 // Hard limit on brother teams if scaling is turned off
var/const/min_team_size = 2
/datum/dynamic_ruleset/roundstart/traitorbro/pre_execute()
- var/num_teams = team_amount
- var/bsc = CONFIG_GET(number/brother_scaling_coeff)
- if(bsc)
- num_teams = max(1, round(num_players() / bsc))
-
+ var/num_teams = (antag_cap[indice_pop]/min_team_size) * (scaled_times + 1) // 1 team per scaling
for(var/j = 1 to num_teams)
if(candidates.len < min_team_size || candidates.len < required_candidates)
break
var/datum/team/brother_team/team = new
var/team_size = prob(10) ? min(3, candidates.len) : 2
for(var/k = 1 to team_size)
- var/mob/bro = pick(candidates)
- candidates -= bro
+ var/mob/bro = pick_n_take(candidates)
assigned += bro.mind
team.add_member(bro.mind)
bro.mind.special_role = "brother"
@@ -109,16 +104,17 @@
protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_candidates = 1
weight = 3
- cost = 30
- requirements = list(80,70,60,50,40,20,20,10,10,10)
+ cost = 15
+ scaling_cost = 15
+ requirements = list(101,101,101,101,101,101,101,101,101,101)
high_population_requirement = 10
+ antag_cap = list(1,1,1,1,1,2,2,2,2,3)
var/team_mode_probability = 30
/datum/dynamic_ruleset/roundstart/changeling/pre_execute()
- var/num_changelings = min(round(mode.candidates.len / 10) + 1, candidates.len)
+ var/num_changelings = antag_cap[indice_pop] * (scaled_times + 1)
for (var/i = 1 to num_changelings)
- var/mob/M = pick(candidates)
- candidates -= M
+ var/mob/M = pick_n_take(candidates)
assigned += M.mind
M.mind.restricted_roles = restricted_roles
M.mind.special_role = ROLE_CHANGELING
@@ -160,8 +156,8 @@
required_candidates = 1
weight = 1
cost = 30
- requirements = list(90,90,70,40,30,20,10,10,10,10)
- high_population_requirement = 10
+ requirements = list(101,101,101,60,50,50,50,50,50,50)
+ high_population_requirement = 50
var/list/roundstart_wizards = list()
/datum/dynamic_ruleset/roundstart/wizard/acceptable(population=0, threat=0)
@@ -174,10 +170,9 @@
/datum/dynamic_ruleset/roundstart/wizard/pre_execute()
if(GLOB.wizardstart.len == 0)
return FALSE
-
- var/mob/M = pick(candidates)
+ mode.antags_rolled += 1
+ var/mob/M = pick_n_take(candidates)
if (M)
- candidates -= M
assigned += M.mind
M.mind.assigned_role = ROLE_WIZARD
M.mind.special_role = ROLE_WIZARD
@@ -207,26 +202,23 @@
required_candidates = 2
weight = 3
cost = 30
- requirements = list(100,90,80,60,40,30,10,10,10,10)
- high_population_requirement = 10
- pop_per_requirement = 5
+ requirements = list(101,101,101,80,70,60,50,50,50,50)
+ high_population_requirement = 50
flags = HIGHLANDER_RULESET
- var/cultist_cap = list(2,2,2,3,3,4,4,4,4,4)
+ antag_cap = list(2,2,2,3,3,4,4,4,4,4)
var/datum/team/cult/main_cult
/datum/dynamic_ruleset/roundstart/bloodcult/ready(forced = FALSE)
- var/indice_pop = min(10,round(mode.roundstart_pop_ready/pop_per_requirement)+1)
- required_candidates = cultist_cap[indice_pop]
+ required_candidates = antag_cap[indice_pop]
. = ..()
/datum/dynamic_ruleset/roundstart/bloodcult/pre_execute()
- var/indice_pop = min(10,round(mode.roundstart_pop_ready/pop_per_requirement)+1)
- var/cultists = cultist_cap[indice_pop]
+ var/cultists = antag_cap[indice_pop]
+ mode.antags_rolled += cultists
for(var/cultists_number = 1 to cultists)
if(candidates.len <= 0)
break
- var/mob/M = pick(candidates)
- candidates -= M
+ var/mob/M = pick_n_take(candidates)
assigned += M.mind
M.mind.special_role = ROLE_CULTIST
M.mind.restricted_roles = restricted_roles
@@ -268,28 +260,24 @@
required_candidates = 5
weight = 3
cost = 40
- requirements = list(90,90,90,80,60,40,30,20,10,10)
- high_population_requirement = 10
- pop_per_requirement = 5
+ requirements = list(100,90,80,70,60,50,50,50,50,50)
+ high_population_requirement = 50
flags = HIGHLANDER_RULESET
- var/operative_cap = list(2,2,2,3,3,3,4,4,5,5)
+ antag_cap = list(2,2,2,3,3,3,4,4,5,5)
var/datum/team/nuclear/nuke_team
/datum/dynamic_ruleset/roundstart/nuclear/ready(forced = FALSE)
- var/indice_pop = min(10,round(mode.roundstart_pop_ready/pop_per_requirement)+1)
- required_candidates = operative_cap[indice_pop]
+ required_candidates = antag_cap[indice_pop]
. = ..()
/datum/dynamic_ruleset/roundstart/nuclear/pre_execute()
// If ready() did its job, candidates should have 5 or more members in it
-
- var/indice_pop = min(10,round(mode.roundstart_pop_ready/5)+1)
- var/operatives = operative_cap[indice_pop]
+ var/operatives = antag_cap[indice_pop]
+ mode.antags_rolled += operatives
for(var/operatives_number = 1 to operatives)
if(candidates.len <= 0)
break
- var/mob/M = pick(candidates)
- candidates -= M
+ var/mob/M = pick_n_take(candidates)
assigned += M.mind
M.mind.assigned_role = "Nuclear Operative"
M.mind.special_role = "Nuclear Operative"
@@ -347,7 +335,7 @@
// //
//////////////////////////////////////////////
-/datum/dynamic_ruleset/roundstart/delayed/revs
+/datum/dynamic_ruleset/roundstart/revs
name = "Revolution"
config_tag = "revolution"
persistent = TRUE
@@ -358,63 +346,98 @@
restricted_roles = list("AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_candidates = 3
weight = 2
+ delay = 7 MINUTES
cost = 35
- requirements = list(101,101,70,40,30,20,10,10,10,10)
- high_population_requirement = 10
- delay = 5 MINUTES
+ requirements = list(101,101,101,60,50,50,50,50,50,50)
+ high_population_requirement = 50
+ antag_cap = list(3,3,3,3,3,3,3,3,3,3)
flags = HIGHLANDER_RULESET
// I give up, just there should be enough heads with 35 players...
minimum_players = 35
var/datum/team/revolution/revolution
- var/finished = 0
+ var/finished = FALSE
-/datum/dynamic_ruleset/roundstart/delayed/revs/execute()
- var/max_canditates = 4
- revolution = new()
- for(var/i = 1 to max_canditates)
+/datum/dynamic_ruleset/roundstart/revs/pre_execute()
+ var/max_candidates = antag_cap[indice_pop]
+ mode.antags_rolled += max_candidates
+ for(var/i = 1 to max_candidates)
if(candidates.len <= 0)
break
- var/mob/M = pick(candidates)
- candidates -= M
+ var/mob/M = pick_n_take(candidates)
assigned += M.mind
M.mind.restricted_roles = restricted_roles
M.mind.special_role = antag_flag
- var/datum/antagonist/rev/head/new_head = new antag_datum()
- new_head.give_flash = TRUE
- new_head.give_hud = TRUE
- new_head.remove_clumsy = TRUE
- M.mind.add_antag_datum(new_head,revolution)
-
- revolution.update_objectives()
- revolution.update_heads()
- SSshuttle.registerHostileEnvironment(src)
-
return TRUE
-/datum/dynamic_ruleset/roundstart/delayed/revs/rule_process()
- if(check_rev_victory())
- finished = 1
- else if(check_heads_victory())
- finished = 2
- return
+/datum/dynamic_ruleset/roundstart/revs/execute()
+ var/success = TRUE
+ revolution = new()
+ for(var/datum/mind/M in assigned)
+ if(check_eligible(M))
+ var/datum/antagonist/rev/head/new_head = new antag_datum()
+ new_head.give_flash = TRUE
+ new_head.give_hud = TRUE
+ new_head.remove_clumsy = TRUE
+ M.add_antag_datum(new_head,revolution)
+ else
+ assigned -= M
+ log_game("DYNAMIC: [ruletype] [name] discarded [M.name] from head revolutionary due to ineligibility.")
+ if(!revolution.members.len)
+ success = FALSE
+ log_game("DYNAMIC: [ruletype] [name] failed to get any eligible headrevs. Refunding [cost] threat.")
+ if(success)
+ revolution.update_objectives()
+ revolution.update_heads()
+ SSshuttle.registerHostileEnvironment(src)
+ return TRUE
+ return FALSE
-/datum/dynamic_ruleset/roundstart/delayed/revs/check_finished()
- if(CONFIG_GET(keyed_list/continuous)["revolution"])
- if(finished)
- SSshuttle.clearHostileEnvironment(src)
- return ..()
- if(finished != 0)
+/datum/dynamic_ruleset/roundstart/revs/clean_up()
+ qdel(revolution)
+ ..()
+
+/datum/dynamic_ruleset/roundstart/revs/rule_process()
+ if(check_rev_victory())
+ finished = REVOLUTION_VICTORY
+ return RULESET_STOP_PROCESSING
+ else if (check_heads_victory())
+ finished = STATION_VICTORY
+ SSshuttle.clearHostileEnvironment(src)
+ priority_announce("It appears the mutiny has been quelled. Please return yourself and your incapacitated colleagues to work. \
+ We have remotely blacklisted the head revolutionaries from your cloning software to prevent accidental cloning.", null, "attention", null, "Central Command Loyalty Monitoring Division")
+
+ for(var/datum/mind/M in revolution.members) // Remove antag datums and prevents podcloned or exiled headrevs restarting rebellions.
+ if(M.has_antag_datum(/datum/antagonist/rev/head))
+ var/datum/antagonist/rev/head/R = M.has_antag_datum(/datum/antagonist/rev/head)
+ R.remove_revolutionary(FALSE, "gamemode")
+ var/mob/living/carbon/C = M.current
+ if(C.stat == DEAD)
+ C.makeUncloneable()
+ if(M.has_antag_datum(/datum/antagonist/rev))
+ var/datum/antagonist/rev/R = M.has_antag_datum(/datum/antagonist/rev)
+ R.remove_revolutionary(FALSE, "gamemode")
+ return RULESET_STOP_PROCESSING
+
+/// Checks for revhead loss conditions and other antag datums.
+/datum/dynamic_ruleset/roundstart/revs/proc/check_eligible(var/datum/mind/M)
+ var/turf/T = get_turf(M.current)
+ if(!considered_afk(M) && considered_alive(M) && is_station_level(T.z) && !M.antag_datums?.len && !HAS_TRAIT(M, TRAIT_MINDSHIELD))
+ return TRUE
+ return FALSE
+
+/datum/dynamic_ruleset/roundstart/revs/check_finished()
+ if(finished == REVOLUTION_VICTORY)
return TRUE
else
return ..()
-/datum/dynamic_ruleset/roundstart/delayed/revs/proc/check_rev_victory()
+/datum/dynamic_ruleset/roundstart/revs/proc/check_rev_victory()
for(var/datum/objective/mutiny/objective in revolution.objectives)
if(!(objective.check_completion()))
return FALSE
return TRUE
-/datum/dynamic_ruleset/roundstart/delayed/revs/proc/check_heads_victory()
+/datum/dynamic_ruleset/roundstart/revs/proc/check_heads_victory()
for(var/datum/mind/rev_mind in revolution.head_revolutionaries())
var/turf/T = get_turf(rev_mind.current)
if(!considered_afk(rev_mind) && considered_alive(rev_mind) && is_station_level(T.z))
@@ -422,11 +445,11 @@
return FALSE
return TRUE
-/datum/dynamic_ruleset/roundstart/delayed/revs/round_result()
- if(finished == 1)
+/datum/dynamic_ruleset/roundstart/revs/round_result()
+ if(finished == REVOLUTION_VICTORY)
SSticker.mode_result = "win - heads killed"
SSticker.news_report = REVS_WIN
- else if(finished == 2)
+ else if(finished == STATION_VICTORY)
SSticker.mode_result = "loss - rev heads killed"
SSticker.news_report = REVS_LOSE
@@ -470,10 +493,11 @@
restricted_roles = list("AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_candidates = 4
weight = 3
- cost = 0
- requirements = list(101,101,101,101,101,101,101,101,101,101)
- high_population_requirement = 101
+ cost = 35
+ requirements = list(101,101,101,80,70,60,50,50,50,50)
+ high_population_requirement = 50
flags = HIGHLANDER_RULESET
+ antag_cap = list(2,3,3,4,4,4,4,4,4,4)
var/ark_time
/datum/dynamic_ruleset/roundstart/clockcult/pre_execute()
@@ -486,16 +510,15 @@
for(var/datum/parsed_map/PM in reebes)
PM.initTemplateBounds()
- var/starter_servants = 4
- var/number_players = num_players()
+ var/starter_servants = antag_cap[indice_pop]
+ var/number_players = mode.roundstart_pop_ready
if(number_players > 30)
number_players -= 30
- starter_servants += round(number_players / 10)
- starter_servants = min(starter_servants, 8)
- GLOB.clockwork_vitality += 50 * starter_servants //some starter Vitality to help recover from initial fuck ups
+ starter_servants += min(round(number_players / 10), 5)
+ mode.antags_rolled += starter_servants
+ GLOB.clockwork_vitality += 50 * starter_servants
for (var/i in 1 to starter_servants)
- var/mob/servant = pick(candidates)
- candidates -= servant
+ var/mob/servant = pick_n_take(candidates)
assigned += servant.mind
servant.mind.assigned_role = ROLE_SERVANT_OF_RATVAR
servant.mind.special_role = ROLE_SERVANT_OF_RATVAR
@@ -604,23 +627,16 @@
cost = 0
requirements = list(101,101,101,101,101,101,101,101,101,101)
high_population_requirement = 101
- var/devil_limit = 4 // Hard limit on devils if scaling is turned off
+ antag_cap = list(1,1,1,2,2,2,3,3,3,4)
/datum/dynamic_ruleset/roundstart/devil/pre_execute()
- var/tsc = CONFIG_GET(number/traitor_scaling_coeff)
- var/num_devils = 1
-
- if(tsc)
- num_devils = max(required_candidates, min(round(num_players() / (tsc * 3)) + 2, round(num_players() / (tsc * 1.5))))
- else
- num_devils = max(required_candidates, min(num_players(), devil_limit))
-
+ var/num_devils = antag_cap[indice_pop]
+ mode.antags_rolled += num_devils
for(var/j = 0, j < num_devils, j++)
if (!candidates.len)
break
- var/mob/devil = pick(candidates)
- assigned += devil
- candidates -= devil
+ var/mob/devil = pick_n_take(candidates)
+ assigned += devil.mind
devil.mind.special_role = ROLE_DEVIL
devil.mind.restricted_roles = restricted_roles
@@ -669,13 +685,13 @@
var/datum/team/monkey/monkey_team
/datum/dynamic_ruleset/roundstart/monkey/pre_execute()
- var/carriers_to_make = max(round(num_players()/players_per_carrier, 1), 1)
+ var/carriers_to_make = max(round(mode.roundstart_pop_ready / players_per_carrier, 1), 1)
+ mode.antags_rolled += carriers_to_make
for(var/j = 0, j < carriers_to_make, j++)
if (!candidates.len)
break
- var/mob/carrier = pick(candidates)
- candidates -= carrier
+ var/mob/carrier = pick_n_take(candidates)
assigned += carrier.mind
carrier.mind.special_role = "Monkey Leader"
carrier.mind.restricted_roles = restricted_roles
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 7fafca69f0..87db105072 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -686,6 +686,20 @@
log_admin("[key_name(usr)] set the pre-game delay to [DisplayTimeText(newtime)].")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Delay Game Start") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+/datum/admins/proc/toggledynamicvote()
+ set category = "Server"
+ set desc="Switches between secret/extended and dynamic voting"
+ set name="Toggle Dynamic Vote"
+ var/prev_dynamic_voting = CONFIG_GET(flag/dynamic_voting)
+ CONFIG_SET(flag/dynamic_voting,!prev_dynamic_voting)
+ if (!prev_dynamic_voting)
+ to_chat(world, "Vote is now between extended and dynamic chaos.")
+ else
+ to_chat(world, "Vote is now between extended and secret.")
+ log_admin("[key_name(usr)] [prev_dynamic_voting ? "disabled" : "enabled"] dynamic voting.")
+ message_admins("[key_name_admin(usr)] toggled dynamic voting.")
+ SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Dynamic Voting", "[prev_dynamic_voting ? "Disabled" : "Enabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+
/datum/admins/proc/unprison(mob/M in GLOB.mob_list)
set category = "Admin"
set name = "Unprison"
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index cb5b33ae08..5900847ef5 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -121,6 +121,7 @@ GLOBAL_LIST_INIT(admin_verbs_server, world.AVerbsServer())
/client/proc/everyone_random,
/datum/admins/proc/toggleAI,
/datum/admins/proc/toggleMulticam,
+ /datum/admins/proc/toggledynamicvote,
/client/proc/cmd_admin_delete, /*delete an instance/object/mob/etc*/
/client/proc/cmd_debug_del_all,
/client/proc/toggle_random_events,
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 8b105da43b..4de2d9b5ca 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -1449,9 +1449,7 @@
if(GLOB.master_mode != "dynamic")
return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
- var/new_centre = input(usr,"Change the centre of the dynamic mode threat curve. A negative value will give a more peaceful round ; a positive value, a round with higher threat. Any number between -5 and +5 is allowed.", "Change curve centre", null) as num
- if (new_centre < -5 || new_centre > 5)
- return alert(usr, "Only values between -5 and +5 are allowed.", null, null, null, null)
+ var/new_centre = input(usr,"Change the centre of the dynamic mode threat curve. A negative value will give a more peaceful round ; a positive value, a round with higher threat. Any number is allowed. This is adjusted by dynamic voting.", "Change curve centre", null) as num
log_admin("[key_name(usr)] changed the distribution curve center to [new_centre].")
message_admins("[key_name(usr)] changed the distribution curve center to [new_centre]", 1)
diff --git a/code/modules/antagonists/disease/disease_event.dm b/code/modules/antagonists/disease/disease_event.dm
index 385cee998b..4365fd7538 100644
--- a/code/modules/antagonists/disease/disease_event.dm
+++ b/code/modules/antagonists/disease/disease_event.dm
@@ -3,6 +3,7 @@
name = "Spawn Sentient Disease"
typepath = /datum/round_event/ghost_role/sentient_disease
weight = 7
+ gamemode_blacklist = list("dynamic")
max_occurrences = 1
min_players = 5
diff --git a/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm b/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm
index 78e4d38b3c..0a223f8b10 100644
--- a/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm
+++ b/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm
@@ -74,6 +74,11 @@ GLOBAL_VAR_INIT(war_declared, FALSE)
new uplink_type(get_turf(user), user.key, CHALLENGE_TELECRYSTALS - tc_malus + CEILING(PLAYER_SCALING * actual_players, 1))
CONFIG_SET(number/shuttle_refuel_delay, max(CONFIG_GET(number/shuttle_refuel_delay), CHALLENGE_SHUTTLE_DELAY))
+ if(istype(SSticker.mode, /datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ var/threat_spent = CONFIG_GET(number/dynamic_warops_cost)
+ mode.spend_threat(threat_spent)
+ mode.log_threat("Nuke ops spent [threat_spent] on war ops.")
SSblackbox.record_feedback("amount", "nuclear_challenge_mode", 1)
qdel(src)
@@ -94,6 +99,14 @@ GLOBAL_VAR_INIT(war_declared, FALSE)
if(board.moved)
to_chat(user, "The shuttle has already been moved! You have forfeit the right to declare war.")
return FALSE
+ if(istype(SSticker.mode, /datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ if(mode.threat_level < CONFIG_GET(number/dynamic_warops_requirement))
+ to_chat(user, "Due to the dynamic space in which the station resides, you are too deep into Nanotrasen territory to reasonably go loud.")
+ return FALSE
+ else if(mode.threat < CONFIG_GET(number/dynamic_warops_cost))
+ to_chat(user, "Due to recent threats on the station, Nanotrasen is looking too closely for a war declaration to be wise.")
+ return FALSE
return TRUE
/obj/item/nuclear_challenge/clownops
diff --git a/code/modules/antagonists/revenant/revenant_spawn_event.dm b/code/modules/antagonists/revenant/revenant_spawn_event.dm
index cb534b6613..7bb7f1aa76 100644
--- a/code/modules/antagonists/revenant/revenant_spawn_event.dm
+++ b/code/modules/antagonists/revenant/revenant_spawn_event.dm
@@ -4,6 +4,7 @@
name = "Spawn Revenant" // Did you mean 'griefghost'?
typepath = /datum/round_event/ghost_role/revenant
weight = 7
+ gamemode_blacklist = list("dynamic")
max_occurrences = 1
min_players = 5
diff --git a/code/modules/antagonists/revolution/revolution.dm b/code/modules/antagonists/revolution/revolution.dm
index fef7061488..c4190d8cd9 100644
--- a/code/modules/antagonists/revolution/revolution.dm
+++ b/code/modules/antagonists/revolution/revolution.dm
@@ -207,9 +207,20 @@
owner.current.visible_message("The frame beeps contentedly, purging the hostile memory engram from the MMI before initalizing it.", null, null, null, owner.current)
to_chat(owner, "The frame's firmware detects and deletes your neural reprogramming! You remember nothing of your time spent reprogrammed, you can't even remember the names or identities of anyone involved...")
+/datum/antagonist/rev/head/farewell()
+ if((ishuman(owner.current) || ismonkey(owner.current)))
+ if(owner.current.stat != DEAD)
+ owner.current.visible_message("[owner.current] looks like [owner.current.p_theyve()] just remembered [owner.current.p_their()] real allegiance!", null, null, null, owner.current)
+ to_chat(owner, "You have given up your cause of overthrowing the command staff. You are no longer a Head Revolutionary.")
+ else
+ to_chat(owner, "The sweet release of death. You are no longer a Head Revolutionary.")
+ else if(issilicon(owner.current))
+ owner.current.visible_message("The frame beeps contentedly, suppressing the disloyal personality traits from the MMI before initalizing it.", null, null, null, owner.current)
+ to_chat(owner, "The frame's firmware detects and suppresses your unwanted personality traits! You feel more content with the leadership around these parts.")
+
//blunt trauma deconversions call this through species.dm spec_attacked_by()
/datum/antagonist/rev/proc/remove_revolutionary(borged, deconverter)
- log_attack("[key_name(owner.current)] has been deconverted from the revolution by [key_name(deconverter)]!")
+ log_attack("[key_name(owner.current)] has been deconverted from the revolution by [ismob(deconverter) ? key_name(deconverter) : deconverter]!")
if(borged)
message_admins("[ADMIN_LOOKUPFLW(owner.current)] has been borged while being a [name]")
owner.special_role = null
@@ -219,9 +230,8 @@
owner.remove_antag_datum(type)
/datum/antagonist/rev/head/remove_revolutionary(borged,deconverter)
- if(!borged)
- return
- . = ..()
+ if(borged || deconverter == "gamemode")
+ . = ..()
/datum/antagonist/rev/head/equip_rev()
var/mob/living/carbon/human/H = owner.current
@@ -253,6 +263,8 @@
/datum/team/revolution
name = "Revolution"
var/max_headrevs = 3
+ var/list/ex_headrevs = list() // Dynamic removes revs on loss, used to keep a list for the roundend report.
+ var/list/ex_revs = list()
/datum/team/revolution/proc/update_objectives(initial = FALSE)
var/untracked_heads = SSjob.get_all_heads()
@@ -296,9 +308,12 @@
addtimer(CALLBACK(src,.proc/update_heads),HEAD_UPDATE_PERIOD,TIMER_UNIQUE)
+/datum/team/revolution/proc/save_members()
+ ex_headrevs = get_antag_minds(/datum/antagonist/rev/head, TRUE)
+ ex_revs = get_antag_minds(/datum/antagonist/rev, TRUE)
/datum/team/revolution/roundend_report()
- if(!members.len)
+ if(!members.len && !ex_headrevs.len)
return
var/list/result = list()
@@ -318,8 +333,17 @@
var/list/targets = list()
- var/list/datum/mind/headrevs = get_antag_minds(/datum/antagonist/rev/head)
- var/list/datum/mind/revs = get_antag_minds(/datum/antagonist/rev,TRUE)
+ var/list/datum/mind/headrevs
+ var/list/datum/mind/revs
+ if(ex_headrevs.len)
+ headrevs = ex_headrevs
+ else
+ headrevs = get_antag_minds(/datum/antagonist/rev/head, TRUE)
+
+ if(ex_revs.len)
+ revs = ex_revs
+ else
+ revs = get_antag_minds(/datum/antagonist/rev, TRUE)
if(headrevs.len)
var/list/headrev_part = list()
headrev_part += ""
diff --git a/code/modules/antagonists/slaughter/slaughterevent.dm b/code/modules/antagonists/slaughter/slaughterevent.dm
index 6ace6a0536..069795cfb8 100644
--- a/code/modules/antagonists/slaughter/slaughterevent.dm
+++ b/code/modules/antagonists/slaughter/slaughterevent.dm
@@ -3,6 +3,7 @@
typepath = /datum/round_event/ghost_role/slaughter
weight = 1 //Very rare
max_occurrences = 1
+ gamemode_blacklist = list("dynamic")
earliest_start = 1 HOURS
min_players = 20
diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm
index 5dfc08b6cf..26eea3f59e 100644
--- a/code/modules/antagonists/traitor/datum_traitor.dm
+++ b/code/modules/antagonists/traitor/datum_traitor.dm
@@ -70,7 +70,17 @@
/datum/antagonist/traitor/proc/forge_human_objectives()
var/is_hijacker = FALSE
- if (GLOB.joined_player_list.len >= 30) // Less murderboning on lowpop thanks
+ var/datum/game_mode/dynamic/mode
+ var/is_dynamic = FALSE
+ if(istype(SSticker.mode,/datum/game_mode/dynamic))
+ mode = SSticker.mode
+ is_dynamic = TRUE
+ if(GLOB.joined_player_list.len>=GLOB.dynamic_high_pop_limit)
+ is_hijacker = (prob(10) && mode.threat_level > CONFIG_GET(number/dynamic_hijack_high_population_requirement))
+ else
+ var/indice_pop = min(10,round(GLOB.joined_player_list.len/mode.pop_per_requirement)+1)
+ is_hijacker = (prob(10) && (mode.threat_level >= CONFIG_GET(number_list/dynamic_hijack_requirements)[indice_pop]))
+ else if (GLOB.joined_player_list.len >= 30) // Less murderboning on lowpop thanks
is_hijacker = prob(10)
var/martyr_chance = prob(20)
var/objective_count = is_hijacker //Hijacking counts towards number of objectives
@@ -91,6 +101,10 @@
var/datum/objective/hijack/hijack_objective = new
hijack_objective.owner = owner
add_objective(hijack_objective)
+ if(is_dynamic)
+ var/threat_spent = CONFIG_GET(number/dynamic_hijack_cost)
+ mode.spend_threat(threat_spent)
+ mode.log_threat("[owner.name] spent [threat_spent] on hijack.")
return
@@ -104,6 +118,10 @@
var/datum/objective/martyr/martyr_objective = new
martyr_objective.owner = owner
add_objective(martyr_objective)
+ if(is_dynamic)
+ var/threat_spent = CONFIG_GET(number/dynamic_hijack_cost)
+ mode.spend_threat(threat_spent)
+ mode.log_threat("[owner.name] spent [threat_spent] on glorious death.")
return
else
@@ -139,7 +157,18 @@
/datum/antagonist/traitor/proc/forge_single_human_objective() //Returns how many objectives are added
.=1
- if(prob(50))
+ var/assassin_prob = 50
+ var/is_dynamic = FALSE
+ var/datum/game_mode/dynamic/mode
+ if(istype(SSticker.mode,/datum/game_mode/dynamic))
+ mode = SSticker.mode
+ is_dynamic = TRUE
+ assassin_prob = mode.threat_level*(2/3)
+ if(prob(assassin_prob))
+ if(is_dynamic)
+ var/threat_spent = CONFIG_GET(number/dynamic_assassinate_cost)
+ mode.spend_threat(threat_spent)
+ mode.log_threat("[owner.name] spent [threat_spent] on an assassination target.")
var/list/active_ais = active_ais()
if(active_ais.len && prob(100/GLOB.joined_player_list.len))
var/datum/objective/destroy/destroy_objective = new
diff --git a/code/modules/antagonists/wizard/equipment/spellbook.dm b/code/modules/antagonists/wizard/equipment/spellbook.dm
index 4ebefd4dab..52d18a37d9 100644
--- a/code/modules/antagonists/wizard/equipment/spellbook.dm
+++ b/code/modules/antagonists/wizard/equipment/spellbook.dm
@@ -286,6 +286,20 @@
desc = "An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself."
item_path = /obj/item/gun/magic/staff/change
+/datum/spellbook_entry/item/staffchange/IsAvailible()
+ if(istype(SSticker.mode,/datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ if(mode.threat < CONFIG_GET(number/dynamic_staff_of_change_requirement))
+ return 0
+
+/datum/spellbook_entry/item/staffchange/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
+ if(istype(SSticker.mode,/datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ var/threat_spent = CONFIG_GET(number/dynamic_staff_of_change_cost)
+ mode.spend_threat(threat_spent)
+ mode.log_threat("Wizard spent [threat_spent] on staff of change.")
+ return ..()
+
/datum/spellbook_entry/item/staffanimation
name = "Staff of Animation"
desc = "An arcane staff capable of shooting bolts of eldritch energy which cause inanimate objects to come to life. This magic doesn't affect machines."
@@ -370,6 +384,20 @@
item_path = /obj/item/antag_spawner/contract
category = "Assistance"
+/datum/spellbook_entry/item/contract/IsAvailible()
+ if(istype(SSticker.mode,/datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ if(mode.threat < CONFIG_GET(number/dynamic_apprentice_cost))
+ return 0
+
+/datum/spellbook_entry/item/contract/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
+ if(istype(SSticker.mode,/datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ var/threat_spent = CONFIG_GET(number/dynamic_apprentice_cost)
+ mode.spend_threat(threat_spent)
+ mode.log_threat("Wizard spent [threat_spent] on apprentice contract.")
+ return ..()
+
/datum/spellbook_entry/item/guardian
name = "Guardian Deck"
desc = "A deck of guardian tarot cards, capable of binding a personal guardian to your body. There are multiple types of guardian available, but all of them will transfer some amount of damage to you. \
@@ -389,6 +417,20 @@
limit = 3
category = "Assistance"
+/datum/spellbook_entry/item/bloodbottle/IsAvailible()
+ if(istype(SSticker.mode,/datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ if(mode.threat < CONFIG_GET(keyed_list/dynamic_cost)["slaughter_demon"])
+ return 0
+
+/datum/spellbook_entry/item/bloodbottle/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
+ if(istype(SSticker.mode,/datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ var/threat_spent = CONFIG_GET(keyed_list/dynamic_cost)["slaughter_demon"]
+ mode.spend_threat(threat_spent)
+ mode.log_threat("Wizard spent [threat_spent] on slaughter demon.")
+ return ..()
+
/datum/spellbook_entry/item/hugbottle
name = "Bottle of Tickles"
desc = "A bottle of magically infused fun, the smell of which will \
@@ -403,6 +445,20 @@
limit = 3
category = "Assistance"
+/datum/spellbook_entry/item/hugbottle/IsAvailible()
+ if(istype(SSticker.mode,/datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ if(mode.threat < round(CONFIG_GET(keyed_list/dynamic_cost)["slaughter_demon"]/3))
+ return 0
+
+/datum/spellbook_entry/item/hugbottle/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
+ if(istype(SSticker.mode,/datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ var/threat_spent = CONFIG_GET(keyed_list/dynamic_cost)["slaughter_demon"]/3
+ mode.spend_threat(threat_spent)
+ mode.log_threat("Wizard spent [threat_spent] on laughter demon.")
+ return ..()
+
/datum/spellbook_entry/item/mjolnir
name = "Mjolnir"
desc = "A mighty hammer on loan from Thor, God of Thunder. It crackles with barely contained power."
@@ -482,6 +538,10 @@
/datum/spellbook_entry/summon/guns/IsAvailible()
if(!SSticker.mode) // In case spellbook is placed on map
return 0
+ if(istype(SSticker.mode,/datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ if(mode.threat < CONFIG_GET(number/dynamic_summon_guns_requirement))
+ return 0
return !CONFIG_GET(flag/no_summon_guns)
/datum/spellbook_entry/summon/guns/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
@@ -490,6 +550,11 @@
active = 1
playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1)
to_chat(user, "You have cast summon guns!")
+ if(istype(SSticker.mode,/datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ var/threat_spent = CONFIG_GET(number/dynamic_summon_guns_cost)
+ mode.spend_threat(threat_spent)
+ mode.log_threat("Wizard spent [threat_spent] on summon guns.")
return 1
/datum/spellbook_entry/summon/magic
@@ -499,6 +564,10 @@
/datum/spellbook_entry/summon/magic/IsAvailible()
if(!SSticker.mode) // In case spellbook is placed on map
return 0
+ if(istype(SSticker.mode,/datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ if(mode.threat < CONFIG_GET(number/dynamic_summon_magic_requirement))
+ return 0
return !CONFIG_GET(flag/no_summon_magic)
/datum/spellbook_entry/summon/magic/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
@@ -507,6 +576,11 @@
active = 1
playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1)
to_chat(user, "You have cast summon magic!")
+ if(istype(SSticker.mode,/datum/game_mode/dynamic))
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ var/threat_spent = CONFIG_GET(number/dynamic_summon_magic_cost)
+ mode.spend_threat(threat_spent)
+ mode.log_threat("Wizard spent [threat_spent] on summon magic.")
return 1
/datum/spellbook_entry/summon/events
@@ -517,6 +591,10 @@
/datum/spellbook_entry/summon/events/IsAvailible()
if(!SSticker.mode) // In case spellbook is placed on map
return 0
+ if(istype(SSticker.mode,/datum/game_mode/dynamic) && times == 0)
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ if(mode.threat < CONFIG_GET(number/dynamic_summon_events_requirement))
+ return 0
return !CONFIG_GET(flag/no_summon_events)
/datum/spellbook_entry/summon/events/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
@@ -525,6 +603,11 @@
times++
playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1)
to_chat(user, "You have cast summon events.")
+ if(istype(SSticker.mode,/datum/game_mode/dynamic) && times == 0)
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ var/threat_spent = CONFIG_GET(number/dynamic_summon_events_cost)
+ mode.spend_threat(threat_spent)
+ mode.log_threat("Wizard spent [threat_spent] on summon events.")
return 1
/datum/spellbook_entry/summon/events/GetInfo()
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 309f3ca069..5aaf589393 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -58,6 +58,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/inquisitive_ghost = 1
var/allow_midround_antag = 1
var/preferred_map = null
+ var/preferred_chaos = null
var/pda_style = MONO
var/pda_color = "#808000"
var/pda_skin = PDA_SKIN_ALT
@@ -834,6 +835,12 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "Screen Shake: [(screenshake==100) ? "Full" : ((screenshake==0) ? "None" : "[screenshake]")]
"
if (user && user.client && !user.client.prefs.screenshake==0)
dat += "Damage Screen Shake: [(damagescreenshake==1) ? "On" : ((damagescreenshake==0) ? "Off" : "Only when down")]
"
+ var/p_chaos
+ if (!preferred_chaos)
+ p_chaos = "No preference"
+ else
+ p_chaos = preferred_chaos
+ dat += "Preferred Chaos Amount: [p_chaos]
"
dat += "
"
dat += ""
dat += ""
@@ -1996,6 +2003,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if (pickedmap)
preferred_map = maplist[pickedmap]
+ if ("preferred_chaos")
+ var/pickedchaos = input(user, "Choose your preferred level of chaos. This will help with dynamic threat level ratings.", "Character Preference") as null|anything in list(CHAOS_NONE,CHAOS_LOW,CHAOS_MED,CHAOS_HIGH,CHAOS_MAX)
+ preferred_chaos = pickedchaos
if ("clientfps")
var/desiredfps = input(user, "Choose your desired fps. (0 = synced with server tick rate (currently:[world.fps]))", "Character Preference", clientfps) as null|num
if (!isnull(desiredfps))
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index 98ef2ed0e8..29ea8f5821 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -183,6 +183,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["autostand"] >> autostand
S["cit_toggles"] >> cit_toggles
S["lewdchem"] >> lewdchem
+ S["preferred_chaos"] >> preferred_chaos
+
//try to fix any outdated data if necessary
if(needs_update >= 0)
@@ -278,6 +280,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["autostand"], autostand)
WRITE_FILE(S["cit_toggles"], cit_toggles)
WRITE_FILE(S["lewdchem"], lewdchem)
+ WRITE_FILE(S["preferred_chaos"], preferred_chaos)
return 1
diff --git a/code/modules/events/abductor.dm b/code/modules/events/abductor.dm
index aee49abcc0..25a5cb2ffa 100755
--- a/code/modules/events/abductor.dm
+++ b/code/modules/events/abductor.dm
@@ -4,7 +4,7 @@
weight = 10
max_occurrences = 1
min_players = 20
- gamemode_blacklist = list("nuclear","wizard","revolution")
+ gamemode_blacklist = list("nuclear","wizard","revolution","dynamic")
/datum/round_event/ghost_role/abductor
minimum_required = 2
diff --git a/code/modules/events/alien_infestation.dm b/code/modules/events/alien_infestation.dm
index 762371886c..69e9a974eb 100644
--- a/code/modules/events/alien_infestation.dm
+++ b/code/modules/events/alien_infestation.dm
@@ -2,7 +2,7 @@
name = "Alien Infestation"
typepath = /datum/round_event/ghost_role/alien_infestation
weight = 5
-
+ gamemode_blacklist = list("dynamic")
min_players = 10
max_occurrences = 1
diff --git a/code/modules/events/anomaly_bluespace.dm b/code/modules/events/anomaly_bluespace.dm
index 8d4c5af540..275cfaf63d 100644
--- a/code/modules/events/anomaly_bluespace.dm
+++ b/code/modules/events/anomaly_bluespace.dm
@@ -3,6 +3,7 @@
typepath = /datum/round_event/anomaly/anomaly_bluespace
max_occurrences = 1
weight = 5
+ gamemode_blacklist = list("dynamic")
/datum/round_event/anomaly/anomaly_bluespace
startWhen = 3
diff --git a/code/modules/events/anomaly_flux.dm b/code/modules/events/anomaly_flux.dm
index 00516fcb5c..289bc9526d 100644
--- a/code/modules/events/anomaly_flux.dm
+++ b/code/modules/events/anomaly_flux.dm
@@ -5,6 +5,7 @@
min_players = 10
max_occurrences = 5
weight = 20
+ gamemode_blacklist = list("dynamic")
/datum/round_event/anomaly/anomaly_flux
startWhen = 10
diff --git a/code/modules/events/anomaly_grav.dm b/code/modules/events/anomaly_grav.dm
index 7761d02ec8..729ee7e32b 100644
--- a/code/modules/events/anomaly_grav.dm
+++ b/code/modules/events/anomaly_grav.dm
@@ -3,6 +3,8 @@
typepath = /datum/round_event/anomaly/anomaly_grav
max_occurrences = 5
weight = 20
+ gamemode_blacklist = list("dynamic")
+
/datum/round_event/anomaly/anomaly_grav
startWhen = 3
diff --git a/code/modules/events/anomaly_pyro.dm b/code/modules/events/anomaly_pyro.dm
index 13137741cc..9594727784 100644
--- a/code/modules/events/anomaly_pyro.dm
+++ b/code/modules/events/anomaly_pyro.dm
@@ -3,6 +3,7 @@
typepath = /datum/round_event/anomaly/anomaly_pyro
max_occurrences = 5
weight = 20
+ gamemode_blacklist = list("dynamic")
/datum/round_event/anomaly/anomaly_pyro
startWhen = 3
diff --git a/code/modules/events/anomaly_vortex.dm b/code/modules/events/anomaly_vortex.dm
index 7646f3e072..ef0d7a8af1 100644
--- a/code/modules/events/anomaly_vortex.dm
+++ b/code/modules/events/anomaly_vortex.dm
@@ -5,6 +5,7 @@
min_players = 20
max_occurrences = 2
weight = 5
+ gamemode_blacklist = list("dynamic")
/datum/round_event/anomaly/anomaly_vortex
startWhen = 10
diff --git a/code/modules/events/blob.dm b/code/modules/events/blob.dm
index d72c80686d..55691cc833 100644
--- a/code/modules/events/blob.dm
+++ b/code/modules/events/blob.dm
@@ -7,7 +7,7 @@
earliest_start = 40 MINUTES
min_players = 35
- gamemode_blacklist = list("blob") //Just in case a blob survives that long
+ gamemode_blacklist = list("blob","dynamic") //Just in case a blob survives that long
/datum/round_event/ghost_role/blob
announceWhen = -1
diff --git a/code/modules/events/brand_intelligence.dm b/code/modules/events/brand_intelligence.dm
index 30b59bf14a..85097684d0 100644
--- a/code/modules/events/brand_intelligence.dm
+++ b/code/modules/events/brand_intelligence.dm
@@ -5,6 +5,7 @@
min_players = 15
max_occurrences = 1
+ gamemode_blacklist = list("dynamic")
/datum/round_event/brand_intelligence
announceWhen = 21
diff --git a/code/modules/events/carp_migration.dm b/code/modules/events/carp_migration.dm
index 5940acad2f..f5b3fbb9f7 100644
--- a/code/modules/events/carp_migration.dm
+++ b/code/modules/events/carp_migration.dm
@@ -5,6 +5,7 @@
min_players = 2
earliest_start = 10 MINUTES
max_occurrences = 6
+ gamemode_blacklist = list("dynamic")
/datum/round_event/carp_migration
announceWhen = 3
diff --git a/code/modules/events/communications_blackout.dm b/code/modules/events/communications_blackout.dm
index da4eff9f39..ccd519b7e1 100644
--- a/code/modules/events/communications_blackout.dm
+++ b/code/modules/events/communications_blackout.dm
@@ -2,6 +2,7 @@
name = "Communications Blackout"
typepath = /datum/round_event/communications_blackout
weight = 30
+ gamemode_blacklist = list("dynamic")
/datum/round_event/communications_blackout
announceWhen = 1
diff --git a/code/modules/events/dust.dm b/code/modules/events/dust.dm
index c22f8cc0d7..090dc95cd6 100644
--- a/code/modules/events/dust.dm
+++ b/code/modules/events/dust.dm
@@ -5,6 +5,7 @@
max_occurrences = 1000
earliest_start = 0 MINUTES
alertadmins = 0
+ gamemode_blacklist = list("dynamic")
/datum/round_event/space_dust
startWhen = 1
diff --git a/code/modules/events/electrical_storm.dm b/code/modules/events/electrical_storm.dm
index 9845832c30..c4c144dc9a 100644
--- a/code/modules/events/electrical_storm.dm
+++ b/code/modules/events/electrical_storm.dm
@@ -5,6 +5,7 @@
min_players = 5
weight = 40
alertadmins = 0
+ gamemode_blacklist = list("dynamic")
/datum/round_event/electrical_storm
var/lightsoutAmount = 1
diff --git a/code/modules/events/heart_attack.dm b/code/modules/events/heart_attack.dm
index 7f9c09dfd9..8db2d98bf0 100644
--- a/code/modules/events/heart_attack.dm
+++ b/code/modules/events/heart_attack.dm
@@ -4,6 +4,7 @@
weight = 20
max_occurrences = 2
min_players = 40 // To avoid shafting lowpop
+ gamemode_blacklist = list("dynamic")
/datum/round_event/heart_attack/start()
var/list/heart_attack_contestants = list()
diff --git a/code/modules/events/ion_storm.dm b/code/modules/events/ion_storm.dm
index cd7c0e3b1f..40364483bf 100644
--- a/code/modules/events/ion_storm.dm
+++ b/code/modules/events/ion_storm.dm
@@ -3,6 +3,7 @@
/datum/round_event_control/ion_storm
name = "Ion Storm"
typepath = /datum/round_event/ion_storm
+ gamemode_blacklist = list("dynamic")
weight = 15
min_players = 2
diff --git a/code/modules/events/nightmare.dm b/code/modules/events/nightmare.dm
index 698f5130f1..6e5512a617 100644
--- a/code/modules/events/nightmare.dm
+++ b/code/modules/events/nightmare.dm
@@ -2,6 +2,7 @@
name = "Spawn Nightmare"
typepath = /datum/round_event/ghost_role/nightmare
max_occurrences = 1
+ gamemode_blacklist = list("dynamic")
min_players = 20
/datum/round_event/ghost_role/nightmare
diff --git a/code/modules/events/pirates.dm b/code/modules/events/pirates.dm
index cd02a06f59..8bc5491970 100644
--- a/code/modules/events/pirates.dm
+++ b/code/modules/events/pirates.dm
@@ -5,7 +5,7 @@
max_occurrences = 1
min_players = 10
earliest_start = 30 MINUTES
- gamemode_blacklist = list("nuclear")
+ gamemode_blacklist = list("nuclear","dynamic")
/datum/round_event_control/pirates/preRunEvent()
if (!SSmapping.empty_space)
diff --git a/code/modules/events/radiation_storm.dm b/code/modules/events/radiation_storm.dm
index 60e4e0e4b8..705eea5409 100644
--- a/code/modules/events/radiation_storm.dm
+++ b/code/modules/events/radiation_storm.dm
@@ -2,6 +2,7 @@
name = "Radiation Storm"
typepath = /datum/round_event/radiation_storm
max_occurrences = 1
+ gamemode_blacklist = list("dynamic")
/datum/round_event/radiation_storm
diff --git a/code/modules/events/spider_infestation.dm b/code/modules/events/spider_infestation.dm
index a6c333145e..8c6b3e23a5 100644
--- a/code/modules/events/spider_infestation.dm
+++ b/code/modules/events/spider_infestation.dm
@@ -2,6 +2,7 @@
name = "Spider Infestation"
typepath = /datum/round_event/spider_infestation
weight = 5
+ gamemode_blacklist = list("dynamic")
max_occurrences = 1
min_players = 15
diff --git a/code/modules/events/vent_clog.dm b/code/modules/events/vent_clog.dm
index 0deb880674..79487a024c 100644
--- a/code/modules/events/vent_clog.dm
+++ b/code/modules/events/vent_clog.dm
@@ -3,6 +3,7 @@
typepath = /datum/round_event/vent_clog
weight = 10
max_occurrences = 3
+ gamemode_blacklist = list("dynamic")
min_players = 25
/datum/round_event/vent_clog
diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm
index 96cc79261d..a09af656f5 100644
--- a/code/modules/mob/living/carbon/human/death.dm
+++ b/code/modules/mob/living/carbon/human/death.dm
@@ -54,11 +54,16 @@
/mob/living/carbon/human/proc/makeSkeleton()
ADD_TRAIT(src, TRAIT_DISFIGURED, TRAIT_GENERIC)
set_species(/datum/species/skeleton)
- return 1
+ return TRUE
/mob/living/carbon/proc/Drain()
become_husk(CHANGELING_DRAIN)
ADD_TRAIT(src, TRAIT_NOCLONE, CHANGELING_DRAIN)
blood_volume = 0
- return 1
+ return TRUE
+
+/mob/living/carbon/proc/makeUncloneable()
+ ADD_TRAIT(src, TRAIT_NOCLONE, MADE_UNCLONEABLE)
+ blood_volume = 0
+ return TRUE
\ No newline at end of file
diff --git a/code/modules/ninja/ninja_event.dm b/code/modules/ninja/ninja_event.dm
index ccd9b765c1..f166717aaf 100644
--- a/code/modules/ninja/ninja_event.dm
+++ b/code/modules/ninja/ninja_event.dm
@@ -14,6 +14,7 @@ Contents:
typepath = /datum/round_event/ghost_role/ninja
max_occurrences = 1
earliest_start = 40 MINUTES
+ gamemode_blacklist = list("dynamic")
min_players = 15
/datum/round_event/ghost_role/ninja
diff --git a/config/config.txt b/config/config.txt
index 0501d1fd2f..e71c2587b7 100644
--- a/config/config.txt
+++ b/config/config.txt
@@ -5,6 +5,7 @@ $include dbconfig.txt
$include comms.txt
$include antag_rep.txt
$include donator_groupings.txt
+$include dynamic_config.txt
# You can use the @ character at the beginning of a config option to lock it from being edited in-game
# Example usage:
diff --git a/config/dynamic_config.txt b/config/dynamic_config.txt
new file mode 100644
index 0000000000..f9b78f046f
--- /dev/null
+++ b/config/dynamic_config.txt
@@ -0,0 +1,305 @@
+## Injection delays: how long (in minutes) will pass before a midround or latejoin antag is injected.
+DYNAMIC_MIDROUND_DELAY_MIN 5
+DYNAMIC_MIDROUND_DELAY_MAX 15
+DYNAMIC_LATEJOIN_DELAY_MIN 10
+DYNAMIC_LATEJOIN_DELAY_MAX 30
+
+DYNAMIC_FIRST_MIDROUND_DELAY_MIN 20
+DYNAMIC_FIRST_MIDROUND_DELAY_MAX 30
+DYNAMIC_FIRST_LATEJOIN_DELAY_MIN 2
+DYNAMIC_FIRST_LATEJOIN_DELAY_MAX 30
+
+## How many roundstart players required for high population override to take effect.
+DYNAMIC_HIGH_POP_LIMIT 80 #80 instead of 55 because fewer robust players
+
+## Threat requirements for a second roundstart ruleset being drafted
+DYNAMIC_SECOND_RULE_REQUIREMENTS 101 101 101 101 100 90 80 70 60 50
+## Threat requirements for a *third* roundstart ruleset being drafted
+DYNAMIC_THIRD_RULE_REQUIREMENTS 101 101 101 101 101 100 90 80 70 60
+## As above, but if there's 79+ players
+DYNAMIC_SECOND_RULE_HIGH_POP_REQUIREMENT 50
+DYNAMIC_THIRD_RULE_HIGH_POP_REQUIREMENT 60
+
+## Pop range per requirement.
+## If the value is five the range is:
+## 0-4, 5-9, 10-14, 15-19, 20-24, 25-29, 30-34, 35-39, 40-54, 45+
+## If it is six the range is:
+## 0-5, 6-11, 12-17, 18-23, 24-29, 30-35, 36-41, 42-47, 48-53, 54+
+## If it is seven the range is:
+## 0-6, 7-13, 14-20, 21-27, 28-34, 35-41, 42-48, 49-55, 56-62, 63+
+## Options outside this range can be used, of course.
+DYNAMIC_POP_PER_REQUIREMENT 9 # 9 instead of 6 because 1/3 of players are probably not doing much?
+
+## 1 -> 9, probability for this rule to be picked against other rules.
+## Note that requirements must also be met, and some requirements are impossible to meet.
+DYNAMIC_WEIGHT TRAITOR 5
+DYNAMIC_WEIGHT MALF_AI 1
+DYNAMIC_WEIGHT TRAITORBRO 4
+DYNAMIC_WEIGHT CHANGELING 1
+DYNAMIC_WEIGHT WIZARD 1
+DYNAMIC_WEIGHT CULT 3
+DYNAMIC_WEIGHT NUCLEAR 3
+DYNAMIC_WEIGHT REVOLUTION 2
+# All below are impossible-by-default
+DYNAMIC_WEIGHT EXTENDED 3
+DYNAMIC_WEIGHT CLOCKWORK_CULT 3
+DYNAMIC_WEIGHT CLOWNOPS 3
+DYNAMIC_WEIGHT DEVIL 3
+DYNAMIC_WEIGHT MONKEY 3
+DYNAMIC_WEIGHT METEOR 3
+
+## Midround antags
+DYNAMIC_WEIGHT MIDROUND_TRAITOR 7
+DYNAMIC_WEIGHT MIDROUND_MALF_AI 8
+DYNAMIC_WEIGHT MIDROUND_WIZARD 9
+DYNAMIC_WEIGHT MIDROUND_NUCLEAR 9
+DYNAMIC_WEIGHT BLOB 8
+DYNAMIC_WEIGHT XENOS 8
+DYNAMIC_WEIGHT NIGHTMARE 8
+DYNAMIC_WEIGHT SENTIENT_DISEASE 6
+DYNAMIC_WEIGHT REVENANT 6
+DYNAMIC_WEIGHT SLAUGHTER_DEMON 4
+DYNAMIC_WEIGHT ABDUCTORS 8
+DYNAMIC_WEIGHT SPACE_NINJA 8
+DYNAMIC_WEIGHT SPIDERS 5
+DYNAMIC_WEIGHT VENTCLOG_NORMAL 3
+DYNAMIC_WEIGHT VENTCLOG_THREATENING 3
+DYNAMIC_WEIGHT VENTCLOG_CATASTROPHIC 3
+DYNAMIC_WEIGHT ION_STORM 7
+DYNAMIC_WEIGHT METEOR_WAVE_NORMAL 3
+DYNAMIC_WEIGHT METEOR_WAVE_THREATENING 2
+DYNAMIC_WEIGHT METEOR_WAVE_CATASTROPHIC 1
+DYNAMIC_WEIGHT PIRATES 8
+DYNAMIC_WEIGHT ANOMALY_BLUESPACE 2
+DYNAMIC_WEIGHT ANOMALY_FLUX 2
+DYNAMIC_WEIGHT ANOMALY_GRAVITATIONAL 2
+DYNAMIC_WEIGHT ANOMALY_PYROCLASTIC 2
+DYNAMIC_WEIGHT ANOMALY_VORTEX 2
+DYNAMIC_WEIGHT BRAND_INTELLIGENCE 1
+DYNAMIC_WEIGHT CARP_MIGRATION 7
+DYNAMIC_WEIGHT COMMUNICATIONS_BLACKOUT 2
+DYNAMIC_WEIGHT PROCESSOR_OVERLOAD 2
+DYNAMIC_WEIGHT SPACE_DUST 2
+DYNAMIC_WEIGHT MAJOR_DUST 1
+DYNAMIC_WEIGHT ELECTRICAL_STORM 2
+DYNAMIC_WEIGHT HEART_ATTACK 2
+DYNAMIC_WEIGHT RADIATION_STORM 1
+
+## Latejoin antags
+DYNAMIC_WEIGHT LATEJOIN_TRAITOR 7
+DYNAMIC_WEIGHT LATEJOIN_REVOLUTION 2
+
+## Threat cost. This is decreased from the mode's threat when the rule is executed.
+DYNAMIC_COST TRAITOR 10
+DYNAMIC_COST MALF_AI 35
+DYNAMIC_COST TRAITORBRO 10
+DYNAMIC_COST CHANGELING 30
+DYNAMIC_COST WIZARD 30
+DYNAMIC_COST CULT 35
+DYNAMIC_COST NUCLEAR 45
+DYNAMIC_COST REVOLUTION 40
+# All below are impossible-by-default
+DYNAMIC_COST EXTENDED 0
+DYNAMIC_COST CLOCKWORK_CULT 35
+DYNAMIC_COST CLOWNOPS 40
+DYNAMIC_COST DEVIL 0
+DYNAMIC_COST MONKEY 0
+DYNAMIC_COST METEOR 0
+
+## Midround antags
+DYNAMIC_COST MIDROUND_TRAITOR 10
+DYNAMIC_COST MIDROUND_MALF_AI 35
+DYNAMIC_COST MIDROUND_WIZARD 20
+DYNAMIC_COST MIDROUND_NUCLEAR 35
+DYNAMIC_COST BLOB 10
+DYNAMIC_COST XENOS 10
+DYNAMIC_COST NIGHTMARE 10
+DYNAMIC_COST SENTIENT_DISEASE 5
+DYNAMIC_COST REVENANT 5
+DYNAMIC_COST SLAUGHTER_DEMON 15
+DYNAMIC_COST ABDUCTORS 10
+DYNAMIC_COST SPACE_NINJA 15
+DYNAMIC_COST SPIDERS 10
+DYNAMIC_COST VENTCLOG_NORMAL 2
+DYNAMIC_COST VENTCLOG_THREATENING 5
+DYNAMIC_COST VENTCLOG_CATASTROPHIC 15
+DYNAMIC_COST ION_STORM 3
+DYNAMIC_COST METEOR_WAVE_NORMAL 15
+DYNAMIC_COST METEOR_WAVE_THREATENING 25
+DYNAMIC_COST METEOR_WAVE_CATASTROPHIC 40
+DYNAMIC_COST PIRATES 10
+DYNAMIC_COST ANOMALY_BLUESPACE 3
+DYNAMIC_COST ANOMALY_FLUX 2
+DYNAMIC_COST ANOMALY_GRAVITATIONAL 3
+DYNAMIC_COST ANOMALY_PYROCLASTIC 5
+DYNAMIC_COST ANOMALY_VORTEX 5
+DYNAMIC_COST BRAND_INTELLIGENCE 5
+DYNAMIC_COST CARP_MIGRATION 3
+DYNAMIC_COST COMMUNICATIONS_BLACKOUT 5
+DYNAMIC_COST PROCESSOR_OVERLOAD 5
+DYNAMIC_COST SPACE_DUST 2
+DYNAMIC_COST MAJOR_DUST 4
+DYNAMIC_COST ELECTRICAL_STORM 1
+DYNAMIC_COST HEART_ATTACK 1
+DYNAMIC_COST RADIATION_STORM 3
+
+## Latejoin antags
+DYNAMIC_COST LATEJOIN_TRAITOR 5
+DYNAMIC_COST LATEJOIN_REVOLUTION 20
+
+## Rule will not be generated with threat levels below requirement at a pop value. Pop values are determined by dynamic's pop-per-requirement.
+## By default it's 0-8, 9-17, 18-26, 27-35, 36-44, 45-53, 54-60, 61-69, 70-78, 79+.
+## This means that 40 30 30 20 20 20 15 15 15 10 will not generate below 40 at 0-8, 30 at 9-17 etc.
+DYNAMIC_REQUIREMENTS TRAITOR 50 50 50 50 50 50 50 50 50 50
+DYNAMIC_REQUIREMENTS TRAITORBRO 101 101 101 101 101 101 101 101 101 101
+DYNAMIC_REQUIREMENTS CHANGELING 101 101 101 101 101 101 101 101 101 101
+DYNAMIC_REQUIREMENTS WIZARD 101 101 101 60 50 50 50 50 50 50
+DYNAMIC_REQUIREMENTS CULT 101 101 101 60 50 50 50 50 50 50
+DYNAMIC_REQUIREMENTS NUCLEAR 101 101 101 60 50 50 50 50 50 50
+DYNAMIC_REQUIREMENTS REVOLUTION 101 101 101 60 50 50 50 50 50 50
+# All below are impossible-by-default
+DYNAMIC_REQUIREMENTS EXTENDED 101 101 101 101 101 101 101 101 101 101
+DYNAMIC_REQUIREMENTS CLOCKWORK_CULT 101 101 101 80 70 60 50 50 50 50
+DYNAMIC_REQUIREMENTS CLOWNOPS 101 101 101 101 101 101 101 101 101 101
+DYNAMIC_REQUIREMENTS DEVIL 101 101 101 101 101 101 101 101 101 101
+DYNAMIC_REQUIREMENTS MONKEY 101 101 101 101 101 101 101 101 101 101
+DYNAMIC_REQUIREMENTS METEOR 101 101 101 101 101 101 101 101 101 101
+
+## Midround antags
+DYNAMIC_REQUIREMENTS MIDROUND_TRAITOR 30 25 20 15 15 15 15 15 15 15
+DYNAMIC_REQUIREMENTS MIDROUND_MALF_AI 101 101 70 50 50 50 40 30 30 30
+DYNAMIC_REQUIREMENTS MIDROUND_WIZARD 90 90 70 50 50 50 50 40 30 30
+DYNAMIC_REQUIREMENTS MIDROUND_NUCLEAR 90 90 90 80 70 60 50 40 40 40
+DYNAMIC_REQUIREMENTS BLOB 101 101 101 80 60 50 50 50 50 50
+DYNAMIC_REQUIREMENTS XENOS 101 101 101 70 50 50 50 50 50 50
+DYNAMIC_REQUIREMENTS NIGHTMARE 101 101 101 70 50 40 20 15 15 15
+DYNAMIC_REQUIREMENTS SENTIENT_DISEASE 30 30 20 20 15 10 10 10 10 5
+DYNAMIC_REQUIREMENTS REVENANT 30 30 30 30 20 15 15 15 15 15
+DYNAMIC_REQUIREMENTS SLAUGHTER_DEMON 101 101 101 90 80 70 60 50 40 30
+DYNAMIC_REQUIREMENTS ABDUCTORS 80 80 70 50 40 30 30 20 15 15
+DYNAMIC_REQUIREMENTS SPACE_NINJA 101 101 101 90 80 70 60 50 40 30
+DYNAMIC_REQUIREMENTS SPIDERS 70 60 50 50 40 40 40 30 20 15
+DYNAMIC_REQUIREMENTS VENTCLOG_NORMAL 5 5 5 5 5 5 5 5 5 5
+DYNAMIC_REQUIREMENTS VENTCLOG_THREATENING 15 15 15 15 15 15 15 15 15 15
+DYNAMIC_REQUIREMENTS VENTCLOG_CATASTROPHIC 30 30 30 30 30 30 30 30 30 30
+DYNAMIC_REQUIREMENTS ION_STORM 5 5 5 5 5 5 5 5 5 5
+DYNAMIC_REQUIREMENTS METEOR_WAVE_NORMAL 60 50 40 30 30 30 30 30 30 30
+DYNAMIC_REQUIREMENTS METEOR_WAVE_THREATENING 80 70 60 50 40 40 40 40 40 40
+DYNAMIC_REQUIREMENTS METEOR_WAVE_CATASTROPHIC 101 100 90 80 70 60 50 50 50 50
+DYNAMIC_REQUIREMENTS PIRATES 70 60 50 50 40 40 40 30 20 15
+DYNAMIC_REQUIREMENTS ANOMALY_BLUESPACE 5 5 5 5 5 5 5 5 5 5
+DYNAMIC_REQUIREMENTS ANOMALY_FLUX 5 5 5 5 5 5 5 5 5 5
+DYNAMIC_REQUIREMENTS ANOMALY_GRAVITATIONAL 5 5 5 5 5 5 5 5 5 5
+DYNAMIC_REQUIREMENTS ANOMALY_PYROCLASTIC 10 10 10 10 10 10 10 10 10 10
+DYNAMIC_REQUIREMENTS ANOMALY_VORTEX 10 10 10 10 10 10 10 10 10 10
+DYNAMIC_REQUIREMENTS BRAND_INTELLIGENCE 10 10 10 10 10 10 10 10 10 10
+DYNAMIC_REQUIREMENTS CARP_MIGRATION 10 10 10 10 10 10 10 10 10 10
+DYNAMIC_REQUIREMENTS COMMUNICATIONS_BLACKOUT 5 5 5 5 5 5 5 5 5 5
+DYNAMIC_REQUIREMENTS PROCESSOR_OVERLOAD 5 5 5 5 5 5 5 5 5 5
+DYNAMIC_REQUIREMENTS SPACE_DUST 5 5 5 5 5 5 5 5 5 5
+DYNAMIC_REQUIREMENTS MAJOR_DUST 10 10 10 10 10 10 10 10 10 10
+DYNAMIC_REQUIREMENTS ELECTRICAL_STORM 5 5 5 5 5 5 5 5 5 5
+DYNAMIC_REQUIREMENTS HEART_ATTACK 5 5 5 5 5 5 5 5 5 5
+DYNAMIC_REQUIREMENTS RADIATION_STORM 5 5 5 5 5 5 5 5 5 5
+
+## Latejoin antags
+DYNAMIC_REQUIREMENTS LATEJOIN_TRAITOR 40 30 20 15 15 15 15 15 15 15
+DYNAMIC_REQUIREMENTS LATEJOIN_REVOLUTION 101 101 70 40 40 40 40 40 40 40
+
+## An alternative, static requirement used instead when pop is over mode's high_pop_limit.
+DYNAMIC_HIGH_POPULATION_REQUIREMENT TRAITOR 50
+DYNAMIC_HIGH_POPULATION_REQUIREMENT MALF_AI 50
+DYNAMIC_HIGH_POPULATION_REQUIREMENT TRAITORBRO 101
+DYNAMIC_HIGH_POPULATION_REQUIREMENT CHANGELING 101
+DYNAMIC_HIGH_POPULATION_REQUIREMENT WIZARD 50
+DYNAMIC_HIGH_POPULATION_REQUIREMENT CULT 50
+DYNAMIC_HIGH_POPULATION_REQUIREMENT NUCLEAR 50
+DYNAMIC_HIGH_POPULATION_REQUIREMENT REVOLUTION 50
+# All below are impossible-by-default
+DYNAMIC_HIGH_POPULATION_REQUIREMENT EXTENDED 101
+DYNAMIC_HIGH_POPULATION_REQUIREMENT CLOCKWORK_CULT 50
+DYNAMIC_HIGH_POPULATION_REQUIREMENT CLOWNOPS 101
+DYNAMIC_HIGH_POPULATION_REQUIREMENT DEVIL 101
+DYNAMIC_HIGH_POPULATION_REQUIREMENT MONKEY 101
+DYNAMIC_HIGH_POPULATION_REQUIREMENT METEOR 101
+
+## Midround antags
+DYNAMIC_HIGH_POPULATION_REQUIREMENT MIDROUND_TRAITOR 15
+DYNAMIC_HIGH_POPULATION_REQUIREMENT MIDROUND_MALF_AI 35
+DYNAMIC_HIGH_POPULATION_REQUIREMENT MIDROUND_WIZARD 50
+DYNAMIC_HIGH_POPULATION_REQUIREMENT MIDROUND_NUCLEAR 35
+DYNAMIC_HIGH_POPULATION_REQUIREMENT BLOB 50
+DYNAMIC_HIGH_POPULATION_REQUIREMENT XENOS 50
+DYNAMIC_HIGH_POPULATION_REQUIREMENT NIGHTMARE 15
+DYNAMIC_HIGH_POPULATION_REQUIREMENT SENTIENT_DISEASE 5
+DYNAMIC_HIGH_POPULATION_REQUIREMENT REVENANT 15
+DYNAMIC_HIGH_POPULATION_REQUIREMENT SLAUGHTER_DEMON 30
+DYNAMIC_HIGH_POPULATION_REQUIREMENT ABDUCTORS 15
+DYNAMIC_HIGH_POPULATION_REQUIREMENT SPACE_NINJA 30
+DYNAMIC_HIGH_POPULATION_REQUIREMENT SPIDERS 15
+DYNAMIC_HIGH_POPULATION_REQUIREMENT VENTCLOG_NORMAL 5
+DYNAMIC_HIGH_POPULATION_REQUIREMENT VENTCLOG_THREATENING 15
+DYNAMIC_HIGH_POPULATION_REQUIREMENT VENTCLOG_CATASTROPHIC 30
+DYNAMIC_HIGH_POPULATION_REQUIREMENT ION_STORM 5
+DYNAMIC_HIGH_POPULATION_REQUIREMENT METEOR_WAVE_NORMAL 30
+DYNAMIC_HIGH_POPULATION_REQUIREMENT METEOR_WAVE_THREATENING 40
+DYNAMIC_HIGH_POPULATION_REQUIREMENT METEOR_WAVE_CATASTROPHIC 50
+DYNAMIC_HIGH_POPULATION_REQUIREMENT PIRATES 15
+DYNAMIC_HIGH_POPULATION_REQUIREMENT ANOMALY_BLUESPACE 5
+DYNAMIC_HIGH_POPULATION_REQUIREMENT ANOMALY_FLUX 5
+DYNAMIC_HIGH_POPULATION_REQUIREMENT ANOMALY_GRAVITATIONAL 5
+DYNAMIC_HIGH_POPULATION_REQUIREMENT ANOMALY_PYROCLASTIC 10
+DYNAMIC_HIGH_POPULATION_REQUIREMENT ANOMALY_VORTEX 10
+DYNAMIC_HIGH_POPULATION_REQUIREMENT BRAND_INTELLIGENCE 10
+DYNAMIC_HIGH_POPULATION_REQUIREMENT CARP_MIGRATION 10
+DYNAMIC_HIGH_POPULATION_REQUIREMENT COMMUNICATIONS_BLACKOUT 5
+DYNAMIC_HIGH_POPULATION_REQUIREMENT PROCESSOR_OVERLOAD 5
+DYNAMIC_HIGH_POPULATION_REQUIREMENT SPACE_DUST 5
+DYNAMIC_HIGH_POPULATION_REQUIREMENT MAJOR_DUST 10
+DYNAMIC_HIGH_POPULATION_REQUIREMENT ELECTRICAL_STORM 5
+DYNAMIC_HIGH_POPULATION_REQUIREMENT HEART_ATTACK 5
+DYNAMIC_HIGH_POPULATION_REQUIREMENT RADIATION_STORM 5
+
+## Latejoin antags
+DYNAMIC_HIGH_POPULATION_REQUIREMENT LATEJOIN_TRAITOR 15
+DYNAMIC_HIGH_POPULATION_REQUIREMENT LATEJOIN_REVOLUTION 50
+
+## Dynamic traitor stuff
+
+## Requirements for "hijack the shuttle" goals.
+DYNAMIC_HIJACK_REQUIREMENTS 101 101 101 95 80 70 60 50 40 30
+
+DYNAMIC_HIJACK_HIGH_POPULATION_REQUIREMENT 25
+
+DYNAMIC_HIJACK_COST 10
+
+DYNAMIC_GLORIOUS_DEATH_COST 5
+
+DYNAMIC_ASSASSINATE_COST 2
+
+## Dynamic wizard stuff
+
+## How much threat level is required to buy summon guns. Setting to 0 makes it always available.
+DYNAMIC_SUMMON_GUNS_REQUIREMENT 10
+
+## How much summon guns reduces the round's remaining threat. Setting to 0 makes it cost none.
+DYNAMIC_SUMMON_GUNS_COST 10
+
+## As above, but for summon magic
+DYNAMIC_SUMMON_MAGIC_REQUIREMENT 10
+DYNAMIC_SUMMON_MAGIC_COST 10
+
+## As above, but for summon events
+DYNAMIC_SUMMON_EVENTS_REQUIREMENT 20
+DYNAMIC_SUMMON_EVENTS_COST 10
+
+DYNAMIC_STAFF_OF_CHANGE_REQUIREMENT 20
+DYNAMIC_STAFF_OF_CHANGE_COST 10
+
+## As above, but for apprentice. Note that this is just a cost, since apprentices aren't as universally disruptive as above.
+DYNAMIC_APPRENTICE_COST 10
+
+## This requirement uses threat level, rather than current threat, which is why it's higher.
+DYNAMIC_WAROPS_REQUIREMENT 60
+
+DYNAMIC_WAROPS_COST 10
diff --git a/config/game_options.txt b/config/game_options.txt
index 93fd00446b..1cac50fc1a 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -254,148 +254,6 @@ EVENTS_MIN_TIME_MUL 1
## Set to 0 to make dangerous events avaliable for all populations.
EVENTS_MIN_PLAYERS_MUL 1
-
-
-### DYNAMIC MODE ###
-
-## Injection delays: how long (in minutes) will pass before a midround or latejoin antag is injected.
-DYNAMIC_MIDROUND_DELAY_MIN 15
-DYNAMIC_MIDROUND_DELAY_MAX 35
-DYNAMIC_LATEJOIN_DELAY_MIN 5
-DYNAMIC_LATEJOIN_DELAY_MAX 25
-
-## How many roundstart players required for high population override to take effect.
-DYNAMIC_HIGH_POP_LIMIT 55
-
-## Pop range per requirement.
-## If the value is five the range is:
-## 0-4, 5-9, 10-14, 15-19, 20-24, 25-29, 30-34, 35-39, 40-54, 45+
-## If it is six the range is:
-## 0-5, 6-11, 12-17, 18-23, 24-29, 30-35, 36-41, 42-47, 48-53, 54+
-## If it is seven the range is:
-## 0-6, 7-13, 14-20, 21-27, 28-34, 35-41, 42-48, 49-55, 56-62, 63+
-## Options outside this range can be used, of course.
-DYNAMIC_POP_PER_REQUIREMENT 6
-
-## 1 -> 9, probability for this rule to be picked against other rules.
-## Note that requirements must also be met, and some requirements are impossible to meet.
-DYNAMIC_WEIGHT TRAITOR 5
-DYNAMIC_WEIGHT TRAITORBRO 4
-DYNAMIC_WEIGHT CHANGELING 3
-DYNAMIC_WEIGHT WIZARD 1
-DYNAMIC_WEIGHT CULT 3
-DYNAMIC_WEIGHT NUCLEAR 3
-DYNAMIC_WEIGHT REVOLUTION 2
-# All below are impossible-by-default
-DYNAMIC_WEIGHT EXTENDED 3
-DYNAMIC_WEIGHT CLOCKWORK_CULT 3
-DYNAMIC_WEIGHT CLOWNOPS 3
-DYNAMIC_WEIGHT DEVIL 3
-DYNAMIC_WEIGHT MONKEY 3
-DYNAMIC_WEIGHT METEOR 3
-
-## Midround antags
-DYNAMIC_WEIGHT MIDROUND_TRAITOR 7
-DYNAMIC_WEIGHT MALF_AI 3
-DYNAMIC_WEIGHT MIDROUND_WIZARD 1
-DYNAMIC_WEIGHT MIDROUND_NUCLEAR 5
-DYNAMIC_WEIGHT BLOB 4
-DYNAMIC_WEIGHT XENOS 3
-DYNAMIC_WEIGHT NIGHTMARE 3
-
-## Latejoin antags
-DYNAMIC_WEIGHT LATEJOIN_TRAITOR 7
-DYNAMIC_WEIGHT LATEJOIN_REVOLUTION 2
-
-## Threat cost. This is decreased from the mode's threat when the rule is executed.
-DYNAMIC_COST TRAITOR 10
-DYNAMIC_COST TRAITORBRO 10
-DYNAMIC_COST CHANGELING 30
-DYNAMIC_COST WIZARD 30
-DYNAMIC_COST CULT 30
-DYNAMIC_COST NUCLEAR 40
-DYNAMIC_COST REVOLUTION 35
-# All below are impossible-by-default
-DYNAMIC_COST EXTENDED 0
-DYNAMIC_COST CLOCKWORK_CULT 0
-DYNAMIC_COST CLOWNOPS 40
-DYNAMIC_COST DEVIL 0
-DYNAMIC_COST MONKEY 0
-DYNAMIC_COST METEOR 0
-
-## Midround antags
-DYNAMIC_COST MIDROUND_TRAITOR 10
-DYNAMIC_COST MALF_AI 35
-DYNAMIC_COST MIDROUND_WIZARD 20
-DYNAMIC_COST MIDROUND_NUCLEAR 35
-DYNAMIC_COST BLOB 10
-DYNAMIC_COST XENOS 10
-DYNAMIC_COST NIGHTMARE 10
-
-## Latejoin antags
-DYNAMIC_COST LATEJOIN_TRAITOR 5
-DYNAMIC_COST LATEJOIN_REVOLUTION 20
-
-## Rule will not be generated with threat levels below requirement at a pop value. Pop values are determined by dynamic's pop-per-requirement.
-## By default it's 0-5, 6-11, 12-17, 18-23, 24-29, 30-35, 36-41, 42-47, 48-53, 54+.
-## This means that 40 30 30 20 20 20 15 15 15 10 will not generate below 40 at 0-5, 30 at 6-11 etc.
-DYNAMIC_REQUIREMENTS TRAITOR 10 10 10 10 10 10 10 10 10 10
-DYNAMIC_REQUIREMENTS TRAITORBRO 40 30 30 20 20 15 15 15 10 10
-DYNAMIC_REQUIREMENTS CHANGELING 80 70 60 50 40 20 20 10 10 10
-DYNAMIC_REQUIREMENTS WIZARD 90 90 70 40 30 20 10 10 10 10
-DYNAMIC_REQUIREMENTS CULT 100 90 80 60 40 30 10 10 10 10
-DYNAMIC_REQUIREMENTS NUCLEAR 90 90 90 80 60 40 30 20 10 10
-DYNAMIC_REQUIREMENTS REVOLUTION 101 101 70 40 30 20 10 10 10 10
-# All below are impossible-by-default
-DYNAMIC_REQUIREMENTS EXTENDED 101 101 101 101 101 101 101 101 101 101
-DYNAMIC_REQUIREMENTS CLOCKWORK_CULT 101 101 101 101 101 101 101 101 101 101
-DYNAMIC_REQUIREMENTS CLOWNOPS 101 101 101 101 101 101 101 101 101 101
-DYNAMIC_REQUIREMENTS DEVIL 101 101 101 101 101 101 101 101 101 101
-DYNAMIC_REQUIREMENTS MONKEY 101 101 101 101 101 101 101 101 101 101
-DYNAMIC_REQUIREMENTS METEOR 101 101 101 101 101 101 101 101 101 101
-
-## Midround antags
-DYNAMIC_REQUIREMENTS MIDROUND_TRAITOR 50 40 30 20 10 10 10 10 10 10
-DYNAMIC_REQUIREMENTS MALF_AI 101 101 80 70 60 60 50 50 40 40
-DYNAMIC_REQUIREMENTS MIDROUND_WIZARD 90 90 70 40 30 20 10 10 10 10
-DYNAMIC_REQUIREMENTS MIDROUND_NUCLEAR 90 90 90 80 60 40 30 20 10 10
-DYNAMIC_REQUIREMENTS BLOB 101 101 101 80 60 50 30 20 10 10
-DYNAMIC_REQUIREMENTS XENOS 101 101 101 70 50 40 20 15 10 10
-DYNAMIC_REQUIREMENTS NIGHTMARE 101 101 101 70 50 40 20 15 10 10
-
-## Latejoin antags
-DYNAMIC_REQUIREMENTS LATEJOIN_TRAITOR 40 30 20 10 10 10 10 10 10 10
-DYNAMIC_REQUIREMENTS LATEJOIN_REVOLUTION 101 101 70 40 30 20 20 20 20 20
-
-## An alternative, static requirement used instead when pop is over mode's high_pop_limit.
-DYNAMIC_HIGH_POPULATION_REQUIREMENT TRAITOR 10
-DYNAMIC_HIGH_POPULATION_REQUIREMENT TRAITORBRO 15
-DYNAMIC_HIGH_POPULATION_REQUIREMENT CHANGELING 10
-DYNAMIC_HIGH_POPULATION_REQUIREMENT WIZARD 10
-DYNAMIC_HIGH_POPULATION_REQUIREMENT CULT 10
-DYNAMIC_HIGH_POPULATION_REQUIREMENT NUCLEAR 10
-DYNAMIC_HIGH_POPULATION_REQUIREMENT REVOLUTION 10
-# All below are impossible-by-default
-DYNAMIC_HIGH_POPULATION_REQUIREMENT EXTENDED 101
-DYNAMIC_HIGH_POPULATION_REQUIREMENT CLOCKWORK_CULT 101
-DYNAMIC_HIGH_POPULATION_REQUIREMENT CLOWNOPS 101
-DYNAMIC_HIGH_POPULATION_REQUIREMENT DEVIL 101
-DYNAMIC_HIGH_POPULATION_REQUIREMENT MONKEY 101
-DYNAMIC_HIGH_POPULATION_REQUIREMENT METEOR 101
-
-## Midround antags
-DYNAMIC_HIGH_POPULATION_REQUIREMENT MIDROUND_TRAITOR 10
-DYNAMIC_HIGH_POPULATION_REQUIREMENT MALF_AI 35
-DYNAMIC_HIGH_POPULATION_REQUIREMENT MIDROUND_WIZARD 50
-DYNAMIC_HIGH_POPULATION_REQUIREMENT MIDROUND_NUCLEAR 10
-DYNAMIC_HIGH_POPULATION_REQUIREMENT BLOB 50
-DYNAMIC_HIGH_POPULATION_REQUIREMENT XENOS 50
-DYNAMIC_HIGH_POPULATION_REQUIREMENT NIGHTMARE 50
-
-## Latejoin antags
-DYNAMIC_HIGH_POPULATION_REQUIREMENT LATEJOIN_TRAITOR 10
-DYNAMIC_HIGH_POPULATION_REQUIREMENT LATEJOIN_REVOLUTION 50
-
## AI ###
## Allow the AI job to be picked.
@@ -711,3 +569,6 @@ MONKEYCAP 64
## Uncomment to use TG-style combat
#DISABLE_STAMBUFFER
+
+#Replaces standard extended/secret dichotomy with extended and calm/chaotic votes for dynamic.
+DYNAMIC_VOTING
diff --git a/html/changelogs/AutoChangeLog-pr-9724.yml b/html/changelogs/AutoChangeLog-pr-9724.yml
deleted file mode 100644
index 853d38b041..0000000000
--- a/html/changelogs/AutoChangeLog-pr-9724.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Ghommie"
-delete-after: True
-changes:
- - bugfix: "Fixed free real estate paraplegic trait and bolas."
diff --git a/tgstation.dme b/tgstation.dme
index 06239cb1fd..0d0eeb18da 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -225,6 +225,7 @@
#include "code\controllers\configuration\entries\comms.dm"
#include "code\controllers\configuration\entries\dbconfig.dm"
#include "code\controllers\configuration\entries\donator.dm"
+#include "code\controllers\configuration\entries\dynamic.dm"
#include "code\controllers\configuration\entries\game_options.dm"
#include "code\controllers\configuration\entries\general.dm"
#include "code\controllers\subsystem\acid.dm"
@@ -567,6 +568,7 @@
#include "code\game\gamemodes\devil\devil agent\devil_agent.dm"
#include "code\game\gamemodes\dynamic\dynamic.dm"
#include "code\game\gamemodes\dynamic\dynamic_rulesets.dm"
+#include "code\game\gamemodes\dynamic\dynamic_rulesets_events.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"