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 += "The head revolutionaries were:" 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