From 90d266371491c877119c380261280e56d0cf4e59 Mon Sep 17 00:00:00 2001 From: Xoxeyos Date: Sun, 14 Mar 2021 03:05:50 -0500 Subject: [PATCH 01/53] Please let this work Good luck to myself, Xoxeyos --- code/__DEFINES/New Text Document.txt | 0 code/__DEFINES/components.dm | 5 + code/__DEFINES/dynamic.dm | 17 + code/__DEFINES/mobs.dm | 6 + code/__HELPERS/_lists.dm | 1 + code/__HELPERS/mobs.dm | 6 +- code/__HELPERS/roundend.dm | 2 +- code/controllers/subsystem/ticker.dm | 4 + code/datums/mocking/client.dm | 4 + code/game/gamemodes/dynamic/dynamic.dm | 684 +++++++++--------- .../gamemodes/dynamic/dynamic_hijacking.dm | 26 + .../game/gamemodes/dynamic/dynamic_logging.dm | 97 +++ .../gamemodes/dynamic/dynamic_rulesets.dm | 141 ++-- .../dynamic/dynamic_rulesets_latejoin.dm | 4 - .../dynamic/dynamic_rulesets_midround.dm | 35 +- .../dynamic/dynamic_rulesets_roundstart.dm | 179 ++--- .../gamemodes/dynamic/dynamic_simulations.dm | 137 ++++ code/game/gamemodes/dynamic/readme.md | 122 +++- .../game/gamemodes/dynamic/ruleset_picking.dm | 117 +++ code/game/gamemodes/game_mode.dm | 6 +- code/modules/admin/admin.dm | 17 +- code/modules/admin/admin_verbs.dm | 6 +- code/modules/admin/topic.dm | 182 +---- .../antagonists/_common/antag_datum.dm | 34 +- .../revenant/revenant_spawn_event.dm | 1 + .../antagonists/slaughter/slaughterevent.dm | 1 + code/modules/events/_event.dm | 21 +- code/modules/events/alien_infestation.dm | 2 + code/modules/events/blob.dm | 2 + code/modules/events/nightmare.dm | 1 + code/modules/events/operative.dm | 1 + code/modules/events/pirates.dm | 1 + code/modules/events/space_dragon.dm | 1 + code/modules/events/spider_infestation.dm | 1 + code/modules/events/swarmer.dm | 1 + code/modules/events/zombie_infection.dm | 1 + code/modules/mob/dead/dead.dm | 2 +- code/modules/mob/dead/observer/observer.dm | 4 +- code/modules/mob/living/brain/MMI.dm | 8 +- code/modules/mob/living/brain/posibrain.dm | 4 +- code/modules/mob/living/death.dm | 4 +- code/modules/mob/living/living.dm | 16 +- code/modules/mob/living/silicon/pai/death.dm | 2 +- .../modules/mob/living/silicon/robot/robot.dm | 4 +- code/modules/mob/login.dm | 2 +- code/modules/mob/logout.dm | 4 +- code/modules/mob/mob.dm | 15 +- code/modules/mob/mob_defines.dm | 17 +- code/modules/mob/mob_lists.dm | 119 +++ code/modules/ninja/ninja_event.dm | 1 + code/modules/unit_tests/_unit_tests.dm | 1 + .../unit_tests/dynamic_ruleset_sanity.dm | 14 + 52 files changed, 1308 insertions(+), 775 deletions(-) create mode 100644 code/__DEFINES/New Text Document.txt create mode 100644 code/__DEFINES/dynamic.dm create mode 100644 code/datums/mocking/client.dm create mode 100644 code/game/gamemodes/dynamic/dynamic_hijacking.dm create mode 100644 code/game/gamemodes/dynamic/dynamic_logging.dm create mode 100644 code/game/gamemodes/dynamic/dynamic_simulations.dm create mode 100644 code/game/gamemodes/dynamic/ruleset_picking.dm create mode 100644 code/modules/mob/mob_lists.dm create mode 100644 code/modules/unit_tests/dynamic_ruleset_sanity.dm diff --git a/code/__DEFINES/New Text Document.txt b/code/__DEFINES/New Text Document.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm index 9b408c4060bb..9b57908bcf91 100644 --- a/code/__DEFINES/components.dm +++ b/code/__DEFINES/components.dm @@ -43,6 +43,11 @@ // signals from globally accessible objects /// from SSsun when the sun changes position : (azimuth) #define COMSIG_SUN_MOVED "sun_moved" +/// Random event is trying to roll. (/datum/round_event_control/random_event) +/// Called by (/datum/round_event_control/preRunEvent). +#define COMSIG_GLOB_PRE_RANDOM_EVENT "!pre_random_event" + /// Do not allow this random event to continue. + #define CANCEL_PRE_RANDOM_EVENT (1<<0) ////////////////////////////////////////////////////////////////// diff --git a/code/__DEFINES/dynamic.dm b/code/__DEFINES/dynamic.dm new file mode 100644 index 000000000000..57a48ff1499f --- /dev/null +++ b/code/__DEFINES/dynamic.dm @@ -0,0 +1,17 @@ +/// This is the only ruleset that should be picked this round, used by admins and should not be on rulesets in code. +#define ONLY_RULESET (1 << 0) + +/// Only one ruleset with this flag will be picked. +#define HIGH_IMPACT_RULESET (1 << 1) + +/// This ruleset can only be picked once. Anything that does not have a scaling_cost MUST have this. +#define LONE_RULESET (1 << 2) + +/// No round event was hijacked this cycle +#define HIJACKED_NOTHING "HIJACKED_NOTHING" + +/// This cycle, a round event was hijacked when the last midround event was too recent. +#define HIJACKED_TOO_RECENT "HIJACKED_TOO_RECENT" + +/// This cycle, a round event was hijacked when the next midround event is too soon. +#define HIJACKED_TOO_SOON "HIJACKED_TOO_SOON" \ No newline at end of file diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 4e440192a4b4..846f8a5a4086 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -7,6 +7,12 @@ #define PLAYER_READY_TO_PLAY 1 #define PLAYER_READY_TO_OBSERVE 2 +//Game mode list indexes +#define CURRENT_LIVING_PLAYERS "living_players_list" +#define CURRENT_LIVING_ANTAGS "living_antags_list" +#define CURRENT_DEAD_PLAYERS "dead_players_list" +#define CURRENT_OBSERVERS "current_observers_list" + //movement intent defines for the m_intent var #define MOVE_INTENT_WALK "walk" #define MOVE_INTENT_RUN "run" diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index e4306bc50f80..b051d846e62a 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -19,6 +19,7 @@ #define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V; #define LAZYLEN(L) length(L) #define LAZYCLEARLIST(L) if(L) L.Cut() +#define LAZYACCESSASSOC(L, I, K) L ? L[I] ? L[I][K] ? L[I][K] : null : null : null #define SANITIZE_LIST(L) ( islist(L) ? L : list() ) #define reverseList(L) reverseRange(L.Copy()) #define LAZYADDASSOC(L, K, V) if(!L) { L = list(); } L[K] += list(V); diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index c0d8540b6d12..abeb8989adab 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -520,4 +520,8 @@ GLOBAL_LIST_EMPTY(species_list) callperrotate?.Invoke() sleep(1) if(set_original_dir) - AM.setDir(originaldir) \ No newline at end of file + AM.setDir(originaldir) + +/// Gets the client of the mob, allowing for mocking of the client. +/// You only need to use this if you know you're going to be mocking clients somewhere else. +#define GET_CLIENT(mob) (##mob.client || ##mob.mock_client) \ No newline at end of file diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm index 0c9e500687b9..d627b80da8a3 100644 --- a/code/__HELPERS/roundend.dm +++ b/code/__HELPERS/roundend.dm @@ -314,7 +314,7 @@ if(istype(SSticker.mode, /datum/game_mode/dynamic)) var/datum/game_mode/dynamic/mode = SSticker.mode parts += "[FOURSPACES]Threat level: [mode.threat_level]" - parts += "[FOURSPACES]Threat left: [mode.threat]" //yes + parts += "[FOURSPACES]Threat left: [mode.mid_round_budget]" parts += "[FOURSPACES]Executed rules:" for(var/datum/dynamic_ruleset/rule in mode.executed_rules) parts += "[FOURSPACES][FOURSPACES][rule.ruletype] - [rule.name]: -[rule.cost + rule.scaled_times * rule.scaling_cost] threat" diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 264a8fffb4b9..df7cb321f7b8 100755 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -619,6 +619,10 @@ SUBSYSTEM_DEF(ticker) fdel(F) WRITE_FILE(F, the_mode) +/// Returns if either the master mode or the forced secret ruleset matches the mode name. +/datum/controller/subsystem/ticker/proc/is_mode(mode_name) + return GLOB.master_mode == mode_name || GLOB.secret_force_mode == mode_name + /datum/controller/subsystem/ticker/proc/SetRoundEndSound(the_sound) set waitfor = FALSE round_end_sound_sent = FALSE diff --git a/code/datums/mocking/client.dm b/code/datums/mocking/client.dm new file mode 100644 index 000000000000..aa60a5e7a109 --- /dev/null +++ b/code/datums/mocking/client.dm @@ -0,0 +1,4 @@ +/// This should match the interface of /client wherever necessary. +/datum/client_interface + /// Player preferences datum for the client + var/datum/preferences/prefs \ No newline at end of file diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index 528722d28220..505eebf25d64 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -1,39 +1,14 @@ -#define CURRENT_LIVING_PLAYERS 1 -#define CURRENT_LIVING_ANTAGS 2 -#define CURRENT_DEAD_PLAYERS 3 -#define CURRENT_OBSERVERS 4 - -#define ONLY_RULESET 1 -#define HIGHLANDER_RULESET 2 -#define TRAITOR_RULESET 4 -#define MINOR_RULESET 8 - #define RULESET_STOP_PROCESSING 1 -// -- Injection delays -GLOBAL_VAR_INIT(dynamic_latejoin_delay_min, (5 MINUTES)) -GLOBAL_VAR_INIT(dynamic_latejoin_delay_max, (25 MINUTES)) +#define FAKE_REPORT_CHANCE 8 +#define REPORT_NEG_DIVERGENCE -15 +#define REPORT_POS_DIVERGENCE 15 -GLOBAL_VAR_INIT(dynamic_midround_delay_min, (15 MINUTES)) -GLOBAL_VAR_INIT(dynamic_midround_delay_max, (60 MINUTES)) - -// Are HIGHLANDER_RULESETs allowed to stack? +// Are HIGH_IMPACT_RULESETs allowed to stack? GLOBAL_VAR_INIT(dynamic_no_stacking, FALSE) -// 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) -// A number between 0.5 and 4. -// 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. -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) // If enabled does not accept or execute any rulesets. GLOBAL_VAR_INIT(dynamic_forced_extended, FALSE) -// How high threat is required for HIGHLANDER_RULESETs stacking. +// How high threat is required for HIGH_IMPACT_RULESETs stacking. // This is independent of dynamic_no_stacking. GLOBAL_VAR_INIT(dynamic_stacking_limit, 90) // List of forced roundstart rulesets. @@ -50,13 +25,20 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) announce_span = "danger" announce_text = "Dynamic mode!" // This needs to be changed maybe - reroll_friendly = FALSE; + 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 - /// Set at the beginning of the round. Spent by the mode to "purchase" rules. - var/threat = 0 + /// Set at the beginning of the round. Spent by the mode to "purchase" rules. Everything else goes in the postround budget. + var/round_start_budget = 0 + + /// Set at the beginning of the round. Spent by midrounds and latejoins. + var/mid_round_budget = 0 + + /// The initial round start budget for logging purposes, set once at the beginning of the round. + var/initial_round_start_budget = 0 + /// Running information about the threat. Can store text or datum entries. var/list/threat_log = list() /// List of roundstart rules used for selecting the rules. @@ -74,20 +56,6 @@ 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. - var/list/second_rule_req = list(100, 100, 80, 70, 60, 50, 30, 20, 10, 0) - /// 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) - /// 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. - 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. @@ -96,22 +64,14 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) var/list/current_rules = list() /// List of executed rulesets. var/list/executed_rules = list() - /// Associative list of current players, in order: living players, living antagonists, dead players and observers. - var/list/list/current_players = list(CURRENT_LIVING_PLAYERS, CURRENT_LIVING_ANTAGS, CURRENT_DEAD_PLAYERS, CURRENT_OBSERVERS) - /// When world.time is over this number the mode tries to inject a latejoin ruleset. - var/latejoin_injection_cooldown = 0 - /// When world.time is over this number the mode tries to inject a midround ruleset. - var/midround_injection_cooldown = 0 /// When TRUE GetInjectionChance returns 100. var/forced_injection = FALSE /// Forced ruleset to be executed for the next latejoin. var/datum/dynamic_ruleset/latejoin/forced_latejoin_rule = null - /// When current_players was updated last time. - var/pop_last_updated = 0 /// How many percent of the rounds are more peaceful. var/peaceful_percentage = 50 - /// If a highlander executed. - var/highlander_executed = FALSE + /// If a high impact ruleset was executed. Only one will run at a time in most circumstances. + var/high_impact_ruleset_executed = FALSE /// If a only ruleset has been executed. var/only_ruleset_executed = FALSE /// Dynamic configuration, loaded on pre_setup @@ -124,17 +84,95 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) /datum/supply_pack/security/vending/security, /datum/supply_pack/service/party) + /// When world.time is over this number the mode tries to inject a latejoin ruleset. + var/latejoin_injection_cooldown = 0 + + /// The minimum time the recurring latejoin ruleset timer is allowed to be. + var/latejoin_delay_min = (5 MINUTES) + + /// The maximum time the recurring latejoin ruleset timer is allowed to be. + var/latejoin_delay_max = (25 MINUTES) + + /// When world.time is over this number the mode tries to inject a midround ruleset. + var/midround_injection_cooldown = 0 + + /// The minimum time the recurring midround ruleset timer is allowed to be. + var/midround_delay_min = (15 MINUTES) + + /// The maximum time the recurring midround ruleset timer is allowed to be. + var/midround_delay_max = (35 MINUTES) + + /// If above this threat, increase the chance of injection + var/higher_injection_chance_minimum_threat = 70 + + /// The chance of injection increase when above higher_injection_chance_minimum_threat + var/higher_injection_chance = 15 + + /// If below this threat, decrease the chance of injection + var/lower_injection_chance_minimum_threat = 10 + + /// The chance of injection decrease when above lower_injection_chance_minimum_threat + var/lower_injection_chance = 15 + + /// 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. + var/threat_curve_centre = 0 + + /// A number between 0.5 and 4. + /// Higher value will favour extreme rounds and + /// lower value rounds closer to the average. + var/threat_curve_width = 1.8 + + /// A number between -5 and +5. + /// Equivalent to threat_curve_centre, but for the budget split. + /// A negative value will weigh towards midround rulesets, and a positive + /// value will weight towards roundstart ones. + var/roundstart_split_curve_centre = 1 + + /// A number between 0.5 and 4. + /// Equivalent to threat_curve_width, but for the budget split. + /// Higher value will favour more variance in splits and + /// lower value rounds closer to the average. + var/roundstart_split_curve_width = 1.8 + + /// The minimum amount of time for antag random events to be hijacked. + var/random_event_hijack_minimum = 10 MINUTES + + /// The maximum amount of time for antag random events to be hijacked. + var/random_event_hijack_maximum = 18 MINUTES + + /// A list of recorded "snapshots" of the round, stored in the dynamic.json log + var/list/datum/dynamic_snapshot/snapshots + + /// The time when the last midround injection was attempted, whether or not it was successful + var/last_midround_injection_attempt = 0 + + /// The amount to inject when a round event is hijacked + var/hijacked_random_event_injection_chance = 50 + + /// Whether or not a random event has been hijacked this midround cycle + var/random_event_hijacked = HIJACKED_NOTHING + + /// The timer ID for the cancellable midround rule injection + var/midround_injection_timer_id + + /// The last drafted midround rulesets (without the current one included). + /// Used for choosing different midround injections. + var/list/current_midround_rulesets + /datum/game_mode/dynamic/admin_panel() var/list/dat = list("Game Mode Panel

Game Mode Panel

