diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 1f37556d1d..f96337f691 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -213,4 +213,5 @@
#define LOCKED_HELMET_TRAIT "locked-helmet"
#define NINJA_SUIT_TRAIT "ninja-suit"
#define ANTI_DROP_IMPLANT_TRAIT "anti-drop-implant"
-#define ABDUCTOR_ANTAGONIST "abductor-antagonist"
\ No newline at end of file
+#define ABDUCTOR_ANTAGONIST "abductor-antagonist"
+#define MADE_UNCLONEABLE "made-uncloneable"
\ No newline at end of file
diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm
index 8e59106d98..85b839c9a1 100644
--- a/code/__HELPERS/roundend.dm
+++ b/code/__HELPERS/roundend.dm
@@ -314,7 +314,7 @@
parts += "[FOURSPACES]Threat left: [mode.threat]"
parts += "[FOURSPACES]Executed rules:"
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/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm
index 473e2ae4a5..78febd0615 100644
--- a/code/game/gamemodes/dynamic/dynamic.dm
+++ b/code/game/gamemodes/dynamic/dynamic.dm
@@ -19,7 +19,7 @@ GLOBAL_VAR_INIT(dynamic_midround_delay_max, (35 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 +27,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 +38,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,14 +49,14 @@ 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
+ var/threat = 0
/// Running information about the threat. Can store text or datum entries.
- var/list/threat_log = list()
+ var/list/threat_log = list()
/// List of roundstart rules used for selecting the rules.
var/list/roundstart_rules = list()
/// List of latejoin rules used for selecting the rules.
@@ -72,14 +72,20 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
* 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.
+ /// 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.
@@ -106,6 +112,8 @@ 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.
..()
@@ -175,7 +183,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
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
@@ -313,18 +321,21 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
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
@@ -332,12 +343,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.delay > 0)
- addtimer(CALLBACK(rule, /datum/dynamic_ruleset/roundstart.proc/execute), rule.delay)
- else
- 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.
@@ -348,6 +354,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(roundstart_pop_ready, threat_level) // Assigns some vars in the modes, running it here for consistency
rule.candidates = candidates.Copy()
rule.trim_candidates()
if (rule.ready(TRUE))
@@ -366,7 +373,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
drafted_rules[rule] = rule.weight
var/indice_pop = min(10,round(roundstart_pop_ready/pop_per_requirement)+1)
- var/extra_rulesets_amount = 0
+ extra_rulesets_amount = 0
if (GLOB.dynamic_classic_secret)
extra_rulesets_amount = 0
else
@@ -378,23 +385,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 >= 10)
+ 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
@@ -402,63 +414,83 @@ 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())
+ 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 ruleset [starting_rule.name]")
+ log_game("DYNAMIC: Picked a ruleset: [starting_rule.name]")
roundstart_rules -= starting_rule
drafted_rules -= starting_rule
starting_rule.trim_candidates()
- if (starting_rule.pre_execute())
- spend_threat(starting_rule.cost)
+
+ var/added_threat = starting_rule.scale_up(extra_rulesets_amount, threat)
+ if(starting_rule.pre_execute())
+ spend_threat(starting_rule.cost + added_threat)
threat_log += "[worldtime2text()]: Roundstart [starting_rule.name] spent [starting_rule.cost]"
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/datum/dynamic_ruleset/roundstart/rule in drafted_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
+/// 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())
+ if(rule.persistent)
+ current_rules += rule
+ return TRUE
+ 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.
/datum/game_mode/dynamic/proc/picking_midround_latejoin_rule(list/drafted_rules = list(), forced = FALSE)
var/datum/dynamic_ruleset/rule = pickweight(drafted_rules)
if(!rule)
return FALSE
-
+
if(!forced)
if(only_ruleset_executed)
return FALSE
@@ -470,39 +502,21 @@ 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")
midround_rules = remove_from_list(midround_rules, rule.type)
-
- 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]"
- if(rule.flags & HIGHLANDER_RULESET)
- highlander_executed = TRUE
- else if(rule.flags & ONLY_RULESET)
- only_ruleset_executed = TRUE
- if(rule.ruletype == "Latejoin")
- var/mob/M = pick(rule.candidates)
- message_admins("[key_name(M)] joined the station, and was selected by the [rule.name] ruleset.")
- log_game("DYNAMIC: [key_name(M)] joined the station, and was selected by the [rule.name] ruleset.")
- executed_rules += rule
- rule.candidates.Cut()
- if (rule.persistent)
- current_rules += rule
- return TRUE
- else
- stack_trace("The [rule.ruletype] rule \"[rule.name]\" failed to execute.")
- return FALSE
+
+ 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)
@@ -513,10 +527,10 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
new_rule = ruletype
else
return FALSE
-
+
if(!new_rule)
return FALSE
-
+
if(!forced)
if(only_ruleset_executed)
return FALSE
@@ -525,10 +539,10 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
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(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()
@@ -549,6 +563,30 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
log_game("DYNAMIC: The ruleset [new_rule.name] couldn't be executed due to lack of elligible 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]"
+ if(rule.flags & HIGHLANDER_RULESET)
+ highlander_executed = TRUE
+ else if(rule.flags & ONLY_RULESET)
+ only_ruleset_executed = TRUE
+ if(rule.ruletype == "Latejoin")
+ var/mob/M = pick(rule.candidates)
+ message_admins("[key_name(M)] joined the station, and was selected by the [rule.name] ruleset.")
+ log_game("DYNAMIC: [key_name(M)] joined the station, and was selected by the [rule.name] ruleset.")
+ executed_rules += rule
+ rule.candidates.Cut()
+ if (rule.persistent)
+ current_rules += rule
+ return TRUE
+ rule.clean_up()
+ stack_trace("The [rule.ruletype] rule \"[rule.name]\" failed to execute.")
+ return FALSE
+
/datum/game_mode/dynamic/process()
if (pop_last_updated < world.time - (60 SECONDS))
pop_last_updated = world.time
@@ -561,19 +599,19 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if (midround_injection_cooldown < world.time)
if (GLOB.dynamic_forced_extended)
return
-
+
// Somehow it managed to trigger midround multiple times so this was moved here.
// There is no way this should be able to trigger an injection twice now.
var/midround_injection_cooldown_middle = 0.5*(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.")
-
+
update_playercounts()
if (get_injection_chance())
var/list/drafted_rules = list()
@@ -587,7 +625,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
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()
@@ -682,11 +720,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 22a54d9b73..266d313a06 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm
@@ -1,3 +1,9 @@
+#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 = ""
@@ -37,6 +43,12 @@
var/weight = 5
/// Threat cost for this rule, this is decreased from the mode's threat when the rule is executed.
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.
@@ -59,7 +71,15 @@
/// The maximum amount of players required for the rule to be considered.
/// 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()
..()
@@ -86,9 +106,6 @@
/datum/dynamic_ruleset/roundstart // One or more of those drafted at roundstart
ruletype = "Roundstart"
- /// Delay for when execute will get called from the time of post_setup.
- /// Make sure your ruleset works with execute being called during the game when using this.
- var/delay = 0
// Can be drafted when a player joins the server
/datum/dynamic_ruleset/latejoin
@@ -102,12 +119,35 @@
if(maximum_players > 0 && population > maximum_players)
return FALSE
if (population >= GLOB.dynamic_high_pop_limit)
+ indice_pop = 10
return (threat_level >= high_population_requirement)
else
pop_per_requirement = pop_per_requirement > 0 ? pop_per_requirement : mode.pop_per_requirement
- var/indice_pop = min(requirements.len,round(population/pop_per_requirement)+1)
+ 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)
return (threat_level >= requirements[indice_pop])
+/// 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
@@ -133,6 +173,12 @@
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.threat_log += "[worldtime2text()]: [ruletype] [name] refunded [cost + (scaled_times * scaling_cost)]"
+
/// 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
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
index 5c4728bc89..6eb7fb37eb 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
@@ -78,6 +78,7 @@
/datum/dynamic_ruleset/latejoin/provocateur
name = "Provocateur"
+ persistent = TRUE
config_tag = "latejoin_revolution"
antag_datum = /datum/antagonist/rev/head
antag_flag = ROLE_REV_HEAD
@@ -87,31 +88,98 @@
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
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
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
\ No newline at end of file
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
index d26aa57856..ef08b11739 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
@@ -41,17 +41,17 @@
continue
if(antag_flag_override)
if(!(antag_flag_override in M.client.prefs.be_special) || is_banned_from(M.ckey, list(antag_flag_override, ROLE_SYNDICATE)))
- candidates.Remove(M)
+ trimmed_list.Remove(M)
continue
else
if(!(antag_flag in M.client.prefs.be_special) || is_banned_from(M.ckey, list(antag_flag, ROLE_SYNDICATE)))
- candidates.Remove(M)
+ 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?
@@ -239,7 +239,7 @@
/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
@@ -324,7 +324,7 @@
/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(operative_cap.len, round(living_players.len/5)+1)
+ indice_pop = min(operative_cap.len, round(living_players.len/5)+1)
required_candidates = operative_cap[indice_pop]
return ..()
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
index d48153b222..346cbe1e00 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
@@ -16,14 +16,15 @@
restricted_roles = list("Cyborg")
required_candidates = 1
weight = 5
- cost = 10
+ cost = 10 // Avoid raising traitor threat above 10, as it is the default low cost ruleset.
+ scaling_cost = 10
requirements = list(10,10,10,10,10,10,10,10,10,10)
high_population_requirement = 10
+ 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_n_take(candidates)
assigned += M.mind
@@ -55,19 +56,16 @@
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
+ cost = 15
+ scaling_cost = 15
requirements = list(40,30,30,20,20,15,15,15,10,10)
high_population_requirement = 15
+ 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(mode.roundstart_pop_ready / 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
@@ -107,13 +105,15 @@
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(70,70,60,50,40,20,20,10,10,10)
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_n_take(candidates)
assigned += M.mind
@@ -171,7 +171,7 @@
/datum/dynamic_ruleset/roundstart/wizard/pre_execute()
if(GLOB.wizardstart.len == 0)
return FALSE
-
+ mode.antags_rolled += 1
var/mob/M = pick_n_take(candidates)
if (M)
assigned += M.mind
@@ -202,22 +202,20 @@
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 = 3
- cost = 30
+ cost = 35
requirements = list(100,90,80,60,40,30,10,10,10,10)
high_population_requirement = 10
- pop_per_requirement = 5
flags = HIGHLANDER_RULESET
- var/list/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(cultist_cap.len, 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(cultist_cap.len, 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
@@ -263,23 +261,22 @@
required_candidates = 5
weight = 3
cost = 40
+ pop_per_requirement = 5
requirements = list(90,90,90,80,60,40,30,20,10,10)
high_population_requirement = 10
pop_per_requirement = 5
flags = HIGHLANDER_RULESET
- var/list/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(operative_cap.len,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(operative_cap.len, 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
@@ -356,15 +353,17 @@
cost = 35
requirements = list(101,101,70,40,30,20,10,10,10,10)
high_population_requirement = 10
+ 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/revs/pre_execute()
- var/max_canditates = 3
- for(var/i = 1 to max_canditates)
+ 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_n_take(candidates)
@@ -374,31 +373,63 @@
return TRUE
/datum/dynamic_ruleset/roundstart/revs/execute()
+ var/success = TRUE
revolution = new()
for(var/datum/mind/M in assigned)
- 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)
- revolution.update_objectives()
- revolution.update_heads()
- SSshuttle.registerHostileEnvironment(src)
- return TRUE
+ 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/revs/clean_up()
+ qdel(revolution)
+ ..()
/datum/dynamic_ruleset/roundstart/revs/rule_process()
if(check_rev_victory())
- finished = 1
- else if(check_heads_victory())
- finished = 2
- return
+ 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(CONFIG_GET(keyed_list/continuous)["revolution"])
- if(finished)
- SSshuttle.clearHostileEnvironment(src)
- return ..()
- if(finished != 0)
+ if(finished == REVOLUTION_VICTORY)
return TRUE
else
return ..()
@@ -418,10 +449,10 @@
return TRUE
/datum/dynamic_ruleset/roundstart/revs/round_result()
- if(finished == 1)
+ 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
@@ -469,6 +500,7 @@
requirements = list(101,101,101,101,101,101,101,101,101,101)
high_population_requirement = 101
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()
@@ -481,12 +513,12 @@
for(var/datum/parsed_map/PM in reebes)
PM.initTemplateBounds()
- var/starter_servants = 4
+ 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)
+ starter_servants += min(round(number_players / 10), 5)
+ mode.antags_rolled += starter_servants
for (var/i in 1 to starter_servants)
var/mob/servant = pick_n_take(candidates)
assigned += servant.mind
@@ -597,17 +629,11 @@
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(mode.roundstart_pop_ready / (tsc * 3)) + 2, round(mode.roundstart_pop_ready / (tsc * 1.5))))
- else
- num_devils = max(required_candidates, min(mode.roundstart_pop_ready, 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
@@ -662,6 +688,7 @@
/datum/dynamic_ruleset/roundstart/monkey/pre_execute()
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)
diff --git a/code/modules/antagonists/revolution/revolution.dm b/code/modules/antagonists/revolution/revolution.dm
index e10d83ffb7..b41731369b 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()
@@ -295,9 +307,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()
@@ -317,8 +332,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/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm
index b0c3f61eec..2f13af95db 100644
--- a/code/modules/mob/living/carbon/human/death.dm
+++ b/code/modules/mob/living/carbon/human/death.dm
@@ -69,11 +69,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