") - dat += "Dynamic Mode \[VV\]\[Refresh\]
" + dat += "Dynamic Mode \[VV\] \[Refresh\]
" dat += "Threat Level: [threat_level]
" + dat += "Budgets (Roundstart/Midrounds): [initial_round_start_budget]/[threat_level - initial_round_start_budget]
" - dat += "Threat to Spend: [threat] \[Adjust\] \[View Log\]
" + dat += "Midround budget to spend: [mid_round_budget] \[Adjust\] \[View Log\]
" dat += "
" - dat += "Parameters: centre = [GLOB.dynamic_curve_centre] ; width = [GLOB.dynamic_curve_width].
" + dat += "Parameters: centre = [threat_curve_centre] ; width = [threat_curve_width].
" + dat += "Split parameters: centre = [roundstart_split_curve_centre] ; width = [roundstart_split_curve_width].
" dat += "On average, [peaceful_percentage]% of the rounds are more peaceful.
" dat += "Forced extended: [GLOB.dynamic_forced_extended ? "On" : "Off"]
" - dat += "Classic secret (only autotraitor): [GLOB.dynamic_classic_secret ? "On" : "Off"]
" dat += "No stacking (only one round-ender): [GLOB.dynamic_no_stacking ? "On" : "Off"]
" dat += "Stacking limit: [GLOB.dynamic_stacking_limit] \[Adjust\]" dat += "
" @@ -150,7 +188,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) dat += "[DR.ruletype] - [DR.name]
" else dat += "none.
" - dat += "
Injection Timers: ([get_injection_chance(TRUE)]% chance)
" + dat += "
Injection Timers: ([get_injection_chance(dry_run = TRUE)]% latejoin chance, [get_midround_injection_chance(dry_run = TRUE)]% midround chance)
" dat += "Latejoin: [(latejoin_injection_cooldown-world.time)>60*10 ? "[round((latejoin_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(latejoin_injection_cooldown-world.time)] seconds"] \[Now!\]
" dat += "Midround: [(midround_injection_cooldown-world.time)>60*10 ? "[round((midround_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(midround_injection_cooldown-world.time)] seconds"] \[Now!\]
" usr << browse(dat.Join(), "window=gamemode_panel;size=500x500") @@ -168,9 +206,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) else if (href_list["no_stacking"]) log_admin("[key_name(usr)] has toggled stacking.") GLOB.dynamic_no_stacking = !GLOB.dynamic_no_stacking - else if (href_list["classic_secret"]) - log_admin("[key_name(usr)] has toggled classic secret.") - GLOB.dynamic_classic_secret = !GLOB.dynamic_classic_secret else if (href_list["adjustthreat"]) var/threatadd = input("Specify how much threat to add (negative to subtract). This can inflate the threat level.", "Adjust Threat", 0) as null|num if(!threatadd) @@ -180,7 +215,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) create_threat(threatadd) threat_log += "[worldtime2text()]: [key_name(usr)] increased threat by [threatadd] threat." else - spend_threat(-threatadd) + spend_midround_budget(-threatadd) threat_log += "[worldtime2text()]: [key_name(usr)] decreased threat by [-threatadd] threat." else if (href_list["injectlate"]) latejoin_injection_cooldown = 0 @@ -199,31 +234,30 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if (!added_rule) return forced_latejoin_rule = added_rule - log_admin("[key_name(usr)] set [added_rule] to proc on the next latejoin.") - message_admins("[key_name(usr)] set [added_rule] to proc on the next latejoin.") + dynamic_log("[key_name(usr)] set [added_rule] to proc on the next latejoin.") else if(href_list["clear_forced_latejoin"]) forced_latejoin_rule = null - log_admin("[key_name(usr)] cleared the forced latejoin ruleset.") - message_admins("[key_name(usr)] cleared the forced latejoin ruleset.") + dynamic_log("[key_name(usr)] cleared the forced latejoin ruleset.") else if(href_list["force_midround_rule"]) var/added_rule = input(usr,"What ruleset do you want to force right now? This will bypass threat level and population restrictions.", "Execute Ruleset", null) as null|anything in sortList(midround_rules) if (!added_rule) return - log_admin("[key_name(usr)] executed the [added_rule] ruleset.") - message_admins("[key_name(usr)] executed the [added_rule] ruleset.") + dynamic_log("[key_name(usr)] executed the [added_rule] ruleset.") picking_specific_rule(added_rule, TRUE) + else if(href_list["cancelmidround"]) + admin_cancel_midround(usr, href_list["cancelmidround"]) + return + else if (href_list["differentmidround"]) + admin_different_midround(usr, href_list["differentmidround"]) + return admin_panel() // Refreshes the window -// Checks if there are HIGHLANDER_RULESETs and calls the rule's round_result() proc +// Checks if there are HIGH_IMPACT_RULESETs and calls the rule's round_result() proc /datum/game_mode/dynamic/set_round_result() + // If it got to this part, just pick one high impact ruleset if it exists for(var/datum/dynamic_ruleset/rule in executed_rules) - if(rule.flags & HIGHLANDER_RULESET) - if(rule.check_finished()) // Only the rule that actually finished the round sets round result. - return rule.round_result() - // If it got to this part, just pick one highlander if it exists - for(var/datum/dynamic_ruleset/rule in executed_rules) - if(rule.flags & HIGHLANDER_RULESET) + if(rule.flags & HIGH_IMPACT_RULESET) return rule.round_result() return ..() @@ -233,9 +267,13 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) // communications title for threat management var/desc = "REACH OUT WITH THE FORCE BOY, AND TEAR THAT STAR DESTROYER FROM THE SKY!" // description for threat management - switch(round(threat_level)) + var/shown_threat + if(prob(FAKE_REPORT_CHANCE)) + shown_threat = rand(1, 100) + else + shown_threat = clamp(threat_level + rand(REPORT_NEG_DIVERGENCE, REPORT_POS_DIVERGENCE), 0, 100) + switch(round(shown_threat)) if(0 to 19) - update_playercounts() if(!current_players[CURRENT_LIVING_ANTAGS].len) title = "Peaceful Waypoint" desc = "Your station orbits deep within controlled, core-sector systems and serves as a waypoint for routine traffic through Nanotrasen's trade empire. Due to the combination of high security, interstellar traffic, and low strategic value, it makes any direct threat of violence unlikely. Your primary enemies will be incompetence and bored crewmen: try to organize team-building events to keep staffers interested and productive." @@ -291,9 +329,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) return TRUE if(force_ending) return TRUE - for(var/datum/dynamic_ruleset/rule in executed_rules) - if(rule.flags & HIGHLANDER_RULESET) - return rule.check_finished() /datum/game_mode/dynamic/proc/show_threatlog(mob/admin) if(!SSticker.HasRoundStarted()) @@ -309,40 +344,46 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if(istext(entry)) out += "[entry]
" - out += "Remaining threat/threat_level: [threat]/[threat_level]" + out += "Remaining threat/threat_level: [mid_round_budget]/[threat_level]" usr << browse(out.Join(), "window=threatlog;size=700x500") /// Generates the threat level using lorentz distribution and assigns peaceful_percentage. /datum/game_mode/dynamic/proc/generate_threat() - var/relative_threat = LORENTZ_DISTRIBUTION(GLOB.dynamic_curve_centre, GLOB.dynamic_curve_width) - threat_level = round(lorentz_to_threat(relative_threat), 0.1) + var/relative_threat = LORENTZ_DISTRIBUTION(threat_curve_centre, threat_curve_width) + threat_level = round(lorentz_to_amount(relative_threat), 0.1) - peaceful_percentage = round(LORENTZ_CUMULATIVE_DISTRIBUTION(relative_threat, GLOB.dynamic_curve_centre, GLOB.dynamic_curve_width), 0.01)*100 + peaceful_percentage = round(LORENTZ_CUMULATIVE_DISTRIBUTION(relative_threat, threat_curve_centre, threat_curve_width), 0.01)*100 - threat = threat_level +/// Generates the midround and roundstart budgets +/datum/game_mode/dynamic/proc/generate_budgets() + var/relative_round_start_budget_scale = LORENTZ_DISTRIBUTION(roundstart_split_curve_centre, roundstart_split_curve_width) + round_start_budget = round((lorentz_to_amount(relative_round_start_budget_scale) / 100) * threat_level, 0.1) + initial_round_start_budget = round_start_budget + mid_round_budget = threat_level - round_start_budget /datum/game_mode/dynamic/can_start() - message_admins("Dynamic mode parameters for the round:") - message_admins("Centre is [GLOB.dynamic_curve_centre], Width is [GLOB.dynamic_curve_width], Forced extended is [GLOB.dynamic_forced_extended ? "Enabled" : "Disabled"], No stacking is [GLOB.dynamic_no_stacking ? "Enabled" : "Disabled"].") - message_admins("Stacking limit is [GLOB.dynamic_stacking_limit], Classic secret is [GLOB.dynamic_classic_secret ? "Enabled" : "Disabled"], High population limit is [GLOB.dynamic_high_pop_limit].") + return TRUE + +/datum/game_mode/dynamic/proc/setup_parameters() log_game("DYNAMIC: Dynamic mode parameters for the round:") - log_game("DYNAMIC: Centre is [GLOB.dynamic_curve_centre], Width is [GLOB.dynamic_curve_width], Forced extended is [GLOB.dynamic_forced_extended ? "Enabled" : "Disabled"], No stacking is [GLOB.dynamic_no_stacking ? "Enabled" : "Disabled"].") - log_game("DYNAMIC: Stacking limit is [GLOB.dynamic_stacking_limit], Classic secret is [GLOB.dynamic_classic_secret ? "Enabled" : "Disabled"], High population limit is [GLOB.dynamic_high_pop_limit].") + log_game("DYNAMIC: Centre is [threat_curve_centre], Width is [threat_curve_width], Forced extended is [GLOB.dynamic_forced_extended ? "Enabled" : "Disabled"], No stacking is [GLOB.dynamic_no_stacking ? "Enabled" : "Disabled"].") + log_game("DYNAMIC: Stacking limit is [GLOB.dynamic_stacking_limit].") if(GLOB.dynamic_forced_threat_level >= 0) threat_level = round(GLOB.dynamic_forced_threat_level, 0.1) - threat = threat_level else generate_threat() + generate_budgets() + set_cooldowns() + dynamic_log("Dynamic Mode initialized with a Threat Level of... [threat_level]! ([round_start_budget] round start budget)") + return TRUE - var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_latejoin_delay_max + GLOB.dynamic_latejoin_delay_min) - latejoin_injection_cooldown = round(clamp(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_latejoin_delay_min, GLOB.dynamic_latejoin_delay_max)) + world.time +/datum/game_mode/dynamic/proc/set_cooldowns() + var/latejoin_injection_cooldown_middle = 0.5*(latejoin_delay_max + latejoin_delay_min) + latejoin_injection_cooldown = round(clamp(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), latejoin_delay_min, latejoin_delay_max)) + world.time - 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 - message_admins("Dynamic Mode initialized with a Threat Level of... [threat_level]!") - log_game("DYNAMIC: Dynamic Mode initialized with a Threat Level of... [threat_level]!") - return TRUE + var/midround_injection_cooldown_middle = 0.5*(midround_delay_max + midround_delay_min) + midround_injection_cooldown = round(clamp(EXP_DISTRIBUTION(midround_injection_cooldown_middle), midround_delay_min, midround_delay_max)) + world.time /datum/game_mode/dynamic/pre_setup() if(CONFIG_GET(flag/dynamic_config_enabled)) @@ -354,25 +395,29 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if(!vars[variable]) stack_trace("Invalid dynamic configuration variable [variable] in game mode variable changes.") continue - vars[variable] = configuration["dynamic"][variable] + vars[variable] = configuration["Dynamic"][variable] + setup_parameters() + setup_hijacking() + + var/valid_roundstart_ruleset = 0 for (var/rule in subtypesof(/datum/dynamic_ruleset)) var/datum/dynamic_ruleset/ruleset = new rule() // Simple check if the ruleset should be added to the lists. if(ruleset.name == "") continue + configure_ruleset(ruleset) switch(ruleset.ruletype) if("Roundstart") roundstart_rules += ruleset + if(ruleset.weight) + valid_roundstart_ruleset++ if ("Latejoin") latejoin_rules += ruleset if ("Midround") - if (ruleset.weight) - midround_rules += ruleset - configure_ruleset(ruleset) - - - for(var/mob/dead/new_player/player in GLOB.player_list) + midround_rules += ruleset + for(var/i in GLOB.new_player_list) + var/mob/dead/new_player/player = i if(player.ready == PLAYER_READY_TO_PLAY && player.mind) roundstart_pop_ready++ candidates.Add(player) @@ -380,15 +425,19 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) 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 if(valid_roundstart_ruleset < 1) + log_game("DYNAMIC: [valid_roundstart_ruleset] enabled roundstart rulesets.") + return TRUE else roundstart() + dynamic_log("[round_start_budget] round start budget was left, donating it to midrounds.") + threat_log += "[worldtime2text()]: [round_start_budget] round start budget was left, donating it to midrounds." + mid_round_budget += round_start_budget + var/starting_rulesets = "" for (var/datum/dynamic_ruleset/roundstart/DR in executed_rules) starting_rulesets += "[DR.name], " @@ -397,7 +446,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) return TRUE /datum/game_mode/dynamic/post_setup(report) - 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. @@ -417,9 +465,14 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) 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() - rule.pop_per_requirement = rule.pop_per_requirement > 0 ? rule.pop_per_requirement : (src.pop_per_requirement > 0 ? src.pop_per_requirement : 6) //i hate myself for this - if (rule.ready(TRUE)) - picking_roundstart_rule(list(rule), forced = TRUE) + if (rule.ready(roundstart_pop_ready, TRUE)) + var/cost = rule.cost + var/scaled_times = 0 + if (!(rule.flags & LONE_RULESET)) + scaled_times = round(max(round_start_budget - cost, 0) / rule.scaling_cost) + cost += rule.scaling_cost * scaled_times + + spend_roundstart_budget(picking_roundstart_rule(rule, scaled_times, forced = TRUE)) /datum/game_mode/dynamic/proc/roundstart() if (GLOB.dynamic_forced_extended) @@ -427,112 +480,73 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) return TRUE var/list/drafted_rules = list() for (var/datum/dynamic_ruleset/roundstart/rule in roundstart_rules) - if (rule.acceptable(roundstart_pop_ready, threat_level) && threat >= rule.cost) // If we got the population and threat required + if (!rule.weight) + continue + if (rule.acceptable(roundstart_pop_ready, threat_level) && round_start_budget >= rule.cost) // If we got the population and threat required rule.candidates = candidates.Copy() rule.trim_candidates() - if (rule.ready() && rule.candidates.len > 0) + if (rule.ready(roundstart_pop_ready) && rule.candidates.len > 0) drafted_rules[rule] = rule.weight - var/indice_pop = min(10,round(roundstart_pop_ready/pop_per_requirement)+1) - extra_rulesets_amount = 0 - if (GLOB.dynamic_classic_secret) - extra_rulesets_amount = 0 - else - if (roundstart_pop_ready > GLOB.dynamic_high_pop_limit) - message_admins("High Population Override is in effect! Threat Level will have more impact on which roles will appear, and player population less.") - log_game("DYNAMIC: High Population Override is in effect! Threat Level will have more impact on which roles will appear, and player population less.") - if (threat_level > high_pop_second_rule_req) - extra_rulesets_amount++ - if (threat_level > high_pop_third_rule_req) - extra_rulesets_amount++ - else - 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] && 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)) - 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) - 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 might want to 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 + var/list/rulesets_picked = list() -/// Picks a random roundstart rule from the list given as an argument and executes it. -/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 + // Kept in case a ruleset can't be initialized for whatever reason, we want to be able to only spend what we can use. + var/round_start_budget_left = round_start_budget - if(!forced) - if(only_ruleset_executed) - log_game("DYNAMIC: Picking [starting_rule.name] failed due to only_ruleset_executed.") - return FALSE - // Check if a blocking ruleset has been executed. - 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) - 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) // Should already be filtered out, but making sure. Check filtering at end of proc if reported. - if(threat_level > GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking) - if(highlander_executed) - drafted_rules -= starting_rule - if(drafted_rules.len <= 0) - log_game("DYNAMIC: Picking [starting_rule.name] failed due to no highlander stacking and no more rulesets available. Report this.") - return FALSE - starting_rule = pickweight(drafted_rules) - // With low pop and high threat there might be rulesets that get executed with no valid candidates. - else if(!starting_rule.ready()) // Should already be filtered out, but making sure. Check filtering at end of proc if reported. - drafted_rules -= starting_rule - if(drafted_rules.len <= 0) - log_game("DYNAMIC: Picking [starting_rule.name] failed because there were not enough candidates and no more rulesets available. Report this.") - return FALSE - starting_rule = pickweight(drafted_rules) - - message_admins("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() - - 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 + added_threat]. [starting_rule.scaling_cost ? "Scaled up[starting_rule.scaled_times]/3 times." : ""]" - if(starting_rule.flags & HIGHLANDER_RULESET) - highlander_executed = TRUE - else if(starting_rule.flags & ONLY_RULESET) - only_ruleset_executed = TRUE - executed_rules += starting_rule - 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 + while (round_start_budget_left > 0) + var/datum/dynamic_ruleset/roundstart/ruleset = pickweightAllowZero(drafted_rules) + if (isnull(ruleset)) + log_game("DYNAMIC: No more rules can be applied, stopping with [round_start_budget] left.") + break - return TRUE + var/cost = (ruleset in rulesets_picked) ? ruleset.scaling_cost : ruleset.cost + if (cost == 0) + stack_trace("[ruleset] cost 0, this is going to result in an infinite loop.") + drafted_rules[ruleset] = null + continue + + if (cost > round_start_budget_left) + drafted_rules[ruleset] = null + continue + + if (check_blocking(ruleset.blocking_rules, rulesets_picked)) + drafted_rules[ruleset] = null + continue + + round_start_budget_left -= cost + + rulesets_picked[ruleset] += 1 + + if (ruleset.flags & HIGH_IMPACT_RULESET) + for (var/_other_ruleset in drafted_rules) + var/datum/dynamic_ruleset/other_ruleset = _other_ruleset + if (other_ruleset.flags & HIGH_IMPACT_RULESET) + drafted_rules[other_ruleset] = null + + if (ruleset.flags & LONE_RULESET) + drafted_rules[ruleset] = null + + for (var/ruleset in rulesets_picked) + spend_roundstart_budget(picking_roundstart_rule(ruleset, rulesets_picked[ruleset] - 1)) + +/// Initializes the round start ruleset provided to it. Returns how much threat to spend. +/datum/game_mode/dynamic/proc/picking_roundstart_rule(datum/dynamic_ruleset/roundstart/ruleset, scaled_times = 0, forced = FALSE) + log_game("DYNAMIC: Picked a ruleset: [ruleset.name], scaled [scaled_times] times") + + ruleset.trim_candidates() + var/added_threat = ruleset.scale_up(roundstart_pop_ready, scaled_times) + + if(ruleset.pre_execute(roundstart_pop_ready)) + threat_log += "[worldtime2text()]: Roundstart [ruleset.name] spent [ruleset.cost + added_threat]. [ruleset.scaling_cost ? "Scaled up [ruleset.scaled_times]/[scaled_times] times." : ""]" + if(ruleset.flags & ONLY_RULESET) + only_ruleset_executed = TRUE + if(ruleset.flags & HIGH_IMPACT_RULESET) + high_impact_ruleset_executed = TRUE + executed_rules += ruleset + return ruleset.cost + added_threat else stack_trace("The starting rule \"[starting_rule.name]\" failed to pre_execute.") - return FALSE + return 0 /// Mainly here to facilitate delayed rulesets. All roundstart rulesets are executed with a timered callback to this proc. /datum/game_mode/dynamic/proc/execute_roundstart_rule(sent_rule) @@ -540,46 +554,13 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if(rule.execute()) if(rule.persistent) current_rules += rule + new_snapshot(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 - // Check if a blocking ruleset has been executed. - else if(check_blocking(rule.blocking_rules, executed_rules)) - drafted_rules -= rule - if(drafted_rules.len <= 0) - return FALSE - 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_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.ruletype == "Midround") - midround_rules = remove_from_list(midround_rules, rule.type) - - addtimer(CALLBACK(src, /datum/game_mode/dynamic/.proc/execute_midround_latejoin_rule, rule), rule.delay) - return TRUE - /// An experimental proc to allow admins to call rules on the fly or have rules call other rules. /datum/game_mode/dynamic/proc/picking_specific_rule(ruletype, forced = FALSE) var/datum/dynamic_ruleset/midround/new_rule @@ -600,21 +581,22 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) // Check if a blocking ruleset has been executed. else if(check_blocking(new_rule.blocking_rules, executed_rules)) return FALSE - // Check if the ruleset is highlander and if a highlander ruleset has been executed - else if(new_rule.flags & HIGHLANDER_RULESET) - if(threat_level > GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking) - if(highlander_executed) + // Check if the ruleset is high impact and if a high impact ruleset has been executed + else if(new_rule.flags & HIGH_IMPACT_RULESET) + if(threat_level < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking) + if(high_impact_ruleset_executed) return FALSE - update_playercounts() - if ((forced || (new_rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && new_rule.cost <= threat))) + var/population = current_players[CURRENT_LIVING_PLAYERS].len + if((new_rule.acceptable(population, threat_level) && new_rule.cost <= mid_round_budget) || forced) new_rule.trim_candidates() if (new_rule.ready(forced)) - spend_threat(new_rule.cost) + spend_midround_budget(new_rule.cost) threat_log += "[worldtime2text()]: Forced rule [new_rule.name] spent [new_rule.cost]" + new_rule.pre_execute(population) if (new_rule.execute()) // This should never fail since ready() returned 1 - if(new_rule.flags & HIGHLANDER_RULESET) - highlander_executed = TRUE + if(new_rule.flags & HIGH_IMPACT_RULESET) + high_impact_ruleset_executed = TRUE else if(new_rule.flags & ONLY_RULESET) only_ruleset_executed = TRUE log_game("DYNAMIC: Making a call to a specific ruleset...[new_rule.name]!") @@ -626,35 +608,7 @@ 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 - spend_threat(rule.cost) - threat_log += "[worldtime2text()]: [rule.ruletype] [rule.name] spent [rule.cost]" - if (rule.execute()) - log_game("DYNAMIC: Injected a [rule.ruletype == "latejoin" ? "latejoin" : "midround"] ruleset [rule.name].") - 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 - update_playercounts() - for (var/datum/dynamic_ruleset/rule in current_rules) if(rule.rule_process() == RULESET_STOP_PROCESSING) // If rule_process() returns 1 (RULESET_STOP_PROCESSING), stop processing. current_rules -= rule @@ -665,60 +619,49 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) // 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) + var/midround_injection_cooldown_middle = 0.5*(midround_delay_max + midround_delay_min) + midround_injection_cooldown = (round(clamp(EXP_DISTRIBUTION(midround_injection_cooldown_middle), midround_delay_min, 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.") + dynamic_log("Checking for midround injection.") + + last_midround_injection_attempt = world.time - update_playercounts() - if (get_injection_chance()) + if (prob(get_midround_injection_chance())) var/list/drafted_rules = list() for (var/datum/dynamic_ruleset/midround/rule in midround_rules) - if (rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost) - // Classic secret : only autotraitor/minor roles - if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET))) + if (!rule.weight) + continue + if (rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && mid_round_budget >= rule.cost) + // If admins have disabled dynamic from picking from the ghost pool + if(rule.ruletype == "Latejoin" && !(GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT)) continue + // If admins have disabled dynamic from picking from the ghost pool rule.trim_candidates() if (rule.ready()) drafted_rules[rule] = rule.get_weight() if (drafted_rules.len > 0) - picking_midround_latejoin_rule(drafted_rules) - -/// Updates current_players. -/datum/game_mode/dynamic/proc/update_playercounts() - current_players[CURRENT_LIVING_PLAYERS] = list() - current_players[CURRENT_LIVING_ANTAGS] = list() - current_players[CURRENT_DEAD_PLAYERS] = list() - current_players[CURRENT_OBSERVERS] = list() - for (var/mob/M in GLOB.player_list) - if (istype(M, /mob/dead/new_player)) - continue - if (M.stat != DEAD) - current_players[CURRENT_LIVING_PLAYERS].Add(M) - if (M.mind && (M.mind.special_role || M.mind.antag_datums?.len > 0)) - current_players[CURRENT_LIVING_ANTAGS].Add(M) - else - if (istype(M,/mob/dead/observer)) - var/mob/dead/observer/O = M - if (O.started_as_observer) // Observers - current_players[CURRENT_OBSERVERS].Add(M) - continue - current_players[CURRENT_DEAD_PLAYERS].Add(M) // Players who actually died (and admins who ghosted, would be nice to avoid counting them somehow) + pick_midround_rule(drafted_rules) + else if (random_event_hijacked == HIJACKED_TOO_SOON) + log_game("DYNAMIC: Midround injection failed when random event was hijacked. Spawning another random event in its place.") -/// Gets the chance for latejoin and midround injection, the dry_run argument is only used for forced injection. + // A random event antag would have rolled had this injection check passed. + // As a refund, spawn a non-ghost-role random event. + SSevents.spawnEvent() + SSevents.reschedule() + + random_event_hijacked = HIJACKED_NOTHING + +/// Gets the chance for latejoin injection, the dry_run argument is only used for forced injection. /datum/game_mode/dynamic/proc/get_injection_chance(dry_run = FALSE) if(forced_injection) - forced_injection = !dry_run + forced_injection = dry_run return 100 var/chance = 0 - // If the high pop override is in effect, we reduce the impact of population on the antag injection chance - var/high_pop_factor = (current_players[CURRENT_LIVING_PLAYERS].len >= GLOB.dynamic_high_pop_limit) - var/max_pop_per_antag = max(5,15 - round(threat_level/10) - round(current_players[CURRENT_LIVING_PLAYERS].len/(high_pop_factor ? 10 : 5))) + var/max_pop_per_antag = max(5,15 - round(threat_level/10) - round(current_players[CURRENT_LIVING_PLAYERS].len/5)) if (!current_players[CURRENT_LIVING_ANTAGS].len) chance += 50 // No antags at all? let's boost those odds! else @@ -729,12 +672,22 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) chance += 25-10*(max_pop_per_antag-current_pop_per_antag) if (current_players[CURRENT_DEAD_PLAYERS].len > current_players[CURRENT_LIVING_PLAYERS].len) chance -= 30 // More than half the crew died? ew, let's calm down on antags - if (threat > 70) - chance += 15 - if (threat < 30) - chance -= 15 + if (mid_round_budget > higher_injection_chance_minimum_threat) + chance += higher_injection_chance + if (mid_round_budget < lower_injection_chance_minimum_threat) + chance -= lower_injection_chance return round(max(0,chance)) +/// Gets the chance for midround injection, the dry_run argument is only used for forced injection. +/// Usually defers to the latejoin injection chance. +/datum/game_mode/dynamic/proc/get_midround_injection_chance(dry_run) + var/chance = get_injection_chance(dry_run) + + if (random_event_hijacked != HIJACKED_NOTHING) + chance += hijacked_random_event_injection_chance + + return chance + /// Removes type from the list /datum/game_mode/dynamic/proc/remove_from_list(list/type_list, type) for(var/I in type_list) @@ -746,7 +699,8 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) /datum/game_mode/dynamic/proc/check_blocking(list/blocking_list, list/rule_list) if(blocking_list.len > 0) for(var/blocking in blocking_list) - for(var/datum/executed in rule_list) + for(var/_executed in rule_list) + var/datum/executed = _executed if(blocking == executed.type) return TRUE return FALSE @@ -766,26 +720,25 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if(EMERGENCY_ESCAPED_OR_ENDGAMED) // No more rules after the shuttle has left return - update_playercounts() - if (forced_latejoin_rule) forced_latejoin_rule.candidates = list(newPlayer) forced_latejoin_rule.trim_candidates() log_game("DYNAMIC: Forcing ruleset [forced_latejoin_rule]") if (forced_latejoin_rule.ready(TRUE)) - picking_midround_latejoin_rule(list(forced_latejoin_rule), forced = TRUE) + if (!forced_latejoin_rule.repeatable) + latejoin_rules = remove_from_list(latejoin_rules, forced_latejoin_rule.type) + addtimer(CALLBACK(src, /datum/game_mode/dynamic/.proc/execute_midround_latejoin_rule, forced_latejoin_rule), forced_latejoin_rule.delay) forced_latejoin_rule = null else if (latejoin_injection_cooldown < world.time && prob(get_injection_chance())) var/list/drafted_rules = list() for (var/datum/dynamic_ruleset/latejoin/rule in latejoin_rules) - if (rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost) - // Classic secret : only autotraitor/minor roles - if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET))) - continue + if (!rule.weight) + continue + if (rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && mid_round_budget >= rule.cost) // 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) + if (threat_level < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking) + if(rule.flags & HIGH_IMPACT_RULESET && high_impact_ruleset_executed) continue rule.candidates = list(newPlayer) @@ -793,27 +746,56 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if (rule.ready()) drafted_rules[rule] = rule.get_weight() - if (drafted_rules.len > 0 && picking_midround_latejoin_rule(drafted_rules)) - var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_latejoin_delay_max + GLOB.dynamic_latejoin_delay_min) - latejoin_injection_cooldown = round(clamp(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_latejoin_delay_min, GLOB.dynamic_latejoin_delay_max)) + world.time + if (drafted_rules.len > 0 && pick_latejoin_rule(drafted_rules)) + var/latejoin_injection_cooldown_middle = 0.5*(latejoin_delay_max + latejoin_delay_min) + latejoin_injection_cooldown = round(clamp(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), latejoin_delay_min, latejoin_delay_max)) + world.time + +/// Apply configurations to rule. +/datum/game_mode/dynamic/proc/configure_ruleset(datum/dynamic_ruleset/ruleset) + var/rule_conf = LAZYACCESSASSOC(configuration, ruleset.ruletype, ruleset.name) + for(var/variable in rule_conf) + if(!(variable in ruleset.vars)) + stack_trace("Invalid dynamic configuration variable [variable] in [ruleset.ruletype] [ruleset.name].") + continue + ruleset.vars[variable] = rule_conf[variable] + if(CONFIG_GET(flag/protect_roles_from_antagonist)) + ruleset.restricted_roles |= ruleset.protected_roles + if(CONFIG_GET(flag/protect_assistant_from_antagonist)) + ruleset.restricted_roles |= "Assistant" + if(CONFIG_GET(flag/protect_heads_from_antagonist)) + ruleset.restricted_roles |= GLOB.command_positions /// Refund threat, but no more than threat_level. /datum/game_mode/dynamic/proc/refund_threat(regain) threat = min(threat_level,threat+regain) + mid_round_budget = min(threat_level, mid_round_budget + regain) /// Generate threat and increase the threat_level if it goes beyond, capped at 100 /datum/game_mode/dynamic/proc/create_threat(gain) threat = min(100, threat+gain) if(threat > threat_level) threat_level = threat + mid_round_budget = min(100, mid_round_budget + gain) + if(mid_round_budget > threat_level) + threat_level = mid_round_budget /// Expend threat, can't fall under 0. /datum/game_mode/dynamic/proc/spend_threat(cost) threat = max(threat-cost,0) +/// Expend round start threat, can't fall under 0. +/datum/game_mode/dynamic/proc/spend_roundstart_budget(cost) + round_start_budget = max(round_start_budget - cost,0) /// Turns the value generated by lorentz distribution to threat value between 0 and 100. /datum/game_mode/dynamic/proc/lorentz_to_threat(x) - switch (x) +/// Expend midround threat, can't fall under 0. +/datum/game_mode/dynamic/proc/spend_midround_budget(cost) + mid_round_budget = max(mid_round_budget - cost,0) + +/// Turns the value generated by lorentz distribution to number between 0 and 100. +/// Used for threat level and splitting the budgets. +/datum/game_mode/dynamic/proc/lorentz_to_amount(x) + switch(x) if (-INFINITY to -20) return rand(0, 10) if (-20 to -10) @@ -834,20 +816,12 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) return RULE_OF_THREE(40, 20, x) + 50 if (20 to INFINITY) return rand(90, 100) - -/datum/game_mode/dynamic/proc/configure_ruleset(datum/dynamic_ruleset/ruleset) - if(configuration) - if(!configuration[ruleset.ruletype]) - return - if(!configuration[ruleset.ruletype][ruleset.name]) - return - var/rule_conf = configuration[ruleset.ruletype][ruleset.name] - for(var/variable in rule_conf) - if(isnull(ruleset.vars[variable])) - stack_trace("Invalid dynamic configuration variable [variable] in [ruleset.ruletype] [ruleset.name].") - continue - ruleset.vars[variable] = rule_conf[variable] - if(CONFIG_GET(flag/protect_roles_from_antagonist)) - ruleset.restricted_roles |= ruleset.protected_roles - if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - ruleset.restricted_roles |= "Assistant" + +/// Log to messages and to the game +/datum/game_mode/dynamic/proc/dynamic_log(text) + message_admins("DYNAMIC: [text]") + log_game("DYNAMIC: [text]") + +#undef FAKE_REPORT_CHANCE +#undef REPORT_NEG_DIVERGENCE +#undef REPORT_POS_DIVERGENCE \ No newline at end of file diff --git a/code/game/gamemodes/dynamic/dynamic_hijacking.dm b/code/game/gamemodes/dynamic/dynamic_hijacking.dm new file mode 100644 index 000000000000..aeb30c6b7b8b --- /dev/null +++ b/code/game/gamemodes/dynamic/dynamic_hijacking.dm @@ -0,0 +1,26 @@ +/datum/game_mode/dynamic/proc/setup_hijacking() + RegisterSignal(SSdcs, COMSIG_GLOB_PRE_RANDOM_EVENT, .proc/on_pre_random_event) + +/datum/game_mode/dynamic/proc/on_pre_random_event(datum/source, datum/round_event_control/round_event_control) + if (!round_event_control.dynamic_should_hijack) + return + + if (random_event_hijacked != HIJACKED_NOTHING) + dynamic_log("Random event [round_event_control.name] tried to roll, but Dynamic vetoed it (random event has already ran).") + SSevents.spawnEvent() + SSevents.reschedule() + return CANCEL_PRE_RANDOM_EVENT + + var/time_range = rand(random_event_hijack_minimum, random_event_hijack_maximum) + + if (world.time - last_midround_injection_attempt < time_range) + random_event_hijacked = HIJACKED_TOO_RECENT + dynamic_log("Random event [round_event_control.name] tried to roll, but the last midround injection \ + was too recent. Injection chance has been raised to [get_midround_injection_chance(dry_run = TRUE)]%.") + return CANCEL_PRE_RANDOM_EVENT + + if (midround_injection_cooldown - world.time < time_range) + random_event_hijacked = HIJACKED_TOO_SOON + dynamic_log("Random event [round_event_control.name] tried to roll, but the next midround injection \ + is too soon. Injection chance has been raised to [get_midround_injection_chance(dry_run = TRUE)]%.") + return CANCEL_PRE_RANDOM_EVENT \ No newline at end of file diff --git a/code/game/gamemodes/dynamic/dynamic_logging.dm b/code/game/gamemodes/dynamic/dynamic_logging.dm new file mode 100644 index 000000000000..9c7e5128c220 --- /dev/null +++ b/code/game/gamemodes/dynamic/dynamic_logging.dm @@ -0,0 +1,97 @@ +/// A "snapshot" of dynamic at an important point in time. +/// Exported to JSON in the dynamic.json log file. +/datum/dynamic_snapshot + /// The remaining midround threat + var/remaining_threat + + /// The world.time when the snapshot was taken + var/time + + /// The total number of players in the server + var/total_players + + /// The number of alive players + var/alive_players + + /// The number of dead players + var/dead_players + + /// The number of observers + var/observers + + /// The number of alive antags + var/alive_antags + + /// The rulesets chosen this snapshot + var/datum/dynamic_snapshot_ruleset/ruleset_chosen + + /// The cached serialization of this snapshot + var/serialization + +/// A ruleset chosen during a snapshot +/datum/dynamic_snapshot_ruleset + /// The name of the ruleset chosen + var/name + + /// If it is a round start ruleset, how much it was scaled by + var/scaled + + /// The number of assigned antags + var/assigned + +/datum/dynamic_snapshot_ruleset/New(datum/dynamic_ruleset/ruleset) + name = ruleset.name + assigned = ruleset.assigned.len + + if (istype(ruleset, /datum/dynamic_ruleset/roundstart)) + scaled = ruleset.scaled_times + +/// Convert the snapshot to an associative list +/datum/dynamic_snapshot/proc/to_list() + if (!isnull(serialization)) + return serialization + + serialization = list( + "remaining_threat" = remaining_threat, + "time" = time, + "total_players" = total_players, + "alive_players" = alive_players, + "dead_players" = dead_players, + "observers" = observers, + "alive_antags" = alive_antags, + "ruleset_chosen" = list( + "name" = ruleset_chosen.name, + "scaled" = ruleset_chosen.scaled, + "assigned" = ruleset_chosen.assigned, + ), + ) + + return serialization + +/// Creates a new snapshot with the given rulesets chosen, and writes to the JSON output. +/datum/game_mode/dynamic/proc/new_snapshot(datum/dynamic_ruleset/ruleset_chosen) + var/datum/dynamic_snapshot/new_snapshot = new + + new_snapshot.remaining_threat = mid_round_budget + new_snapshot.time = world.time + new_snapshot.alive_players = current_players[CURRENT_LIVING_PLAYERS].len + new_snapshot.dead_players = current_players[CURRENT_DEAD_PLAYERS].len + new_snapshot.observers = current_players[CURRENT_OBSERVERS].len + new_snapshot.total_players = new_snapshot.alive_players + new_snapshot.dead_players + new_snapshot.observers + new_snapshot.alive_antags = current_players[CURRENT_LIVING_ANTAGS].len + new_snapshot.ruleset_chosen = new /datum/dynamic_snapshot_ruleset(ruleset_chosen) + + LAZYADD(snapshots, new_snapshot) + + var/list/serialized = list() + serialized["threat_level"] = threat_level + serialized["round_start_budget"] = initial_round_start_budget + serialized["mid_round_budget"] = threat_level - initial_round_start_budget + + var/list/serialized_snapshots = list() + for (var/_snapshot in snapshots) + var/datum/dynamic_snapshot/snapshot = _snapshot + serialized_snapshots += list(snapshot.to_list()) + serialized["snapshots"] = serialized_snapshots + + rustg_file_write(json_encode(serialized), "[GLOB.log_directory]/dynamic.json") \ No newline at end of file diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm index f329d4ae90e6..1cda8c95a2ac 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm @@ -1,6 +1,3 @@ -#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 @@ -8,19 +5,19 @@ /// For admin logging and round end screen. var/name = "" /// For admin logging and round end screen, do not change this unless making a new rule type. - var/ruletype = "" + var/ruletype = "" /// If set to TRUE, the rule won't be discarded after being executed, and dynamic will call rule_process() every time it ticks. - var/persistent = FALSE + var/persistent = FALSE /// If set to TRUE, dynamic mode will be able to draft this ruleset again later on. (doesn't apply for roundstart rules) - var/repeatable = FALSE + var/repeatable = FALSE /// If set higher than 0 decreases weight by itself causing the ruleset to appear less often the more it is repeated. - var/repeatable_weight_decrease = 2 + var/repeatable_weight_decrease = 2 /// List of players that are being drafted for this rule - var/list/mob/candidates = list() + var/list/mob/candidates = list() /// List of players that were selected for this rule - var/list/datum/mind/assigned = list() + var/list/datum/mind/assigned = list() /// Preferences flag such as ROLE_WIZARD that need to be turned on for players to be antag - var/antag_flag = null + var/antag_flag = null /// The antagonist datum that is assigned to the mobs mind on ruleset execution. var/datum/antagonist/antag_datum = null /// The required minimum account age for this ruleset. @@ -28,63 +25,63 @@ /// If set, and config flag protect_roles_from_antagonist is false, then the rule will not pick players from these roles. var/list/protected_roles = list() /// If set, rule will deny candidates from those roles always. - var/list/restricted_roles = list() + var/list/restricted_roles = list() /// If set, rule will only accept candidates from those roles, IMPORTANT: DOES NOT WORK ON ROUNDSTART RULESETS. - var/list/exclusive_roles = list() + var/list/exclusive_roles = list() /// If set, there needs to be a certain amount of players doing those roles (among the players who won't be drafted) for the rule to be drafted IMPORTANT: DOES NOT WORK ON ROUNDSTART RULESETS. - var/list/enemy_roles = list() + var/list/enemy_roles = list() /// If enemy_roles was set, this is the amount of enemy job workers needed per threat_level range (0-10,10-20,etc) IMPORTANT: DOES NOT WORK ON ROUNDSTART RULESETS. - var/required_enemies = list(1,1,0,0,0,0,0,0,0,0) + var/required_enemies = list(1,1,0,0,0,0,0,0,0,0) /// The rule needs this many candidates (post-trimming) to be executed (example: Cult needs 4 players at round start) - var/required_candidates = 0 - /// 1 -> 9, probability for this rule to be picked against other rules - var/weight = 5 + var/required_candidates = 0 + /// 0 -> 9, probability for this rule to be picked against other rules. If zero this will effectively disable the rule. + var/weight = 5 /// Threat cost for this rule, this is decreased from the mode's threat when the rule is executed. - var/cost = 0 + var/cost = 0 /// Cost per level the rule scales up. var/scaling_cost = 0 /// How many times a rule has scaled up upon getting picked. var/scaled_times = 0 /// Used for the roundend report var/total_cost = 0 - /// A flag that determines how the ruleset is handled - /// HIGHLANDER_RULESET are rulesets can end the round. - /// TRAITOR_RULESET and MINOR_RULESET can't end the round and have no difference right now. - var/flags = 0 + /// A flag that determines how the ruleset is handled. Check __DEFINES/dynamic.dm for an explanation of the accepted values. + var/flags = NONE /// Pop range per requirement. If zero defaults to mode's pop_per_requirement. var/pop_per_requirement = 0 /// Requirements are the threat level requirements per pop range. /// With the default values, The rule will never get drafted below 10 threat level (aka: "peaceful extended"), and it requires a higher threat level at lower pops. var/list/requirements = list(40,30,20,10,10,10,10,10,10,10) - /// An alternative, static requirement used instead when pop is over mode's high_pop_limit. - var/high_population_requirement = 10 /// Reference to the mode, use this instead of SSticker.mode. var/datum/game_mode/dynamic/mode = null /// If a role is to be considered another for the purpose of banning. - var/antag_flag_override = null + var/antag_flag_override = null /// If a ruleset type which is in this list has been executed, then the ruleset will not be executed. var/list/blocking_rules = list() - /// The minimum amount of players required for the rule to be considered. + /// The minimum amount of players required for the rule to be considered. var/minimum_players = 0 /// The maximum amount of players required for the rule to be considered. - /// Anything below zero or exactly zero is ignored. + /// Anything below zero or exactly zero is ignored. var/maximum_players = 0 /// Calculated during acceptable(), used in scaling and team sizes. var/indice_pop = 0 - /// Population scaling. Used by team antags and scaling for solo antags. - var/list/antag_cap = list() /// Base probability used in scaling. The higher it is, the more likely to scale. Kept as a var to allow for config editing._SendSignal(sigtype, list/arguments) var/base_prob = 60 /// Delay for when execute will get called from the time of post_setup (roundstart) or process (midround/latejoin). /// Make sure your ruleset works with execute being called during the game when using this, and that the clean_up proc reverts it properly in case of faliure. var/delay = 0 + /// Judges the amount of antagonists to apply, for both solo and teams. + /// Note that some antagonists (such as traitors, lings, heretics, etc) will add more based on how many times they've been scaled. + /// Written as a linear equation--ceil(x/denominator) + offset, or as a fixed constant. + /// If written as a linear equation, will be in the form of `list("denominator" = denominator, "offset" = offset). + var/antag_cap = 0 + /datum/dynamic_ruleset/New() ..() if (istype(SSticker.mode, /datum/game_mode/dynamic)) mode = SSticker.mode - else if (GLOB.master_mode != "dynamic") // This is here to make roundstart forced ruleset function. + else if (!SSticker.is_mode("dynamic")) // This is here to make roundstart forced ruleset function. qdel(src) /datum/dynamic_ruleset/roundstart // One or more of those drafted at roundstart ruletype = "Roundstart" @@ -100,35 +97,36 @@ return FALSE 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 - 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 + pop_per_requirement = pop_per_requirement > 0 ? pop_per_requirement : mode.pop_per_requirement + indice_pop = min(requirements.len,round(population/pop_per_requirement)+1) + return (threat_level >= requirements[indice_pop]) + +/// When picking rulesets, if dynamic picks the same one multiple times, it will "scale up". +/// However, doing this blindly would result in lowpop rounds (think under 10 people) where over 80% of the crew is antags! +/// This function is here to ensure the antag ratio is kept under control while scaling up. +/// Returns how much threat to actually spend in the end. +/datum/dynamic_ruleset/proc/scale_up(population, max_scale) + if (!scaling_cost) + return 0 + + var/antag_fraction = 0 + for(var/_ruleset in (mode.executed_rules + list(src))) // we care about the antags we *will* assign, too + var/datum/dynamic_ruleset/ruleset = _ruleset + antag_fraction += ((1 + ruleset.scaled_times) * ruleset.get_antag_cap(population)) / mode.roundstart_pop_ready + + for(var/i in 1 to max_scale) + if(antag_fraction < 0.25) + scaled_times += 1 + antag_fraction += get_antag_cap(population) / mode.roundstart_pop_ready // we added new antags, gotta update the % + + return scaled_times * scaling_cost + +/// Returns what the antag cap with the given population is. +/datum/dynamic_ruleset/proc/get_antag_cap(population) + if (isnum(antag_cap)) + return antag_cap + + return CEILING(population / antag_cap["denominator"], 1) + (antag_cap["offset"] || 0) /// This is called if persistent variable is true everytime SSTicker ticks. /datum/dynamic_ruleset/proc/rule_process() @@ -150,11 +148,11 @@ /// Here you can perform any additional checks you want. (such as checking the map etc) /// Remember that on roundstart no one knows what their job is at this point. /// IMPORTANT: If ready() returns TRUE, that means pre_execute() or execute() should never fail! -/datum/dynamic_ruleset/proc/ready(forced = 0) - if (required_candidates > candidates.len) +/datum/dynamic_ruleset/proc/ready(forced = 0) + if (required_candidates > candidates.len) return FALSE return TRUE - + /// Runs from gamemode process() if ruleset fails to start, like delayed rulesets not getting valid candidates. /// This one only handles refunding the threat, override in ruleset to clean up the rest. /datum/dynamic_ruleset/proc/clean_up() @@ -178,11 +176,11 @@ return /// Set mode result and news report here. -/// Only called if ruleset is flagged as HIGHLANDER_RULESET +/// Only called if ruleset is flagged as HIGH_IMPACT_RULESET /datum/dynamic_ruleset/proc/round_result() /// Checks if round is finished, return true to end the round. -/// Only called if ruleset is flagged as HIGHLANDER_RULESET +/// Only called if ruleset is flagged as HIGH_IMPACT_RULESET /datum/dynamic_ruleset/proc/check_finished() return FALSE @@ -195,25 +193,24 @@ /// Checks if candidates are connected and if they are banned or don't want to be the antagonist. /datum/dynamic_ruleset/roundstart/trim_candidates() for(var/mob/dead/new_player/P in candidates) - if (!P.client || !P.mind) // Are they connected? + var/client/client = GET_CLIENT(P) + if (!client || !P.mind) // Are they connected? candidates.Remove(P) - continue - if(!mode.check_age(P.client, minimum_required_age)) + else if(!mode.check_age(client, minimum_required_age)) candidates.Remove(P) continue if(P.mind.special_role) // We really don't want to give antag to an antag. candidates.Remove(P) - continue - if(antag_flag_override) - if(!(antag_flag_override in P.client.prefs.be_special) || is_banned_from(P.ckey, list(antag_flag_override, ROLE_SYNDICATE))) + else if(antag_flag_override) + if(!(antag_flag_override in client.prefs.be_special) || is_banned_from(P.ckey, list(antag_flag_override, ROLE_SYNDICATE))) candidates.Remove(P) continue else - if(!(antag_flag in P.client.prefs.be_special) || is_banned_from(P.ckey, list(antag_flag, ROLE_SYNDICATE))) + if(!(antag_flag in client.prefs.be_special) || is_banned_from(P.ckey, list(antag_flag, ROLE_SYNDICATE))) candidates.Remove(P) continue /// Do your checks if the ruleset is ready to be executed here. /// Should ignore certain checks if forced is TRUE -/datum/dynamic_ruleset/roundstart/ready(forced = FALSE) +/datum/dynamic_ruleset/roundstart/ready(population, forced = FALSE) return ..() diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index 8d80fff93fea..92a0761a0417 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -65,9 +65,7 @@ weight = 1 cost = 5 requirements = list(40,30,20,10,10,10,10,10,10,10) - high_population_requirement = 10 repeatable = TRUE - flags = TRAITOR_RULESET ////////////////////////////////////////////// // // @@ -89,8 +87,6 @@ cost = 20 delay = 1 MINUTES requirements = list(80,75,60,60,55,50,40,30,20,20) - high_population_requirement = 50 - flags = HIGHLANDER_RULESET blocking_rules = list(/datum/dynamic_ruleset/roundstart/revs) var/required_heads_of_staff = 3 var/finished = FALSE diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index 9bff678b47d9..68a74f55c754 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -1,4 +1,4 @@ -////////////////////////////////////////////// +////////////////////////////////////////////// // // // MIDROUND RULESETS // // // @@ -171,23 +171,29 @@ cost = 10 requirements = list(50,40,30,20,10,10,10,10,10,10) repeatable = TRUE - high_population_requirement = 10 - flags = TRAITOR_RULESET /datum/dynamic_ruleset/midround/autotraitor/acceptable(population = 0, threat = 0) var/player_count = mode.current_players[CURRENT_LIVING_PLAYERS].len var/antag_count = mode.current_players[CURRENT_LIVING_ANTAGS].len var/max_traitors = round(player_count / 10) + 1 - if ((antag_count < max_traitors) && prob(mode.threat_level))//adding traitors if the antag population is getting low - return ..() - else + + // adding traitors if the antag population is getting low + var/too_little_antags = antag_count < max_traitors + if (!too_little_antags) + log_game("DYNAMIC: Too many living antags compared to living players ([antag_count] living antags, [player_count] living players, [max_traitors] max traitors)") return FALSE + if (!prob(mode.threat_level)) + log_game("DYNAMIC: Random chance to roll autotraitor failed, it was a [mode.threat_level]% chance.") + return FALSE + + return ..() + /datum/dynamic_ruleset/midround/autotraitor/trim_candidates() ..() for(var/mob/living/player in living_players) if(issilicon(player)) // Your assigned role doesn't change when you are turned into a silicon. - living_players -= player + living_players -= player continue if(is_centcom_level(player.z)) living_players -= player // We don't autotator people in CentCom @@ -229,7 +235,6 @@ weight = 1 cost = 35 requirements = list(100,100,80,70,60,60,50,50,45,40) - high_population_requirement = 35 required_type = /mob/living/silicon/ai var/ion_announce = 33 var/removeDontImproveChance = 10 @@ -282,7 +287,6 @@ weight = 1 cost = 20 requirements = list(90,90,70,40,30,20,10,10,10,10) - high_population_requirement = 50 repeatable = TRUE /datum/dynamic_ruleset/midround/from_ghosts/wizard/ready(forced = FALSE) @@ -314,10 +318,9 @@ weight = 1 cost = 35 requirements = list(90,90,90,80,60,40,30,20,10,10) - high_population_requirement = 10 var/list/operative_cap = list(2,2,3,3,4,5,5,5,5,5) var/datum/team/nuclear/nuke_team - flags = HIGHLANDER_RULESET + flags = HIGH_IMPACT_RULESET /datum/dynamic_ruleset/midround/from_ghosts/nuclear/acceptable(population=0, threat=0) if (locate(/datum/dynamic_ruleset/roundstart/nuclear) in mode.executed_rules) @@ -357,7 +360,6 @@ weight = 1 cost = 30 requirements = list(100,100,100,80,60,50,45,30,20,20) - high_population_requirement = 50 repeatable = TRUE /datum/dynamic_ruleset/midround/from_ghosts/blob/generate_ruleset_body(mob/applicant) @@ -380,7 +382,6 @@ weight = 1 cost = 20 requirements = list(100,100,100,70,50,40,30,25,20,10) - high_population_requirement = 50 repeatable = TRUE var/list/vents = list() @@ -457,7 +458,7 @@ log_game("DYNAMIC: [key_name(S)] was spawned as a Nightmare by the midround ruleset.") return S -////////////////////////////////////////////// +////////////////////////////////////////////// // // // VAMPIRE // // // @@ -474,7 +475,7 @@ cost = 25 requirements = list(80,70,60,50,50,45,30,30,25,25) minimum_players = 30 - + /datum/dynamic_ruleset/midround/autovamp/acceptable(population = 0, threat = 0) var/player_count = mode.current_players[CURRENT_LIVING_PLAYERS].len var/antag_count = mode.current_players[CURRENT_LIVING_ANTAGS].len @@ -511,7 +512,7 @@ var/datum/antagonist/vampire/newVampire = new M.mind.add_antag_datum(newVampire) return TRUE - + ////////////////////////////////////////////// // // // ZOMBIE (GHOST) // @@ -561,4 +562,4 @@ log_game("[key_name(M)] was spawned as a Zombie by an event.") spawned_mobs += M return SUCCESSFUL_SPAWN - + diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index 007b89551e72..2b1be4cf4225 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -15,15 +15,17 @@ restricted_roles = list("Cyborg") required_candidates = 1 weight = 1 - cost = 10 // Avoid raising traitor threat above 10, as it is the default low cost ruleset. - scaling_cost = 10 + cost = 8 // Avoid raising traitor threat above 10, as it is the default low cost ruleset. + scaling_cost = 9 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) + antag_cap = list("denominator" = 24) + var/autotraitor_cooldown = (15 MINUTES) + COOLDOWN_DECLARE(autotraitor_cooldown_check) -/datum/dynamic_ruleset/roundstart/traitor/pre_execute() - var/num_traitors = antag_cap[indice_pop] * (scaled_times + 1) +/datum/dynamic_ruleset/roundstart/traitor/pre_execute(population) + . = ..() + COOLDOWN_START(src, autotraitor_cooldown_check, autotraitor_cooldown) + var/num_traitors = get_antag_cap(population) * (scaled_times + 1) for (var/i = 1 to num_traitors) var/mob/M = pick_n_take(candidates) assigned += M.mind @@ -33,11 +35,8 @@ return TRUE /datum/dynamic_ruleset/roundstart/traitor/rule_process() - if (autotraitor_cooldown > 0) - autotraitor_cooldown-- - else - autotraitor_cooldown = 450 // 15 minutes - message_admins("Checking if we can turn someone into a traitor.") + if (COOLDOWN_FINISHED(src, autotraitor_cooldown_check)) + COOLDOWN_START(src, autotraitor_cooldown_check, autotraitor_cooldown) log_game("DYNAMIC: Checking if we can turn someone into a traitor.") mode.picking_specific_rule(/datum/dynamic_ruleset/midround/autotraitor) @@ -58,14 +57,14 @@ cost = 10 scaling_cost = 10 requirements = list(40,30,30,20,20,15,15,15,10,10) - high_population_requirement = 10 - 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. + antag_cap = 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 = (antag_cap[indice_pop]/min_team_size) * (scaled_times + 1) // 1 team per scaling +/datum/dynamic_ruleset/roundstart/traitorbro/pre_execute(population) + . = ..() + var/num_teams = (get_antag_cap(population)/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 @@ -104,15 +103,14 @@ restricted_roles = list("AI", "Cyborg") required_candidates = 1 weight = 1 - cost = 15 - scaling_cost = 15 + cost = 16 + scaling_cost = 10 requirements = list(75,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) + antag_cap = list("denominator" = 29) -/datum/dynamic_ruleset/roundstart/changeling/pre_execute() - var/num_changelings = antag_cap[indice_pop] * (scaled_times + 1) - for (var/i = 1 to num_changelings) +/datum/dynamic_ruleset/roundstart/changeling/pre_execute(population) + . = ..() + var/num_changelings = get_antag_cap(population) * (scaled_times + 1) var/mob/M = pick_n_take(candidates) assigned += M.mind M.mind.restricted_roles = restricted_roles @@ -139,13 +137,15 @@ restricted_roles = list("AI", "Cyborg") required_candidates = 1 weight = 3 - cost = 20 + cost = 15 + scaling_cost = 9 requirements = list(50,45,45,40,35,20,20,15,10,10) + antag_cap = list("denominator" = 24) -/datum/dynamic_ruleset/roundstart/heretics/pre_execute() +/datum/dynamic_ruleset/roundstart/heretics/pre_execute(population) . = ..() - var/num_ecult = antag_cap[indice_pop] * (scaled_times + 1) + var/num_ecult = get_antag_cap(population) * (scaled_times + 1) for (var/i = 1 to num_ecult) var/mob/picked_candidate = pick_n_take(candidates) @@ -177,13 +177,13 @@ persistent = TRUE antag_flag = ROLE_WIZARD antag_datum = /datum/antagonist/wizard + flags = LONE_RULESET minimum_required_age = 14 restricted_roles = list("Head of Security", "Captain") // Just to be sure that a wizard getting picked won't ever imply a Captain or HoS not getting drafted required_candidates = 1 weight = 1 - cost = 30 + cost = 20 requirements = list(90,90,70,40,30,20,10,10,10,10) - high_population_requirement = 10 var/list/roundstart_wizards = list() /datum/dynamic_ruleset/roundstart/wizard/acceptable(population=0, threat=0) @@ -226,20 +226,19 @@ restricted_roles = list("AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Chaplain", "Head of Personnel") required_candidates = 2 weight = 1 - cost = 30 + cost = 20 requirements = list(100,80,70,60,40,30,30,20,10,10) - high_population_requirement = 10 - flags = HIGHLANDER_RULESET - antag_cap = list(2,2,2,3,3,4,4,4,4,4) + flags = HIGH_IMPACT_RULESET + antag_cap = list("denominator" = 20, "offset" = 1) var/datum/team/cult/main_cult -/datum/dynamic_ruleset/roundstart/bloodcult/ready(forced = FALSE) - required_candidates = antag_cap[indice_pop] +/datum/dynamic_ruleset/roundstart/bloodcult/ready(population, forced = FALSE) + required_candidates = get_antag_cap(population) . = ..() -/datum/dynamic_ruleset/roundstart/bloodcult/pre_execute() - var/cultists = antag_cap[indice_pop] - mode.antags_rolled += cultists +/datum/dynamic_ruleset/roundstart/bloodcult/pre_execute(population) + . = ..() + var/cultists = get_antag_cap(population) for(var/cultists_number = 1 to cultists) if(candidates.len <= 0) break @@ -283,21 +282,20 @@ restricted_roles = list("Head of Security", "Captain") // Just to be sure that a nukie getting picked won't ever imply a Captain or HoS not getting drafted required_candidates = 5 weight = 1 - cost = 40 + cost = 20 requirements = list(90,90,90,80,60,40,30,20,10,10) - high_population_requirement = 10 - flags = HIGHLANDER_RULESET - antag_cap = list(2,2,2,3,3,3,4,4,5,5) + flags = HIGH_IMPACT_RULESET + antag_cap = list("denominator" = 18, "offset" = 1) var/datum/team/nuclear/nuke_team -/datum/dynamic_ruleset/roundstart/nuclear/ready(forced = FALSE) - required_candidates = antag_cap[indice_pop] +/datum/dynamic_ruleset/roundstart/nuclear/ready(population, forced = FALSE) + required_candidates = get_antag_cap(population) . = ..() -/datum/dynamic_ruleset/roundstart/nuclear/pre_execute() +/datum/dynamic_ruleset/roundstart/nuclear/pre_execute(population) + . = ..() // If ready() did its job, candidates should have 5 or more members in it - var/operatives = antag_cap[indice_pop] - mode.antags_rolled += operatives + var/operatives = get_antag_cap(population) for(var/operatives_number = 1 to operatives) if(candidates.len <= 0) break @@ -369,20 +367,19 @@ required_candidates = 3 weight = 1 delay = 7 MINUTES - cost = 35 + cost = 20 requirements = list(101,101,101,101,101,101,101,101,101,101) - high_population_requirement = 10 - antag_cap = list(3,3,3,3,3,3,3,3,3,3) - flags = TRAITOR_RULESET + antag_cap = 3 + flags = HIGH_IMPACT_RULESET blocking_rules = list(/datum/dynamic_ruleset/latejoin/provocateur) // I give up, just there should be enough heads with 35 players... minimum_players = 35 var/datum/team/revolution/revolution var/finished = FALSE -/datum/dynamic_ruleset/roundstart/revs/pre_execute() - var/max_candidates = antag_cap[indice_pop] - mode.antags_rolled += max_candidates +/datum/dynamic_ruleset/roundstart/revs/pre_execute(population) + . = ..() + var/max_candidates = get_antag_cap(population) for(var/i = 1 to max_candidates) if(candidates.len <= 0) break @@ -494,12 +491,13 @@ weight = 1 cost = 0 requirements = list(101,101,101,101,101,101,101,101,101,101) - high_population_requirement = 101 + flags = LONE_RULESET /datum/dynamic_ruleset/roundstart/extended/pre_execute() message_admins("Starting a round of extended.") log_game("Starting a round of extended.") - mode.spend_threat(mode.threat) + mode.spend_roundstart_budget(mode.round_start_budget) + mode.spend_midround_budget(mode.mid_round_budget) mode.threat_log += "[worldtime2text()]: Extended ruleset set threat to 0." return TRUE @@ -518,11 +516,11 @@ weight = 1 cost = 40 requirements = list(100,90,80,70,60,50,30,30,30,30) - high_population_requirement = 50 - flags = HIGHLANDER_RULESET + flags = HIGH_IMPACT_RULESET minimum_players = 38 var/ark_time +//FIX(?) CLOCKCULT XOXEYOS 3/13/2021 IF IT ALL GOES TO SHIT! /datum/dynamic_ruleset/roundstart/clockcult/pre_execute() var/starter_servants = 4 var/number_players = mode.roundstart_pop_ready @@ -608,7 +606,7 @@ antag_datum = /datum/antagonist/nukeop/clownop antag_leader_datum = /datum/antagonist/nukeop/leader/clownop requirements = list(101,101,101,101,101,101,101,101,101,101) - high_population_requirement = 101 + flags = HIGH_IMPACT_RULESET /datum/dynamic_ruleset/roundstart/nuclear/clown_ops/pre_execute() . = ..() @@ -636,13 +634,13 @@ required_candidates = 1 weight = 1 cost = 60 + flags = LONE_RULESET requirements = list(101,101,101,101,101,101,101,101,101,101) - high_population_requirement = 101 - antag_cap = list(1,1,1,2,2,2,3,3,3,4) + antag_cap = list("denominator" = 30) -/datum/dynamic_ruleset/roundstart/devil/pre_execute() - var/num_devils = antag_cap[indice_pop] - mode.antags_rolled += num_devils +/datum/dynamic_ruleset/roundstart/devil/pre_execute(population) + . = ..() + var/num_devils = get_antag_cap(population) * (scaled_times + 1) for(var/j = 0, j < num_devils, j++) if (!candidates.len) @@ -676,7 +674,7 @@ ////////////////////////////////////////////// // // -// MONKEY // +// // // // ////////////////////////////////////////////// @@ -694,9 +692,9 @@ var/escaped_monkeys = 0 var/datum/team/monkey/monkey_team -/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 +/datum/dynamic_ruleset/roundstart/monkey/pre_execute(population) + . = ..() + var/carriers_to_make = get_antag_cap(population) * (scaled_times + 1) for(var/j = 0, j < carriers_to_make, j++) if (!candidates.len) @@ -785,14 +783,18 @@ weight = 1 cost = 30 requirements = list(90,80,80,70,60,40,30,30,20,10) - flags = HIGHLANDER_RULESET + flags = HIGH_IMPACT_RULESET minimum_players = 30 - antag_cap = list(3,3,3,3,3,3,3,3,3,4) + antag_cap = 3 var/datum/team/shadowling/shadowling -/datum/dynamic_ruleset/roundstart/shadowling/pre_execute() - var/shadowlings = antag_cap[indice_pop] - mode.antags_rolled += shadowlings +/datum/dynamic_ruleset/roundstart/shadowling/ready(population, forced = FALSE) + required_candidates = get_antag_cap(population) + . = ..() + +/datum/dynamic_ruleset/roundstart/shadowling/pre_execute(population) /// DON'T BREAK PLEASE - Xoxeyos 3/13/2021 + . = ..() + var/shadowlings = get_antag_cap(population) for(var/shadowling_number = 1 to shadowlings) if(candidates.len <= 0) break @@ -823,14 +825,18 @@ restricted_roles = list("Cyborg", "AI") required_candidates = 3 weight = 1 - cost = 10 - scaling_cost = 10 + cost = 8 + scaling_cost = 9 requirements = list(80,70,60,50,50,45,30,30,25,20) + antag_cap = list("denominator" = 24) minimum_players = 30 - var/autovamp_cooldown = 450 // 15 minutes (ticks once per 2 sec) + var/autovamp_cooldown = (15 MINUTES) + COOLDOWN_DECLARE(autovamp_cooldown_check) -/datum/dynamic_ruleset/roundstart/vampire/pre_execute() - var/num_vampires = antag_cap[indice_pop] * (scaled_times + 1) +/datum/dynamic_ruleset/roundstart/vampire/pre_execute(population) + . = ..() + COOLDOWN_START(src, autovamp_cooldown_check, autovamp_cooldown) + var/num_vampires = get_antag_cap(population) * (scaled_times + 1) for (var/i = 1 to num_vampires) var/mob/M = pick_n_take(candidates) assigned += M.mind @@ -839,12 +845,8 @@ return TRUE /datum/dynamic_ruleset/roundstart/vampire/rule_process() - if (autovamp_cooldown > 0) - autovamp_cooldown-- - else - autovamp_cooldown = 450 // 15 minutes - message_admins("Checking if we can turn someone into a vampire.") - log_game("DYNAMIC: Checking if we can turn someone into a vampire.") + if (COOLDOWN_FINISHED(src, autovamp_cooldown_check)) + COOLDOWN_START(src, autovamp_cooldown_check, autovamp_cooldown) mode.picking_specific_rule(/datum/dynamic_ruleset/midround/autovamp) ////////////////////////////////////////////// @@ -858,6 +860,7 @@ name = "Ragin' Mages" antag_flag = ROLE_RAGINMAGES antag_datum = /datum/antagonist/wizard/ + flags = LONE_RULESET minimum_required_age = 14 restricted_roles = list("Head of Security", "Captain") // Just to be sure that a wizard getting picked won't ever imply a Captain or HoS not getting drafted required_candidates = 1 @@ -903,6 +906,7 @@ name = "Bullshit Mages" antag_flag = ROLE_BULLSHITMAGES antag_datum = /datum/antagonist/wizard/ + flags = LONE_RULESET minimum_required_age = 14 restricted_roles = list("Head of Security", "Captain") // Just to be sure that a wizard getting picked won't ever imply a Captain or HoS not getting drafted required_candidates = 4 @@ -960,15 +964,16 @@ weight = 1 cost = 30 scaling_cost = 20 - antag_cap = list(3,3,3,3,3,3,3,3) + antag_cap = 3 requirements = list(80,75,70,65,50,30,30,30,25,20) -/datum/dynamic_ruleset/roundstart/darkspawn/pre_execute() - var/num_darkspawn = antag_cap[indice_pop] * (scaled_times + 1) +/datum/dynamic_ruleset/roundstart/darkspawn/pre_execute(population) + . = ..() + var/num_darkspawn = get_antag_cap(population) * (scaled_times + 1) for (var/i = 1 to num_darkspawn) var/mob/M = pick_n_take(candidates) assigned += M.mind M.mind.special_role = ROLE_DARKSPAWN M.mind.restricted_roles = restricted_roles log_game("[key_name(M)] has been selected as a Darkspawn") - return TRUE */ + return TRUE */ \ No newline at end of file diff --git a/code/game/gamemodes/dynamic/dynamic_simulations.dm b/code/game/gamemodes/dynamic/dynamic_simulations.dm new file mode 100644 index 000000000000..317da14c6c55 --- /dev/null +++ b/code/game/gamemodes/dynamic/dynamic_simulations.dm @@ -0,0 +1,137 @@ +#ifdef TESTING +/datum/dynamic_simulation + var/datum/game_mode/dynamic/gamemode + var/datum/dynamic_simulation_config/config + var/list/mock_candidates = list() + +/datum/dynamic_simulation/proc/initialize_gamemode(forced_threat) + gamemode = new + + if (forced_threat) + gamemode.create_threat(forced_threat) + else + gamemode.generate_threat() + + gamemode.generate_budgets() + gamemode.set_cooldowns() + +/datum/dynamic_simulation/proc/create_candidates(players) + GLOB.new_player_list.Cut() + + for (var/_ in 1 to players) + var/mob/dead/new_player/mock_new_player = new + mock_new_player.ready = PLAYER_READY_TO_PLAY + + var/datum/mind/mock_mind = new + mock_new_player.mind = mock_mind + + var/datum/client_interface/mock_client = new + + var/datum/preferences/prefs = new + var/list/be_special = list() + for (var/special_role in GLOB.special_roles) + be_special += special_role + + prefs.be_special = be_special + mock_client.prefs = prefs + + mock_new_player.mock_client = mock_client + + mock_candidates += mock_new_player + +/datum/dynamic_simulation/proc/simulate(datum/dynamic_simulation_config/config) + src.config = config + + initialize_gamemode(config.forced_threat_level) + create_candidates(config.roundstart_players) + gamemode.pre_setup() + + var/total_antags = 0 + for (var/_ruleset in gamemode.executed_rules) + var/datum/dynamic_ruleset/ruleset = _ruleset + total_antags += ruleset.assigned.len + + return list( + "roundstart_players" = config.roundstart_players, + "threat_level" = gamemode.threat_level, + "snapshot" = list( + "antag_percent" = total_antags / config.roundstart_players, + "remaining_threat" = gamemode.mid_round_budget, + "rulesets" = gamemode.executed_rules.Copy(), + ), + ) + +/datum/dynamic_simulation_config + /// How many players round start should there be? + var/roundstart_players + + /// Optional, force this threat level instead of picking randomly through the lorentz distribution + var/forced_threat_level + +/client/proc/run_dynamic_simulations() + set name = "Run Dynamic Simulations" + set category = "Debug" + + var/simulations = input(usr, "Enter number of simulations") as num + var/roundstart_players = input(usr, "Enter number of round start players") as num + var/forced_threat_level = input(usr, "Enter forced threat level, if you want one") as num | null + + SSticker.mode = config.pick_mode("dynamic") + message_admins("Running dynamic simulations...") + + var/list/outputs = list() + + var/datum/dynamic_simulation_config/dynamic_config = new + + if (roundstart_players) + dynamic_config.roundstart_players = roundstart_players + + if (forced_threat_level) + dynamic_config.forced_threat_level = forced_threat_level + + for (var/count in 1 to simulations) + var/datum/dynamic_simulation/simulator = new + var/output = simulator.simulate(dynamic_config) + outputs += list(output) + + if (CHECK_TICK) + log_world("[count]/[simulations]") + + message_admins("Writing file...") + WRITE_FILE(file("[GLOB.log_directory]/dynamic_simulations.json"), json_encode(outputs)) + message_admins("Writing complete.") + +/proc/export_dynamic_json_of(ruleset_list) + var/list/export = list() + + for (var/_ruleset in ruleset_list) + var/datum/dynamic_ruleset/ruleset = _ruleset + export[ruleset.name] = list( + "repeatable_weight_decrease" = ruleset.repeatable_weight_decrease, + "weight" = ruleset.weight, + "cost" = ruleset.cost, + "scaling_cost" = ruleset.scaling_cost, + "antag_cap" = ruleset.antag_cap, + "pop_per_requirement" = ruleset.pop_per_requirement, + "requirements" = ruleset.requirements, + "base_prob" = ruleset.base_prob, + ) + + return export + +/client/proc/export_dynamic_json() + set name = "Export dynamic.json" + set category = "Debug" + + var/datum/game_mode/dynamic/dynamic = SSticker.mode + + var/list/export = list() + export["Roundstart"] = export_dynamic_json_of(dynamic.roundstart_rules) + export["Midround"] = export_dynamic_json_of(dynamic.midround_rules) + export["Latejoin"] = export_dynamic_json_of(dynamic.latejoin_rules) + + message_admins("Writing file...") + WRITE_FILE(file("[GLOB.log_directory]/dynamic.json"), json_encode(export)) + message_admins("Writing complete.") + +#endif \ No newline at end of file diff --git a/code/game/gamemodes/dynamic/readme.md b/code/game/gamemodes/dynamic/readme.md index 5d979b400163..f3a02856aba2 100644 --- a/code/game/gamemodes/dynamic/readme.md +++ b/code/game/gamemodes/dynamic/readme.md @@ -5,16 +5,47 @@ Dynamic rolls threat based on a special sauce formula: "dynamic_curve_width \* tan((3.1416 \* (rand() - 0.5) \* 57.2957795)) + dynamic_curve_centre" +This threat is split into two separate budgets--`round_start_budget` and `mid_round_budget`. For example, a round with 50 threat might be split into a 30 roundstart budget, and a 20 midround budget. The roundstart budget is used to apply antagonists applied on readied players when the roundstarts (`/datum/dynamic_ruleset/roundstart`). The midround budget is used for two types of rulesets: +- `/datum/dynamic_ruleset/midround` - Rulesets that apply to either existing alive players, or to ghosts. Think Blob or Space Ninja, which poll ghosts asking if they want to play as these roles. +- `/datum/dynamic_ruleset/latejoin` - Rulesets that apply to the next player that joins. Think Syndicate Infiltrator, which converts a player just joining an existing round into traitor. + +This split is done with a similar method, known as the ["lorentz distribution"](https://en.wikipedia.org/wiki/Cauchy_distribution), exists to create a bell curve that ensures that while most rounds will have a threat level around ~50, chaotic and tame rounds still exist for variety. + +The process of creating these numbers occurs in `/datum/game_mode/dynamic/proc/generate_threat` (for creating the threat level) and `/datum/game_mode/dynamic/proc/generate_budgets` (for splitting the threat level into budgets). + +## Deciding roundstart threats +In `/datum/game_mode/dynamic/proc/roundstart()` (called when no admin chooses the rulesets explicitly), Dynamic uses the available roundstart budget to pick threats. This is done through the following system: + +- All roundstart rulesets (remember, `/datum/dynamic_ruleset/roundstart`) are put into an associative list with their weight as the values (`drafted_rules`). +- Until there is either no roundstart budget left, or until there is no ruleset we can choose from with the available threat, a `pickweight` is done based on the drafted_rules. If the same threat is picked twice, it will "scale up". The meaning of this depends on the ruleset itself, using the `scaled_times` variable; traitors for instance will create more the higher they scale. + - If a ruleset is chosen with the `HIGH_IMPACT_RULESET` in its `flags`, then all other `HIGH_IMPACT_RULESET`s will be removed from `drafted_rules`. This is so that only one can ever be chosen. + - If a ruleset has `LONE_RULESET` in its `flags`, then it will be removed from `drafted_rules`. This is to ensure it will only ever be picked once. An example of this in use is Wizard, to avoid creating multiple wizards. +- After all roundstart threats are chosen, `/datum/dynamic_ruleset/proc/picking_roundstart_rule` is called for each, passing in the ruleset and the number of times it is scaled. + - In this stage, `pre_execute` is called, which is the function that will determine what players get what antagonists. If this function returns FALSE for whatever reason (in the case of an error), then its threat is refunded. + +After this process is done, any leftover roundstart threat will be given to the existing midround budget (done in `/datum/game_mode/dynamic/pre_setup()`). + +## Deciding midround threats + Latejoin and midround injection cooldowns are set using exponential distribution between 5 minutes and 25 for latejoin 15 minutes and 35 for midround this value is then added to world.time and assigned to the injection cooldown variables. -rigged_roundstart() is called instead if there are forced rules (an admin set the mode) +- 5 minutes and 25 for latejoin (configurable as latejoin_delay_min and latejoin_delay_max) +- 15 minutes and 35 for midround (configurable as midround_delay_min and midround_delay_max) + +this value is then added to `world.time` and assigned to the injection cooldown variables. -can_start() -> pre_setup() -> roundstart() OR rigged_roundstart() -> picking_roundstart_rule(drafted_rules) -> post_setup() +[rigged_roundstart][/datum/game_mode/dynamic/proc/rigged_roundstart] is called instead if there are forced rules (an admin set the mode) -## PROCESS +1. [setup_parameters][/datum/game_mode/proc/setup_parameters]\() +2. [pre_setup][/datum/game_mode/proc/pre_setup]\() +3. [roundstart][/datum/game_mode/dynamic/proc/roundstart]\() OR [rigged_roundstart][/datum/game_mode/dynamic/proc/rigged_roundstart]\() +4. [picking_roundstart_rule][/datum/game_mode/dynamic/proc/picking_roundstart_rule]\(drafted_rules) +5. [post_setup][/datum/game_mode/proc/post_setup]\() + +## Rule Processing Calls rule_process on every rule which is in the current_rules list. Every sixty seconds, update_playercounts() @@ -65,3 +96,88 @@ Midround: Instead of building a single list candidates, candidates contains four Midround - Rulesets have additional types /from_ghosts: execute() -> send_applications() -> review_applications() -> finish_setup(mob/newcharacter, index) -> setup_role(role) **NOTE: execute() here adds dead players and observers to candidates list + +## Configuration and variables + +### Configuration +Configuration can be done through a `config/dynamic.json` file. One is provided as example in the codebase. This config file, loaded in `/datum/game_mode/dynamic/pre_setup()`, directly overrides the values in the codebase, and so is perfect for making some rulesets harder/easier to get, turning them off completely, changing how much they cost, etc. + +The format of this file is: +```json +{ + "Dynamic": { + /* Configuration in here will directly override `/datum/game_mode/dynamic` itself. */ + /* Keys are variable names, values are their new values. */ + }, + "Roundstart": { + /* Configuration in here will apply to `/datum/dynamic_ruleset/roundstart` instances. */ + /* Keys are the ruleset names, values are another associative list with keys being variable names and values being new values. */ + "Wizard": { + /* I, a head admin, have died to wizard, and so I made it cost a lot more threat than it does in the codebase. */ + "cost": 80 + } + }, + "Midround": { + /* Same as "Roundstart", but for `/datum/dynamic_ruleset/midround` instead. */ + }, + "Latejoin": { + /* Same as "Roundstart", but for `/datum/dynamic_ruleset/latejoin` instead. */ + } +} +``` + +Note: Comments are not possible in this format, and are just in this document for the sake of readability. + +### Rulesets +Rulesets have the following variables notable to developers and those interested in tuning. + +- `required_candidates` - The number of people that *must be willing* (in their preferences) to be an antagonist with this ruleset. If the candidates do not meet this requirement, then the ruleset will not bother to be drafted. +- `antag_cap` - Judges the amount of antagonists to apply, for both solo and teams. Note that some antagonists (such as traitors, lings, heretics, etc) will add more based on how many times they've been scaled. Written as a linear equation--ceil(x/denominator) + offset, or as a fixed constant. If written as a linear equation, will be in the form of `list("denominator" = denominator, "offset" = offset)`. + - Examples include: + - Traitor: `antag_cap = list("denominator" = 24)`. This means that for every 24 players, 1 traitor will be added (assuming no scaling). + - Nuclear Emergency: `antag_cap = list("denominator" = 18, "offset" = 1)`. For every 18 players, 1 nuke op will be added. Starts at 1, meaning at 30 players, 3 nuke ops will be created, rather than 2. + - Revolution: `antag_cap = 3`. There will always be 3 rev-heads, no matter what. +- `minimum_required_age` - The minimum age in order to apply for the ruleset. +- `weight` - How likely this ruleset is to be picked. A higher weight results in a higher chance of drafting. +- `cost` - The initial cost of the ruleset. This cost is taken from either the roundstart or midround budget, depending on the ruleset. +- `scaling_cost` - Cost for every *additional* application of this ruleset. + - Suppose traitors has a `cost` of 8, and a `scaling_cost` of 5. This means that buying 1 application of the traitor ruleset costs 8 threat, but buying two costs 13 (8 + 5). Buying it a third time is 18 (8 + 5 + 5), etc. +- `pop_per_requirement` - The range of population each value in `requirements` represents. By default, this is 6. + - If the value is five the range is 0-4, 5-9, 10-14, 15-19, 20-24, 25-29, 30-34, 35-39, 40-54, 45+. + - If it is six the range is 0-5, 6-11, 12-17, 18-23, 24-29, 30-35, 36-41, 42-47, 48-53, 54+. + - If it is seven the range is 0-6, 7-13, 14-20, 21-27, 28-34, 35-41, 42-48, 49-55, 56-62, 63+. +- `requirements` - A list that represents, per population range (see: `pop_per_requirement`), how much threat is required to *consider* this ruleset. This is independent of how much it'll actually cost. This uses *threat level*, not the budget--meaning if a round has 50 threat level, but only 10 points of round start threat, a ruleset with a requirement of 40 can still be picked if it can be bought. + - Suppose wizard has a `requirements` of `list(90,90,70,40,30,20,10,10,10,10)`. This means that, at 0-5 and 6-11 players, A station must have 90 threat in order for a wizard to be possible. At 12-17, 70 threat is required instead, etc. +- `restricted_roles` - A list of jobs that *can't* be drafted by this ruleset. For example, cyborgs cannot be changelings, and so are in the `restricted_roles`. +- `protected_roles` - Serves the same purpose of `restricted_roles`, except it can be turned off through configuration (`protect_roles_from_antagonist`). For example, security officers *shouldn't* be made traitor, so they are in Traitor's `protected_roles`. + - When considering putting a role in `protected_roles` or `restricted_roles`, the rule of thumb is if it is *technically infeasible* to support that job in that role. There's no *technical* reason a security officer can't be a traitor, and so they are simply in `protected_roles`. There *are* technical reasons a cyborg can't be a changeling, so they are in `restricted_roles` instead. + +### Dynamic + +The "Dynamic" key has the following configurable values: +- `pop_per_requirement` - The default value of `pop_per_requirement` for any ruleset that does not explicitly set it. Defaults to 6. +- `latejoin_delay_min`, `latejoin_delay_max` - The time range, in deciseconds (take your seconds, and multiply by 10), for a latejoin to attempt rolling. Once this timer is finished, a new one will be created within the same range. + - Suppose you have a `latejoin_delay_min` of 600 (60 seconds, 1 minute) and a `latejoin_delay_max` of 1800 (180 seconds, 3 minutes). Once the round starts, a random number in this range will be picked--let's suppose 1.5 minutes. After 1.5 minutes, Dynamic will decide if a latejoin threat should be created (a probability of `/datum/game_mode/dynamic/proc/get_injection_chance()`). Regardless of its decision, a new timer will be started within the range of 1 to 3 minutes, repeatedly. +- `midround_delay_min`, `midround_delay_max` - Same as `latejoin_delay_min` and `latejoin_delay_max`, except for midround threats instead of latejoin ones. +- `higher_injection_chance`, `higher_injection_chance_minimum_threat` - Manipulates the injection chance (`/datum/game_mode/dynamic/proc/get_injection_chance()`). If the *current midround budget* is above `higher_injection_chance_minimum_threat`, then this chance will be increased by `higher_injection_chance`. + - For example: suppose you have a `higher_injection_chance_minimum_threat` of 70, and a `higher_injection_chance` of 15. This means that, if when a midround threat is trying to roll, there is 75 midround budget left, then the injection chance will go up 15%. +- `lower_injection_chance`, `lower_injection_chance_minimum_threat` - The inverse of the `higher_injection_chance` variables. If the *current midround budget* is *below* `lower_injection_chance`, then the chance is lowered by `lower_injection_chance_minimum_threat`. + - For example: suppose you have a `lower_injection_chance_minimum_threat` of 30, and a `lower_injection_chance` of 15. This means if there is 20 midround budget left, then the chance will lower by 15%. +- `threat_curve_centre` - A number between -5 and +5. A negative value will give a more peaceful round and a positive value will give a round with higher threat. +- `threat_curve_width` - A number between 0.5 and 4. Higher value will favour extreme rounds and lower value rounds closer to the average. +- `roundstart_split_curve_centre` - A number between -5 and +5. Equivalent to threat_curve_centre, but for the budget split. A negative value will weigh towards midround rulesets, and a positive value will weight towards roundstart ones. +- `roundstart_split_curve_width` - A number between 0.5 and 4. Equivalent to threat_curve_width, but for the budget split. Higher value will favour more variance in splits and lower value rounds closer to the average. +- `random_event_hijack_minimum` - The minimum amount of time for antag random events to be hijacked. (See [Random Event Hijacking](#random-event-hijacking)) +- `random_event_hijack_maximum` - The maximum amount of time for antag random events to be hijacked. (See [Random Event Hijacking](#random-event-hijacking)) +- `hijacked_random_event_injection_chance` - The amount of injection chance to give to Dynamic when a random event is hijacked. (See [Random Event Hijacking](#random-event-hijacking)) + +## Random Event "Hijacking" +Random events have the potential to be hijacked by Dynamic to keep the pace of midround injections, while also allowing greenshifts to contain some antagonists. + +`/datum/round_event_control/dynamic_should_hijack` is a variable to random events to allow Dynamic to hijack them, and defaults to FALSE. This is set to TRUE for random events that spawn antagonists. + +In `/datum/game_mode/dynamic/on_pre_random_event` (in `dynamic_hijacking.dm`), Dynamic hooks to random events. If the `dynamic_should_hijack` variable is TRUE, the following sequence of events occurs: + +![Flow chart to describe the chain of events for Dynamic 2021 to take](https://user-images.githubusercontent.com/35135081/109071468-9cab7e00-76a8-11eb-8f9f-2b920c602ef4.png) + +`n` is a random value between `random_event_hijack_minimum` and `random_event_hijack_maximum`. Injection chance, should it need to be raised, is increased by `hijacked_random_event_injection_chance`. \ No newline at end of file diff --git a/code/game/gamemodes/dynamic/ruleset_picking.dm b/code/game/gamemodes/dynamic/ruleset_picking.dm new file mode 100644 index 000000000000..29776dda1bd7 --- /dev/null +++ b/code/game/gamemodes/dynamic/ruleset_picking.dm @@ -0,0 +1,117 @@ +#define ADMIN_CANCEL_MIDROUND_TIME (10 SECONDS) + +/// From a list of rulesets, returns one based on weight and availability. +/// Mutates the list that is passed into it to remove invalid rules. +/datum/game_mode/dynamic/proc/pick_ruleset(list/drafted_rules) + if (only_ruleset_executed) + return null + + while (TRUE) + var/datum/dynamic_ruleset/rule = pickweight(drafted_rules) + if (!rule) + return null + + if (check_blocking(rule.blocking_rules, executed_rules)) + drafted_rules -= rule + if(drafted_rules.len <= 0) + return null + continue + else if ( + rule.flags & HIGH_IMPACT_RULESET \ + && threat_level < GLOB.dynamic_stacking_limit \ + && GLOB.dynamic_no_stacking \ + && high_impact_ruleset_executed \ + ) + drafted_rules -= rule + if(drafted_rules.len <= 0) + return null + continue + + return rule + +/// Executes a random midround ruleset from the list of drafted rules. +/datum/game_mode/dynamic/proc/pick_midround_rule(list/drafted_rules) + var/datum/dynamic_ruleset/rule = pick_ruleset(drafted_rules) + if (isnull(rule)) + return + current_midround_rulesets = drafted_rules - rule + + midround_injection_timer_id = addtimer( + CALLBACK(src, .proc/execute_midround_rule, rule), \ + ADMIN_CANCEL_MIDROUND_TIME, \ + TIMER_STOPPABLE, \ + ) + + log_game("DYNAMIC: [rule] ruleset executing...") + message_admins("DYNAMIC: Executing midround ruleset [rule] in [DisplayTimeText(ADMIN_CANCEL_MIDROUND_TIME)]. \ + CANCEL | \ + SOMETHING ELSE") + +/// Fired after admins do not cancel a midround injection. +/datum/game_mode/dynamic/proc/execute_midround_rule(datum/dynamic_ruleset/rule) + current_midround_rulesets = null + midround_injection_timer_id = null + if (!rule.repeatable) + midround_rules = remove_from_list(midround_rules, rule.type) + addtimer(CALLBACK(src, .proc/execute_midround_latejoin_rule, rule), rule.delay) + +/// Executes a random latejoin ruleset from the list of drafted rules. +/datum/game_mode/dynamic/proc/pick_latejoin_rule(list/drafted_rules) + var/datum/dynamic_ruleset/rule = pick_ruleset(drafted_rules) + if (isnull(rule)) + return + if (!rule.repeatable) + latejoin_rules = remove_from_list(latejoin_rules, rule.type) + addtimer(CALLBACK(src, .proc/execute_midround_latejoin_rule, rule), rule.delay) + +/// Mainly here to facilitate delayed rulesets. All midround/latejoin rulesets are executed with a timered callback to this proc. +/datum/game_mode/dynamic/proc/execute_midround_latejoin_rule(sent_rule) + var/datum/dynamic_ruleset/rule = sent_rule + spend_midround_budget(rule.cost) + threat_log += "[worldtime2text()]: [rule.ruletype] [rule.name] spent [rule.cost]" + rule.pre_execute(current_players[CURRENT_LIVING_PLAYERS].len) + if (rule.execute()) + log_game("DYNAMIC: Injected a [rule.ruletype == "latejoin" ? "latejoin" : "midround"] ruleset [rule.name].") + if(rule.flags & HIGH_IMPACT_RULESET) + high_impact_ruleset_executed = TRUE + else if(rule.flags & ONLY_RULESET) + only_ruleset_executed = TRUE + if(rule.ruletype == "Latejoin") + var/mob/M = pick(rule.candidates) + dynamic_log("[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 + new_snapshot(rule) + return TRUE + rule.clean_up() + stack_trace("The [rule.ruletype] rule \"[rule.name]\" failed to execute.") + return FALSE + +/// Fired when an admin cancels the current midround injection. +/datum/game_mode/dynamic/proc/admin_cancel_midround(mob/user, timer_id) + if (midround_injection_timer_id != timer_id || !deltimer(midround_injection_timer_id)) + to_chat(user, "Too late!") + return + + dynamic_log("[key_name(user)] cancelled the next midround injection.") + midround_injection_timer_id = null + current_midround_rulesets = null + +/// Fired when an admin requests a different midround injection. +/datum/game_mode/dynamic/proc/admin_different_midround(mob/user, timer_id) + if (midround_injection_timer_id != timer_id || !deltimer(midround_injection_timer_id)) + to_chat(user, "Too late!") + return + + midround_injection_timer_id = null + + if (isnull(current_midround_rulesets) || current_midround_rulesets.len == 0) + dynamic_log("[key_name(user)] asked for a different midround injection, but there were none left.") + return + + dynamic_log("[key_name(user)] asked for a different midround injection.") + pick_midround_rule(current_midround_rulesets) + +#undef ADMIN_CANCEL_MIDROUND_TIME \ No newline at end of file diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index c35e06340963..3dcc8a242ff1 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -53,8 +53,8 @@ var/gamemode_ready = FALSE //Is the gamemode all set up and ready to start checking for ending conditions. var/setup_error //What stopepd setting up the mode. -/// Associative list of current players, in order: living players, living antagonists, dead players and observers. - //var/list/list/current_players = list(CURRENT_LIVING_PLAYERS = list(), CURRENT_LIVING_ANTAGS = list(), CURRENT_DEAD_PLAYERS = list(), CURRENT_OBSERVERS = list()) + /// Associative list of current players, in order: living players, living antagonists, dead players and observers. + var/list/list/current_players = list(CURRENT_LIVING_PLAYERS = list(), CURRENT_LIVING_ANTAGS = list(), CURRENT_DEAD_PLAYERS = list(), CURRENT_OBSERVERS = list()) /datum/game_mode/proc/announce() //Shows the gamemode's name and a fast description. to_chat(world, "The gamemode is: [name]!") @@ -90,7 +90,7 @@ ///Everyone should now be on the station and have their normal gear. This is the place to give the special roles extra things /datum/game_mode/proc/post_setup(report) //Gamemodes can override the intercept report. Passing TRUE as the argument will force a report. SHOULD_CALL_PARENT(TRUE) - + if(!report) report = !CONFIG_GET(flag/no_intercept_report) addtimer(CALLBACK(GLOBAL_PROC, .proc/display_roundstart_logout_report), ROUNDSTART_LOGOUT_REPORT_TIME) diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index ee4cc49c8672..b5c14d9cb993 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -436,7 +436,7 @@ if(GLOB.master_mode == "secret") dat += "(Force Secret Mode)
" - if(GLOB.master_mode == "dynamic") + if(SSticker.is_mode("dynamic")) if(SSticker.current_state <= GAME_STATE_PREGAME) dat += "(Force Roundstart Rulesets)
" if (GLOB.dynamic_forced_roundstart_ruleset.len > 0) @@ -887,27 +887,12 @@ No stacking: - Option is [GLOB.dynamic_no_stacking ? "ON" : "OFF"].
Unless the threat goes above [GLOB.dynamic_stacking_limit], only one "round-ender" ruleset will be drafted.

- Classic secret mode: - Option is [GLOB.dynamic_classic_secret ? "ON" : "OFF"]. -
Only one roundstart ruleset will be drafted. Only traitors and minor roles will latespawn.
-
-
Forced threat level: Current value : [GLOB.dynamic_forced_threat_level].
The value threat is set to if it is higher than -1.

- High population limit: Current value : [GLOB.dynamic_high_pop_limit]. -
The threshold at which "high population override" will be in effect.

Stacking threeshold: Current value : [GLOB.dynamic_stacking_limit].
The threshold at which "round-ender" rulesets will stack. A value higher than 100 ensure this never happens.
-

Advanced parameters

- Curve centre: -> [GLOB.dynamic_curve_centre] <-
- Curve width: -> [GLOB.dynamic_curve_width] <-
- Latejoin injection delay:
- Minimum: -> [GLOB.dynamic_latejoin_delay_min / 60 / 10] <- Minutes
- Maximum: -> [GLOB.dynamic_latejoin_delay_max / 60 / 10] <- Minutes
- Midround injection delay:
- Minimum: -> [GLOB.dynamic_midround_delay_min / 60 / 10] <- Minutes
- Maximum: -> [GLOB.dynamic_midround_delay_max / 60 / 10] <- Minutes
"} user << browse(dat, "window=dyn_mode_options;size=900x650") diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index fb6caa1c6126..5370ca6ed384 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -167,7 +167,11 @@ GLOBAL_PROTECT(admin_verbs_debug) /client/proc/enable_debug_verbs, /client/proc/callproc, /client/proc/callproc_datum, - /client/proc/cmd_admin_list_open_jobs + /client/proc/cmd_admin_list_open_jobs, + #ifdef TESTING //Xoxeyos 3/14/2021 + /client/proc/export_dynamic_json, + /client/proc/run_dynamic_simulations, + #endif ) GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release)) GLOBAL_PROTECT(admin_verbs_possess) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index a8f6b98f0204..d93f233971c2 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -246,7 +246,7 @@ return if(SSticker && SSticker.mode) return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode.", null, null, null, null) var/roundstart_rules = list() for (var/rule in subtypesof(/datum/dynamic_ruleset/roundstart)) @@ -282,122 +282,16 @@ if(SSticker && SSticker.mode) return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) dynamic_mode_options(usr) - else if(href_list["f_dynamic_roundstart_centre"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - var/new_centre = input(usr,"Change the centre of the dynamic mode threat curve. A negative value will give a more peaceful round ; a positive value, a round with higher threat. Any number between -5 and +5 is allowed.", "Change curve centre", null) as num - if (new_centre < -5 || new_centre > 5) - return alert(usr, "Only values between -5 and +5 are allowed.", null, null, null, null) - - log_admin("[key_name(usr)] changed the distribution curve center to [new_centre].") - message_admins("[key_name(usr)] changed the distribution curve center to [new_centre]", 1) - GLOB.dynamic_curve_centre = new_centre - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_width"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - var/new_width = input(usr,"Change the width of the dynamic mode threat curve. A higher value will favour extreme rounds ; a lower value, a round closer to the average. Any Number between 0.5 and 4 are allowed.", "Change curve width", null) as num - if (new_width < 0.5 || new_width > 4) - return alert(usr, "Only values between 0.5 and +2.5 are allowed.", null, null, null, null) - - log_admin("[key_name(usr)] changed the distribution curve width to [new_width].") - message_admins("[key_name(usr)] changed the distribution curve width to [new_width]", 1) - GLOB.dynamic_curve_width = new_width - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_latejoin_min"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_min = input(usr,"Change the minimum delay of latejoin injection in minutes.", "Change latejoin injection delay minimum", null) as num - if(new_min <= 0) - return alert(usr, "The minimum can't be zero or lower.", null, null, null, null) - if((new_min MINUTES) > GLOB.dynamic_latejoin_delay_max) - return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes.") - message_admins("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes", 1) - GLOB.dynamic_latejoin_delay_min = (new_min MINUTES) - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_latejoin_max"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_max = input(usr,"Change the maximum delay of latejoin injection in minutes.", "Change latejoin injection delay maximum", null) as num - if(new_max <= 0) - return alert(usr, "The maximum can't be zero or lower.", null, null, null, null) - if((new_max MINUTES) < GLOB.dynamic_latejoin_delay_min) - return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes.") - message_admins("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes", 1) - GLOB.dynamic_latejoin_delay_max = (new_max MINUTES) - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_midround_min"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_min = input(usr,"Change the minimum delay of midround injection in minutes.", "Change midround injection delay minimum", null) as num - if(new_min <= 0) - return alert(usr, "The minimum can't be zero or lower.", null, null, null, null) - if((new_min MINUTES) > GLOB.dynamic_midround_delay_max) - return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes.") - message_admins("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes", 1) - GLOB.dynamic_midround_delay_min = (new_min MINUTES) - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_midround_max"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_max = input(usr,"Change the maximum delay of midround injection in minutes.", "Change midround injection delay maximum", null) as num - if(new_max <= 0) - return alert(usr, "The maximum can't be zero or lower.", null, null, null, null) - if((new_max MINUTES) > GLOB.dynamic_midround_delay_max) - return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes.") - message_admins("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes", 1) - GLOB.dynamic_midround_delay_max = (new_max MINUTES) - dynamic_mode_options(usr) - else if(href_list["f_dynamic_force_extended"]) if(!check_rights(R_ADMIN)) return - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) GLOB.dynamic_forced_extended = !GLOB.dynamic_forced_extended @@ -409,7 +303,7 @@ if(!check_rights(R_ADMIN)) return - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) GLOB.dynamic_no_stacking = !GLOB.dynamic_no_stacking @@ -417,23 +311,11 @@ message_admins("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].") dynamic_mode_options(usr) - else if(href_list["f_dynamic_classic_secret"]) - if(!check_rights(R_ADMIN)) - return - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - GLOB.dynamic_classic_secret = !GLOB.dynamic_classic_secret - log_admin("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].") - message_admins("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].") - dynamic_mode_options(usr) - else if(href_list["f_dynamic_stacking_limit"]) if(!check_rights(R_ADMIN)) return - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) 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 @@ -441,25 +323,6 @@ message_admins("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].") dynamic_mode_options(usr) - else if(href_list["f_dynamic_high_pop_limit"]) - if(!check_rights(R_ADMIN)) - return - - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - var/new_value = input(usr, "Enter the high-pop override threshold for dynamic mode.", "High pop override") as num - if (new_value < 0) - return alert(usr, "Only positive values allowed!", null, null, null, null) - GLOB.dynamic_high_pop_limit = new_value - - log_admin("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].") - message_admins("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].") - dynamic_mode_options(usr) - else if(href_list["f_dynamic_forced_threat"]) if(!check_rights(R_ADMIN)) return @@ -467,7 +330,7 @@ if(SSticker && SSticker.mode) return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) var/new_value = input(usr, "Enter the forced threat level for dynamic mode.", "Forced threat level") as num @@ -483,7 +346,6 @@ if(!check_rights(R_ADMIN)) return - switch(href_list["call_shuttle"]) if("1") if(EMERGENCY_AT_LEAST_DOCKED) @@ -887,7 +749,7 @@ return if(SSticker && SSticker.mode) return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode.", null, null, null, null) var/roundstart_rules = list() for (var/rule in subtypesof(/datum/dynamic_ruleset/roundstart)) @@ -922,7 +784,7 @@ return if(!SSticker || !SSticker.mode) return alert(usr, "The game must start first.", null, null, null, null) - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) var/latejoin_rules = list() for (var/rule in subtypesof(/datum/dynamic_ruleset/latejoin)) @@ -951,7 +813,7 @@ return if(!SSticker || !SSticker.mode) return alert(usr, "The game must start first.", null, null, null, null) - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) var/midround_rules = list() var/datum/game_mode/dynamic/mode = SSticker.mode @@ -971,7 +833,7 @@ if(SSticker && SSticker.mode) return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) dynamic_mode_options(usr) @@ -981,7 +843,7 @@ return if(SSticker && SSticker.mode) return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) var/new_centre = input(usr,"Change the centre of the dynamic mode threat curve. A negative value will give a more peaceful round ; a positive value, a round with higher threat. Any number between -5 and +5 is allowed.", "Change curve centre", null) as num @@ -998,7 +860,7 @@ return if(SSticker && SSticker.mode) return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) var/new_width = input(usr,"Change the width of the dynamic mode threat curve. A higher value will favour extreme rounds ; a lower value, a round closer to the average. Any Number between 0.5 and 4 are allowed.", "Change curve width", null) as num @@ -1015,7 +877,7 @@ return if(SSticker && SSticker.mode) return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) var/new_min = input(usr,"Change the minimum delay of latejoin injection in minutes.", "Change latejoin injection delay minimum", null) as num if(new_min <= 0) @@ -1033,7 +895,7 @@ return if(SSticker && SSticker.mode) return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) var/new_max = input(usr,"Change the maximum delay of latejoin injection in minutes.", "Change latejoin injection delay maximum", null) as num if(new_max <= 0) @@ -1051,7 +913,7 @@ return if(SSticker && SSticker.mode) return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) var/new_min = input(usr,"Change the minimum delay of midround injection in minutes.", "Change midround injection delay minimum", null) as num if(new_min <= 0) @@ -1069,7 +931,7 @@ return if(SSticker && SSticker.mode) return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) var/new_max = input(usr,"Change the maximum delay of midround injection in minutes.", "Change midround injection delay maximum", null) as num if(new_max <= 0) @@ -1086,7 +948,7 @@ if(!check_rights(R_ADMIN)) return - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) GLOB.dynamic_forced_extended = !GLOB.dynamic_forced_extended @@ -1098,7 +960,7 @@ if(!check_rights(R_ADMIN)) return - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) GLOB.dynamic_no_stacking = !GLOB.dynamic_no_stacking @@ -1110,7 +972,7 @@ if(!check_rights(R_ADMIN)) return - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) GLOB.dynamic_classic_secret = !GLOB.dynamic_classic_secret @@ -1122,7 +984,7 @@ if(!check_rights(R_ADMIN)) return - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) 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 @@ -1137,7 +999,7 @@ if(SSticker && SSticker.mode) return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) var/new_value = input(usr, "Enter the high-pop override threshold for dynamic mode.", "High pop override") as num @@ -1156,7 +1018,7 @@ if(SSticker && SSticker.mode) return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") + if(!SSticker.is_mode("dynamic")) return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) var/new_value = input(usr, "Enter the forced threat level for dynamic mode.", "Forced threat level") as num diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index 188c97a23c72..8241971e078f 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -52,10 +52,15 @@ GLOBAL_LIST_EMPTY(antagonists) /datum/antagonist/proc/specialization(datum/mind/new_owner) return src + //Called by the transfer_to() mind proc after the mind (mind.current and new_character.mind) has moved but before the player (key and client) is transfered. /datum/antagonist/proc/on_body_transfer(mob/living/old_body, mob/living/new_body) SHOULD_CALL_PARENT(TRUE) remove_innate_effects(old_body) + if(old_body.stat != DEAD && !LAZYLEN(old_body.mind?.antag_datums)) + old_body.remove_from_current_living_antags() apply_innate_effects(new_body) + if(new_body.stat != DEAD) + new_body.add_to_current_living_antags() //This handles the application of antag huds/special abilities /datum/antagonist/proc/apply_innate_effects(mob/living/mob_override) @@ -69,18 +74,24 @@ GLOBAL_LIST_EMPTY(antagonists) /datum/antagonist/proc/create_team(datum/team/team) return -//Proc called when the datum is given to a mind. +//Called by the add_antag_datum() mind proc after the instanced datum is added to the mind's antag_datums list. /datum/antagonist/proc/on_gain() SHOULD_CALL_PARENT(TRUE) - if(owner && owner.current) - if(!silent) - greet() - apply_innate_effects() - give_antag_moodies() - if(is_banned(owner.current) && replace_banned) - replace_banned_player() - else if(owner.current.client?.holder && (CONFIG_GET(flag/auto_deadmin_antagonists) || owner.current.client.prefs?.toggles & DEADMIN_ANTAGONIST)) - owner.current.client.holder.auto_deadmin() + if(!owner) + CRASH("[src] ran on_gain() without a mind") + if(!owner.current) + CRASH("[src] ran on_gain() on a mind without a mob") + if(!silent && tips) + show_tips(tips) + greet() + apply_innate_effects() + give_antag_moodies() + if(is_banned(owner.current) && replace_banned) + replace_banned_player() + else if(owner.current.client?.holder && (CONFIG_GET(flag/auto_deadmin_antagonists) || owner.current.client.prefs?.toggles & DEADMIN_ANTAGONIST)) + owner.current.client.holder.auto_deadmin() + if(owner.current.stat != DEAD) + owner.current.add_to_current_living_antags() /datum/antagonist/proc/is_banned(mob/M) if(!M) @@ -98,12 +109,15 @@ GLOBAL_LIST_EMPTY(antagonists) owner.current.ghostize(0) owner.current.key = C.key +//Called by the remove_antag_datum() and remove_all_antag_datums() mind procs for the antag datum to handle its own removal and deletion. /datum/antagonist/proc/on_removal() SHOULD_CALL_PARENT(TRUE) remove_innate_effects() clear_antag_moodies() if(owner) LAZYREMOVE(owner.antag_datums, src) + if(!LAZYLEN(owner.antag_datums)) + owner.current.remove_from_current_living_antags() if(!silent && owner.current) farewell() var/datum/team/team = get_team() diff --git a/code/modules/antagonists/revenant/revenant_spawn_event.dm b/code/modules/antagonists/revenant/revenant_spawn_event.dm index c9a892cd64b6..10ee621a9b87 100644 --- a/code/modules/antagonists/revenant/revenant_spawn_event.dm +++ b/code/modules/antagonists/revenant/revenant_spawn_event.dm @@ -6,6 +6,7 @@ weight = 7 max_occurrences = 1 min_players = 5 + dynamic_should_hijack = TRUE /datum/round_event/ghost_role/revenant diff --git a/code/modules/antagonists/slaughter/slaughterevent.dm b/code/modules/antagonists/slaughter/slaughterevent.dm index ceed4ef990dd..0c111a666eb4 100644 --- a/code/modules/antagonists/slaughter/slaughterevent.dm +++ b/code/modules/antagonists/slaughter/slaughterevent.dm @@ -5,6 +5,7 @@ max_occurrences = 1 earliest_start = 1 HOURS min_players = 20 + dynamic_should_hijack = TRUE diff --git a/code/modules/events/_event.dm b/code/modules/events/_event.dm index a229a06ad946..d99e09f9f3b1 100644 --- a/code/modules/events/_event.dm +++ b/code/modules/events/_event.dm @@ -1,4 +1,6 @@ -//this datum is used by the events controller to dictate how it selects events +#define RANDOM_EVENT_ADMIN_INTERVENTION_TIME 10 + +//this singleton datum is used by the events controller to dictate how it selects events /datum/round_event_control var/name //The human-readable name of the event var/typepath //The typepath of the event datum /datum/round_event @@ -27,6 +29,9 @@ var/triggering //admin cancellation + /// Whether or not dynamic should hijack this event + var/dynamic_should_hijack = FALSE + /datum/round_event_control/New() if(config && !wizardevent) // Magic is unaffected by configs earliest_start = CEILING(earliest_start * CONFIG_GET(number/events_min_time_mul), 1) @@ -54,15 +59,21 @@ return FALSE return TRUE + var/datum/game_mode/dynamic/dynamic = SSticker.mode + if (istype(dynamic) && dynamic_should_hijack && dynamic.random_event_hijacked != HIJACKED_NOTHING) + return FALSE + /datum/round_event_control/proc/preRunEvent() if(!ispath(typepath, /datum/round_event)) return EVENT_CANT_RUN + if (SEND_GLOBAL_SIGNAL(COMSIG_GLOB_PRE_RANDOM_EVENT, src) & CANCEL_PRE_RANDOM_EVENT) + return EVENT_INTERRUPTED + triggering = TRUE if (alert_observers) - //Yogs start -- 20 seconds instead of 10 - message_admins("Random Event triggering in 20 seconds: [name] (CANCEL)") - sleep(20 SECONDS) + message_admins("Random Event triggering in [RANDOM_EVENT_ADMIN_INTERVENTION_TIME] seconds: [name] (CANCEL)") + sleep(RANDOM_EVENT_ADMIN_INTERVENTION_TIME SECONDS) //Yogs end var/gamemode = SSticker.mode.config_tag var/players_amt = get_active_player_count(alive_check = TRUE, afk_check = TRUE, human_check = TRUE) @@ -222,3 +233,5 @@ processing = my_processing SSevents.running += src return ..() + +#undef RANDOM_EVENT_ADMIN_INTERVENTION_TIME \ No newline at end of file diff --git a/code/modules/events/alien_infestation.dm b/code/modules/events/alien_infestation.dm index 99d9a266bc03..3f5bedc960e6 100644 --- a/code/modules/events/alien_infestation.dm +++ b/code/modules/events/alien_infestation.dm @@ -5,6 +5,8 @@ min_players = 10 + dynamic_should_hijack = TRUE + /datum/round_event_control/alien_infestation/canSpawnEvent() . = ..() if(!.) diff --git a/code/modules/events/blob.dm b/code/modules/events/blob.dm index efeb6d19ed39..42b847753f5f 100644 --- a/code/modules/events/blob.dm +++ b/code/modules/events/blob.dm @@ -6,6 +6,8 @@ min_players = 25 + dynamic_should_hijack = TRUE + gamemode_blacklist = list("blob") //Just in case a blob survives that long /datum/round_event/ghost_role/blob diff --git a/code/modules/events/nightmare.dm b/code/modules/events/nightmare.dm index ae99d6197dfd..edc6d9aab154 100644 --- a/code/modules/events/nightmare.dm +++ b/code/modules/events/nightmare.dm @@ -4,6 +4,7 @@ max_occurrences = 1 min_players = 30 earliest_start = 45 MINUTES + dynamic_should_hijack = TRUE /datum/round_event/ghost_role/nightmare minimum_required = 1 diff --git a/code/modules/events/operative.dm b/code/modules/events/operative.dm index 7fca4188b769..b5bba1902945 100644 --- a/code/modules/events/operative.dm +++ b/code/modules/events/operative.dm @@ -3,6 +3,7 @@ typepath = /datum/round_event/ghost_role/operative weight = 0 //Admin only max_occurrences = 1 + dynamic_should_hijack = TRUE /datum/round_event/ghost_role/operative minimum_required = 1 diff --git a/code/modules/events/pirates.dm b/code/modules/events/pirates.dm index 27e3f96c9e4e..1b9df47f93b7 100644 --- a/code/modules/events/pirates.dm +++ b/code/modules/events/pirates.dm @@ -5,6 +5,7 @@ max_occurrences = 1 min_players = 10 earliest_start = 30 MINUTES + dynamic_should_hijack = TRUE gamemode_blacklist = list("nuclear") /datum/round_event_control/pirates/preRunEvent() diff --git a/code/modules/events/space_dragon.dm b/code/modules/events/space_dragon.dm index 3eb70c65f864..a6100cba7edc 100644 --- a/code/modules/events/space_dragon.dm +++ b/code/modules/events/space_dragon.dm @@ -5,6 +5,7 @@ weight = 8 earliest_start = 70 MINUTES min_players = 30 + dynamic_should_hijack = TRUE /datum/round_event/ghost_role/space_dragon minimum_required = 1 diff --git a/code/modules/events/spider_infestation.dm b/code/modules/events/spider_infestation.dm index 7afe14aeb0ed..a8ea7f5573d3 100644 --- a/code/modules/events/spider_infestation.dm +++ b/code/modules/events/spider_infestation.dm @@ -4,6 +4,7 @@ weight = 5 max_occurrences = 1 min_players = 15 + dynamic_should_hijack = TRUE /datum/round_event/spider_infestation announceWhen = 400 diff --git a/code/modules/events/swarmer.dm b/code/modules/events/swarmer.dm index 13633b0942d6..39f35b325baa 100644 --- a/code/modules/events/swarmer.dm +++ b/code/modules/events/swarmer.dm @@ -5,6 +5,7 @@ max_occurrences = 1 //Only once okay fam earliest_start = 30 MINUTES min_players = 15 + dynamic_should_hijack = TRUE /datum/round_event/spawn_swarmer/announce(fake) priority_announce("Our long-range sensors have detected that your station's defenses have been breached by some sort of alien device. We suggest searching for and destroying it as soon as possible.", "[command_name()] High-Priority Update") diff --git a/code/modules/events/zombie_infection.dm b/code/modules/events/zombie_infection.dm index 8a8497ca5b0b..cd84551fef0d 100644 --- a/code/modules/events/zombie_infection.dm +++ b/code/modules/events/zombie_infection.dm @@ -4,6 +4,7 @@ max_occurrences = 1 min_players = 20 weight = 4 + dynamic_should_hijack = TRUE /datum/round_event/ghost_role/zombie minimum_required = 1 role_name = "zombie" diff --git a/code/modules/mob/dead/dead.dm b/code/modules/mob/dead/dead.dm index 2dc3b2ad26ac..c2ed9a8fbfea 100644 --- a/code/modules/mob/dead/dead.dm +++ b/code/modules/mob/dead/dead.dm @@ -13,7 +13,7 @@ INITIALIZE_IMMEDIATE(/mob/dead) stack_trace("Warning: [src]([type]) initialized multiple times!") flags_1 |= INITIALIZED_1 tag = "mob_[next_mob_id++]" - GLOB.mob_list += src + add_to_mob_list() prepare_huds() diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 4243c2d8996c..6b1ad5b9916c 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -130,7 +130,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) animate(src, pixel_y = 2, time = 10, loop = -1) - GLOB.dead_mob_list += src + add_to_dead_mob_list() for(var/v in GLOB.active_alternate_appearances) if(!v) @@ -727,7 +727,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp remove_data_huds() else show_data_huds() - + data_huds_on = !data_huds_on to_chat(src, "Data HUDs [data_huds_on ? "enabled" : "disabled"].") diff --git a/code/modules/mob/living/brain/MMI.dm b/code/modules/mob/living/brain/MMI.dm index 4a4fd7f49834..aa59256b80b0 100644 --- a/code/modules/mob/living/brain/MMI.dm +++ b/code/modules/mob/living/brain/MMI.dm @@ -60,8 +60,8 @@ var/fubar_brain = newbrain.brain_death && newbrain.suicided && brainmob.suiciding //brain is damaged beyond repair or from a suicider if(!fubar_brain && !(newbrain.organ_flags & ORGAN_FAILING)) // the brain organ hasn't been beaten to death, nor was from a suicider. brainmob.stat = CONSCIOUS //we manually revive the brain mob - GLOB.dead_mob_list -= brainmob - GLOB.alive_mob_list += brainmob + brainmob.remove_from_dead_mob_list() + brainmob.add_to_alive_mob_list() else if(!fubar_brain && newbrain.organ_flags & ORGAN_FAILING) // the brain is damaged, but not from a suicider to_chat(user, "[src]'s indicator light turns yellow and its brain integrity alarm beeps softly. Perhaps you should check [newbrain] for damage.") playsound(src, "sound/machines/synth_no.ogg", 5, TRUE) @@ -99,8 +99,8 @@ brainmob.stat = DEAD brainmob.emp_damage = 0 brainmob.reset_perspective() //so the brainmob follows the brain organ instead of the mmi. And to update our vision - GLOB.alive_mob_list -= brainmob //Get outta here - GLOB.dead_mob_list += brainmob + brainmob.remove_from_alive_mob_list() //Get outta here + brainmob.add_to_dead_mob_list() brain.brainmob = brainmob //Set the brain to use the brainmob brainmob = null //Set mmi brainmob var to null if(user) diff --git a/code/modules/mob/living/brain/posibrain.dm b/code/modules/mob/living/brain/posibrain.dm index cf58fc2f3224..55c1362299a7 100644 --- a/code/modules/mob/living/brain/posibrain.dm +++ b/code/modules/mob/living/brain/posibrain.dm @@ -130,8 +130,8 @@ GLOBAL_VAR(posibrain_notify_cooldown) to_chat(brainmob, welcome_message) brainmob.mind.assigned_role = new_role brainmob.stat = CONSCIOUS - GLOB.dead_mob_list -= brainmob - GLOB.alive_mob_list += brainmob + brainmob.remove_from_dead_mob_list() + brainmob.add_to_alive_mob_list() visible_message(new_mob_message) check_success() diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index a7e474034c8f..7dea7dc31ed4 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -58,9 +58,9 @@ deadchat_broadcast(" has died at [get_area_name(T)].", "[mind.name]", follow_target = src, turf_target = T, message_type=DEADCHAT_DEATHRATTLE) if(mind) mind.store_memory("Time of death: [tod]", 0) - GLOB.alive_mob_list -= src + remove_from_alive_mob_list() if(!gibbed) - GLOB.dead_mob_list += src + add_to_dead_mob_list() set_drugginess(0) set_disgust(0) SetSleeping(0, 0) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 729e74d7723e..8286320f6f79 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -507,8 +507,8 @@ if(full_heal) fully_heal(admin_revive) if(stat == DEAD && can_be_revived()) //in some cases you can't revive (e.g. no brain) - GLOB.dead_mob_list -= src - GLOB.alive_mob_list += src + remove_from_dead_mob_list() + add_to_alive_mob_list() set_suicide(FALSE) stat = UNCONSCIOUS //the mob starts unconscious, blind_eyes(1) @@ -1340,11 +1340,11 @@ return FALSE if("stat") if((stat == DEAD) && (var_value < DEAD))//Bringing the dead back to life - GLOB.dead_mob_list -= src - GLOB.alive_mob_list += src + remove_from_dead_mob_list() + add_to_alive_mob_list() if((stat < DEAD) && (var_value == DEAD))//Kill he - GLOB.alive_mob_list -= src - GLOB.dead_mob_list += src + remove_from_dead_mob_list() + add_to_alive_mob_list() . = ..() switch(var_name) if("knockdown") @@ -1369,7 +1369,7 @@ update_transform() if("lighting_alpha") sync_lighting_plane_alpha() - + /mob/living/proc/is_convert_antag() var/list/bad_antags = list( /datum/antagonist/clockcult, @@ -1383,4 +1383,4 @@ if(mind?.has_antag_datum(antagcheck)) return TRUE return FALSE - + diff --git a/code/modules/mob/living/silicon/pai/death.dm b/code/modules/mob/living/silicon/pai/death.dm index fce2dd023773..599ccab4ec51 100644 --- a/code/modules/mob/living/silicon/pai/death.dm +++ b/code/modules/mob/living/silicon/pai/death.dm @@ -7,6 +7,6 @@ clear_fullscreens() //New pAI's get a brand new mind to prevent meta stuff from their previous life. This new mind causes problems down the line if it's not deleted here. - GLOB.alive_mob_list -= src + remove_from_alive_mob_list() ghostize() qdel(src) diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 164139efb8b9..c2bc298ffabd 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -176,8 +176,8 @@ if(mmi.brainmob) if(mmi.brainmob.stat == DEAD) mmi.brainmob.stat = CONSCIOUS - GLOB.dead_mob_list -= mmi.brainmob - GLOB.alive_mob_list += mmi.brainmob + mmi.brainmob.remove_from_dead_mob_list() + mmi.brainmob.add_to_alive_mob_list() mind.transfer_to(mmi.brainmob) mmi.update_icon() else diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index 1930f0a82af7..2f40f21dcdba 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -24,7 +24,7 @@ /mob/Login() if(!client) return FALSE - GLOB.player_list |= src + add_to_player_list() lastKnownIP = client.address computer_id = client.computer_id log_access("Mob Login: [key_name(src)] was assigned to a [type]") diff --git a/code/modules/mob/logout.dm b/code/modules/mob/logout.dm index 9d69b43fdbbe..b89d5770aaf4 100644 --- a/code/modules/mob/logout.dm +++ b/code/modules/mob/logout.dm @@ -2,13 +2,13 @@ log_message("[key_name(src)] is no longer owning mob [src]", LOG_OWNERSHIP) SStgui.on_logout(src) unset_machine() - GLOB.player_list -= src + remove_from_player_list() ..() if(loc) loc.on_log(FALSE) - + if(client) for(var/foo in client.player_details.post_logout_callbacks) var/datum/callback/CB = foo diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 5be6861bef7a..3cb22ac69bd0 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -23,11 +23,9 @@ * Returns QDEL_HINT_HARDDEL (don't change this) */ /mob/Destroy()//This makes sure that mobs with clients/keys are not just deleted from the game. - GLOB.mob_list -= src - GLOB.dead_mob_list -= src - GLOB.alive_mob_list -= src - GLOB.all_clockwork_mobs -= src - GLOB.mob_directory -= tag + remove_from_mob_list() + remove_from_dead_mob_list() + remove_from_alive_mob_list() focus = null for (var/alert in alerts) clear_alert(alert, TRUE) @@ -63,12 +61,11 @@ */ /mob/Initialize() SEND_GLOBAL_SIGNAL(COMSIG_GLOB_MOB_CREATED, src) - GLOB.mob_list += src - GLOB.mob_directory[tag] = src + add_to_mob_list() if(stat == DEAD) - GLOB.dead_mob_list += src + add_to_mob_list() else - GLOB.alive_mob_list += src + add_to_mob_list() set_focus(src) prepare_huds() for(var/v in GLOB.active_alternate_appearances) diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 652f6c4a3d04..f3d03fa282d6 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -55,7 +55,7 @@ /** * Magic var that stops you moving and interacting with anything - * + * * Set when you're being turned into something else and also used in a bunch of places * it probably shouldn't really be */ @@ -74,7 +74,7 @@ /** * back up of the real name during admin possession - * + * * If an admin possesses an object it's real name is set to the admin name and this * stores whatever the real name was previously. When possession ends, the real name * is reset to this value @@ -117,12 +117,12 @@ var/active_hand_index = 1 /** * list of items held in hands - * + * * len = number of hands, eg: 2 nulls is 2 empty hands, 1 item and 1 null is 1 full hand * and 1 empty hand. - * + * * NB: contains nulls! - * + * * held_items[active_hand_index] is the actively held item, but please use * get_active_held_item() instead, because OOP */ @@ -160,7 +160,7 @@ */ var/list/mob_spell_list = list() - + /// bitflags defining which status effects can be inflicted (replaces canknockdown, canstun, etc) var/status_flags = CANSTUN|CANKNOCKDOWN|CANUNCONSCIOUS|CANPUSH @@ -204,7 +204,7 @@ ///THe z level this mob is currently registered in var/registered_z = null - + ///Size of the user's memory(IC notes) var/memory_amt = 0 @@ -213,3 +213,6 @@ ///Whether the mob is updating glide size when movespeed updates or not var/updating_glide_size = TRUE + + /// A mock client, provided by tests and friends + var/datum/client_interface/mock_client diff --git a/code/modules/mob/mob_lists.dm b/code/modules/mob/mob_lists.dm new file mode 100644 index 000000000000..4f71e110dd96 --- /dev/null +++ b/code/modules/mob/mob_lists.dm @@ -0,0 +1,119 @@ +///Adds the mob reference to the list and directory of all mobs. Called on Initialize(). +/mob/proc/add_to_mob_list() + GLOB.mob_list |= src + GLOB.mob_directory[tag] = src + +///Removes the mob reference from the list and directory of all mobs. Called on Destroy(). +/mob/proc/remove_from_mob_list() + GLOB.mob_list -= src + GLOB.mob_directory -= tag + +///Adds the mob reference to the list of all mobs alive. If mob is cliented, it adds it to the list of all living player-mobs. +/mob/proc/add_to_alive_mob_list() + GLOB.alive_mob_list |= src + if(client) + add_to_current_living_players() + +///Removes the mob reference from the list of all mobs alive. If mob is cliented, it removes it from the list of all living player-mobs. +/mob/proc/remove_from_alive_mob_list() + GLOB.alive_mob_list -= src + if(client) + remove_from_current_living_players() + + +///Adds the mob reference to the list of all the dead mobs. If mob is cliented, it adds it to the list of all dead player-mobs. +/mob/proc/add_to_dead_mob_list() + GLOB.dead_mob_list |= src + if(client) + add_to_current_dead_players() + +///Remvoes the mob reference from list of all the dead mobs. If mob is cliented, it adds it to the list of all dead player-mobs. +/mob/proc/remove_from_dead_mob_list() + GLOB.dead_mob_list -= src + if(client) + remove_from_current_dead_players() + + +///Adds the cliented mob reference to the list of all player-mobs, besides to either the of dead or alive player-mob lists, as appropriate. Called on Login(). +/mob/proc/add_to_player_list() + SHOULD_CALL_PARENT(TRUE) + GLOB.player_list |= src + if(!SSticker?.mode) + return + if(stat == DEAD) + add_to_current_dead_players() + else + add_to_current_living_players() + +///Removes the mob reference from the list of all player-mobs, besides from either the of dead or alive player-mob lists, as appropriate. Called on Logout(). +/mob/proc/remove_from_player_list() + SHOULD_CALL_PARENT(TRUE) + GLOB.player_list -= src + if(!SSticker?.mode) + return + if(stat == DEAD) + remove_from_current_dead_players() + else + remove_from_current_living_players() + + +///Adds the cliented mob reference to either the list of dead player-mobs or to the list of observers, depending on how they joined the game. +/mob/proc/add_to_current_dead_players() + if(!SSticker?.mode) + return + SSticker.mode.current_players[CURRENT_DEAD_PLAYERS] |= src + +/mob/dead/observer/add_to_current_dead_players() + if(!SSticker?.mode) + return + if(started_as_observer) + SSticker.mode.current_players[CURRENT_OBSERVERS] |= src + return + return ..() + +/mob/dead/new_player/add_to_current_dead_players() + return + +///Removes the mob reference from either the list of dead player-mobs or from the list of observers, depending on how they joined the game. +/mob/proc/remove_from_current_dead_players() + if(!SSticker?.mode) + return + SSticker.mode.current_players[CURRENT_DEAD_PLAYERS] -= src + +/mob/dead/observer/remove_from_current_dead_players() + if(!SSticker?.mode) + return + if(started_as_observer) + SSticker.mode.current_players[CURRENT_OBSERVERS] -= src + return + return ..() + + +///Adds the cliented mob reference to the list of living player-mobs. If the mob is an antag, it adds it to the list of living antag player-mobs. +/mob/proc/add_to_current_living_players() + if(!SSticker?.mode) + return + SSticker.mode.current_players[CURRENT_LIVING_PLAYERS] |= src + if(mind && (mind.special_role || length(mind.antag_datums))) + add_to_current_living_antags() + +///Removes the mob reference from the list of living player-mobs. If the mob is an antag, it removes it from the list of living antag player-mobs. +/mob/proc/remove_from_current_living_players() + if(!SSticker?.mode) + return + SSticker.mode.current_players[CURRENT_LIVING_PLAYERS] -= src + if(LAZYLEN(mind?.antag_datums)) + remove_from_current_living_antags() + + +///Adds the cliented mob reference to the list of living antag player-mobs. +/mob/proc/add_to_current_living_antags() + if(!SSticker?.mode) + return + SSticker.mode.current_players[CURRENT_LIVING_ANTAGS] |= src + +///Removes the mob reference from the list of living antag player-mobs. +/mob/proc/remove_from_current_living_antags() + if(!SSticker?.mode) + return + SSticker.mode.current_players[CURRENT_LIVING_ANTAGS] -= src \ No newline at end of file diff --git a/code/modules/ninja/ninja_event.dm b/code/modules/ninja/ninja_event.dm index 26f41f2de237..cac4b414e7f6 100644 --- a/code/modules/ninja/ninja_event.dm +++ b/code/modules/ninja/ninja_event.dm @@ -15,6 +15,7 @@ Contents: max_occurrences = 1 earliest_start = 40 MINUTES min_players = 15 + dynamic_should_hijack = TRUE /datum/round_event/ghost_role/ninja var/success_spawn = 0 diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index cbce2553cdf1..e2a542c3fefb 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -4,6 +4,7 @@ #if defined(UNIT_TESTS) || defined(SPACEMAN_DMM) #include "anchored_mobs.dm" #include "component_tests.dm" +#include "dynamic_ruleset_sanity.dm" #include "reagent_id_typos.dm" #include "reagent_recipe_collisions.dm" #include "spawn_humans.dm" diff --git a/code/modules/unit_tests/dynamic_ruleset_sanity.dm b/code/modules/unit_tests/dynamic_ruleset_sanity.dm new file mode 100644 index 000000000000..0aeacc132f5d --- /dev/null +++ b/code/modules/unit_tests/dynamic_ruleset_sanity.dm @@ -0,0 +1,14 @@ +/// Verifies that roundstart dynamic rulesets are setup properly without external configuration. +/datum/unit_test/dynamic_roundstart_ruleset_sanity + +/datum/unit_test/dynamic_roundstart_ruleset_sanity/Run() + for (var/_ruleset in subtypesof(/datum/dynamic_ruleset/roundstart)) + var/datum/dynamic_ruleset/roundstart/ruleset = _ruleset + + var/has_scaling_cost = initial(ruleset.scaling_cost) + var/is_lone = initial(ruleset.flags) & (LONE_RULESET | HIGH_IMPACT_RULESET) + + if (has_scaling_cost && is_lone) + Fail("[ruleset] has a scaling_cost, but is also a lone/highlander ruleset.") + else if (!has_scaling_cost && !is_lone) + Fail("[ruleset] has no scaling cost, but is also not a lone/highlander ruleset.") \ No newline at end of file From d299f4d22ff2c323f22851b225fd1d54ca998828 Mon Sep 17 00:00:00 2001 From: Xoxeyos Date: Sun, 14 Mar 2021 17:46:19 -0500 Subject: [PATCH 02/53] This shit is fucking abhorrent Admin.dm, rust_g, game.dm, mobs.dm, pai.dm, dynamic folder, holy_weapons.dm, admin_verbs.dm, topic.dm, ghost_pool_protection, antag_datum.dm, CTF.dm, corpse.dm, _event.dm, living/brain/posibrain.dm, giant_spider.dm, dynamic.json --- code/__DEFINES/admin.dm | 15 ++ code/__DEFINES/rust_g.dm | 5 + code/__HELPERS/game.dm | 2 + code/_globalvars/lists/mobs.dm | 3 +- code/controllers/subsystem/pai.dm | 3 + code/game/gamemodes/dynamic/dynamic.dm | 13 +- .../dynamic/dynamic_rulesets_latejoin.dm | 1 - .../dynamic/dynamic_rulesets_midround.dm | 1 - .../dynamic/dynamic_rulesets_roundstart.dm | 1 + code/game/objects/items/holy_weapons.dm | 3 + code/modules/admin/admin_verbs.dm | 1 + code/modules/admin/topic.dm | 137 ------------------ .../admin/verbs/ghost_pool_protection.dm | 86 +++++++++++ .../antagonists/_common/antag_datum.dm | 2 - code/modules/awaymissions/capture_the_flag.dm | 4 +- code/modules/awaymissions/corpse.dm | 3 + code/modules/events/_event.dm | 2 + code/modules/mob/living/brain/posibrain.dm | 3 + .../simple_animal/hostile/giant_spider.dm | 2 +- config/dynamic.json | 20 +-- yogstation.dme | 8 + 21 files changed, 144 insertions(+), 171 deletions(-) create mode 100644 code/modules/admin/verbs/ghost_pool_protection.dm diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm index d7672256bdad..812e82ede0ce 100644 --- a/code/__DEFINES/admin.dm +++ b/code/__DEFINES/admin.dm @@ -97,3 +97,18 @@ #define POLICY_POLYMORPH "polymorph" /// Shown on top of policy verb window #define POLICY_VERB_HEADER "policy_verb_header" + +// allowed ghost roles this round, starts as everything allowed +GLOBAL_VAR_INIT(ghost_role_flags, (~0)) + +//Flags that control what ways ghosts can get back into the round +//ie fugitives, space dragon, etc. also includes dynamic midrounds as it's the same deal +#define GHOSTROLE_MIDROUND_EVENT (1<<0) +//ie ashwalkers, free golems, beach bums +#define GHOSTROLE_SPAWNER (1<<1) +//ie mind monkeys, sentience potion +#define GHOSTROLE_STATION_SENTIENCE (1<<2) +//ie pais, posibrains +#define GHOSTROLE_SILICONS (1<<3) +//ie mafia, ctf +#define GHOSTROLE_MINIGAME (1<<4) \ No newline at end of file diff --git a/code/__DEFINES/rust_g.dm b/code/__DEFINES/rust_g.dm index 7ef75cfceded..fb43f98bcf33 100644 --- a/code/__DEFINES/rust_g.dm +++ b/code/__DEFINES/rust_g.dm @@ -62,6 +62,11 @@ #define RUSTG_HTTP_METHOD_PATCH "patch" #define RUSTG_HTTP_METHOD_HEAD "head" +#define rustg_file_read(fname) call(RUST_G, "file_read")("[fname]") +#define rustg_file_exists(fname) call(RUST_G, "file_exists")("[fname]") +#define rustg_file_write(text, fname) call(RUST_G, "file_write")(text, "[fname]") +#define rustg_file_append(text, fname) call(RUST_G, "file_append")(text, "[fname]") + #define rustg_sql_connect_pool(options) call(RUST_G, "sql_connect_pool")(options) #define rustg_sql_query_async(handle, query, params) call(RUST_G, "sql_query_async")(handle, query, params) #define rustg_sql_query_blocking(handle, query, params) call(RUST_G, "sql_query_blocking")(handle, query, params) diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index 3aab2941a22f..1cc419e39020 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -476,6 +476,8 @@ */ /proc/pollGhostCandidates(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, ignore_category = null, flashwindow = TRUE) var/list/candidates = list() + if(!(GLOB.ghost_role_flags & GHOSTROLE_STATION_SENTIENCE)) + return candidates for(var/mob/dead/observer/G in GLOB.player_list) candidates += G diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index b84b766e378d..08b8ce9b6cb8 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -27,6 +27,7 @@ GLOBAL_LIST_INIT(simple_animals, list(list(),list(),list(),list())) // One for e GLOBAL_LIST_EMPTY(spidermobs) //all sentient spider mobs GLOBAL_LIST_EMPTY(bots_list) GLOBAL_LIST_EMPTY(aiEyes) +GLOBAL_LIST_EMPTY(new_player_list) //all /mob/dead/new_player, in theory all should have clients and those that don't are in the process of spawning and get deleted when done. ///underages who have been reported to security for trying to buy things they shouldn't, so they can't spam GLOBAL_LIST_EMPTY(narcd_underages) @@ -72,7 +73,7 @@ GLOBAL_LIST_EMPTY(walkingmushroom) .[E.key] = list(E) else .[E.key] += E - + if(!.[E.key_third_person]) .[E.key_third_person] = list(E) else diff --git a/code/controllers/subsystem/pai.dm b/code/controllers/subsystem/pai.dm index 1194987410b0..dc7a60918661 100644 --- a/code/controllers/subsystem/pai.dm +++ b/code/controllers/subsystem/pai.dm @@ -144,6 +144,9 @@ SUBSYSTEM_DEF(pai) return FALSE /datum/controller/subsystem/pai/proc/findPAI(obj/item/paicard/p, mob/user) + if(!(GLOB.ghost_role_flags & GHOSTROLE_SILICONS)) + to_chat(user, "Due to growing incidents of SELF corrupted independent artificial intelligences, freeform personality devices have been temporarily banned in this sector.") + return if(!ghost_spam) ghost_spam = TRUE for(var/mob/dead/observer/G in GLOB.player_list) diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index 505eebf25d64..87c10d165d0c 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -545,7 +545,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) executed_rules += ruleset return ruleset.cost + added_threat else - stack_trace("The starting rule \"[starting_rule.name]\" failed to pre_execute.") + stack_trace("The starting rule \"[ruleset.name]\" failed to pre_execute.") return 0 /// Mainly here to facilitate delayed rulesets. All roundstart rulesets are executed with a timered callback to this proc. @@ -762,32 +762,21 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) ruleset.restricted_roles |= ruleset.protected_roles if(CONFIG_GET(flag/protect_assistant_from_antagonist)) ruleset.restricted_roles |= "Assistant" - if(CONFIG_GET(flag/protect_heads_from_antagonist)) - ruleset.restricted_roles |= GLOB.command_positions /// Refund threat, but no more than threat_level. /datum/game_mode/dynamic/proc/refund_threat(regain) - threat = min(threat_level,threat+regain) mid_round_budget = min(threat_level, mid_round_budget + regain) /// Generate threat and increase the threat_level if it goes beyond, capped at 100 /datum/game_mode/dynamic/proc/create_threat(gain) - threat = min(100, threat+gain) - if(threat > threat_level) - threat_level = threat mid_round_budget = min(100, mid_round_budget + gain) if(mid_round_budget > threat_level) threat_level = mid_round_budget -/// Expend threat, can't fall under 0. -/datum/game_mode/dynamic/proc/spend_threat(cost) - threat = max(threat-cost,0) /// Expend round start threat, can't fall under 0. /datum/game_mode/dynamic/proc/spend_roundstart_budget(cost) round_start_budget = max(round_start_budget - cost,0) -/// Turns the value generated by lorentz distribution to threat value between 0 and 100. -/datum/game_mode/dynamic/proc/lorentz_to_threat(x) /// Expend midround threat, can't fall under 0. /datum/game_mode/dynamic/proc/spend_midround_budget(cost) mid_round_budget = max(mid_round_budget - cost,0) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index 92a0761a0417..4545720eab3c 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -201,7 +201,6 @@ requirements = list(80,70,60,50,50,45,30,30,20,25) minimum_players = 30 repeatable = TRUE - flags = TRAITOR_RULESET ////////////////////////////////////////////// diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index 68a74f55c754..58a3cad6c8a2 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -428,7 +428,6 @@ weight = 1 cost = 5 requirements = list(90,85,80,70,50,40,30,25,20,10) - high_population_requirement = 50 repeatable = TRUE var/list/spawn_locs = list() diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index 2b1be4cf4225..05caab522338 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -111,6 +111,7 @@ /datum/dynamic_ruleset/roundstart/changeling/pre_execute(population) . = ..() var/num_changelings = get_antag_cap(population) * (scaled_times + 1) + for (var/i = 1 to num_changelings) var/mob/M = pick_n_take(candidates) assigned += M.mind M.mind.restricted_roles = restricted_roles diff --git a/code/game/objects/items/holy_weapons.dm b/code/game/objects/items/holy_weapons.dm index 7f9b84cdf504..76c3d21d4567 100644 --- a/code/game/objects/items/holy_weapons.dm +++ b/code/game/objects/items/holy_weapons.dm @@ -509,6 +509,9 @@ /obj/item/nullrod/scythe/talking/attack_self(mob/living/user) if(possessed) return + if(!(GLOB.ghost_role_flags & GHOSTROLE_STATION_SENTIENCE)) + to_chat(user, "Anomalous otherworldly energies block you from awakening the blade!") + return to_chat(user, "You attempt to wake the spirit of the blade...") diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 5370ca6ed384..8f53e9ab1e0c 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -33,6 +33,7 @@ GLOBAL_PROTECT(admin_verbs_admin) /datum/verbs/menu/Admin/verb/playerpanel, /client/proc/game_panel, /*game panel, allows to change game-mode etc*/ /client/proc/check_ai_laws, /*shows AI and borg laws*/ + /client/proc/ghost_pool_protection, /*opens a menu for toggling ghost roles*/ /datum/admins/proc/toggleooc, /*toggles ooc on/off for everyone*/ /datum/admins/proc/toggleoocdead, /*toggles ooc on/off for everyone who is dead*/ /datum/admins/proc/togglelooc, /*toggles looc on/off for everyone*/ // yogs - LOOC diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index d93f233971c2..f898a5a355ba 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -838,112 +838,6 @@ dynamic_mode_options(usr) - else if(href_list["f_dynamic_roundstart_centre"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(!SSticker.is_mode("dynamic")) - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - var/new_centre = input(usr,"Change the centre of the dynamic mode threat curve. A negative value will give a more peaceful round ; a positive value, a round with higher threat. Any number between -5 and +5 is allowed.", "Change curve centre", null) as num - if (new_centre < -5 || new_centre > 5) - return alert(usr, "Only values between -5 and +5 are allowed.", null, null, null, null) - - log_admin("[key_name(usr)] changed the distribution curve center to [new_centre].") - message_admins("[key_name(usr)] changed the distribution curve center to [new_centre]", 1) - GLOB.dynamic_curve_centre = new_centre - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_width"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(!SSticker.is_mode("dynamic")) - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - var/new_width = input(usr,"Change the width of the dynamic mode threat curve. A higher value will favour extreme rounds ; a lower value, a round closer to the average. Any Number between 0.5 and 4 are allowed.", "Change curve width", null) as num - if (new_width < 0.5 || new_width > 4) - return alert(usr, "Only values between 0.5 and +2.5 are allowed.", null, null, null, null) - - log_admin("[key_name(usr)] changed the distribution curve width to [new_width].") - message_admins("[key_name(usr)] changed the distribution curve width to [new_width]", 1) - GLOB.dynamic_curve_width = new_width - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_latejoin_min"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(!SSticker.is_mode("dynamic")) - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_min = input(usr,"Change the minimum delay of latejoin injection in minutes.", "Change latejoin injection delay minimum", null) as num - if(new_min <= 0) - return alert(usr, "The minimum can't be zero or lower.", null, null, null, null) - if((new_min MINUTES) > GLOB.dynamic_latejoin_delay_max) - return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes.") - message_admins("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes", 1) - GLOB.dynamic_latejoin_delay_min = (new_min MINUTES) - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_latejoin_max"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(!SSticker.is_mode("dynamic")) - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_max = input(usr,"Change the maximum delay of latejoin injection in minutes.", "Change latejoin injection delay maximum", null) as num - if(new_max <= 0) - return alert(usr, "The maximum can't be zero or lower.", null, null, null, null) - if((new_max MINUTES) < GLOB.dynamic_latejoin_delay_min) - return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes.") - message_admins("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes", 1) - GLOB.dynamic_latejoin_delay_max = (new_max MINUTES) - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_midround_min"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(!SSticker.is_mode("dynamic")) - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_min = input(usr,"Change the minimum delay of midround injection in minutes.", "Change midround injection delay minimum", null) as num - if(new_min <= 0) - return alert(usr, "The minimum can't be zero or lower.", null, null, null, null) - if((new_min MINUTES) > GLOB.dynamic_midround_delay_max) - return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes.") - message_admins("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes", 1) - GLOB.dynamic_midround_delay_min = (new_min MINUTES) - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_midround_max"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(!SSticker.is_mode("dynamic")) - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_max = input(usr,"Change the maximum delay of midround injection in minutes.", "Change midround injection delay maximum", null) as num - if(new_max <= 0) - return alert(usr, "The maximum can't be zero or lower.", null, null, null, null) - if((new_max MINUTES) > GLOB.dynamic_midround_delay_max) - return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes.") - message_admins("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes", 1) - GLOB.dynamic_midround_delay_max = (new_max MINUTES) - dynamic_mode_options(usr) - else if(href_list["f_dynamic_force_extended"]) if(!check_rights(R_ADMIN)) return @@ -968,18 +862,6 @@ message_admins("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].") dynamic_mode_options(usr) - else if(href_list["f_dynamic_classic_secret"]) - if(!check_rights(R_ADMIN)) - return - - if(!SSticker.is_mode("dynamic")) - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - GLOB.dynamic_classic_secret = !GLOB.dynamic_classic_secret - log_admin("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].") - message_admins("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].") - dynamic_mode_options(usr) - else if(href_list["f_dynamic_stacking_limit"]) if(!check_rights(R_ADMIN)) return @@ -992,25 +874,6 @@ message_admins("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].") dynamic_mode_options(usr) - else if(href_list["f_dynamic_high_pop_limit"]) - if(!check_rights(R_ADMIN)) - return - - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - - if(!SSticker.is_mode("dynamic")) - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - var/new_value = input(usr, "Enter the high-pop override threshold for dynamic mode.", "High pop override") as num - if (new_value < 0) - return alert(usr, "Only positive values allowed!", null, null, null, null) - GLOB.dynamic_high_pop_limit = new_value - - log_admin("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].") - message_admins("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].") - dynamic_mode_options(usr) - else if(href_list["f_dynamic_forced_threat"]) if(!check_rights(R_ADMIN)) return diff --git a/code/modules/admin/verbs/ghost_pool_protection.dm b/code/modules/admin/verbs/ghost_pool_protection.dm new file mode 100644 index 000000000000..b06d34b5e090 --- /dev/null +++ b/code/modules/admin/verbs/ghost_pool_protection.dm @@ -0,0 +1,86 @@ +//very similar to centcom_podlauncher in terms of how this is coded, so i kept a lot of comments from it + +/client/proc/ghost_pool_protection() //Creates a verb for admins to open up the ui + set name = "Ghost Pool Protection" + set desc = "Choose which ways people can get into the round, or just clear it out completely for admin events." + set category = "Adminbus" + var/datum/ghost_pool_menu/tgui = new(usr)//create the datum + tgui.ui_interact(usr)//datum has a tgui component, here we open the window + +/datum/ghost_pool_menu + var/client/holder //client of whoever is using this datum + + //when submitted, what the pool flags will be set to + var/new_role_flags = ALL + + //EVERY TYPE OF WAY SOMEONE IS GETTING BACK INTO THE ROUND! + //these are the same comments as the ones in admin.dm defines, please update those if you change them here + /* + var/events_or_midrounds = TRUE //ie fugitives, space dragon, etc. also includes dynamic midrounds as it's the same deal + var/spawners = TRUE //ie ashwalkers, free golems, beach bums + var/station_sentience = TRUE //ie posibrains, mind monkeys, sentience potion, etc. + var/minigames = TRUE //ie mafia, ctf + var/misc = TRUE //oddities like split personality and any animal ones like spiders, xenos + */ + +/datum/ghost_pool_menu/New(user)//user can either be a client or a mob due to byondcode(tm) + if (istype(user, /client)) + var/client/user_client = user + holder = user_client //if its a client, assign it to holder + else + var/mob/user_mob = user + holder = user_mob.client //if its a mob, assign the mob's client to holder + new_role_flags = GLOB.ghost_role_flags + +/datum/ghost_pool_menu/ui_state(mob/user) + return GLOB.admin_state + +/datum/ghost_pool_menu/ui_close() + qdel(src) + +/datum/ghost_pool_menu/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "GhostPoolProtection") + ui.open() + +/datum/ghost_pool_menu/ui_data(mob/user) + var/list/data = list() + data["events_or_midrounds"] = (new_role_flags & GHOSTROLE_MIDROUND_EVENT) + data["spawners"] = (new_role_flags & GHOSTROLE_SPAWNER) + data["station_sentience"] = (new_role_flags & GHOSTROLE_STATION_SENTIENCE) + data["silicons"] = (new_role_flags & GHOSTROLE_SILICONS) + data["minigames"] = (new_role_flags & GHOSTROLE_MINIGAME) + return data + +/datum/ghost_pool_menu/ui_act(action, params) + . = ..() + if(.) + return + switch(action) + if("toggle_events_or_midrounds") + new_role_flags ^= GHOSTROLE_MIDROUND_EVENT + if("toggle_spawners") + new_role_flags ^= GHOSTROLE_SPAWNER + if("toggle_station_sentience") + new_role_flags ^= GHOSTROLE_STATION_SENTIENCE + if("toggle_silicons") + new_role_flags ^= GHOSTROLE_SILICONS + if("toggle_minigames") + new_role_flags ^= GHOSTROLE_MINIGAME + if("all_roles") + new_role_flags = ALL + if("no_roles") + new_role_flags = NONE + if("apply_settings") + to_chat(usr, "Settings Applied!") + var/msg + switch(new_role_flags) + if(ALL) + msg = "enabled all of" + if(NONE) + msg = "disabled all of" + else + msg = "modified" + message_admins("[key_name_admin(holder)] has [msg] this round's allowed ghost roles.") + GLOB.ghost_role_flags = new_role_flags \ No newline at end of file diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index 8241971e078f..684196cb4371 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -81,8 +81,6 @@ GLOBAL_LIST_EMPTY(antagonists) CRASH("[src] ran on_gain() without a mind") if(!owner.current) CRASH("[src] ran on_gain() on a mind without a mob") - if(!silent && tips) - show_tips(tips) greet() apply_innate_effects() give_antag_moodies() diff --git a/code/modules/awaymissions/capture_the_flag.dm b/code/modules/awaymissions/capture_the_flag.dm index 483e23174016..87ec608247fc 100644 --- a/code/modules/awaymissions/capture_the_flag.dm +++ b/code/modules/awaymissions/capture_the_flag.dm @@ -211,7 +211,9 @@ toggle_all_ctf(user) return - + if(!(GLOB.ghost_role_flags & GHOSTROLE_MINIGAME)) + to_chat(user, "CTF has been temporarily disabled by admins.") + return people_who_want_to_play |= user.ckey var/num = people_who_want_to_play.len var/remaining = CTF_REQUIRED_PLAYERS - num diff --git a/code/modules/awaymissions/corpse.dm b/code/modules/awaymissions/corpse.dm index 407a37fa15d6..dafb5c9e27e3 100644 --- a/code/modules/awaymissions/corpse.dm +++ b/code/modules/awaymissions/corpse.dm @@ -36,6 +36,9 @@ /obj/effect/mob_spawn/attack_ghost(mob/user) if(!SSticker.HasRoundStarted() || !loc || !ghost_usable) return + if(!(GLOB.ghost_role_flags & GHOSTROLE_SPAWNER) && !(flags_1 & ADMIN_SPAWNED_1)) + to_chat(user, "An admin has temporarily disabled non-admin ghost roles!") + return if(!uses) to_chat(user, "This spawner is out of charges!") return diff --git a/code/modules/events/_event.dm b/code/modules/events/_event.dm index d99e09f9f3b1..a49f52d9fe38 100644 --- a/code/modules/events/_event.dm +++ b/code/modules/events/_event.dm @@ -57,6 +57,8 @@ return FALSE if(holidayID && (!SSevents.holidays || !SSevents.holidays[holidayID])) return FALSE + if(ispath(typepath, /datum/round_event/ghost_role) && GHOSTROLE_MIDROUND_EVENT) + return FALSE return TRUE var/datum/game_mode/dynamic/dynamic = SSticker.mode diff --git a/code/modules/mob/living/brain/posibrain.dm b/code/modules/mob/living/brain/posibrain.dm index 55c1362299a7..a9396e912d21 100644 --- a/code/modules/mob/living/brain/posibrain.dm +++ b/code/modules/mob/living/brain/posibrain.dm @@ -87,6 +87,9 @@ GLOBAL_VAR(posibrain_notify_cooldown) return if(is_occupied() || is_banned_from(user.ckey, ROLE_POSIBRAIN) || QDELETED(brainmob) || QDELETED(src) || QDELETED(user)) return + if(!(GLOB.ghost_role_flags & GHOSTROLE_SILICONS)) + to_chat(user, "Central Command has temporarily outlawed posibrain sentience in this sector...") + return if(user.suiciding) //if they suicided, they're out forever. to_chat(user, "[src] fizzles slightly. Sadly it doesn't take those who suicided!") return diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm index 88732f3a3011..654ce97c660b 100644 --- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm +++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm @@ -66,7 +66,7 @@ /mob/living/simple_animal/hostile/poison/giant_spider/Topic(href, href_list) if(href_list["activate"]) var/mob/dead/observer/ghost = usr - if(istype(ghost) && playable_spider) + if(istype(ghost) && playable_spider && !(GLOB.ghost_role_flags & GHOSTROLE_SPAWNER)) humanize_spider(ghost) /mob/living/simple_animal/hostile/poison/giant_spider/Login() diff --git a/config/dynamic.json b/config/dynamic.json index ec2b65d1b591..91c80669288c 100644 --- a/config/dynamic.json +++ b/config/dynamic.json @@ -2,11 +2,10 @@ "Dynamic": {}, "Roundstart": { "Traitors": { - "cost": 10, - "scaling_cost": 10, + "cost": 8, + "scaling_cost": 9, "weight": 1, "required_candidates": 1, - "high_population_requirement": 10, "minimum_required_age": 0, "requirements": [ 10, @@ -20,18 +19,9 @@ 10, 10 ], - "antag_cap": [ - 1, - 1, - 1, - 1, - 2, - 2, - 2, - 2, - 3, - 3 - ], + "antag_cap": { + "denominator": 24 + }, "protected_roles": [ "Security Officer", "Warden", diff --git a/yogstation.dme b/yogstation.dme index e4deddab2805..2122b08b05bb 100644 --- a/yogstation.dme +++ b/yogstation.dme @@ -40,6 +40,7 @@ #include "code\__DEFINES\cult.dm" #include "code\__DEFINES\diseases.dm" #include "code\__DEFINES\DNA.dm" +#include "code\__DEFINES\dynamic.dm" #include "code\__DEFINES\economy.dm" #include "code\__DEFINES\events.dm" #include "code\__DEFINES\exosuit_fab.dm" @@ -542,6 +543,7 @@ #include "code\datums\martial\wrestling.dm" #include "code\datums\materials\_material.dm" #include "code\datums\materials\basemats.dm" +#include "code\datums\mocking\client.dm" #include "code\datums\mood_events\drink_events.dm" #include "code\datums\mood_events\drug_events.dm" #include "code\datums\mood_events\generic_negative_events.dm" @@ -638,10 +640,14 @@ #include "code\game\gamemodes\devil\objectives.dm" #include "code\game\gamemodes\devil\devil agent\devil_agent.dm" #include "code\game\gamemodes\dynamic\dynamic.dm" +#include "code\game\gamemodes\dynamic\dynamic_hijacking.dm" +#include "code\game\gamemodes\dynamic\dynamic_logging.dm" #include "code\game\gamemodes\dynamic\dynamic_rulesets.dm" #include "code\game\gamemodes\dynamic\dynamic_rulesets_latejoin.dm" #include "code\game\gamemodes\dynamic\dynamic_rulesets_midround.dm" #include "code\game\gamemodes\dynamic\dynamic_rulesets_roundstart.dm" +#include "code\game\gamemodes\dynamic\dynamic_simulations.dm" +#include "code\game\gamemodes\dynamic\ruleset_picking.dm" #include "code\game\gamemodes\eldritch_cult\eldritch_cult.dm" #include "code\game\gamemodes\extended\extended.dm" #include "code\game\gamemodes\hivemind\hivemind.dm" @@ -1258,6 +1264,7 @@ #include "code\modules\admin\verbs\diagnostics.dm" #include "code\modules\admin\verbs\fps.dm" #include "code\modules\admin\verbs\getlogs.dm" +#include "code\modules\admin\verbs\ghost_pool_protection.dm" #include "code\modules\admin\verbs\individual_logging.dm" #include "code\modules\admin\verbs\machine_upgrade.dm" #include "code\modules\admin\verbs\manipulate_organs.dm" @@ -2109,6 +2116,7 @@ #include "code\modules\mob\mob.dm" #include "code\modules\mob\mob_defines.dm" #include "code\modules\mob\mob_helpers.dm" +#include "code\modules\mob\mob_lists.dm" #include "code\modules\mob\mob_movement.dm" #include "code\modules\mob\mob_movespeed.dm" #include "code\modules\mob\mob_transformation_simple.dm" From 780ab22b33000a8e04e20c895c6805d74f3e1ef1 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Sun, 14 Mar 2021 23:48:09 -0500 Subject: [PATCH 03/53] Why is this here? --- code/__DEFINES/New Text Document.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 code/__DEFINES/New Text Document.txt diff --git a/code/__DEFINES/New Text Document.txt b/code/__DEFINES/New Text Document.txt deleted file mode 100644 index e69de29bb2d1..000000000000 From 0761ac8a0cc8e23b8579a91ed7100a16253151ee Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Mon, 15 Mar 2021 00:05:36 -0500 Subject: [PATCH 04/53] Update dynamic_rulesets_roundstart.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index 05caab522338..50eb58336222 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -675,7 +675,7 @@ ////////////////////////////////////////////// // // -// // +// MONKEY // // // ////////////////////////////////////////////// @@ -688,6 +688,7 @@ weight = 1 cost = 70 requirements = list(100,100,95,90,85,80,80,80,80,70) + flags = LONE_RULESET var/players_per_carrier = 25 var/monkeys_to_win = 1 var/escaped_monkeys = 0 @@ -747,6 +748,7 @@ weight = 1 cost = 75 requirements = list(100,100,100,100,100,100,100,100,99,98) + flags = LONE_RULESET var/meteordelay = 2000 var/nometeors = 0 var/rampupdelta = 5 @@ -977,4 +979,4 @@ M.mind.special_role = ROLE_DARKSPAWN M.mind.restricted_roles = restricted_roles log_game("[key_name(M)] has been selected as a Darkspawn") - return TRUE */ \ No newline at end of file + return TRUE */ From b1dbe8e10fc638714994029b362abefe78f4380e Mon Sep 17 00:00:00 2001 From: Xoxeyos Date: Mon, 15 Mar 2021 22:40:43 -0500 Subject: [PATCH 05/53] Merges Dynamic 2021 final fixes --- code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index 50eb58336222..fdaeb8c00261 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -675,7 +675,7 @@ ////////////////////////////////////////////// // // -// MONKEY // +// MONKEY // // // ////////////////////////////////////////////// @@ -750,6 +750,7 @@ requirements = list(100,100,100,100,100,100,100,100,99,98) flags = LONE_RULESET var/meteordelay = 2000 + flags = LONE_RULESET var/nometeors = 0 var/rampupdelta = 5 From 35064ac6a96450896a27e1f7191a3c98b3772868 Mon Sep 17 00:00:00 2001 From: Xoxeyos Date: Mon, 15 Mar 2021 22:53:46 -0500 Subject: [PATCH 06/53] Will this work? --- code/modules/events/_event.dm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/modules/events/_event.dm b/code/modules/events/_event.dm index a49f52d9fe38..ba8191b90909 100644 --- a/code/modules/events/_event.dm +++ b/code/modules/events/_event.dm @@ -62,18 +62,18 @@ return TRUE var/datum/game_mode/dynamic/dynamic = SSticker.mode - if (istype(dynamic) && dynamic_should_hijack && dynamic.random_event_hijacked != HIJACKED_NOTHING) + if(istype(dynamic) && dynamic_should_hijack && dynamic.random_event_hijacked != HIJACKED_NOTHING) return FALSE /datum/round_event_control/proc/preRunEvent() if(!ispath(typepath, /datum/round_event)) return EVENT_CANT_RUN - if (SEND_GLOBAL_SIGNAL(COMSIG_GLOB_PRE_RANDOM_EVENT, src) & CANCEL_PRE_RANDOM_EVENT) + if(SEND_GLOBAL_SIGNAL(COMSIG_GLOB_PRE_RANDOM_EVENT, src) & CANCEL_PRE_RANDOM_EVENT) return EVENT_INTERRUPTED triggering = TRUE - if (alert_observers) + if(alert_observers) message_admins("Random Event triggering in [RANDOM_EVENT_ADMIN_INTERVENTION_TIME] seconds: [name] (CANCEL)") sleep(RANDOM_EVENT_ADMIN_INTERVENTION_TIME SECONDS) //Yogs end From f560d3f740e3c721cb41bcd679c46ef9376025a2 Mon Sep 17 00:00:00 2001 From: Xoxeyos Date: Mon, 15 Mar 2021 23:35:13 -0500 Subject: [PATCH 07/53] Maybe this might work. --- code/modules/events/_event.dm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/code/modules/events/_event.dm b/code/modules/events/_event.dm index ba8191b90909..0ef08734f960 100644 --- a/code/modules/events/_event.dm +++ b/code/modules/events/_event.dm @@ -59,12 +59,13 @@ return FALSE if(ispath(typepath, /datum/round_event/ghost_role) && GHOSTROLE_MIDROUND_EVENT) return FALSE - return TRUE var/datum/game_mode/dynamic/dynamic = SSticker.mode if(istype(dynamic) && dynamic_should_hijack && dynamic.random_event_hijacked != HIJACKED_NOTHING) return FALSE + return TRUE + /datum/round_event_control/proc/preRunEvent() if(!ispath(typepath, /datum/round_event)) return EVENT_CANT_RUN @@ -109,7 +110,7 @@ testing("[time2text(world.time, "hh:mm:ss")] [E.type]") if(random) log_game("Random Event triggering: [name] ([typepath])") - if (alert_observers) + if(alert_observers) deadchat_broadcast(" has just been[random ? " randomly" : ""] triggered!", "[name]") //STOP ASSUMING IT'S BADMINS! return E @@ -150,7 +151,7 @@ //Only called once. /datum/round_event/proc/announce_to_ghosts(atom/atom_of_interest) if(control.alert_observers) - if (atom_of_interest) + if(atom_of_interest) //Yogs start -- Makes this a bit more specific var/typeofthing = "object" if(iscarbon(atom_of_interest)) From 6213487fceb7551048d258bed7b539555a524da6 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Tue, 16 Mar 2021 15:08:25 -0500 Subject: [PATCH 08/53] This was suggested as a change. --- code/modules/events/_event.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/events/_event.dm b/code/modules/events/_event.dm index 0ef08734f960..095ea720d009 100644 --- a/code/modules/events/_event.dm +++ b/code/modules/events/_event.dm @@ -64,7 +64,7 @@ if(istype(dynamic) && dynamic_should_hijack && dynamic.random_event_hijacked != HIJACKED_NOTHING) return FALSE - return TRUE + . = TRUE /datum/round_event_control/proc/preRunEvent() if(!ispath(typepath, /datum/round_event)) @@ -237,4 +237,4 @@ SSevents.running += src return ..() -#undef RANDOM_EVENT_ADMIN_INTERVENTION_TIME \ No newline at end of file +#undef RANDOM_EVENT_ADMIN_INTERVENTION_TIME From 44e8be2a7907f18860bf86a0b7d38832af68079b Mon Sep 17 00:00:00 2001 From: Redmoogle Date: Thu, 18 Mar 2021 19:19:04 -0400 Subject: [PATCH 09/53] Fixes bad bugs --- code/modules/mob/mob.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 3cb22ac69bd0..fc80845695de 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -63,9 +63,9 @@ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_MOB_CREATED, src) add_to_mob_list() if(stat == DEAD) - add_to_mob_list() + remove_from_alive_mob_list() else - add_to_mob_list() + add_to_alive_mob_list() set_focus(src) prepare_huds() for(var/v in GLOB.active_alternate_appearances) From 1f9ae7909c477d9bac55901dd0d406e34bd493ee Mon Sep 17 00:00:00 2001 From: Xoxeyos Date: Sun, 21 Mar 2021 16:04:48 -0500 Subject: [PATCH 10/53] Easy enough. --- code/modules/admin/verbs/ghost_pool_protection.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/admin/verbs/ghost_pool_protection.dm b/code/modules/admin/verbs/ghost_pool_protection.dm index b06d34b5e090..8142a6c5c9a7 100644 --- a/code/modules/admin/verbs/ghost_pool_protection.dm +++ b/code/modules/admin/verbs/ghost_pool_protection.dm @@ -3,7 +3,7 @@ /client/proc/ghost_pool_protection() //Creates a verb for admins to open up the ui set name = "Ghost Pool Protection" set desc = "Choose which ways people can get into the round, or just clear it out completely for admin events." - set category = "Adminbus" + set category = "Admin" var/datum/ghost_pool_menu/tgui = new(usr)//create the datum tgui.ui_interact(usr)//datum has a tgui component, here we open the window From 107b8d2d02237f7012111ac90f26dd2630c89bc1 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Sun, 21 Mar 2021 16:09:38 -0500 Subject: [PATCH 11/53] Update ghost_pool_protection.dm --- code/modules/admin/verbs/ghost_pool_protection.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/admin/verbs/ghost_pool_protection.dm b/code/modules/admin/verbs/ghost_pool_protection.dm index b06d34b5e090..30bd9159c031 100644 --- a/code/modules/admin/verbs/ghost_pool_protection.dm +++ b/code/modules/admin/verbs/ghost_pool_protection.dm @@ -3,7 +3,7 @@ /client/proc/ghost_pool_protection() //Creates a verb for admins to open up the ui set name = "Ghost Pool Protection" set desc = "Choose which ways people can get into the round, or just clear it out completely for admin events." - set category = "Adminbus" + set category = "Admin" var/datum/ghost_pool_menu/tgui = new(usr)//create the datum tgui.ui_interact(usr)//datum has a tgui component, here we open the window @@ -83,4 +83,4 @@ else msg = "modified" message_admins("[key_name_admin(holder)] has [msg] this round's allowed ghost roles.") - GLOB.ghost_role_flags = new_role_flags \ No newline at end of file + GLOB.ghost_role_flags = new_role_flags From e821d6855ee0cc365067be39a7e62474444e6f7b Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Sun, 21 Mar 2021 17:09:25 -0500 Subject: [PATCH 12/53] Update ghost_pool_protection.dm From 3ae426bb74a27b88ef2dd555c7e0093e9eddb1cd Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Mon, 22 Mar 2021 00:06:56 -0500 Subject: [PATCH 13/53] Update dynamic.dm --- code/game/gamemodes/dynamic/dynamic.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index 87c10d165d0c..b6bd12476521 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -468,7 +468,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if (rule.ready(roundstart_pop_ready, TRUE)) var/cost = rule.cost var/scaled_times = 0 - if (!(rule.flags & LONE_RULESET)) + if (rule.scaling_cost) scaled_times = round(max(round_start_budget - cost, 0) / rule.scaling_cost) cost += rule.scaling_cost * scaled_times @@ -813,4 +813,4 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) #undef FAKE_REPORT_CHANCE #undef REPORT_NEG_DIVERGENCE -#undef REPORT_POS_DIVERGENCE \ No newline at end of file +#undef REPORT_POS_DIVERGENCE From b4352e16dab94e04d819644e7b661b7e4455c79e Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Mon, 22 Mar 2021 00:13:23 -0500 Subject: [PATCH 14/53] Update dynamic.dm --- code/game/gamemodes/dynamic/dynamic.dm | 1 - 1 file changed, 1 deletion(-) diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index b6bd12476521..620d0a919cc1 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -460,7 +460,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) configure_ruleset(rule) message_admins("Drafting players for forced ruleset [rule.name].") log_game("DYNAMIC: Drafting players for forced ruleset [rule.name].") - configure_ruleset(rule) 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() From 6f36a13d6b7a71333c1ed277155b18c0055175b7 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Thu, 25 Mar 2021 02:50:26 -0500 Subject: [PATCH 15/53] Update dynamic_rulesets_latejoin.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index 4545720eab3c..4830d844e0d4 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -63,7 +63,7 @@ restricted_roles = list("AI","Cyborg") required_candidates = 1 weight = 1 - cost = 5 + cost = 10 requirements = list(40,30,20,10,10,10,10,10,10,10) repeatable = TRUE @@ -217,6 +217,6 @@ restricted_roles = list("AI","Cyborg") required_candidates = 1 weight = 4 - cost = 10 + cost = 15 requirements = list(40,30,20,10,10,10,10,10,10,10) repeatable = TRUE */ From 5ea6965cbac53a4a2dd57597a92a557fa16f8d9c Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Sat, 27 Mar 2021 21:06:28 -0500 Subject: [PATCH 16/53] Update dynamic_rulesets_latejoin.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index 4830d844e0d4..d3d591f27ddb 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -63,7 +63,7 @@ restricted_roles = list("AI","Cyborg") required_candidates = 1 weight = 1 - cost = 10 + cost = 15 requirements = list(40,30,20,10,10,10,10,10,10,10) repeatable = TRUE From fb0efe43e09e0e40dfc3ad7e7e1d490ff66267f7 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Sun, 18 Apr 2021 00:42:43 -0500 Subject: [PATCH 17/53] Update dynamic.dm --- code/game/gamemodes/dynamic/dynamic.dm | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index 88e372e97528..69a2716e000f 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -748,19 +748,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) var/latejoin_injection_cooldown_middle = 0.5*(latejoin_delay_max + latejoin_delay_min) latejoin_injection_cooldown = round(clamp(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), latejoin_delay_min, latejoin_delay_max)) + world.time -/// Apply configurations to rule. -/datum/game_mode/dynamic/proc/configure_ruleset(datum/dynamic_ruleset/ruleset) - var/rule_conf = LAZYACCESSASSOC(configuration, ruleset.ruletype, ruleset.name) - for(var/variable in rule_conf) - if(!(variable in ruleset.vars)) - stack_trace("Invalid dynamic configuration variable [variable] in [ruleset.ruletype] [ruleset.name].") - continue - ruleset.vars[variable] = rule_conf[variable] - if(CONFIG_GET(flag/protect_roles_from_antagonist)) - ruleset.restricted_roles |= ruleset.protected_roles - if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - ruleset.restricted_roles |= "Assistant" - /// Refund threat, but no more than threat_level. /datum/game_mode/dynamic/proc/refund_threat(regain) mid_round_budget = min(threat_level, mid_round_budget + regain) From 0e82c53092d81ea2f1efacecd33897e4d0443b76 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Thu, 6 May 2021 15:54:22 -0500 Subject: [PATCH 18/53] Update dynamic_rulesets_roundstart.dm --- .../dynamic/dynamic_rulesets_roundstart.dm | 71 +++++-------------- 1 file changed, 19 insertions(+), 52 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index 661142bc10d9..d88e73ca6e10 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -517,6 +517,7 @@ weight = 1 cost = 40 requirements = list(100,90,80,70,60,50,30,30,30,30) + antag_cap = list(4,4,4,5,5,6,6,7,7,8) //this isn't used but having it probably stops a runtime flags = HIGH_IMPACT_RULESET minimum_players = 38 var/ark_time @@ -679,7 +680,7 @@ // // ////////////////////////////////////////////// -/datum/dynamic_ruleset/roundstart/monkey +/*/datum/dynamic_ruleset/roundstart/monkey name = "Monkey" antag_flag = ROLE_MONKEY antag_datum = /datum/antagonist/monkey/leader @@ -733,7 +734,7 @@ if(check_monkey_victory()) SSticker.mode_result = "win - monkey win" else - SSticker.mode_result = "loss - staff stopped the monkeys" + SSticker.mode_result = "loss - staff stopped the monkeys"*/ ////////////////////////////////////////////// // // @@ -834,6 +835,7 @@ requirements = list(80,70,60,50,50,45,30,30,25,20) antag_cap = list("denominator" = 24) minimum_players = 30 + antag_cap = list(3,3,3,3,3,3,3,3,3,4) var/autovamp_cooldown = (15 MINUTES) COOLDOWN_DECLARE(autovamp_cooldown_check) @@ -871,33 +873,22 @@ weight = 1 cost = 100 requirements = list(100,100,100,100,90,90,85,85,85,80) + antag_cap = list(5,5,5,5,5,5,5,5,5,5) roundstart_wizards = list() - var/bullshit_mode = 0 - -/datum/dynamic_ruleset/roundstart/wizard/acceptable(population=0, threat=0) - if(GLOB.wizardstart.len == 0) - log_admin("Cannot accept Wizard ruleset. Couldn't find any wizard spawn points.") - message_admins("Cannot accept Wizard ruleset. Couldn't find any wizard spawn points.") - return FALSE - return ..() /datum/dynamic_ruleset/roundstart/wizard/ragin/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 - M.mind.assigned_role = ROLE_RAGINMAGES - M.mind.special_role = ROLE_RAGINMAGES - - return TRUE + for(var/i in antag_cap[indice_pop]) + var/mob/M = pick_n_take(candidates) + if (M) + assigned += M.mind + M.mind.assigned_role = ROLE_RAGINMAGES + M.mind.special_role = ROLE_RAGINMAGES + else + break -/datum/dynamic_ruleset/roundstart/wizard/ragin/execute() - for(var/datum/mind/M in assigned) - M.current.forceMove(pick(GLOB.wizardstart)) - M.add_antag_datum(new antag_datum()) return TRUE ////////////////////////////////////////////// @@ -912,44 +903,20 @@ antag_datum = /datum/antagonist/wizard/ flags = LONE_RULESET minimum_required_age = 14 - restricted_roles = list("Head of Security", "Captain") // Just to be sure that a wizard getting picked won't ever imply a Captain or HoS not getting drafted + restricted_roles = list() required_candidates = 4 weight = 1 cost = 101 minimum_players = 40 requirements = list(100,100,100,100,100,100,100,100,100,100) - var/mage_cap = 999 - bullshit_mode = 1 - -/datum/dynamic_ruleset/roundstart/wizard/ragin/bullshit/acceptable(population=0, threat=0) - if(GLOB.wizardstart.len == 0) - log_admin("Cannot accept Wizard ruleset. Couldn't find any wizard spawn points.") - message_admins("Cannot accept Wizard ruleset. Couldn't find any wizard spawn points.") - return FALSE - return ..() - + antag_cap = list(999,999,999,999,999) + /datum/dynamic_ruleset/roundstart/wizard/ragin/bullshit/pre_execute() - var/indice_pop = min(45,round(mode.roundstart_pop_ready/2)+1) - var/mages = mage_cap[indice_pop] - for(var/mages_number = 1 to mages) - if(GLOB.wizardstart.len == 0) - return FALSE - - mode.antags_rolled += 1 - var/mob/M = pick_n_take(candidates) - if (M) - assigned += M.mind - M.mind.assigned_role = ROLE_RAGINMAGES - M.mind.special_role = ROLE_RAGINMAGES + . = ..() + if(.) log_admin("Shit is about to get wild. -Bullshit Wizards") - return TRUE - -/datum/dynamic_ruleset/roundstart/wizard/ragin/bullshit/execute() - for(var/datum/mind/M in assigned) - M.current.forceMove(pick(GLOB.wizardstart)) - M.add_antag_datum(new antag_datum()) - return TRUE + return TRUE ////////////////////////////////////////////// // // From 2e2b44362f3582e70ad331104014b9d258fea66f Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Thu, 6 May 2021 19:15:16 -0500 Subject: [PATCH 19/53] I can put in sound_enviroment when I figure out what it is --- code/modules/mob/mob_defines.dm | 3 --- 1 file changed, 3 deletions(-) diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 7935580db0e4..6382b6829d20 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -216,6 +216,3 @@ /// A mock client, provided by tests and friends var/datum/client_interface/mock_client - - ///Override for sound_environments. If this is set the user will always hear a specific type of reverb (Instead of the area defined reverb) - var/sound_environment_override = SOUND_ENVIRONMENT_NONE From 141b61ba18f82a144306c774ddc913aa7a78078a Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Thu, 6 May 2021 19:20:38 -0500 Subject: [PATCH 20/53] Update mob_defines.dm --- code/modules/mob/mob_defines.dm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 6382b6829d20..d9cf3648d891 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -216,3 +216,6 @@ /// A mock client, provided by tests and friends var/datum/client_interface/mock_client + + ///Override for sound_environments. If this is set the user will always hear a specific type of reverb (Instead of the area defined reverb) + var/sound_environment_override = SOUND_ENVIRONMENT_NONE From cb85bd8dbe7d500b4df4fa1398c79c691dea6970 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Thu, 6 May 2021 19:24:27 -0500 Subject: [PATCH 21/53] Update mob_defines.dm From 9271265bbda5624f7374577853aa8eeaf1ee9515 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Thu, 6 May 2021 19:40:01 -0500 Subject: [PATCH 22/53] Update mob_defines.dm --- code/modules/mob/mob_defines.dm | 3 --- 1 file changed, 3 deletions(-) diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index d9cf3648d891..6382b6829d20 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -216,6 +216,3 @@ /// A mock client, provided by tests and friends var/datum/client_interface/mock_client - - ///Override for sound_environments. If this is set the user will always hear a specific type of reverb (Instead of the area defined reverb) - var/sound_environment_override = SOUND_ENVIRONMENT_NONE From e7f8bef4dc8f771046c5ed4ec947dbf96cca044b Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Tue, 11 May 2021 22:44:53 -0500 Subject: [PATCH 23/53] Security and Command personnel rolled infiltrator again --- code/game/gamemodes/dynamic/dynamic.dm | 32 ++++++++++++-------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index 69a2716e000f..950f7ad15dd2 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -747,6 +747,21 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if (drafted_rules.len > 0 && pick_latejoin_rule(drafted_rules)) var/latejoin_injection_cooldown_middle = 0.5*(latejoin_delay_max + latejoin_delay_min) latejoin_injection_cooldown = round(clamp(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), latejoin_delay_min, latejoin_delay_max)) + world.time + +/// Apply configurations to rule. +/datum/game_mode/dynamic/proc/configure_ruleset(datum/dynamic_ruleset/ruleset) + var/rule_conf = LAZYACCESSASSOC(configuration, ruleset.ruletype, ruleset.name) + for(var/variable in rule_conf) + if(!(variable in ruleset.vars)) + stack_trace("Invalid dynamic configuration variable [variable] in [ruleset.ruletype] [ruleset.name].") + continue + ruleset.vars[variable] = rule_conf[variable] + if(CONFIG_GET(flag/protect_roles_from_antagonist)) + ruleset.restricted_roles |= ruleset.protected_roles + if(CONFIG_GET(flag/protect_assistant_from_antagonist)) + ruleset.restricted_roles |= "Assistant" + if(CONFIG_GET(flag/protect_heads_from_antagonist)) + ruleset.restricted_roles |= GLOB.command_positions /// Refund threat, but no more than threat_level. /datum/game_mode/dynamic/proc/refund_threat(regain) @@ -795,23 +810,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) /datum/game_mode/dynamic/proc/dynamic_log(text) message_admins("DYNAMIC: [text]") log_game("DYNAMIC: [text]") - -/datum/game_mode/dynamic/proc/configure_ruleset(datum/dynamic_ruleset/ruleset) - if(configuration) - if(!configuration[ruleset.ruletype]) - return - if(!configuration[ruleset.ruletype][ruleset.name]) - return - var/rule_conf = configuration[ruleset.ruletype][ruleset.name] - for(var/variable in rule_conf) - if(isnull(ruleset.vars[variable])) - stack_trace("Invalid dynamic configuration variable [variable] in [ruleset.ruletype] [ruleset.name].") - continue - ruleset.vars[variable] = rule_conf[variable] - if(CONFIG_GET(flag/protect_roles_from_antagonist)) - ruleset.restricted_roles |= ruleset.protected_roles - if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - ruleset.restricted_roles |= "Assistant" #undef FAKE_REPORT_CHANCE #undef REPORT_NEG_DIVERGENCE From dda0376fb5d4c21e8b0e8fe8ad2eb8de4ff9640d Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Tue, 11 May 2021 22:48:01 -0500 Subject: [PATCH 24/53] Update dynamic.dm --- code/game/gamemodes/dynamic/dynamic.dm | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index 950f7ad15dd2..9d6211401ea9 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -759,9 +759,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if(CONFIG_GET(flag/protect_roles_from_antagonist)) ruleset.restricted_roles |= ruleset.protected_roles if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - ruleset.restricted_roles |= "Assistant" - if(CONFIG_GET(flag/protect_heads_from_antagonist)) - ruleset.restricted_roles |= GLOB.command_positions + ruleset.restricted_roles |= "Assistant" /// Refund threat, but no more than threat_level. /datum/game_mode/dynamic/proc/refund_threat(regain) From 24e0a63ed1d2c9aa28da86f2054fe8c5bbfe6c64 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Tue, 18 May 2021 09:23:12 -0500 Subject: [PATCH 25/53] Ports over https://github.com/tgstation/tgstation/pull/58644 - No more mass infiltrations? This is an attempt to keep infiltrators from spawning in nonstop. --- code/game/gamemodes/dynamic/ruleset_picking.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/game/gamemodes/dynamic/ruleset_picking.dm b/code/game/gamemodes/dynamic/ruleset_picking.dm index 29776dda1bd7..9221e4a3c152 100644 --- a/code/game/gamemodes/dynamic/ruleset_picking.dm +++ b/code/game/gamemodes/dynamic/ruleset_picking.dm @@ -63,6 +63,7 @@ if (!rule.repeatable) latejoin_rules = remove_from_list(latejoin_rules, rule.type) addtimer(CALLBACK(src, .proc/execute_midround_latejoin_rule, rule), rule.delay) + return TRUE /// 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) @@ -114,4 +115,4 @@ dynamic_log("[key_name(user)] asked for a different midround injection.") pick_midround_rule(current_midround_rulesets) -#undef ADMIN_CANCEL_MIDROUND_TIME \ No newline at end of file +#undef ADMIN_CANCEL_MIDROUND_TIME From d272a692745417b92a6e5b9105241ca810324dac Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Tue, 18 May 2021 09:28:21 -0500 Subject: [PATCH 26/53] Update ruleset_picking.dm From 8eabc26633748ef10827fcf7283e0347648c175f Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Tue, 18 May 2021 12:46:58 -0500 Subject: [PATCH 27/53] Update dynamic_rulesets_latejoin.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index aaf714b5fab0..55610c3d0a86 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -195,7 +195,7 @@ antag_datum = /datum/antagonist/vampire protected_roles = list("Head of Security", "Captain", "Security Officer", "Chaplain", "Detective", "Warden", "Head of Personnel") restricted_roles = list("AI", "Cyborg") - required_candidates = 3 + required_candidates = 1 weight = 1 cost = 15 requirements = list(80,70,60,50,50,45,30,30,20,25) @@ -213,7 +213,7 @@ name = "Heretic Smuggler" antag_datum = /datum/antagonist/heretic antag_flag = ROLE_HERETIC - protected_roles = list("Security Officer", "Warden", "Head of Personnel", "Detective", "Head of Security", "Captain","Prisoner") + protected_roles = list("Security Officer", "Warden", "Head of Personnel", "Detective", "Head of Security", "Captain") restricted_roles = list("AI","Cyborg") required_candidates = 1 weight = 4 From 45abf5b7aff181fb3d9698e7d2674c76c078d64d Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Tue, 18 May 2021 12:49:27 -0500 Subject: [PATCH 28/53] Update dynamic_rulesets_roundstart.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index d88e73ca6e10..c85ee565aa23 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -134,7 +134,7 @@ name = "Heretics" antag_flag = ROLE_HERETIC antag_datum = /datum/antagonist/heretic - protected_roles = list("Prisoner","Security Officer", "Warden", "Detective", "Head of Security", "Captain") + protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain") restricted_roles = list("AI", "Cyborg") required_candidates = 1 weight = 3 From 5a6e75d51fd0393bef7e5fedd5c5ee93df63b8d8 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Tue, 18 May 2021 12:55:39 -0500 Subject: [PATCH 29/53] Update dynamic_rulesets_roundstart.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index c85ee565aa23..93354dd72cd2 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -9,7 +9,7 @@ name = "Traitors" persistent = TRUE antag_flag = ROLE_TRAITOR - antag_datum = /datum/antagonist/traitor/ + antag_datum = /datum/antagonist/traitor minimum_required_age = 0 protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain") restricted_roles = list("Cyborg") @@ -153,7 +153,6 @@ assigned += picked_candidate.mind picked_candidate.mind.restricted_roles = restricted_roles picked_candidate.mind.special_role = ROLE_HERETIC - GLOB.pre_setup_antags += picked_candidate.mind return TRUE /datum/dynamic_ruleset/roundstart/heretics/execute() @@ -162,7 +161,6 @@ var/datum/mind/cultie = c var/datum/antagonist/heretic/new_antag = new antag_datum() cultie.add_antag_datum(new_antag) - GLOB.pre_setup_antags -= cultie return TRUE */ From 46bbe9e88e41e25af46914543331076d89600625 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Mon, 24 May 2021 19:26:09 -0500 Subject: [PATCH 30/53] weight configurement --- .../dynamic/dynamic_rulesets_roundstart.dm | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index 93354dd72cd2..82f732dbeea3 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -14,7 +14,7 @@ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain") restricted_roles = list("Cyborg") required_candidates = 1 - weight = 1 + weight = 5 cost = 8 // Avoid raising traitor threat above 10, as it is the default low cost ruleset. scaling_cost = 9 requirements = list(10,10,10,10,10,10,10,10,10,10) @@ -53,7 +53,7 @@ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain") restricted_roles = list("Cyborg", "AI") required_candidates = 2 - weight = 1 + weight = 4 cost = 10 scaling_cost = 10 requirements = list(40,30,30,20,20,15,15,15,10,10) @@ -102,7 +102,7 @@ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain") restricted_roles = list("AI", "Cyborg") required_candidates = 1 - weight = 1 + weight = 3 cost = 16 scaling_cost = 10 requirements = list(75,70,60,50,40,20,20,10,10,10) @@ -180,7 +180,7 @@ minimum_required_age = 14 restricted_roles = list("Head of Security", "Captain") // Just to be sure that a wizard getting picked won't ever imply a Captain or HoS not getting drafted required_candidates = 1 - weight = 1 + weight = 2 cost = 20 requirements = list(90,90,70,40,30,20,10,10,10,10) var/list/roundstart_wizards = list() @@ -224,7 +224,7 @@ minimum_required_age = 14 restricted_roles = list("AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Chaplain", "Head of Personnel") required_candidates = 2 - weight = 1 + weight = 3 cost = 20 requirements = list(100,80,70,60,40,30,30,20,10,10) flags = HIGH_IMPACT_RULESET @@ -280,7 +280,7 @@ minimum_required_age = 14 restricted_roles = list("Head of Security", "Captain") // Just to be sure that a nukie getting picked won't ever imply a Captain or HoS not getting drafted required_candidates = 5 - weight = 1 + weight = 3 cost = 20 requirements = list(90,90,90,80,60,40,30,20,10,10) flags = HIGH_IMPACT_RULESET @@ -783,7 +783,7 @@ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain") restricted_roles = list("Cyborg", "AI") required_candidates = 3 - weight = 1 + weight = 3 cost = 30 requirements = list(90,80,80,70,60,40,30,30,20,10) flags = HIGH_IMPACT_RULESET @@ -827,7 +827,7 @@ protected_roles = list("Head of Security", "Captain", "Security Officer", "Chaplain", "Detective", "Warden", "Head of Personnel") restricted_roles = list("Cyborg", "AI") required_candidates = 3 - weight = 1 + weight = 3 cost = 8 scaling_cost = 9 requirements = list(80,70,60,50,50,45,30,30,25,20) @@ -930,7 +930,7 @@ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel") restricted_roles = list("AI", "Cyborg") required_candidates = 3 - weight = 1 + weight = 3 cost = 30 scaling_cost = 20 antag_cap = 3 From 246380866bf34f83b5d9737b7c40415da46a6905 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Mon, 24 May 2021 19:27:24 -0500 Subject: [PATCH 31/53] weight configurement --- code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index 55610c3d0a86..6b04ce9fade7 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -62,7 +62,7 @@ protected_roles = list("Security Officer", "Warden", "Head of Personnel", "Detective", "Head of Security", "Captain") restricted_roles = list("AI","Cyborg") required_candidates = 1 - weight = 1 + weight = 7 cost = 15 requirements = list(40,30,20,10,10,10,10,10,10,10) repeatable = TRUE @@ -196,7 +196,7 @@ protected_roles = list("Head of Security", "Captain", "Security Officer", "Chaplain", "Detective", "Warden", "Head of Personnel") restricted_roles = list("AI", "Cyborg") required_candidates = 1 - weight = 1 + weight = 4 cost = 15 requirements = list(80,70,60,50,50,45,30,30,20,25) minimum_players = 30 @@ -216,7 +216,7 @@ protected_roles = list("Security Officer", "Warden", "Head of Personnel", "Detective", "Head of Security", "Captain") restricted_roles = list("AI","Cyborg") required_candidates = 1 - weight = 4 + weight = 3 cost = 15 requirements = list(40,30,20,10,10,10,10,10,10,10) repeatable = TRUE */ From 0654b570a9b2d589f6489d5b8c88f9ffa1547919 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Mon, 24 May 2021 19:57:18 -0500 Subject: [PATCH 32/53] Update dynamic_rulesets_roundstart.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index 82f732dbeea3..a0c51e87f4a6 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -356,7 +356,7 @@ // // ////////////////////////////////////////////// -/datum/dynamic_ruleset/roundstart/revs +/*/datum/dynamic_ruleset/roundstart/revs name = "Revolution" persistent = TRUE antag_flag = ROLE_REV_HEAD @@ -473,7 +473,7 @@ SSticker.mode_result = "loss - rev heads killed" SSticker.news_report = REVS_LOSE -// Admin only rulesets. The threat requirement is 101 so it is not possible to roll them. +// Admin only rulesets. The threat requirement is 101 so it is not possible to roll them. */ ////////////////////////////////////////////// // // From 22a6c93d467dedc9343edc41822d3b537c35bc6e Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Mon, 24 May 2021 19:58:22 -0500 Subject: [PATCH 33/53] Update dynamic_rulesets_latejoin.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index 6b04ce9fade7..1dc2a5bfbb1d 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -73,7 +73,7 @@ // // ////////////////////////////////////////////// -/datum/dynamic_ruleset/latejoin/provocateur +/*/datum/dynamic_ruleset/latejoin/provocateur name = "Provocateur" persistent = TRUE antag_datum = /datum/antagonist/rev/head @@ -181,7 +181,7 @@ SSticker.news_report = REVS_WIN else if(finished == STATION_VICTORY) SSticker.mode_result = "loss - rev heads killed" - SSticker.news_report = REVS_LOSE + SSticker.news_report = REVS_LOSE */ ////////////////////////////////////////////// // // From fc3c59d88d2200d07720315b3c6b8130470da8e0 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Mon, 24 May 2021 21:15:48 -0500 Subject: [PATCH 34/53] Update dynamic_rulesets_roundstart.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index a0c51e87f4a6..9befae427952 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -31,6 +31,7 @@ assigned += M.mind M.mind.special_role = ROLE_TRAITOR M.mind.restricted_roles = restricted_roles + GLOB.pre_setup_antags += M.mind log_game("[key_name(M)] has been selected as a Traitor") return TRUE @@ -356,7 +357,7 @@ // // ////////////////////////////////////////////// -/*/datum/dynamic_ruleset/roundstart/revs +/datum/dynamic_ruleset/roundstart/revs name = "Revolution" persistent = TRUE antag_flag = ROLE_REV_HEAD @@ -473,7 +474,7 @@ SSticker.mode_result = "loss - rev heads killed" SSticker.news_report = REVS_LOSE -// Admin only rulesets. The threat requirement is 101 so it is not possible to roll them. */ +// Admin only rulesets. The threat requirement is 101 so it is not possible to roll them. ////////////////////////////////////////////// // // From 1e1526007911b583b8429b8d6cbd5902b66861e7 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Mon, 24 May 2021 21:20:39 -0500 Subject: [PATCH 35/53] Update dynamic_rulesets.dm --- code/game/gamemodes/dynamic/dynamic_rulesets.dm | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm index 1cda8c95a2ac..48523457569e 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm @@ -79,10 +79,13 @@ /datum/dynamic_ruleset/New() ..() + if (istype(SSticker.mode, /datum/game_mode/dynamic)) mode = SSticker.mode else if (!SSticker.is_mode("dynamic")) // This is here to make roundstart forced ruleset function. qdel(src) + + /datum/dynamic_ruleset/roundstart // One or more of those drafted at roundstart ruletype = "Roundstart" @@ -97,6 +100,7 @@ return FALSE if(maximum_players > 0 && population > maximum_players) return FALSE + pop_per_requirement = pop_per_requirement > 0 ? pop_per_requirement : mode.pop_per_requirement indice_pop = min(requirements.len,round(population/pop_per_requirement)+1) return (threat_level >= requirements[indice_pop]) @@ -179,11 +183,6 @@ /// Only called if ruleset is flagged as HIGH_IMPACT_RULESET /datum/dynamic_ruleset/proc/round_result() -/// Checks if round is finished, return true to end the round. -/// Only called if ruleset is flagged as HIGH_IMPACT_RULESET -/datum/dynamic_ruleset/proc/check_finished() - return FALSE - ////////////////////////////////////////////// // // // ROUNDSTART RULESETS // From c787e40fbb82440755dd94950520bff4e42f4980 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Mon, 24 May 2021 21:24:34 -0500 Subject: [PATCH 36/53] Update dynamic.dm --- code/game/gamemodes/dynamic/dynamic.dm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index 9d6211401ea9..93152ee61004 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -638,6 +638,8 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if(rule.ruletype == "Latejoin" && !(GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT)) continue // If admins have disabled dynamic from picking from the ghost pool + if(rule.ruletype == "Latejoin" && !(GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT)) + continue rule.trim_candidates() if (rule.ready()) drafted_rules[rule] = rule.get_weight() From c570372862c5862cd13d17345c5c13ba65c979e0 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Mon, 24 May 2021 21:29:53 -0500 Subject: [PATCH 37/53] Update dynamic_rulesets_latejoin.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index 1dc2a5bfbb1d..cb5bf6fefb20 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -73,7 +73,7 @@ // // ////////////////////////////////////////////// -/*/datum/dynamic_ruleset/latejoin/provocateur +/datum/dynamic_ruleset/latejoin/provocateur name = "Provocateur" persistent = TRUE antag_datum = /datum/antagonist/rev/head @@ -84,7 +84,7 @@ required_enemies = list(2,2,1,1,1,1,1,0,0,0) required_candidates = 1 weight = 1 - cost = 20 + cost = 100000 delay = 1 MINUTES requirements = list(80,75,60,60,55,50,40,30,20,20) blocking_rules = list(/datum/dynamic_ruleset/roundstart/revs) @@ -181,7 +181,7 @@ SSticker.news_report = REVS_WIN else if(finished == STATION_VICTORY) SSticker.mode_result = "loss - rev heads killed" - SSticker.news_report = REVS_LOSE */ + SSticker.news_report = REVS_LOSE ////////////////////////////////////////////// // // From faf815fa3b39c33537d71a105a3cf5873df309bd Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Mon, 24 May 2021 21:34:28 -0500 Subject: [PATCH 38/53] Update dynamic_rulesets_roundstart.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index 9befae427952..3ff23e7b81ae 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -367,7 +367,7 @@ required_candidates = 3 weight = 1 delay = 7 MINUTES - cost = 20 + cost = 101 requirements = list(101,101,101,101,101,101,101,101,101,101) antag_cap = 3 flags = HIGH_IMPACT_RULESET From b64568df5e88d4f30f964137790b34a4a39da74f Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Mon, 24 May 2021 21:35:23 -0500 Subject: [PATCH 39/53] Update dynamic_rulesets_roundstart.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm | 1 - 1 file changed, 1 deletion(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index 3ff23e7b81ae..63da97a9c034 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -31,7 +31,6 @@ assigned += M.mind M.mind.special_role = ROLE_TRAITOR M.mind.restricted_roles = restricted_roles - GLOB.pre_setup_antags += M.mind log_game("[key_name(M)] has been selected as a Traitor") return TRUE From 91d4606ab2d0df61cabe58ba872fdaade18e38c7 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Mon, 24 May 2021 21:41:37 -0500 Subject: [PATCH 40/53] Update dynamic_rulesets.dm --- code/game/gamemodes/dynamic/dynamic_rulesets.dm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm index 48523457569e..7719910544b9 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm @@ -183,6 +183,11 @@ /// Only called if ruleset is flagged as HIGH_IMPACT_RULESET /datum/dynamic_ruleset/proc/round_result() +/// Checks if round is finished, return true to end the round. +/// Only called if ruleset is flagged as HIGH_IMPACT_RULESET +/datum/dynamic_ruleset/proc/check_finished() + return FALSE + ////////////////////////////////////////////// // // // ROUNDSTART RULESETS // From 31cd1306e639639d9fd6916a7fa43dfca293c56a Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Tue, 15 Jun 2021 14:44:11 -0500 Subject: [PATCH 41/53] Update new_player.dm --- code/modules/mob/dead/new_player/new_player.dm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index d30c24d16ec5..ab34405974ee 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -29,6 +29,12 @@ ComponentInitialize() . = ..() + + GLOB.new_player_list += src + +/mob/dead/new_player/Destroy() + GLOB.new_player_list -= src + return ..() /mob/dead/new_player/prepare_huds() return From 9d2c87d10707453fea24663650d24272907446b4 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Thu, 17 Jun 2021 00:51:09 -0500 Subject: [PATCH 42/53] Update dynamic_rulesets_roundstart.dm --- .../dynamic/dynamic_rulesets_roundstart.dm | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index 63da97a9c034..03d3f2ba26eb 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -11,7 +11,7 @@ antag_flag = ROLE_TRAITOR antag_datum = /datum/antagonist/traitor minimum_required_age = 0 - protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain") + protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director") restricted_roles = list("Cyborg") required_candidates = 1 weight = 5 @@ -50,7 +50,7 @@ name = "Blood Brothers" antag_flag = ROLE_BROTHER antag_datum = /datum/antagonist/brother/ - protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain") + protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director") restricted_roles = list("Cyborg", "AI") required_candidates = 2 weight = 4 @@ -99,7 +99,7 @@ name = "Changelings" antag_flag = ROLE_CHANGELING antag_datum = /datum/antagonist/changeling - protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain") + protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director") restricted_roles = list("AI", "Cyborg") required_candidates = 1 weight = 3 @@ -134,7 +134,7 @@ name = "Heretics" antag_flag = ROLE_HERETIC antag_datum = /datum/antagonist/heretic - protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain") + protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director") restricted_roles = list("AI", "Cyborg") required_candidates = 1 weight = 3 @@ -281,7 +281,7 @@ restricted_roles = list("Head of Security", "Captain") // Just to be sure that a nukie getting picked won't ever imply a Captain or HoS not getting drafted required_candidates = 5 weight = 3 - cost = 20 + cost = 25 requirements = list(90,90,90,80,60,40,30,20,10,10) flags = HIGH_IMPACT_RULESET antag_cap = list("denominator" = 18, "offset" = 1) @@ -927,11 +927,11 @@ antag_flag = ROLE_DARKSPAWN antag_datum = /datum/antagonist/darkspawn/ minimum_required_age = 20 - protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel") + protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director") restricted_roles = list("AI", "Cyborg") required_candidates = 3 weight = 3 - cost = 30 + cost = 20 scaling_cost = 20 antag_cap = 3 requirements = list(80,75,70,65,50,30,30,30,25,20) From e615f4723cb741f538119495ea025294cc45bb3f Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Thu, 17 Jun 2021 00:56:51 -0500 Subject: [PATCH 43/53] Update dynamic_rulesets_midround.dm --- .../dynamic/dynamic_rulesets_midround.dm | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index 9533443bfa65..b01818bfd06a 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -164,10 +164,10 @@ name = "Syndicate Sleeper Agent" antag_datum = /datum/antagonist/traitor antag_flag = ROLE_TRAITOR - protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel") + protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director") restricted_roles = list("Cyborg", "AI", "Positronic Brain") required_candidates = 1 - weight = 1 + weight = 7 cost = 10 requirements = list(50,40,30,20,10,10,10,10,10,10) repeatable = TRUE @@ -232,7 +232,7 @@ exclusive_roles = list("AI") required_enemies = list(4,4,4,4,4,4,2,2,2,0) required_candidates = 1 - weight = 1 + weight = 3 cost = 35 requirements = list(100,100,80,70,60,60,50,50,45,40) required_type = /mob/living/silicon/ai @@ -315,8 +315,8 @@ enemy_roles = list("AI", "Cyborg", "Security Officer", "Warden","Detective","Head of Security", "Captain") required_enemies = list(3,3,3,3,2,2,1,1,0,0) required_candidates = 5 - weight = 1 - cost = 35 + weight = 2 + cost = 25 requirements = list(90,90,90,80,60,40,30,20,10,10) var/list/operative_cap = list(2,2,3,3,4,5,5,5,5,5) var/datum/team/nuclear/nuke_team @@ -357,8 +357,8 @@ enemy_roles = list("Security Officer", "Detective", "Head of Security", "Captain") required_enemies = list(2,2,1,1,1,1,1,0,0,0) required_candidates = 1 - weight = 1 - cost = 30 + weight = 4 + cost = 10 requirements = list(100,100,100,80,60,50,45,30,20,20) repeatable = TRUE @@ -379,7 +379,7 @@ enemy_roles = list("Security Officer", "Detective", "Head of Security", "Captain") required_enemies = list(2,2,1,1,1,1,1,0,0,0) required_candidates = 1 - weight = 1 + weight = 3 cost = 20 requirements = list(100,100,100,70,50,40,30,25,20,10) repeatable = TRUE @@ -425,7 +425,7 @@ enemy_roles = list("Security Officer", "Detective", "Head of Security", "Captain") required_enemies = list(2,2,1,1,1,1,0,0,0,0) required_candidates = 1 - weight = 1 + weight = 4 cost = 5 requirements = list(90,85,80,70,50,40,30,25,20,10) repeatable = TRUE @@ -467,10 +467,10 @@ name = "Vampire" antag_flag = ROLE_VAMPIRE antag_datum = /datum/antagonist/vampire - protected_roles = list("Head of Security", "Captain", "Security Officer", "Chaplain", "Detective", "Warden", "Head of Personnel") + protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director") restricted_roles = list("Cyborg", "AI") required_candidates = 1 - weight = 1 + weight = 5 cost = 25 requirements = list(80,70,60,50,50,45,30,30,25,25) minimum_players = 30 From ebcead7f21f269689ed409953f7d24a55862402d Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Thu, 17 Jun 2021 00:57:48 -0500 Subject: [PATCH 44/53] Update dynamic_rulesets_latejoin.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index cb5bf6fefb20..1f0c914e5e22 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -59,7 +59,7 @@ name = "Syndicate Infiltrator" antag_datum = /datum/antagonist/traitor antag_flag = ROLE_TRAITOR - protected_roles = list("Security Officer", "Warden", "Head of Personnel", "Detective", "Head of Security", "Captain") + protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director") restricted_roles = list("AI","Cyborg") required_candidates = 1 weight = 7 @@ -193,7 +193,7 @@ name = "Vampiric Infiltrator" antag_flag = ROLE_VAMPIRE antag_datum = /datum/antagonist/vampire - protected_roles = list("Head of Security", "Captain", "Security Officer", "Chaplain", "Detective", "Warden", "Head of Personnel") + protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director") restricted_roles = list("AI", "Cyborg") required_candidates = 1 weight = 4 @@ -213,7 +213,7 @@ name = "Heretic Smuggler" antag_datum = /datum/antagonist/heretic antag_flag = ROLE_HERETIC - protected_roles = list("Security Officer", "Warden", "Head of Personnel", "Detective", "Head of Security", "Captain") + protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director") restricted_roles = list("AI","Cyborg") required_candidates = 1 weight = 3 From db43616c6a7145033a4eca6b871ebad7707765a4 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Thu, 17 Jun 2021 01:12:31 -0500 Subject: [PATCH 45/53] Update dynamic_rulesets_midround.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index b01818bfd06a..b7d08dda1926 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -321,6 +321,7 @@ var/list/operative_cap = list(2,2,3,3,4,5,5,5,5,5) var/datum/team/nuclear/nuke_team flags = HIGH_IMPACT_RULESET + minimum_players = 40 /datum/dynamic_ruleset/midround/from_ghosts/nuclear/acceptable(population=0, threat=0) if (locate(/datum/dynamic_ruleset/roundstart/nuclear) in mode.executed_rules) @@ -384,6 +385,7 @@ requirements = list(100,100,100,70,50,40,30,25,20,10) repeatable = TRUE var/list/vents = list() + minimum_players = 30 /datum/dynamic_ruleset/midround/from_ghosts/xenomorph/execute() // 50% chance of being incremented by one @@ -473,7 +475,7 @@ weight = 5 cost = 25 requirements = list(80,70,60,50,50,45,30,30,25,25) - minimum_players = 30 + minimum_players = 25 /datum/dynamic_ruleset/midround/autovamp/acceptable(population = 0, threat = 0) var/player_count = mode.current_players[CURRENT_LIVING_PLAYERS].len @@ -529,6 +531,7 @@ requirements = list(90,85,80,75,70,65,60,55) repeatable = TRUE var/list/spawn_locs = list() + minimum_players = 40 /datum/round_event/ghost_role/zombie/spawn_role() var/list/candidates = get_candidates(ROLE_ZOMBIE, null, ROLE_ZOMBIE) From 52cb75582cff593cfeaba38f5c3405ce15184c50 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Thu, 17 Jun 2021 01:21:57 -0500 Subject: [PATCH 46/53] Update dynamic_rulesets_roundstart.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index 03d3f2ba26eb..bc671ea70f43 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -286,6 +286,7 @@ flags = HIGH_IMPACT_RULESET antag_cap = list("denominator" = 18, "offset" = 1) var/datum/team/nuclear/nuke_team + minimum_players = 36 /datum/dynamic_ruleset/roundstart/nuclear/ready(population, forced = FALSE) required_candidates = get_antag_cap(population) From 34886c0165ed43e67d1bd9afdc49540f224d0e99 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Thu, 17 Jun 2021 01:25:50 -0500 Subject: [PATCH 47/53] Update dynamic_rulesets_roundstart.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index bc671ea70f43..46f8a9df7fec 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -130,7 +130,7 @@ // // ////////////////////////////////////////////// -/*/datum/dynamic_ruleset/roundstart/heretics +/datum/dynamic_ruleset/roundstart/heretics name = "Heretics" antag_flag = ROLE_HERETIC antag_datum = /datum/antagonist/heretic @@ -162,7 +162,7 @@ var/datum/antagonist/heretic/new_antag = new antag_datum() cultie.add_antag_datum(new_antag) - return TRUE */ + return TRUE ////////////////////////////////////////////// // // From f75abfe621d6f5f6618fc8c58b8220da37d82e9b Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Mon, 21 Jun 2021 19:27:08 -0500 Subject: [PATCH 48/53] Update dynamic_rulesets_latejoin.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index 1f0c914e5e22..fe39e2901c6a 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -218,5 +218,5 @@ required_candidates = 1 weight = 3 cost = 15 - requirements = list(40,30,20,10,10,10,10,10,10,10) + requirements = list(101,101,101,10,10,10,10,10,10,10) repeatable = TRUE */ From 7e9e2184ec214d6b5ed5721bb90712737f1c9796 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Mon, 21 Jun 2021 19:29:40 -0500 Subject: [PATCH 49/53] Update dynamic_rulesets_roundstart.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index 46f8a9df7fec..5636c1d718e8 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -140,7 +140,7 @@ weight = 3 cost = 15 scaling_cost = 9 - requirements = list(50,45,45,40,35,20,20,15,10,10) + requirements = list(101,101,101,40,35,20,20,15,10,10) antag_cap = list("denominator" = 24) @@ -182,7 +182,7 @@ required_candidates = 1 weight = 2 cost = 20 - requirements = list(90,90,70,40,30,20,10,10,10,10) + requirements = list(90,90,90,80,60,40,30,20,10,10) var/list/roundstart_wizards = list() /datum/dynamic_ruleset/roundstart/wizard/acceptable(population=0, threat=0) @@ -226,7 +226,7 @@ required_candidates = 2 weight = 3 cost = 20 - requirements = list(100,80,70,60,40,30,30,20,10,10) + requirements = list(100,90,80,60,40,30,10,10,10,10) flags = HIGH_IMPACT_RULESET antag_cap = list("denominator" = 20, "offset" = 1) var/datum/team/cult/main_cult From af22426daf873a1b697e90e9c2d9fce5bbdf1df0 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Mon, 21 Jun 2021 19:34:36 -0500 Subject: [PATCH 50/53] Adds from logging from https://github.com/tgstation/tgstation/pull/58451/ --- code/game/gamemodes/dynamic/dynamic.dm | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index 93152ee61004..3dec7f95ea93 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -160,6 +160,10 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) /// The last drafted midround rulesets (without the current one included). /// Used for choosing different midround injections. var/list/current_midround_rulesets + + /// The amount of threat shown on the piece of paper. + /// Can differ from the actual threat amount. + var/shown_threat /datum/game_mode/dynamic/admin_panel() var/list/dat = list("Game Mode Panel

Game Mode Panel

") @@ -267,11 +271,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) // communications title for threat management var/desc = "REACH OUT WITH THE FORCE BOY, AND TEAR THAT STAR DESTROYER FROM THE SKY!" // description for threat management - var/shown_threat - if(prob(FAKE_REPORT_CHANCE)) - shown_threat = rand(1, 100) - else - shown_threat = clamp(threat_level + rand(REPORT_NEG_DIVERGENCE, REPORT_POS_DIVERGENCE), 0, 100) switch(round(shown_threat)) if(0 to 19) if(!current_players[CURRENT_LIVING_ANTAGS].len) @@ -376,6 +375,12 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) set_cooldowns() dynamic_log("Dynamic Mode initialized with a Threat Level of... [threat_level]! ([round_start_budget] round start budget)") return TRUE + +/datum/game_mode/dynamic/proc/setup_shown_threat() + if (prob(FAKE_REPORT_CHANCE)) + shown_threat = rand(1, 100) + else + shown_threat = clamp(threat_level + rand(REPORT_NEG_DIVERGENCE, REPORT_POS_DIVERGENCE), 0, 100) /datum/game_mode/dynamic/proc/set_cooldowns() var/latejoin_injection_cooldown_middle = 0.5*(latejoin_delay_max + latejoin_delay_min) @@ -398,6 +403,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) setup_parameters() setup_hijacking() + setup_shown_threat() var/valid_roundstart_ruleset = 0 for (var/rule in subtypesof(/datum/dynamic_ruleset)) From 1a41f89ebc7e6fb86358b3c790c4550350e45ac1 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Mon, 21 Jun 2021 19:35:27 -0500 Subject: [PATCH 51/53] Finishes up adding https://github.com/tgstation/tgstation/pull/58451/ --- code/game/gamemodes/dynamic/dynamic_logging.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/game/gamemodes/dynamic/dynamic_logging.dm b/code/game/gamemodes/dynamic/dynamic_logging.dm index 9c7e5128c220..fab7d7853d37 100644 --- a/code/game/gamemodes/dynamic/dynamic_logging.dm +++ b/code/game/gamemodes/dynamic/dynamic_logging.dm @@ -87,6 +87,7 @@ serialized["threat_level"] = threat_level serialized["round_start_budget"] = initial_round_start_budget serialized["mid_round_budget"] = threat_level - initial_round_start_budget + serialized["shown_threat"] = shown_threat var/list/serialized_snapshots = list() for (var/_snapshot in snapshots) @@ -94,4 +95,4 @@ serialized_snapshots += list(snapshot.to_list()) serialized["snapshots"] = serialized_snapshots - rustg_file_write(json_encode(serialized), "[GLOB.log_directory]/dynamic.json") \ No newline at end of file + rustg_file_write(json_encode(serialized), "[GLOB.log_directory]/dynamic.json") From 17565e3b03ff154410346833ddaeaa94f903f76c Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Tue, 22 Jun 2021 19:44:36 -0500 Subject: [PATCH 52/53] Update dynamic_rulesets_midround.dm --- .../dynamic/dynamic_rulesets_midround.dm | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index b7d08dda1926..330cfc448f2b 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -538,7 +538,7 @@ if(!candidates.len) return NOT_ENOUGH_PLAYERS - var/mob/dead/selected = pick(candidates) + var/mob/dead/selected = pick(candidates) var/datum/mind/player_mind = new /datum/mind(selected.key) player_mind.active = TRUE @@ -554,14 +554,13 @@ message_admins("No valid spawn locations found, aborting...") return MAP_ERROR - var/mob/living/carbon/human/M = new ((pick(spawn_locs))) - player_mind.transfer_to(M) + var/mob/living/carbon/human/S = new ((pick(spawn_locs))) + player_mind.transfer_to(S) player_mind.assigned_role = "Zombie" player_mind.special_role = "Zombie" - M.set_species(/datum/species/zombie/infectious) - playsound(M, 'sound/hallucinations/growl1.ogg', 50, 1, -1) - message_admins("[ADMIN_LOOKUPFLW(M)] has been made into a Zombie by an event.") - log_game("[key_name(M)] was spawned as a Zombie by an event.") - spawned_mobs += M + S.set_species(/datum/species/zombie/infectious) + playsound(S, 'sound/hallucinations/growl1.ogg', 50, 1, -1) + message_admins("[ADMIN_LOOKUPFLW(S)] has been made into a Zombie by an event.") + log_game("[key_name(S)] was spawned as a Zombie by an event.") + spawned_mobs += S return SUCCESSFUL_SPAWN - From 85ce65dd36343531fec6bb05c124d4527009da47 Mon Sep 17 00:00:00 2001 From: Xoxeyos <39686570+Xoxeyos@users.noreply.github.com> Date: Tue, 22 Jun 2021 19:49:00 -0500 Subject: [PATCH 53/53] Update dynamic_rulesets_midround.dm --- code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index 330cfc448f2b..06ea24b1b71c 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -538,7 +538,7 @@ if(!candidates.len) return NOT_ENOUGH_PLAYERS - var/mob/dead/selected = pick(candidates) + var/mob/dead/selected = pick(candidates) var/datum/mind/player_mind = new /datum/mind(selected.key) player_mind.active = TRUE