From 60714205545998d4b0adeb1a2dbf40bd3d80316e Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 9 Apr 2025 03:03:10 -0500 Subject: [PATCH 1/3] Dynamic Deletus --- code/__DEFINES/dynamic.dm | 40 - code/__HELPERS/logging/dynamic.dm | 8 - code/__HELPERS/roundend.dm | 13 - code/controllers/subsystem/communications.dm | 64 + code/controllers/subsystem/dynamic/dynamic.dm | 1055 ----------------- .../subsystem/dynamic/dynamic_hijacking.dm | 25 - .../subsystem/dynamic/dynamic_logging.dm | 101 -- .../dynamic/dynamic_midround_rolling.dm | 108 -- .../subsystem/dynamic/dynamic_rulesets.dm | 285 ----- .../dynamic/dynamic_rulesets_latejoin.dm | 254 ---- .../dynamic/dynamic_rulesets_midround.dm | 933 --------------- .../dynamic/dynamic_rulesets_roundstart.dm | 701 ----------- .../dynamic/dynamic_unfavorable_situation.dm | 74 -- code/controllers/subsystem/dynamic/readme.md | 194 --- .../subsystem/dynamic/ruleset_picking.dm | 139 --- code/controllers/subsystem/ticker.dm | 10 +- code/datums/station_traits/_station_trait.dm | 5 - code/datums/station_traits/negative_traits.dm | 1 - .../game/machinery/computer/communications.dm | 3 + code/modules/admin/admin.dm | 133 --- code/modules/admin/topic.dm | 121 -- .../changeling/fallen_changeling.dm | 3 +- .../antagonists/disease/disease_datum.dm | 1 + .../antagonists/heretic/heretic_monsters.dm | 2 +- code/modules/antagonists/pirate/pirate.dm | 2 +- .../antagonists/revenant/revenant_antag.dm | 1 + .../sentient_creature/sentient_creature.dm | 1 + .../equipment/spellbook_entries/summons.dm | 11 - .../wizard/grand_ritual/finales/armageddon.dm | 5 +- .../antagonists/wizard/slaughter_antag.dm | 2 +- .../client/preferences/middleware/antags.dm | 74 +- code/modules/events/_event.dm | 4 - code/modules/events/ghost_role/abductor.dm | 2 +- .../modules/mob/dead/new_player/new_player.dm | 9 - code/modules/unit_tests/_unit_tests.dm | 1 - .../unit_tests/dynamic_ruleset_sanity.dm | 43 - code/modules/unit_tests/traitor.dm | 6 +- config/dynamic.json | 163 --- maplestation.dme | 12 - tgstation.dme | 12 - 40 files changed, 124 insertions(+), 4497 deletions(-) delete mode 100644 code/__DEFINES/dynamic.dm delete mode 100644 code/__HELPERS/logging/dynamic.dm delete mode 100644 code/controllers/subsystem/dynamic/dynamic.dm delete mode 100644 code/controllers/subsystem/dynamic/dynamic_hijacking.dm delete mode 100644 code/controllers/subsystem/dynamic/dynamic_logging.dm delete mode 100644 code/controllers/subsystem/dynamic/dynamic_midround_rolling.dm delete mode 100644 code/controllers/subsystem/dynamic/dynamic_rulesets.dm delete mode 100644 code/controllers/subsystem/dynamic/dynamic_rulesets_latejoin.dm delete mode 100644 code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm delete mode 100644 code/controllers/subsystem/dynamic/dynamic_rulesets_roundstart.dm delete mode 100644 code/controllers/subsystem/dynamic/dynamic_unfavorable_situation.dm delete mode 100644 code/controllers/subsystem/dynamic/readme.md delete mode 100644 code/controllers/subsystem/dynamic/ruleset_picking.dm delete mode 100644 code/modules/unit_tests/dynamic_ruleset_sanity.dm delete mode 100644 config/dynamic.json diff --git a/code/__DEFINES/dynamic.dm b/code/__DEFINES/dynamic.dm deleted file mode 100644 index 07c833cdc494..000000000000 --- a/code/__DEFINES/dynamic.dm +++ /dev/null @@ -1,40 +0,0 @@ -/// 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) - -/// This is a "heavy" midround ruleset, and should be run later into the round -#define MIDROUND_RULESET_STYLE_HEAVY "Heavy" - -/// This is a "light" midround ruleset, and should be run early into the round -#define MIDROUND_RULESET_STYLE_LIGHT "Light" - -/// 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" - -/// Kill this ruleset from continuing to process -#define RULESET_STOP_PROCESSING 1 - -/// Requirements when something needs a lot of threat to run, but still possible at low-pop -#define REQUIREMENTS_VERY_HIGH_THREAT_NEEDED list(90,90,90,80,60,50,40,40,40,40) - -/// Max number of teams we can have for the abductor ruleset -#define ABDUCTOR_MAX_TEAMS 4 - -// Ruletype defines -#define ROUNDSTART_RULESET "Roundstart" -#define LATEJOIN_RULESET "Latejoin" -#define MIDROUND_RULESET "Midround" - -#define RULESET_NOT_FORCED "not forced" -/// Ruleset should run regardless of population and threat available -#define RULESET_FORCE_ENABLED "force enabled" -/// Ruleset should not run regardless of population and threat available -#define RULESET_FORCE_DISABLED "force disabled" diff --git a/code/__HELPERS/logging/dynamic.dm b/code/__HELPERS/logging/dynamic.dm deleted file mode 100644 index 488aeaec3be7..000000000000 --- a/code/__HELPERS/logging/dynamic.dm +++ /dev/null @@ -1,8 +0,0 @@ -/// Log to dynamic and message admins -/datum/controller/subsystem/dynamic/proc/log_dynamic_and_announce(text) - message_admins("DYNAMIC: [text]") - log_dynamic("[text]") - -/// Logging for dynamic procs -/proc/log_dynamic(text, list/data) - logger.Log(LOG_CATEGORY_DYNAMIC, text, data) diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm index a3734cfd7a62..c7a1b47e46d1 100644 --- a/code/__HELPERS/roundend.dm +++ b/code/__HELPERS/roundend.dm @@ -241,9 +241,6 @@ GLOBAL_LIST_INIT(achievements_unlocked, list()) CHECK_TICK - //Set news report and mode result - SSdynamic.set_round_result() - to_chat(world, span_infoplain(span_big(span_bold("


The round has ended.")))) log_game("The round has ended.") send2chat(new /datum/tgs_message_content("[GLOB.round_id ? "Round [GLOB.round_id]" : "The round has"] just ended."), CONFIG_GET(string/channel_announce_end_game)) @@ -354,16 +351,6 @@ GLOBAL_LIST_INIT(achievements_unlocked, list()) else parts += "[FOURSPACES]Nobody died this shift!" - parts += "[FOURSPACES]Threat level: [SSdynamic.threat_level]" - parts += "[FOURSPACES]Threat left: [SSdynamic.mid_round_budget]" - if(SSdynamic.roundend_threat_log.len) - parts += "[FOURSPACES]Threat edits:" - for(var/entry as anything in SSdynamic.roundend_threat_log) - parts += "[FOURSPACES][FOURSPACES][entry]
" - parts += "[FOURSPACES]Executed rules:" - for(var/datum/dynamic_ruleset/rule in SSdynamic.executed_rules) - parts += "[FOURSPACES][FOURSPACES][rule.ruletype] - [rule.name]: -[rule.cost + rule.scaled_times * rule.scaling_cost] threat" - return parts.Join("
") /client/proc/roundend_report_file() diff --git a/code/controllers/subsystem/communications.dm b/code/controllers/subsystem/communications.dm index 5d2d5a2e7f40..48eebff87710 100644 --- a/code/controllers/subsystem/communications.dm +++ b/code/controllers/subsystem/communications.dm @@ -59,6 +59,70 @@ SUBSYSTEM_DEF(communications) printed_paper.add_raw_text(sending.content) printed_paper.update_appearance() +/datum/controller/subsystem/communications/proc/send_roundstart_report(greenshift) + addtimer(CALLBACK(src, PROC_REF(send_roundstart_report_delayed), greenshift), rand(600 SECONDS, 1800 SECONDS)) + +/datum/controller/subsystem/communications/proc/send_roundstart_report_delayed(greenshift = TRUE) + if(SScommunications.block_command_report) //If we don't want the report to be printed just yet, we put it off until it's ready + addtimer(CALLBACK(src, PROC_REF(send_roundstart_report_delayed), greenshift), 10 SECONDS) + return + + . = "Nanotrasen Department of Intelligence Threat Advisory, Spinward Sector, TCD [time2text(world.realtime, "DDD, MMM DD")], [CURRENT_STATION_YEAR]:
" + . += "All quiet on the Western Front." + + SSstation.generate_station_goals(greenshift ? INFINITY : CONFIG_GET(number/station_goal_budget)) + + var/list/datum/station_goal/goals = SSstation.get_station_goals() + if(length(goals)) + var/list/texts = list("
Special Orders for [station_name()]:
") + for(var/datum/station_goal/station_goal as anything in goals) + station_goal.on_report() + texts += station_goal.get_report() + . += texts.Join("
") + + var/list/trait_list_strings = list() + for(var/datum/station_trait/station_trait as anything in SSstation.station_traits) + if(!station_trait.show_in_report) + continue + trait_list_strings += "[station_trait.get_report()]
" + if(trait_list_strings.len > 0) + . += "
Identified shift divergencies:
" + trait_list_strings.Join() + + if(length(command_report_footnotes)) + var/footnote_pile = "" + + for(var/datum/command_footnote/footnote as anything in command_report_footnotes) + footnote_pile += "[footnote.message]
" + footnote_pile += "[footnote.signature]
" + footnote_pile += "
" + + . += "
Additional Notes:

" + footnote_pile + +#ifndef MAP_TEST + print_command_report(., "[command_name()] Status Summary", announce=FALSE) + if(greenshift) + priority_announce( + "Thanks to the tireless efforts of our security and intelligence divisions, \ + there are currently no credible threats to [station_name()]. \ + All station construction projects have been authorized. Have a secure shift!", + "Security Report", + SSstation.announcer.get_rand_report_sound(), + color_override = "green", + ) + else + if(SSsecurity_level.get_current_level_as_number() < SEC_LEVEL_BLUE) + SSsecurity_level.set_level(SEC_LEVEL_BLUE, announce = FALSE) + priority_announce( + "[SSsecurity_level.current_security_level.elevating_to_announcement]\n\n\ + A summary has been copied and printed to all communications consoles.", + "Security level elevated.", + ANNOUNCER_INTERCEPT, + color_override = SSsecurity_level.current_security_level.announcement_color, + ) +#endif + + return . + #undef COMMUNICATION_COOLDOWN #undef COMMUNICATION_COOLDOWN_AI #undef COMMUNICATION_COOLDOWN_MEETING diff --git a/code/controllers/subsystem/dynamic/dynamic.dm b/code/controllers/subsystem/dynamic/dynamic.dm deleted file mode 100644 index aca8ff1fc9e7..000000000000 --- a/code/controllers/subsystem/dynamic/dynamic.dm +++ /dev/null @@ -1,1055 +0,0 @@ -#define FAKE_GREENSHIFT_FORM_CHANCE 15 -#define FAKE_REPORT_CHANCE 8 -#define PULSAR_REPORT_CHANCE 8 -#define REPORT_NEG_DIVERGENCE -15 -#define REPORT_POS_DIVERGENCE 15 - -// Are HIGH_IMPACT_RULESETs allowed to stack? -GLOBAL_VAR_INIT(dynamic_no_stacking, TRUE) -// If enabled does not accept or execute any rulesets. -GLOBAL_VAR_INIT(dynamic_forced_extended, FALSE) -// 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. -GLOBAL_LIST_EMPTY(dynamic_forced_roundstart_ruleset) -// Forced threat level, setting this to zero or higher forces the roundstart threat to the value. -GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) -/// Modify the threat level for station traits before dynamic can be Initialized. List(instance = threat_reduction) -GLOBAL_LIST_EMPTY(dynamic_station_traits) -/// Rulesets which have been forcibly enabled or disabled -GLOBAL_LIST_EMPTY(dynamic_forced_rulesets) - -SUBSYSTEM_DEF(dynamic) - name = "Dynamic" - flags = SS_NO_INIT - wait = 1 SECONDS - - // 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. 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() - /// Threat log shown on the roundend report. Should only list player-made edits. - var/list/roundend_threat_log = list() - /// List of latejoin rules used for selecting the rules. - var/list/latejoin_rules - /// List of midround rules used for selecting the rules. - var/list/midround_rules - /** # Pop range per requirement. - * If the value is five the range is: - * 0-4, 5-9, 10-14, 15-19, 20-24, 25-29, 30-34, 35-39, 40-54, 45+ - * If it is six the range is: - * 0-5, 6-11, 12-17, 18-23, 24-29, 30-35, 36-41, 42-47, 48-53, 54+ - * If it is seven the range is: - * 0-6, 7-13, 14-20, 21-27, 28-34, 35-41, 42-48, 49-55, 56-62, 63+ - */ - var/pop_per_requirement = 6 - /// Number of players who were ready on roundstart. - var/roundstart_pop_ready = 0 - /// List of candidates used on roundstart rulesets. - var/list/candidates = list() - /// Rules that are processed, rule_process is called on the rules in this list. - var/list/current_rules = list() - /// List of executed rulesets. - var/list/executed_rules = list() - /// If TRUE, the next player to latejoin will guarantee roll for a random latejoin antag - /// (this does not guarantee they get said antag roll, depending on preferences and circumstances) - var/forced_injection = FALSE - /// Forced ruleset to be executed for the next latejoin. - var/datum/dynamic_ruleset/latejoin/forced_latejoin_rule = null - /// How many percent of the rounds are more peaceful. - var/peaceful_percentage = 50 - /// 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 - var/list/configuration = null - - /// 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) - - /// The low bound for the midround roll time splits. - /// This number influences where to place midround rolls, making this smaller - /// will make midround rolls more frequent, and vice versa. - /// A midround will never be able to roll before this. - var/midround_lower_bound = 10 MINUTES - - /// The upper bound for the midround roll time splits. - /// This number influences where to place midround rolls, making this larger - /// will make midround rolls less frequent, and vice versa. - /// A midround will never be able to roll farther than this. - var/midround_upper_bound = 100 MINUTES - - /// The distance between the chosen midround roll point (which is deterministic), - /// and when it can actually roll. - /// Basically, if this is set to 5 minutes, and a midround roll point is decided to be at 20 minutes, - /// then it can roll anywhere between 15 and 25 minutes. - var/midround_roll_distance = 3 MINUTES - - /// The amount of threat per midround roll. - /// Basically, if this is set to 5, then for every 5 threat, one midround roll will be added. - /// The equation this is used in rounds up, meaning that if this is set to 5, and you have 6 - /// threat, then you will get 2 midround rolls. - var/threat_per_midround_roll = 7 - - /// 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 - - /// What is the lower bound of when the roundstart annoucement is sent out? - var/waittime_l = 600 - - /// What is the higher bound of when the roundstart annoucement is sent out? - var/waittime_h = 1800 - - /// A number between 0 and 100. The maximum amount of threat allowed to generate. - var/max_threat_level = 100 - - /// The extra chance multiplier that a heavy impact midround ruleset will run next time. - /// For example, if this is set to 50, then the next heavy roll will be about 50% more likely to happen. - var/hijacked_random_event_injection_chance_modifier = 50 - - /// Any midround before this point is guaranteed to be light - var/midround_light_upper_bound = 25 MINUTES - - /// Any midround after this point is guaranteed to be heavy - var/midround_heavy_lower_bound = 55 MINUTES - - /// If there are less than this many players readied, threat level will be lowered. - /// This number should be kept fairly low, as there are other measures that population - /// impacts Dynamic, such as the requirements variable on rulesets. - var/low_pop_player_threshold = 20 - - /// The maximum threat that can roll with *zero* players. - /// As the number of players approaches `low_pop_player_threshold`, the maximum - /// threat level will increase. - /// For example, if `low_pop_maximum_threat` is 50, `low_pop_player_threshold` is 20, - /// and the number of readied players is 10, then the highest threat that can roll is - /// lerp(50, 100, 10 / 20), AKA 75. - var/low_pop_maximum_threat = 40 - - /// The chance for latejoins to roll when ready - var/latejoin_roll_chance = 50 - - // == EVERYTHING BELOW THIS POINT SHOULD NOT BE CONFIGURED == - - /// 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 - - /// 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 - - /// The amount of threat shown on the piece of paper. - /// Can differ from the actual threat amount. - var/shown_threat - - VAR_PRIVATE/next_midround_injection - -/datum/controller/subsystem/dynamic/proc/admin_panel() - var/list/dat = list("Game Mode Panel

Game Mode Panel

") - 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 += "Midround budget to spend: [mid_round_budget] \[Adjust\] \[View Log\]
" - dat += "
" - 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, [clamp(peaceful_percentage, 1, 99)]% of the rounds are more peaceful.
" - dat += "Forced extended: [GLOB.dynamic_forced_extended ? "On" : "Off"]
" - dat += "No stacking (only one round-ender): [GLOB.dynamic_no_stacking ? "On" : "Off"]
" - dat += "Stacking limit: [GLOB.dynamic_stacking_limit] \[Adjust\]" - dat += "
" - dat += "\[Force Next Latejoin Ruleset\]
" - if (forced_latejoin_rule) - dat += {"-> [forced_latejoin_rule.name] <-
"} - dat += "\[Execute Midround Ruleset\]
" - dat += "
" - dat += "Executed rulesets: " - if (executed_rules.len > 0) - dat += "
" - for (var/datum/dynamic_ruleset/DR in executed_rules) - dat += "[DR.ruletype] - [DR.name]
" - else - dat += "none.
" - dat += "
Injection Timers: ([get_heavy_midround_injection_chance(dry_run = TRUE)]% heavy midround chance)
" - dat += "Latejoin: [DisplayTimeText(latejoin_injection_cooldown-world.time)] \[Now!\]
" - - var/next_injection = next_midround_injection() - if (next_injection == INFINITY) - dat += "All midrounds have been exhausted." - else - dat += "Midround: [DisplayTimeText(next_injection - world.time)] \[Now!\]
" - - usr << browse(dat.Join(), "window=gamemode_panel;size=500x500") - -/datum/controller/subsystem/dynamic/Topic(href, href_list) - if (..()) // Sanity, maybe ? - return - if(!check_rights(R_ADMIN)) - message_admins("[usr.key] has attempted to override the game mode panel!") - log_admin("[key_name(usr)] tried to use the game mode panel without authorization.") - return - if (href_list["forced_extended"]) - GLOB.dynamic_forced_extended = !GLOB.dynamic_forced_extended - else if (href_list["no_stacking"]) - GLOB.dynamic_no_stacking = !GLOB.dynamic_no_stacking - 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) - return - if(threatadd > 0) - create_threat(threatadd, threat_log, "[worldtime2text()]: increased by [key_name(usr)]") - else - spend_midround_budget(-threatadd, threat_log, "[worldtime2text()]: decreased by [key_name(usr)]") - else if (href_list["injectlate"]) - latejoin_injection_cooldown = 0 - forced_injection = TRUE - message_admins("[key_name(usr)] forced a latejoin injection.") - else if (href_list["injectmid"]) - forced_injection = TRUE - message_admins("[key_name(usr)] forced a midround injection.") - try_midround_roll() - else if (href_list["threatlog"]) - show_threatlog(usr) - else if (href_list["stacking_limit"]) - GLOB.dynamic_stacking_limit = input(usr,"Change the threat limit at which round-endings rulesets will start to stack.", "Change stacking limit", null) as num - else if(href_list["force_latejoin_rule"]) - var/added_rule = input(usr,"What ruleset do you want to force upon the next latejoiner? This will bypass threat level and population restrictions.", "Rigging Latejoin", null) as null|anything in sort_names(init_rulesets(/datum/dynamic_ruleset/latejoin)) - 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 valid 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.") - 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 sort_names(init_rulesets(/datum/dynamic_ruleset/midround)) - if (!added_rule) - return - log_admin("[key_name(usr)] executed the [added_rule] ruleset.") - message_admins("[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 - -// Set result and news report here -/datum/controller/subsystem/dynamic/proc/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 & HIGH_IMPACT_RULESET) - rule.round_result() - // One was set, so we're done here - if(SSticker.news_report) - return - - SSticker.mode_result = "undefined" - - // Something nuked the station - it wasn't nuke ops (they set their own via their rulset) - if(GLOB.station_was_nuked) - SSticker.news_report = STATION_NUKED - - if(SSsupermatter_cascade.cascade_initiated) - SSticker.news_report = SUPERMATTER_CASCADE - - // Only show this one if we have nothing better to show - if(EMERGENCY_ESCAPED_OR_ENDGAMED && !SSticker.news_report) - SSticker.news_report = SSshuttle.emergency?.is_hijacked() ? SHUTTLE_HIJACK : STATION_EVACUATED - -/datum/controller/subsystem/dynamic/proc/send_intercept() - if(SScommunications.block_command_report) //If we don't want the report to be printed just yet, we put it off until it's ready - addtimer(CALLBACK(src, PROC_REF(send_intercept)), 10 SECONDS) - return - - . = "Nanotrasen Department of Intelligence Threat Advisory, Spinward Sector, TCD [time2text(world.realtime, "DDD, MMM DD")], [CURRENT_STATION_YEAR]:
" - . += generate_advisory_level() - - var/min_threat = 100 - for(var/datum/dynamic_ruleset/ruleset as anything in init_rulesets(/datum/dynamic_ruleset)) - if(ruleset.weight <= 0 || ruleset.cost <= 0) - continue - min_threat = min(ruleset.cost, min_threat) - - var/greenshift = GLOB.dynamic_forced_extended || (threat_level < min_threat && shown_threat < min_threat) //if both shown and real threat are below any ruleset, its extended time - SSstation.generate_station_goals(greenshift ? INFINITY : CONFIG_GET(number/station_goal_budget)) - - var/list/datum/station_goal/goals = SSstation.get_station_goals() - if(length(goals)) - var/list/texts = list("
Special Orders for [station_name()]:
") - for(var/datum/station_goal/station_goal as anything in goals) - station_goal.on_report() - texts += station_goal.get_report() - . += texts.Join("
") - - var/list/trait_list_strings = list() - for(var/datum/station_trait/station_trait as anything in SSstation.station_traits) - if(!station_trait.show_in_report) - continue - trait_list_strings += "[station_trait.get_report()]
" - if(trait_list_strings.len > 0) - . += "
Identified shift divergencies:
" + trait_list_strings.Join() - - if(length(SScommunications.command_report_footnotes)) - var/footnote_pile = "" - - for(var/datum/command_footnote/footnote in SScommunications.command_report_footnotes) - footnote_pile += "[footnote.message]
" - footnote_pile += "[footnote.signature]
" - footnote_pile += "
" - - . += "
Additional Notes:

" + footnote_pile - -#ifndef MAP_TEST - print_command_report(., "[command_name()] Status Summary", announce=FALSE) - if(greenshift) - priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", SSstation.announcer.get_rand_report_sound(), color_override = "green") - else - if(SSsecurity_level.get_current_level_as_number() < SEC_LEVEL_BLUE) - SSsecurity_level.set_level(SEC_LEVEL_BLUE, announce = FALSE) - priority_announce("[SSsecurity_level.current_security_level.elevating_to_announcement]\n\nA summary has been copied and printed to all communications consoles.", "Security level elevated.", ANNOUNCER_INTERCEPT, color_override = SSsecurity_level.current_security_level.announcement_color) -#endif - - return . - -/// Generate the advisory level depending on the shown threat level. -/datum/controller/subsystem/dynamic/proc/generate_advisory_level() - var/advisory_string = "" - if (prob(PULSAR_REPORT_CHANCE)) - if(HAS_TRAIT(SSstation, STATION_TRAIT_BANANIUM_SHIPMENTS)) - advisory_string += "Advisory Level: Clown Planet
" - advisory_string += "Your sector's advisory level is Clown Planet! Our bike horns have picked up on a large bananium stash. Clowns show a large influx of clowns on your station. We highly advice you to slip any threats to keep Honkotrasen assets within the Banana Sector. The Department advises defending chemistry from any clowns that are trying to make baldium or space lube." - return advisory_string - - advisory_string += "Advisory Level: Pulsar Star
" - advisory_string += "Your sector's advisory level is Pulsar Star. A large unknown electromagnetic field has stormed through nearby surveillance equipment. No surveillance data has been able to be obtained showing no credible threats to Nanotrasen assets within the Spinward Sector. The Department advises maintaining high alert against potential threats, regardless of a lack of information." - return advisory_string - - switch(round(shown_threat)) - if(0) - advisory_string += "Advisory Level: White Dwarf
" - advisory_string += "Your sector's advisory level is White Dwarf. Our surveillors have ruled out any and all potential risks known in our database, ruling out the loss of our assets in the Spinward Sector. We advise a lower level of security, alongside distributing ressources on potential profit." - if(1 to 19) - var/show_core_territory = (GLOB.current_living_antags.len > 0) - if (prob(FAKE_GREENSHIFT_FORM_CHANCE)) - show_core_territory = !show_core_territory - - if (show_core_territory) - advisory_string += "Advisory Level: Blue Star
" - advisory_string += "Your sector's advisory level is Blue Star. At this threat advisory, the risk of attacks on Nanotrasen assets within the sector is minor, but cannot be ruled out entirely. Remain vigilant." - else - advisory_string += "Advisory Level: Green Star
" - advisory_string += "Your sector's advisory level is Green Star. Surveillance information shows no credible threats to Nanotrasen assets within the Spinward Sector at this time. As always, the Department advises maintaining vigilance against potential threats, regardless of a lack of known threats." - if(20 to 39) - advisory_string += "Advisory Level: Yellow Star
" - advisory_string += "Your sector's advisory level is Yellow Star. Surveillance shows a credible risk of enemy attack against our assets in the Spinward Sector. We advise a heightened level of security, alongside maintaining vigilance against potential threats." - if(40 to 65) - advisory_string += "Advisory Level: Orange Star
" - advisory_string += "Your sector's advisory level is Orange Star. Upon reviewing your sector's intelligence, the Department has determined that the risk of enemy activity is moderate to severe. At this advisory, we recommend maintaining a higher degree of security and alertness, and vigilance against threats that may (or will) arise." - if(66 to 79) - advisory_string += "Advisory Level: Red Star
" - advisory_string += "Your sector's advisory level is Red Star. The Department of Intelligence has decrypted Cybersun communications suggesting a high likelihood of attacks on Nanotrasen assets within the Spinward Sector. Stations in the region are advised to remain highly vigilant for signs of enemy activity and to be on high alert." - if(80 to 99) - advisory_string += "Advisory Level: Black Orbit
" - advisory_string += "Your sector's advisory level is Black Orbit. Your sector's local comms network is currently undergoing a blackout, and we are therefore unable to accurately judge enemy movements within the region. However, information passed to us by GDI suggests a high amount of enemy activity in the sector, indicative of an impending attack. Remain on high alert, and as always, we advise remaining vigilant against any other potential threats." - if(100) - advisory_string += "Advisory Level: Midnight Sun
" - advisory_string += "Your sector's advisory level is Midnight Sun. Credible information passed to us by GDI suggests that the Syndicate is preparing to mount a major concerted offensive on Nanotrasen assets in the Spinward Sector to cripple our foothold there. All stations should remain on high alert and prepared to defend themselves." - - return advisory_string - -/datum/controller/subsystem/dynamic/proc/show_threatlog(mob/admin) - if(!SSticker.HasRoundStarted()) - tgui_alert(usr, "The round hasn't started yet!") - return - - if(!check_rights(R_ADMIN)) - return - - var/list/out = list("Threat LogThreat Log
Starting Threat: [threat_level]
") - - for(var/entry in threat_log) - if(istext(entry)) - out += "[entry]
" - - 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/controller/subsystem/dynamic/proc/generate_threat() - // At lower pop levels we run a Liner Interpolation against the max threat based proportionally on the number - // of players ready. This creates a balanced lorentz curve within a smaller range than 0 to max_threat_level. - var/calculated_max_threat = (SSticker.totalPlayersReady < low_pop_player_threshold) ? LERP(low_pop_maximum_threat, max_threat_level, SSticker.totalPlayersReady / low_pop_player_threshold) : max_threat_level - log_dynamic("Calculated maximum threat level based on player count of [SSticker.totalPlayersReady]: [calculated_max_threat]") - - threat_level = lorentz_to_amount(threat_curve_centre, threat_curve_width, calculated_max_threat) - - for(var/datum/station_trait/station_trait in GLOB.dynamic_station_traits) - threat_level = max(threat_level - GLOB.dynamic_station_traits[station_trait], 0) - log_dynamic("Threat reduced by [GLOB.dynamic_station_traits[station_trait]]. Source: [type].") - - peaceful_percentage = (threat_level/max_threat_level)*100 - -/// Generates the midround and roundstart budgets -/datum/controller/subsystem/dynamic/proc/generate_budgets() - round_start_budget = lorentz_to_amount(roundstart_split_curve_centre, roundstart_split_curve_width, threat_level, 0.1) - initial_round_start_budget = round_start_budget - mid_round_budget = threat_level - round_start_budget - -/datum/controller/subsystem/dynamic/proc/setup_parameters() - log_dynamic("Dynamic mode parameters for the round:") - log_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_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) - else - generate_threat() - generate_budgets() - set_cooldowns() - log_dynamic("Dynamic Mode initialized with a Threat Level of... [threat_level]! ([round_start_budget] round start budget)") - SSblackbox.record_feedback( - "associative", - "dynamic_threat", - 1, - list( - "server_name" = CONFIG_GET(string/serversqlname), - "forced_threat_level" = GLOB.dynamic_forced_threat_level, - "threat_level" = threat_level, - "max_threat" = (SSticker.totalPlayersReady < low_pop_player_threshold) ? LERP(low_pop_maximum_threat, max_threat_level, SSticker.totalPlayersReady / low_pop_player_threshold) : max_threat_level, - "player_count" = SSticker.totalPlayersReady, - "round_start_budget" = round_start_budget, - "parameters" = list( - "threat_curve_centre" = threat_curve_centre, - "threat_curve_width" = threat_curve_width, - "forced_extended" = GLOB.dynamic_forced_extended, - "no_stacking" = GLOB.dynamic_no_stacking, - "stacking_limit" = GLOB.dynamic_stacking_limit, - ), - ), - ) - return TRUE - -/datum/controller/subsystem/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/controller/subsystem/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 - -// Called BEFORE everyone is equipped with their job -/datum/controller/subsystem/dynamic/proc/pre_setup() - if(CONFIG_GET(flag/dynamic_config_enabled)) - var/json_file = file("[global.config.directory]/dynamic.json") - if(fexists(json_file)) - configuration = json_decode(file2text(json_file)) - if(configuration["Dynamic"]) - for(var/variable in configuration["Dynamic"]) - if(!(variable in vars)) - stack_trace("Invalid dynamic configuration variable [variable] in game mode variable changes.") - continue - vars[variable] = configuration["Dynamic"][variable] - - configure_station_trait_costs() - setup_parameters() - setup_hijacking() - setup_shown_threat() - setup_rulesets() - - //We do this here instead of with the midround rulesets and such because these rules can hang refs - //To new_player and such, and we want the datums to just free when the roundstart work is done - var/list/roundstart_rules = init_rulesets(/datum/dynamic_ruleset/roundstart) - - SSjob.DivideOccupations(pure = TRUE, allow_all = TRUE) - for(var/i in GLOB.new_player_list) - var/mob/dead/new_player/player = i - if(player.ready == PLAYER_READY_TO_PLAY && player.mind && player.check_preferences()) - if(is_unassigned_job(player.mind.assigned_role)) - var/list/job_data = list() - var/job_prefs = player.client.prefs.job_preferences - for(var/job in job_prefs) - var/priority = job_prefs[job] - job_data += "[job]: [SSjob.job_priority_level_to_string(priority)]" - to_chat(player, span_danger("You were unable to qualify for any roundstart antagonist role this round because your job preferences presented a high chance of all of your selected jobs being unavailable, along with 'return to lobby if job is unavailable' enabled. Increase the number of roles set to medium or low priority to reduce the chances of this happening.")) - log_admin("[player.ckey] failed to qualify for any roundstart antagonist role because their job preferences presented a high chance of all of their selected jobs being unavailable, along with 'return to lobby if job is unavailable' enabled and has [player.client.prefs.be_special.len] antag preferences enabled. They will be unable to qualify for any roundstart antagonist role. These are their job preferences - [job_data.Join(" | ")]") - else - roundstart_pop_ready++ - candidates.Add(player) - SSjob.ResetOccupations() - log_dynamic("Listing [roundstart_rules.len] round start rulesets, and [candidates.len] players ready.") - if (candidates.len <= 0) - log_dynamic("[candidates.len] candidates.") - return TRUE - - if(GLOB.dynamic_forced_roundstart_ruleset.len > 0) - rigged_roundstart() - else - roundstart(roundstart_rules) - - log_dynamic("[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], " - log_dynamic("Picked the following roundstart rules: [starting_rulesets]") - candidates.Cut() - return TRUE - -// Called AFTER everyone is equipped with their job -/datum/controller/subsystem/dynamic/proc/post_setup(report) - 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. - addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/controller/subsystem/dynamic/, execute_roundstart_rule), rule), rule.delay) - - if (!CONFIG_GET(flag/no_intercept_report)) - addtimer(CALLBACK(src, PROC_REF(send_intercept)), rand(waittime_l, waittime_h)) - - addtimer(CALLBACK(src, PROC_REF(display_roundstart_logout_report)), ROUNDSTART_LOGOUT_REPORT_TIME) - - if(CONFIG_GET(flag/reopen_roundstart_suicide_roles)) - var/delay = CONFIG_GET(number/reopen_roundstart_suicide_roles_delay) - if(delay) - delay *= (1 SECONDS) - else - delay = (4 MINUTES) //default to 4 minutes if the delay isn't defined. - addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(reopen_roundstart_suicide_roles)), delay) - - if(SSdbcore.Connect()) - var/list/to_set = list() - var/arguments = list() - if(GLOB.revdata.originmastercommit) - to_set += "commit_hash = :commit_hash" - arguments["commit_hash"] = GLOB.revdata.originmastercommit - if(to_set.len) - arguments["round_id"] = GLOB.round_id - var/datum/db_query/query_round_game_mode = SSdbcore.NewQuery( - "UPDATE [format_table_name("round")] SET [to_set.Join(", ")] WHERE id = :round_id", - arguments - ) - query_round_game_mode.Execute() - qdel(query_round_game_mode) - return TRUE - -/datum/controller/subsystem/dynamic/proc/display_roundstart_logout_report() - var/list/msg = list("[span_boldnotice("Roundstart logout report")]\n\n") - for(var/i in GLOB.mob_living_list) - var/mob/living/L = i - var/mob/living/carbon/C = L - if (istype(C) && !C.last_mind) - continue // never had a client - - if(L.ckey && !GLOB.directory[L.ckey]) - msg += "[L.name] ([L.key]), the [L.job] (Disconnected)\n" - - - if(L.ckey && L.client) - var/failed = FALSE - if(L.client.inactivity >= ROUNDSTART_LOGOUT_AFK_THRESHOLD) //Connected, but inactive (alt+tabbed or something) - msg += "[L.name] ([L.key]), the [L.job] (Connected, Inactive)\n" - failed = TRUE //AFK client - if(!failed && L.stat) - if(HAS_TRAIT(L, TRAIT_SUICIDED)) //Suicider - msg += "[L.name] ([L.key]), the [L.job] ([span_boldannounce("Suicide")])\n" - failed = TRUE //Disconnected client - if(!failed && (L.stat == UNCONSCIOUS || L.stat == HARD_CRIT)) - msg += "[L.name] ([L.key]), the [L.job] (Dying)\n" - failed = TRUE //Unconscious - if(!failed && L.stat == DEAD) - msg += "[L.name] ([L.key]), the [L.job] (Dead)\n" - failed = TRUE //Dead - - continue //Happy connected client - for(var/mob/dead/observer/D in GLOB.dead_mob_list) - if(D.mind && D.mind.current == L) - if(L.stat == DEAD) - if(HAS_TRAIT(L, TRAIT_SUICIDED)) //Suicider - msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] ([span_boldannounce("Suicide")])\n" - continue //Disconnected client - else - msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Dead)\n" - continue //Dead mob, ghost abandoned - else - if(D.can_reenter_corpse) - continue //Adminghost, or cult/wizard ghost - else - msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] ([span_boldannounce("Ghosted")])\n" - continue //Ghosted while alive - - var/concatenated_message = msg.Join() - log_admin(concatenated_message) - to_chat(GLOB.admins, concatenated_message) - - -/// Initializes the internal ruleset variables -/datum/controller/subsystem/dynamic/proc/setup_rulesets() - midround_rules = init_rulesets(/datum/dynamic_ruleset/midround) - latejoin_rules = init_rulesets(/datum/dynamic_ruleset/latejoin) - -/// Returns a list of the provided rulesets. -/// Configures their variables to match config. -/datum/controller/subsystem/dynamic/proc/init_rulesets(ruleset_subtype) - var/list/rulesets = list() - - for (var/datum/dynamic_ruleset/ruleset_type as anything in subtypesof(ruleset_subtype)) - if (initial(ruleset_type.name) == "") - continue - - if (initial(ruleset_type.weight) == 0) - continue - - var/ruleset = new ruleset_type - configure_ruleset(ruleset) - rulesets += ruleset - - return rulesets - -/// A simple roundstart proc used when dynamic_forced_roundstart_ruleset has rules in it. -/datum/controller/subsystem/dynamic/proc/rigged_roundstart() - message_admins("[GLOB.dynamic_forced_roundstart_ruleset.len] rulesets being forced. Will now attempt to draft players for them.") - log_dynamic("[GLOB.dynamic_forced_roundstart_ruleset.len] rulesets being forced. Will now attempt to draft players for them.") - for (var/datum/dynamic_ruleset/roundstart/rule in GLOB.dynamic_forced_roundstart_ruleset) - configure_ruleset(rule) - message_admins("Drafting players for forced ruleset [rule.name].") - log_dynamic("Drafting players for forced ruleset [rule.name].") - 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.load_templates() - if (rule.ready(roundstart_pop_ready, TRUE)) - var/cost = rule.cost - var/scaled_times = 0 - if (rule.scaling_cost) - 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/controller/subsystem/dynamic/proc/roundstart(list/roundstart_rules) - if (GLOB.dynamic_forced_extended) - log_dynamic("Starting a round of forced extended.") - return TRUE - var/list/drafted_rules = list() - for (var/datum/dynamic_ruleset/roundstart/rule in roundstart_rules) - 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() - rule.load_templates() - if (rule.ready(roundstart_pop_ready) && rule.candidates.len > 0) - drafted_rules[rule] = rule.weight - - var/list/rulesets_picked = list() - - // 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 - - while (round_start_budget_left > 0) - var/datum/dynamic_ruleset/roundstart/ruleset = pick_weight(drafted_rules) - if (isnull(ruleset)) - log_dynamic("No more rules can be applied, stopping with [round_start_budget] left.") - break - - 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)) - - update_log() - -/// Initializes the round start ruleset provided to it. Returns how much threat to spend. -/datum/controller/subsystem/dynamic/proc/picking_roundstart_rule(datum/dynamic_ruleset/roundstart/ruleset, scaled_times = 0, forced = FALSE) - log_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 \"[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. -/datum/controller/subsystem/dynamic/proc/execute_roundstart_rule(sent_rule) - var/datum/dynamic_ruleset/rule = sent_rule - if(rule.execute()) - if(rule.persistent) - current_rules += rule - new_snapshot(rule) - rule.forget_startup() - return TRUE - rule.clean_up() // Refund threat, delete teams and so on. - rule.forget_startup() - executed_rules -= rule - stack_trace("The starting rule \"[rule.name]\" failed to execute.") - return FALSE - -/// An experimental proc to allow admins to call rules on the fly or have rules call other rules. -/datum/controller/subsystem/dynamic/proc/picking_specific_rule(ruletype, forced = FALSE, ignore_cost = FALSE) - var/datum/dynamic_ruleset/midround/new_rule - if(ispath(ruletype)) - new_rule = new ruletype() // You should only use it to call midround rules though. - configure_ruleset(new_rule) // This makes sure the rule is set up properly. - else if(istype(ruletype, /datum/dynamic_ruleset)) - new_rule = ruletype - else - return FALSE - - if(!new_rule) - return FALSE - - if(!forced) - if(only_ruleset_executed) - return FALSE - // Check if a blocking ruleset has been executed. - else if(check_blocking(new_rule.blocking_rules, executed_rules)) - return FALSE - // Check if the ruleset is 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 - - var/population = GLOB.alive_player_list.len - if((new_rule.acceptable(population, threat_level) && (ignore_cost || new_rule.cost <= mid_round_budget)) || forced) - new_rule.trim_candidates() - new_rule.load_templates() - if (new_rule.ready(forced)) - if (!ignore_cost) - spend_midround_budget(new_rule.cost, threat_log, "[worldtime2text()]: Forced rule [new_rule.name]") - new_rule.pre_execute(population) - if (new_rule.execute()) // This should never fail since ready() returned 1 - if(new_rule.flags & HIGH_IMPACT_RULESET) - high_impact_ruleset_executed = TRUE - else if(new_rule.flags & ONLY_RULESET) - only_ruleset_executed = TRUE - log_dynamic("Making a call to a specific ruleset...[new_rule.name]!") - executed_rules += new_rule - if (new_rule.persistent) - current_rules += new_rule - new_rule.forget_startup() - return TRUE - else if (forced) - log_dynamic("The ruleset [new_rule.name] couldn't be executed due to lack of elligible players.") - new_rule.forget_startup() - return FALSE - -/datum/controller/subsystem/dynamic/fire() - 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 - - try_midround_roll() - -/// Removes type from the list -/datum/controller/subsystem/dynamic/proc/remove_from_list(list/type_list, type) - for(var/I in type_list) - if(istype(I, type)) - type_list -= I - return type_list - -/// Checks if a type in blocking_list is in rule_list. -/datum/controller/subsystem/dynamic/proc/check_blocking(list/blocking_list, list/rule_list) - if(blocking_list.len > 0) - for(var/blocking in blocking_list) - for(var/_executed in rule_list) - var/datum/executed = _executed - if(blocking == executed.type) - log_dynamic("FAIL: check_blocking - [blocking] conflicts with [executed.type]") - return TRUE - return FALSE - -/// Handles late-join antag assignments -/datum/controller/subsystem/dynamic/proc/make_antag_chance(mob/living/carbon/human/newPlayer) - if (GLOB.dynamic_forced_extended) - return - if(EMERGENCY_ESCAPED_OR_ENDGAMED) // No more rules after the shuttle has left - return - - if (forced_latejoin_rule) - log_dynamic("Forcing specific [forced_latejoin_rule.ruletype] ruleset [forced_latejoin_rule].") - if(!handle_executing_latejoin(forced_latejoin_rule, newPlayer, forced = TRUE)) - message_admins("The forced latejoin ruleset [forced_latejoin_rule.name] couldn't be executed \ - as the most recent latejoin did not fulfill the ruleset's requirements.") - forced_latejoin_rule = null - return - - if(!forced_injection) - if(latejoin_injection_cooldown >= world.time) - return - if(!prob(latejoin_roll_chance)) - return - - var/was_forced = forced_injection - forced_injection = FALSE - var/list/possible_latejoin_rules = list() - for (var/datum/dynamic_ruleset/latejoin/rule in latejoin_rules) - if(!rule.weight) - continue - if(mid_round_budget < rule.cost) - continue - if(!rule.acceptable(GLOB.alive_player_list.len, threat_level)) - continue - possible_latejoin_rules[rule] = rule.get_weight() - - if(!length(possible_latejoin_rules)) - log_dynamic("FAIL: [newPlayer] was selected to roll for a latejoin ruleset, but there were no valid rulesets.") - return - - log_dynamic("[newPlayer] was selected to roll for a latejoin ruleset from the following list: [english_list(possible_latejoin_rules)].") - // You get one shot at becoming a latejoin antag, if it fails the next guy will try. - var/datum/dynamic_ruleset/latejoin/picked_rule = pick_ruleset(possible_latejoin_rules, max_allowed_attempts = 1) - if(isnull(picked_rule)) - log_dynamic("FAIL: No valid rulset was selected for [newPlayer]'s latejoin[was_forced ? "" : ", the next player will be checked instead"].") - return - if(was_forced) - log_dynamic("Forcing random [picked_rule.ruletype] ruleset [picked_rule].") - handle_executing_latejoin(picked_rule, newPlayer, forced = was_forced) - -/** - * This proc handles the execution of a latejoin ruleset, including removing it from latejoin rulesets if not repeatable, - * upping the injection cooldown, and starting a timer to execute the ruleset on delay. - */ -/datum/controller/subsystem/dynamic/proc/handle_executing_latejoin(datum/dynamic_ruleset/ruleset, mob/living/carbon/human/only_candidate, forced = FALSE) - ruleset.candidates = list(only_candidate) - ruleset.trim_candidates() - ruleset.load_templates() - if (!ruleset.ready(forced)) - log_dynamic("FAIL: [only_candidate] was selected to latejoin with the [ruleset] ruleset, \ - but the ruleset failed to execute[length(ruleset.candidates) ? "":" as they were not a valid candiate"].") - return FALSE - if (!ruleset.repeatable) - latejoin_rules = remove_from_list(latejoin_rules, ruleset.type) - addtimer(CALLBACK(src, PROC_REF(execute_midround_latejoin_rule), ruleset), ruleset.delay) - - if(!forced) - 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 - log_dynamic("A latejoin rulset triggered successfully, the next latejoin injection will happen at [latejoin_injection_cooldown] round time.") - - return TRUE - -/// Apply configurations to rule. -/datum/controller/subsystem/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] - ruleset.restricted_roles |= SSstation.antag_restricted_roles - if(length(ruleset.protected_roles)) //if we care to protect any role, we should protect station trait roles too - ruleset.protected_roles |= SSstation.antag_protected_roles - 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 |= JOB_ASSISTANT - -/// Get station traits and call for their config -/datum/controller/subsystem/dynamic/proc/configure_station_trait_costs() - if(!CONFIG_GET(flag/dynamic_config_enabled)) - return - for(var/datum/station_trait/station_trait as anything in GLOB.dynamic_station_traits) - configure_station_trait(station_trait) - -/// Apply configuration for station trait costs -/datum/controller/subsystem/dynamic/proc/configure_station_trait(datum/station_trait/station_trait) - var/list/station_trait_config = LAZYACCESSASSOC(configuration, "Station", station_trait.dynamic_threat_id) - var/cost = station_trait_config["cost"] - - if(isnull(cost)) //0 is valid so check for null specifically - return - - if(cost != GLOB.dynamic_station_traits[station_trait]) - log_dynamic("Config set [station_trait.dynamic_threat_id] cost from [station_trait.threat_reduction] to [cost]") - - GLOB.dynamic_station_traits[station_trait] = cost - -/// Refund threat, but no more than threat_level. -/datum/controller/subsystem/dynamic/proc/refund_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/controller/subsystem/dynamic/proc/create_threat(gain, list/threat_log, reason) - mid_round_budget = min(100, mid_round_budget + gain) - if(mid_round_budget > threat_level) - threat_level = mid_round_budget - for(var/list/logs in threat_log) - log_threat(gain, logs, reason) - -/datum/controller/subsystem/dynamic/proc/log_threat(threat_change, list/threat_log, reason) - var/gain_or_loss = "+" - if(threat_change < 0) - gain_or_loss = "-" - threat_log += "Threat [gain_or_loss][abs(threat_change)] - [reason]." - -/// Expend round start threat, can't fall under 0. -/datum/controller/subsystem/dynamic/proc/spend_roundstart_budget(cost, list/threat_log, reason) - round_start_budget = max(round_start_budget - cost,0) - if (!isnull(threat_log)) - log_threat(-cost, threat_log, reason) - -/// Expend midround threat, can't fall under 0. -/datum/controller/subsystem/dynamic/proc/spend_midround_budget(cost, list/threat_log, reason) - mid_round_budget = max(mid_round_budget - cost,0) - if (!isnull(threat_log)) - log_threat(-cost, threat_log, reason) - -#define MAXIMUM_DYN_DISTANCE 5 - -/** - * Returns the comulative distribution of threat centre and width, and a random location of -0.5 to 0.5 - * plus or minus the otherwise unattainable lower and upper percentiles. All multiplied by the maximum - * threat and then rounded to the nearest interval. - * rand() calls without arguments returns a value between 0 and 1, allowing for smaller intervals. - */ -/datum/controller/subsystem/dynamic/proc/lorentz_to_amount(centre = 0, scale = 1.8, max_threat = 100, interval = 1) - var/location = RANDOM_DECIMAL(-MAXIMUM_DYN_DISTANCE, MAXIMUM_DYN_DISTANCE) * rand() - var/lorentz_result = LORENTZ_CUMULATIVE_DISTRIBUTION(centre, location, scale) - var/std_threat = lorentz_result * max_threat - ///Without these, the amount won't come close to hitting 0% or 100% of the max threat. - var/lower_deviation = max(std_threat * (location-centre)/MAXIMUM_DYN_DISTANCE, 0) - var/upper_deviation = max((max_threat - std_threat) * (centre-location)/MAXIMUM_DYN_DISTANCE, 0) - return clamp(round(std_threat + upper_deviation - lower_deviation, interval), 0, 100) - -/proc/reopen_roundstart_suicide_roles() - var/include_command = CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_positions) - var/list/reopened_jobs = list() - - for(var/mob/living/quitter in GLOB.suicided_mob_list) - var/datum/job/job = SSjob.GetJob(quitter.job) - if(!job || !(job.job_flags & JOB_REOPEN_ON_ROUNDSTART_LOSS)) - continue - if(!include_command && job.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND) - continue - job.current_positions = max(job.current_positions - 1, 0) - reopened_jobs += quitter.job - - if(CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_report)) - if(reopened_jobs.len) - var/reopened_job_report_positions - for(var/dead_dudes_job in reopened_jobs) - reopened_job_report_positions = "[reopened_job_report_positions ? "[reopened_job_report_positions]\n":""][dead_dudes_job]" - - var/suicide_command_report = {" - [command_name()] Human Resources Board
- Notice of Personnel Change

- To personnel management staff aboard [station_name()]:

- Our medical staff have detected a series of anomalies in the vital sensors - of some of the staff aboard your station.

- Further investigation into the situation on our end resulted in us discovering - a series of rather... unforturnate decisions that were made on the part of said staff.

- As such, we have taken the liberty to automatically reopen employment opportunities for the positions of the crew members - who have decided not to partake in our research. We will be forwarding their cases to our employment review board - to determine their eligibility for continued service with the company (and of course the - continued storage of cloning records within the central medical backup server.)

- The following positions have been reopened on our behalf:

- [reopened_job_report_positions]
- "} - - print_command_report(suicide_command_report, "Central Command Personnel Update") - - -#undef MAXIMUM_DYN_DISTANCE - -#undef FAKE_REPORT_CHANCE -#undef FAKE_GREENSHIFT_FORM_CHANCE -#undef PULSAR_REPORT_CHANCE -#undef REPORT_NEG_DIVERGENCE -#undef REPORT_POS_DIVERGENCE diff --git a/code/controllers/subsystem/dynamic/dynamic_hijacking.dm b/code/controllers/subsystem/dynamic/dynamic_hijacking.dm deleted file mode 100644 index 7577cbcd84f6..000000000000 --- a/code/controllers/subsystem/dynamic/dynamic_hijacking.dm +++ /dev/null @@ -1,25 +0,0 @@ -/datum/controller/subsystem/dynamic/proc/setup_hijacking() - RegisterSignal(SSdcs, COMSIG_GLOB_PRE_RANDOM_EVENT, PROC_REF(on_pre_random_event)) - -/datum/controller/subsystem/dynamic/proc/on_pre_random_event(datum/source, datum/round_event_control/round_event_control) - SIGNAL_HANDLER - if (!round_event_control.dynamic_should_hijack) - return - - if (random_event_hijacked != HIJACKED_NOTHING) - log_dynamic_and_announce("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 - log_dynamic_and_announce("Random event [round_event_control.name] tried to roll, but the last midround injection \ - was too recent. Heavy injection chance has been raised to [get_heavy_midround_injection_chance(dry_run = TRUE)]%.") - return CANCEL_PRE_RANDOM_EVENT - - if (next_midround_injection() - world.time < time_range) - log_dynamic_and_announce("Random event [round_event_control.name] tried to roll, but the next midround injection is too soon.") - return CANCEL_PRE_RANDOM_EVENT diff --git a/code/controllers/subsystem/dynamic/dynamic_logging.dm b/code/controllers/subsystem/dynamic/dynamic_logging.dm deleted file mode 100644 index 16bd56a73031..000000000000 --- a/code/controllers/subsystem/dynamic/dynamic_logging.dm +++ /dev/null @@ -1,101 +0,0 @@ -/// 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 - -/// Updates the log for the current snapshots. -/datum/controller/subsystem/dynamic/proc/update_log() - 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 - serialized["shown_threat"] = shown_threat - - var/list/serialized_snapshots = list() - for (var/datum/dynamic_snapshot/snapshot as anything in snapshots) - serialized_snapshots += list(snapshot.to_list()) - serialized["snapshots"] = serialized_snapshots - - rustg_file_write(json_encode(serialized), "[GLOB.log_directory]/dynamic.json") - -/// Creates a new snapshot with the given rulesets chosen, and writes to the JSON output. -/datum/controller/subsystem/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 = GLOB.alive_player_list.len - new_snapshot.dead_players = GLOB.dead_player_list.len - new_snapshot.observers = GLOB.current_observers_list.len - new_snapshot.total_players = new_snapshot.alive_players + new_snapshot.dead_players + new_snapshot.observers - new_snapshot.alive_antags = GLOB.current_living_antags.len - new_snapshot.ruleset_chosen = new /datum/dynamic_snapshot_ruleset(ruleset_chosen) - - LAZYADD(snapshots, new_snapshot) - - update_log() diff --git a/code/controllers/subsystem/dynamic/dynamic_midround_rolling.dm b/code/controllers/subsystem/dynamic/dynamic_midround_rolling.dm deleted file mode 100644 index 968037b9fa2e..000000000000 --- a/code/controllers/subsystem/dynamic/dynamic_midround_rolling.dm +++ /dev/null @@ -1,108 +0,0 @@ -/// Returns the world.time of the next midround injection. -/// Will return a cached result from `next_midround_injection`, the variable. -/// If that variable is null, will generate a new one. -/datum/controller/subsystem/dynamic/proc/next_midround_injection() - if (!isnull(next_midround_injection)) - return next_midround_injection - - // Admins can futz around with the midround threat, and we want to be able to react to that - var/midround_threat = threat_level - round_start_budget - - var/rolls = CEILING(midround_threat / threat_per_midround_roll, 1) - var/distance = ((1 / (rolls + 1)) * midround_upper_bound) + midround_lower_bound - - if (last_midround_injection_attempt == 0) - last_midround_injection_attempt = SSticker.round_start_time - - return last_midround_injection_attempt + distance - -/datum/controller/subsystem/dynamic/proc/try_midround_roll() - if (!forced_injection && next_midround_injection() > world.time) - return - - if (GLOB.dynamic_forced_extended) - return - - if (EMERGENCY_PAST_POINT_OF_NO_RETURN) - return - - var/spawn_heavy = prob(get_heavy_midround_injection_chance()) - - last_midround_injection_attempt = world.time - next_midround_injection = null - forced_injection = FALSE - - log_dynamic_and_announce("A midround ruleset is rolling, and will be [spawn_heavy ? "HEAVY" : "LIGHT"].") - - random_event_hijacked = HIJACKED_NOTHING - - var/list/drafted_heavies = list() - var/list/drafted_lights = list() - - for (var/datum/dynamic_ruleset/midround/ruleset in midround_rules) - if (ruleset.weight == 0) - log_dynamic("FAIL: [ruleset] has a weight of 0") - continue - - if (!ruleset.acceptable(GLOB.alive_player_list.len, threat_level)) - var/ruleset_forced = GLOB.dynamic_forced_rulesets[type] || RULESET_NOT_FORCED - if (ruleset_forced == RULESET_NOT_FORCED) - log_dynamic("FAIL: [ruleset] is not acceptable with the current parameters. Alive players: [GLOB.alive_player_list.len], threat level: [threat_level]") - else - log_dynamic("FAIL: [ruleset] was disabled.") - continue - - if (mid_round_budget < ruleset.cost) - log_dynamic("FAIL: [ruleset] is too expensive, and cannot be bought. Midround budget: [mid_round_budget], ruleset cost: [ruleset.cost]") - continue - - if (ruleset.minimum_round_time > world.time - SSticker.round_start_time) - log_dynamic("FAIL: [ruleset] is trying to run too early. Minimum round time: [ruleset.minimum_round_time], current round time: [world.time - SSticker.round_start_time]") - continue - - // If admins have disabled dynamic from picking from the ghost pool - if(istype(ruleset, /datum/dynamic_ruleset/midround/from_ghosts) && !(GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT)) - log_dynamic("FAIL: [ruleset] is a from_ghosts ruleset, but ghost roles are disabled") - continue - - ruleset.trim_candidates() - ruleset.load_templates() - if (!ruleset.ready()) - log_dynamic("FAIL: [ruleset] is not ready()") - continue - - var/ruleset_is_heavy = (ruleset.midround_ruleset_style == MIDROUND_RULESET_STYLE_HEAVY) - if (ruleset_is_heavy) - drafted_heavies[ruleset] = ruleset.get_weight() - else - drafted_lights[ruleset] = ruleset.get_weight() - - var/heavy_light_log_count = "[drafted_heavies.len] heavies / [drafted_lights.len] lights" - - log_dynamic("Rolling [spawn_heavy ? "HEAVY" : "LIGHT"]... [heavy_light_log_count]") - - if (spawn_heavy && drafted_heavies.len > 0 && pick_midround_rule(drafted_heavies, "heavy rulesets")) - return - else if (drafted_lights.len > 0 && pick_midround_rule(drafted_lights, "light rulesets")) - if (spawn_heavy) - log_dynamic_and_announce("A heavy ruleset was intended to roll, but there weren't any available. [heavy_light_log_count]") - else - log_dynamic_and_announce("No midround rulesets could be drafted. ([heavy_light_log_count])") - -/// Gets the chance for a heavy ruleset midround injection, the dry_run argument is only used for forced injection. -/datum/controller/subsystem/dynamic/proc/get_heavy_midround_injection_chance(dry_run) - var/chance_modifier = 1 - var/next_midround_roll = next_midround_injection() - SSticker.round_start_time - - if (random_event_hijacked != HIJACKED_NOTHING) - chance_modifier += (hijacked_random_event_injection_chance_modifier / 100) - - if (GLOB.current_living_antags.len == 0) - chance_modifier += 0.5 - - if (GLOB.dead_player_list.len > GLOB.alive_player_list.len) - chance_modifier -= 0.3 - - var/heavy_coefficient = CLAMP01((next_midround_roll - midround_light_upper_bound) / (midround_heavy_lower_bound - midround_light_upper_bound)) - - return 100 * (heavy_coefficient * max(1, chance_modifier)) diff --git a/code/controllers/subsystem/dynamic/dynamic_rulesets.dm b/code/controllers/subsystem/dynamic/dynamic_rulesets.dm deleted file mode 100644 index b5f84176d594..000000000000 --- a/code/controllers/subsystem/dynamic/dynamic_rulesets.dm +++ /dev/null @@ -1,285 +0,0 @@ -/datum/dynamic_ruleset - /// For admin logging and round end screen. - // If you want to change this variable name, the force latejoin/midround rulesets - // to not use sort_names. - var/name = "" - /// For admin logging and round end screen, do not change this unless making a new rule type. - 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 - /// If set to TRUE, dynamic will be able to draft this ruleset again later on. (doesn't apply for roundstart rules) - 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 - /// List of players that are being drafted for this rule - var/list/mob/candidates = list() - /// List of players that were selected for this rule. This can be minds, or mobs. - var/list/assigned = list() - /// Preferences flag such as ROLE_WIZARD that need to be turned on for players to be antag. - 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. - var/minimum_required_age = 7 - /// 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() - /// If set, rule will only accept candidates from those roles. If on a roundstart ruleset, requires the player to have the correct antag pref enabled and any of the possible roles enabled. - 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( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_SECURITY, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - /// 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) - /// The rule needs this many candidates (post-trimming) to be executed (example: Cult needs 4 players at round start) - 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 threat level when the rule is executed. - var/cost = 0 - /// Cost per level the rule scales up. - var/scaling_cost = 0 - /// How many times a rule has scaled up upon getting picked. - var/scaled_times = 0 - /// Used for the roundend report - var/total_cost = 0 - /// A flag that determines how the ruleset is handled. Check __DEFINES/dynamic.dm for an explanation of the accepted values. - var/flags = NONE - /// Pop range per requirement. If zero defaults to dynamic'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) - /// If a role is to be considered another for the purpose of banning. - var/antag_flag_override = null - /// If set, will check this preference instead of antag_flag. - var/antag_preference = 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. - var/minimum_players = 0 - /// The maximum amount of players required for the rule to be considered. - /// Anything below zero or exactly zero is ignored. - var/maximum_players = 0 - /// Calculated during acceptable(), used in scaling and team sizes. - var/indice_pop = 0 - /// 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 - - /// A list, or null, of templates that the ruleset depends on to function correctly - var/list/ruleset_lazy_templates - -/datum/dynamic_ruleset/New() - // Rulesets can be instantiated more than once, such as when an admin clicks - // "Execute Midround Ruleset". Thus, it would be wrong to perform any - // side effects here. Dynamic rulesets should be stateless anyway. - SHOULD_NOT_OVERRIDE(TRUE) - - ..() - -/datum/dynamic_ruleset/roundstart // One or more of those drafted at roundstart - ruletype = ROUNDSTART_RULESET - -// Can be drafted when a player joins the server -/datum/dynamic_ruleset/latejoin - ruletype = LATEJOIN_RULESET - -/// By default, a rule is acceptable if it satisfies the threat level/population requirements. -/// If your rule has extra checks, such as counting security officers, do that in ready() instead -/datum/dynamic_ruleset/proc/acceptable(population = 0, threat_level = 0) - var/ruleset_forced = GLOB.dynamic_forced_rulesets[type] || RULESET_NOT_FORCED - if (ruleset_forced != RULESET_NOT_FORCED) - if (ruleset_forced == RULESET_FORCE_ENABLED) - return TRUE - else - log_dynamic("FAIL: [src] was disabled in admin panel.") - return FALSE - - if(!is_valid_population(population)) - var/range = maximum_players > 0 ? "([minimum_players] - [maximum_players])" : "(minimum: [minimum_players])" - log_dynamic("FAIL: [src] failed acceptable: min/max players out of range [range] vs population ([population])") - return FALSE - - if (!is_valid_threat(population, threat_level)) - log_dynamic("FAIL: [src] failed acceptable: threat_level ([threat_level]) < requirement ([requirements[indice_pop]])") - return FALSE - - return TRUE - -/// Returns true if we have enough players to run -/datum/dynamic_ruleset/proc/is_valid_population(population) - if(minimum_players > population) - return FALSE - if(maximum_players > 0 && population > maximum_players) - return FALSE - return TRUE - -/// Sets the current threat indices and returns true if we're inside of them -/datum/dynamic_ruleset/proc/is_valid_threat(population, threat_level) - pop_per_requirement = pop_per_requirement > 0 ? pop_per_requirement : SSdynamic.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 (SSdynamic.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)) / SSdynamic.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) / SSdynamic.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() - return - -/// Called on pre_setup for roundstart rulesets. -/// Do everything you need to do before job is assigned here. -/// IMPORTANT: ASSIGN special_role HERE -/datum/dynamic_ruleset/proc/pre_execute() - return TRUE - -/// Called on post_setup on roundstart and when the rule executes on midround and latejoin. -/// Give your candidates or assignees equipment and antag datum here. -/datum/dynamic_ruleset/proc/execute() - for(var/datum/mind/M in assigned) - M.add_antag_datum(antag_datum) - GLOB.pre_setup_antags -= M - return TRUE - -/// Rulesets can be reused, so when we're done setting one up we want to wipe its memory of the people it was selecting over -/// This isn't Destroy we aren't deleting it here, rulesets free when nothing holds a ref. This is just to prevent hung refs. -/datum/dynamic_ruleset/proc/forget_startup() - SHOULD_CALL_PARENT(TRUE) - candidates = list() - assigned = list() - antag_datum = null - -/// 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) - return check_candidates() - -/// This should always be called before ready is, to ensure that the ruleset can locate map/template based landmarks as needed -/datum/dynamic_ruleset/proc/load_templates() - for(var/template in ruleset_lazy_templates) - SSmapping.lazy_load_template(template) - -/// 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() - SSdynamic.refund_threat(cost + (scaled_times * scaling_cost)) - SSdynamic.threat_log += "[worldtime2text()]: [ruletype] [name] refunded [cost + (scaled_times * scaling_cost)]. Failed to execute." - -/// Gets weight of the ruleset -/// Note that this decreases weight if repeatable is TRUE and repeatable_weight_decrease is higher than 0 -/// Note: If you don't want repeatable rulesets to decrease their weight use the weight variable directly -/datum/dynamic_ruleset/proc/get_weight() - if(repeatable && weight > 1 && repeatable_weight_decrease > 0) - for(var/datum/dynamic_ruleset/DR in SSdynamic.executed_rules) - if(istype(DR, type)) - weight = max(weight-repeatable_weight_decrease,1) - return weight - -/// Checks if there are enough candidates to run, and logs otherwise -/datum/dynamic_ruleset/proc/check_candidates() - if (required_candidates <= candidates.len) - return TRUE - - log_dynamic("FAIL: [src] does not have enough candidates ([required_candidates] needed, [candidates.len] found)") - return FALSE - -/// Here you can remove candidates that do not meet your requirements. -/// This means if their job is not correct or they have disconnected you can remove them from candidates here. -/// Usually this does not need to be changed unless you need some specific requirements from your candidates. -/datum/dynamic_ruleset/proc/trim_candidates() - return - -/// Set mode_result and news report here. -/// Only called if ruleset is flagged as HIGH_IMPACT_RULESET -/datum/dynamic_ruleset/proc/round_result() - -////////////////////////////////////////////// -// // -// ROUNDSTART RULESETS // -// // -////////////////////////////////////////////// - -/// 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/candidate_player in candidates) - var/client/candidate_client = GET_CLIENT(candidate_player) - if (!candidate_client || !candidate_player.mind) // Are they connected? - candidates.Remove(candidate_player) - continue - - if(candidate_client.get_remaining_days(minimum_required_age) > 0) - candidates.Remove(candidate_player) - continue - - if(candidate_player.mind.special_role) // We really don't want to give antag to an antag. - candidates.Remove(candidate_player) - continue - - if (!((antag_preference || antag_flag) in candidate_client.prefs.be_special)) - candidates.Remove(candidate_player) - continue - - if (is_banned_from(candidate_player.ckey, list(antag_flag_override || antag_flag, ROLE_SYNDICATE))) - candidates.Remove(candidate_player) - continue - - // If this ruleset has exclusive_roles set, we want to only consider players who have those - // job prefs enabled and are eligible to play that job. Otherwise, continue as before. - if(length(exclusive_roles)) - var/exclusive_candidate = FALSE - for(var/role in exclusive_roles) - var/datum/job/job = SSjob.GetJob(role) - - if((role in candidate_client.prefs.job_preferences) && SSjob.check_job_eligibility(candidate_player, job, "Dynamic Roundstart TC", add_job_to_log = TRUE) == JOB_AVAILABLE) - exclusive_candidate = TRUE - break - - // If they didn't have any of the required job prefs enabled or were banned from all enabled prefs, - // they're not eligible for this antag type. - if(!exclusive_candidate) - candidates.Remove(candidate_player) - -/// 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(population, forced = FALSE) - return ..() diff --git a/code/controllers/subsystem/dynamic/dynamic_rulesets_latejoin.dm b/code/controllers/subsystem/dynamic/dynamic_rulesets_latejoin.dm deleted file mode 100644 index d853876fed14..000000000000 --- a/code/controllers/subsystem/dynamic/dynamic_rulesets_latejoin.dm +++ /dev/null @@ -1,254 +0,0 @@ -////////////////////////////////////////////// -// // -// LATEJOIN RULESETS // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/latejoin/trim_candidates() - for(var/mob/P in candidates) - if(!P.client || !P.mind || is_unassigned_job(P.mind.assigned_role)) // Are they connected? - candidates.Remove(P) - else if (P.client.get_remaining_days(minimum_required_age) > 0) - candidates.Remove(P) - else if(P.mind.assigned_role.title in restricted_roles) // Does their job allow for it? - candidates.Remove(P) - else if((exclusive_roles.len > 0) && !(P.mind.assigned_role.title in exclusive_roles)) // Is the rule exclusive to their job? - candidates.Remove(P) - else if (!((antag_preference || antag_flag) in P.client.prefs.be_special) || is_banned_from(P.ckey, list(antag_flag_override || antag_flag, ROLE_SYNDICATE))) - candidates.Remove(P) - -/datum/dynamic_ruleset/latejoin/ready(forced = 0) - if (forced) - return ..() - - var/job_check = 0 - if (enemy_roles.len > 0) - for (var/mob/M in GLOB.alive_player_list) - if (M.stat == DEAD) - continue // Dead players cannot count as opponents - if (M.mind && (M.mind.assigned_role.title in enemy_roles) && (!(M in candidates) || (M.mind.assigned_role.title in restricted_roles))) - job_check++ // Checking for "enemies" (such as sec officers). To be counters, they must either not be candidates to that rule, or have a job that restricts them from it - - var/threat = round(SSdynamic.threat_level/10) - var/ruleset_forced = (GLOB.dynamic_forced_rulesets[type] || RULESET_NOT_FORCED) == RULESET_FORCE_ENABLED - if (!ruleset_forced && job_check < required_enemies[threat]) - log_dynamic("FAIL: [src] is not ready, because there are not enough enemies: [required_enemies[threat]] needed, [job_check] found") - return FALSE - - return ..() - -/datum/dynamic_ruleset/latejoin/execute() - var/mob/M = pick(candidates) - assigned += M.mind - M.mind.special_role = antag_flag - M.mind.add_antag_datum(antag_datum) - return TRUE - -////////////////////////////////////////////// -// // -// SYNDICATE TRAITORS // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/latejoin/infiltrator - name = "Syndicate Infiltrator" - antag_datum = /datum/antagonist/traitor/infiltrator - antag_flag = ROLE_SYNDICATE_INFILTRATOR - antag_flag_override = ROLE_TRAITOR - protected_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_PERSONNEL, - JOB_HEAD_OF_SECURITY, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ) - required_candidates = 1 - weight = 11 - cost = 5 - requirements = list(5,5,5,5,5,5,5,5,5,5) - repeatable = TRUE - -////////////////////////////////////////////// -// // -// REVOLUTIONARY PROVOCATEUR // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/latejoin/provocateur - name = "Provocateur" - persistent = TRUE - antag_datum = /datum/antagonist/rev/head - antag_flag = ROLE_PROVOCATEUR - antag_flag_override = ROLE_REV_HEAD - restricted_roles = list( - JOB_AI, - JOB_CAPTAIN, - JOB_CHIEF_ENGINEER, - JOB_CHIEF_MEDICAL_OFFICER, - JOB_CYBORG, - JOB_DETECTIVE, - JOB_HEAD_OF_PERSONNEL, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_QUARTERMASTER, - JOB_RESEARCH_DIRECTOR, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - enemy_roles = list( - JOB_AI, - JOB_CYBORG, - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_SECURITY, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - weight = 1 - delay = 1 MINUTES // Prevents rule start while head is offstation. - cost = 10 - requirements = list(101,101,70,40,30,20,20,20,20,20) - flags = HIGH_IMPACT_RULESET - blocking_rules = list(/datum/dynamic_ruleset/roundstart/revs) - var/required_heads_of_staff = 3 - var/finished = FALSE - var/datum/team/revolution/revolution - -/datum/dynamic_ruleset/latejoin/provocateur/ready(forced=FALSE) - if (forced) - required_heads_of_staff = 1 - if(!..()) - return FALSE - var/head_check = 0 - for(var/mob/player in GLOB.alive_player_list) - if (player.mind.assigned_role.job_flags & JOB_HEAD_OF_STAFF) - head_check++ - return (head_check >= required_heads_of_staff) - -/datum/dynamic_ruleset/latejoin/provocateur/execute() - var/mob/M = pick(candidates) // This should contain a single player, but in case. - if(check_eligible(M.mind)) // Didnt die/run off z-level/get implanted since leaving shuttle. - assigned += M.mind - M.mind.special_role = antag_flag - revolution = new() - var/datum/antagonist/rev/head/new_head = new() - new_head.give_flash = TRUE - new_head.give_hud = TRUE - new_head.remove_clumsy = TRUE - new_head = M.mind.add_antag_datum(new_head, revolution) - revolution.update_objectives() - revolution.update_rev_heads() - SSshuttle.registerHostileEnvironment(revolution) - return TRUE - else - log_dynamic("[ruletype] [name] discarded [M.name] from head revolutionary due to ineligibility.") - log_dynamic("[ruletype] [name] failed to get any eligible headrevs. Refunding [cost] threat.") - return FALSE - -/datum/dynamic_ruleset/latejoin/provocateur/rule_process() - var/winner = revolution.process_victory() - if (isnull(winner)) - return - - finished = winner - - if(winner == REVOLUTION_VICTORY) - GLOB.revolutionary_win = TRUE - - return RULESET_STOP_PROCESSING - -/// Checks for revhead loss conditions and other antag datums. -/datum/dynamic_ruleset/latejoin/provocateur/proc/check_eligible(datum/mind/M) - var/turf/T = get_turf(M.current) - if(!considered_afk(M) && considered_alive(M) && is_station_level(T.z) && !M.antag_datums?.len && !HAS_TRAIT(M, TRAIT_MINDSHIELD)) - return TRUE - return FALSE - -/datum/dynamic_ruleset/latejoin/provocateur/round_result() - revolution.round_result(finished) - -////////////////////////////////////////////// -// // -// HERETIC SMUGGLER // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/latejoin/heretic_smuggler - name = "Heretic Smuggler" - antag_datum = /datum/antagonist/heretic - antag_flag = ROLE_HERETIC_SMUGGLER - antag_flag_override = ROLE_HERETIC - protected_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_PERSONNEL, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ) - required_candidates = 1 - weight = 8 - cost = 6 - requirements = list(101,101,50,10,10,10,10,10,10,10) - repeatable = TRUE - -/datum/dynamic_ruleset/latejoin/heretic_smuggler/execute() - var/mob/picked_mob = pick(candidates) - assigned += picked_mob.mind - picked_mob.mind.special_role = antag_flag - var/datum/antagonist/heretic/new_heretic = picked_mob.mind.add_antag_datum(antag_datum) - - // Heretics passively gain influence over time. - // As a consequence, latejoin heretics start out at a massive - // disadvantage if the round's been going on for a while. - // Let's give them some influence points when they arrive. - new_heretic.knowledge_points += round((world.time - SSticker.round_start_time) / new_heretic.passive_gain_timer) - // BUT let's not give smugglers a million points on arrival. - // Limit it to four missed passive gain cycles (4 points). - new_heretic.knowledge_points = min(new_heretic.knowledge_points, 5) - - return TRUE - -/// Ruleset for latejoin changelings -/datum/dynamic_ruleset/latejoin/stowaway_changeling - name = "Stowaway Changeling" - antag_datum = /datum/antagonist/changeling - antag_flag = ROLE_STOWAWAY_CHANGELING - antag_flag_override = ROLE_CHANGELING - protected_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_PERSONNEL, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ) - required_candidates = 1 - weight = 2 - cost = 12 - requirements = list(101,101,60,50,40,20,20,10,10,10) - repeatable = TRUE - -/datum/dynamic_ruleset/latejoin/stowaway_changeling/execute() - var/mob/picked_mob = pick(candidates) - assigned += picked_mob.mind - picked_mob.mind.special_role = antag_flag - picked_mob.mind.add_antag_datum(antag_datum) - return TRUE diff --git a/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm b/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm deleted file mode 100644 index 55fff992602b..000000000000 --- a/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm +++ /dev/null @@ -1,933 +0,0 @@ -/// Probability the AI going malf will be accompanied by an ion storm announcement and some ion laws. -#define MALF_ION_PROB 33 -/// The probability to replace an existing law with an ion law instead of adding a new ion law. -#define REPLACE_LAW_WITH_ION_PROB 10 - -/// Midround Rulesets -/datum/dynamic_ruleset/midround // Can be drafted once in a while during a round - ruletype = MIDROUND_RULESET - var/midround_ruleset_style - /// If the ruleset should be restricted from ghost roles. - var/restrict_ghost_roles = TRUE - /// What mob type the ruleset is restricted to. - var/required_type = /mob/living/carbon/human - var/list/living_players = list() - var/list/living_antags = list() - var/list/dead_players = list() - var/list/list_observers = list() - - /// The minimum round time before this ruleset will show up - var/minimum_round_time = 0 - /// Abstract root value - var/abstract_type = /datum/dynamic_ruleset/midround - -/datum/dynamic_ruleset/midround/forget_startup() - living_players = list() - living_antags = list() - dead_players = list() - list_observers = list() - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts - weight = 0 - required_type = /mob/dead/observer - abstract_type = /datum/dynamic_ruleset/midround/from_ghosts - /// Whether the ruleset should call generate_ruleset_body or not. - var/makeBody = TRUE - /// The rule needs this many applicants to be properly executed. - var/required_applicants = 1 - -/datum/dynamic_ruleset/midround/from_ghosts/check_candidates() - var/dead_count = dead_players.len + list_observers.len - if (required_candidates <= dead_count) - return TRUE - - log_dynamic("FAIL: [src], a from_ghosts ruleset, did not have enough dead candidates: [required_candidates] needed, [dead_count] found") - - return FALSE - -/datum/dynamic_ruleset/midround/trim_candidates() - living_players = trim_list(GLOB.alive_player_list) - living_antags = trim_list(GLOB.current_living_antags) - dead_players = trim_list(GLOB.dead_player_list) - list_observers = trim_list(GLOB.current_observers_list) - -/datum/dynamic_ruleset/midround/proc/trim_list(list/to_trim = list()) - var/list/trimmed_list = to_trim.Copy() - for(var/mob/creature in trimmed_list) - if (!istype(creature, required_type)) - trimmed_list.Remove(creature) - continue - if (isnull(creature.client)) // Are they connected? - trimmed_list.Remove(creature) - continue - if(creature.client.get_remaining_days(minimum_required_age) > 0) - trimmed_list.Remove(creature) - continue - if (!((antag_preference || antag_flag) in creature.client.prefs.be_special)) - trimmed_list.Remove(creature) - continue - if (is_banned_from(creature.ckey, list(antag_flag_override || antag_flag, ROLE_SYNDICATE))) - trimmed_list.Remove(creature) - continue - - if (isnull(creature.mind)) - continue - - if (restrict_ghost_roles && (creature.mind.assigned_role.title in GLOB.exp_specialmap[EXP_TYPE_SPECIAL])) // Are they playing a ghost role? - trimmed_list.Remove(creature) - continue - if (creature.mind.assigned_role.title in restricted_roles) // Does their job allow it? - trimmed_list.Remove(creature) - continue - if (length(exclusive_roles) && !(creature.mind.assigned_role.title in exclusive_roles)) // Is the rule exclusive to their job? - trimmed_list.Remove(creature) - continue - if(HAS_TRAIT(creature, TRAIT_MIND_TEMPORARILY_GONE)) // are they out of body? - trimmed_list.Remove(creature) - continue - if(HAS_TRAIT(creature, TRAIT_TEMPORARY_BODY)) // are they an avatar? - trimmed_list.Remove(creature) - continue - return trimmed_list - -// You can then for example prompt dead players in execute() to join as strike teams or whatever -// Or autotator someone - -// IMPORTANT, since /datum/dynamic_ruleset/midround may accept candidates from both living, dead, and even antag players -// subtype your midround with /from_ghosts or /from_living to get candidate checking. Or check yourself by subtyping from neither -/datum/dynamic_ruleset/midround/ready(forced = FALSE) - if (forced) - return TRUE - - var/job_check = 0 - if (enemy_roles.len > 0) - for (var/mob/M in GLOB.alive_player_list) - if (M.stat == DEAD || !M.client) - continue // Dead/disconnected players cannot count as opponents - if (M.mind && (M.mind.assigned_role.title in enemy_roles) && (!(M in candidates) || (M.mind.assigned_role.title in restricted_roles))) - job_check++ // Checking for "enemies" (such as sec officers). To be counters, they must either not be candidates to that rule, or have a job that restricts them from it - - var/threat = round(SSdynamic.threat_level/10) - var/ruleset_forced = (GLOB.dynamic_forced_rulesets[type] || RULESET_NOT_FORCED) == RULESET_FORCE_ENABLED - if (!ruleset_forced && job_check < required_enemies[threat]) - log_dynamic("FAIL: [src] is not ready, because there are not enough enemies: [required_enemies[threat]] needed, [job_check] found") - return FALSE - - return TRUE - -/datum/dynamic_ruleset/midround/from_ghosts/execute() - var/list/possible_candidates = list() - possible_candidates.Add(dead_players) - possible_candidates.Add(list_observers) - send_applications(possible_candidates) - if(assigned.len > 0) - return TRUE - else - return FALSE - -/// This sends a poll to ghosts if they want to be a ghost spawn from a ruleset. -/datum/dynamic_ruleset/midround/from_ghosts/proc/send_applications(list/possible_volunteers = list()) - if (possible_volunteers.len <= 0) // This shouldn't happen, as ready() should return FALSE if there is not a single valid candidate - message_admins("Possible volunteers was 0. This shouldn't appear, because of ready(), unless you forced it!") - return - - SSdynamic.log_dynamic_and_announce("Polling [possible_volunteers.len] players to apply for the [name] ruleset.") - candidates = SSpolling.poll_ghost_candidates("Looking for volunteers to become [antag_flag] for [name]", check_jobban = antag_flag_override, role = antag_flag || antag_flag_override, poll_time = 30 SECONDS, pic_source = /obj/structure/sign/poster/contraband/syndicate_recruitment, role_name_text = antag_flag) - - if(!candidates || candidates.len <= 0) - SSdynamic.log_dynamic_and_announce("The ruleset [name] received no applications.") - SSdynamic.executed_rules -= src - attempt_replacement() - return - - SSdynamic.log_dynamic_and_announce("[candidates.len] players volunteered for [name].") - review_applications() - -/// Here is where you can check if your ghost applicants are valid for the ruleset. -/// Called by send_applications(). -/datum/dynamic_ruleset/midround/from_ghosts/proc/review_applications() - if(candidates.len < required_applicants) - SSdynamic.executed_rules -= src - return - for (var/i = 1, i <= required_candidates, i++) - if(candidates.len <= 0) - break - var/mob/applicant = pick(candidates) - candidates -= applicant - if(!isobserver(applicant)) - if(applicant.stat == DEAD) // Not an observer? If they're dead, make them one. - applicant = applicant.ghostize(FALSE) - else // Not dead? Disregard them, pick a new applicant - i-- - continue - if(!applicant) - i-- - continue - assigned += applicant - finish_applications() - -/// Here the accepted applications get generated bodies and their setup is finished. -/// Called by review_applications() -/datum/dynamic_ruleset/midround/from_ghosts/proc/finish_applications() - var/i = 0 - for(var/mob/applicant as anything in assigned) - i++ - var/mob/new_character = applicant - if(makeBody) - new_character = generate_ruleset_body(applicant) - finish_setup(new_character, i) - notify_ghosts( - "[applicant.name] has been picked for the ruleset [name]!", - source = new_character, - ) - -/datum/dynamic_ruleset/midround/from_ghosts/proc/generate_ruleset_body(mob/applicant) - var/mob/living/carbon/human/new_character = make_body(applicant) - new_character.dna.remove_all_mutations() - return new_character - -/datum/dynamic_ruleset/midround/from_ghosts/proc/finish_setup(mob/new_character, index) - var/datum/antagonist/new_role = new antag_datum() - setup_role(new_role) - new_character.mind.add_antag_datum(new_role) - new_character.mind.special_role = antag_flag - -/datum/dynamic_ruleset/midround/from_ghosts/proc/setup_role(datum/antagonist/new_role) - return - -/// Fired when there are no valid candidates. Will spawn a sleeper agent or latejoin traitor. -/datum/dynamic_ruleset/midround/from_ghosts/proc/attempt_replacement() - var/datum/dynamic_ruleset/midround/from_living/autotraitor/sleeper_agent = new - - SSdynamic.configure_ruleset(sleeper_agent) - - if (!SSdynamic.picking_specific_rule(sleeper_agent)) - return - - SSdynamic.picking_specific_rule(/datum/dynamic_ruleset/latejoin/infiltrator) - -///subtype to handle checking players -/datum/dynamic_ruleset/midround/from_living - weight = 0 - abstract_type = /datum/dynamic_ruleset/midround/from_living - -/datum/dynamic_ruleset/midround/from_living/ready(forced) - if(!check_candidates()) - return FALSE - return ..() - - -/// Midround Traitor Ruleset (From Living) -/datum/dynamic_ruleset/midround/from_living/autotraitor - name = "Syndicate Sleeper Agent" - midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT - antag_datum = /datum/antagonist/traitor/infiltrator/sleeper_agent - antag_flag = ROLE_SLEEPER_AGENT - antag_flag_override = ROLE_TRAITOR - protected_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_PERSONNEL, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ROLE_POSITRONIC_BRAIN, - ) - required_candidates = 1 - weight = 35 - cost = 3 - requirements = list(3,3,3,3,3,3,3,3,3,3) - repeatable = TRUE - -/datum/dynamic_ruleset/midround/from_living/autotraitor/trim_candidates() - ..() - candidates = living_players - for(var/mob/living/player in candidates) - if(issilicon(player)) // Your assigned role doesn't change when you are turned into a silicon. - candidates -= player - else if(is_centcom_level(player.z)) - candidates -= player // We don't autotator people in CentCom - else if(player.mind && (player.mind.special_role || player.mind.antag_datums?.len > 0)) - candidates -= player // We don't autotator people with roles already - -/datum/dynamic_ruleset/midround/from_living/autotraitor/execute() - var/mob/M = pick(candidates) - assigned += M - candidates -= M - var/datum/antagonist/traitor/infiltrator/sleeper_agent/newTraitor = new - M.mind.add_antag_datum(newTraitor) - message_admins("[ADMIN_LOOKUPFLW(M)] was selected by the [name] ruleset and has been made into a midround traitor.") - log_dynamic("[key_name(M)] was selected by the [name] ruleset and has been made into a midround traitor.") - return TRUE - -/// Midround Malf AI Ruleset (From Living) -/datum/dynamic_ruleset/midround/malf - name = "Malfunctioning AI" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_datum = /datum/antagonist/malf_ai - antag_flag = ROLE_MALF_MIDROUND - antag_flag_override = ROLE_MALF - enemy_roles = list( - JOB_CHEMIST, - JOB_CHIEF_ENGINEER, - JOB_HEAD_OF_SECURITY, - JOB_RESEARCH_DIRECTOR, - JOB_SCIENTIST, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - exclusive_roles = list(JOB_AI) - required_enemies = list(4,4,4,4,4,4,2,2,2,0) - required_candidates = 1 - minimum_players = 25 - weight = 2 - cost = 10 - required_type = /mob/living/silicon/ai - blocking_rules = list(/datum/dynamic_ruleset/roundstart/malf_ai) - -/datum/dynamic_ruleset/midround/malf/trim_candidates() - ..() - candidates = living_players - for(var/mob/living/player in candidates) - if(!isAI(player)) - candidates -= player - continue - - if(is_centcom_level(player.z)) - candidates -= player - continue - - if(player.mind && (player.mind.special_role || player.mind.antag_datums?.len > 0)) - candidates -= player - -/datum/dynamic_ruleset/midround/malf/execute() - if(!candidates || !candidates.len) - return FALSE - var/mob/living/silicon/ai/new_malf_ai = pick_n_take(candidates) - assigned += new_malf_ai.mind - var/datum/antagonist/malf_ai/malf_antag_datum = new - new_malf_ai.mind.special_role = antag_flag - new_malf_ai.mind.add_antag_datum(malf_antag_datum) - if(prob(MALF_ION_PROB)) - priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", ANNOUNCER_IONSTORM) - if(prob(REPLACE_LAW_WITH_ION_PROB)) - new_malf_ai.replace_random_law(generate_ion_law(), list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION), LAW_ION) - else - new_malf_ai.add_ion_law(generate_ion_law()) - return TRUE - -/// Midround Wizard Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/wizard - name = "Wizard" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_datum = /datum/antagonist/wizard - antag_flag = ROLE_WIZARD_MIDROUND - antag_flag_override = ROLE_WIZARD - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - weight = 1 - cost = 10 - requirements = REQUIREMENTS_VERY_HIGH_THREAT_NEEDED - flags = HIGH_IMPACT_RULESET - ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_WIZARDDEN) - -/datum/dynamic_ruleset/midround/from_ghosts/wizard/ready(forced = FALSE) - if(!check_candidates()) - return FALSE - if(!length(GLOB.wizardstart)) - 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/midround/from_ghosts/wizard/finish_setup(mob/new_character, index) - ..() - new_character.forceMove(pick(GLOB.wizardstart)) - -/// Midround Nuclear Operatives Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/nuclear - name = "Nuclear Assault" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_flag = ROLE_OPERATIVE_MIDROUND - antag_flag_override = ROLE_OPERATIVE - antag_datum = /datum/antagonist/nukeop - enemy_roles = list( - JOB_AI, - JOB_CYBORG, - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_SECURITY, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - required_enemies = list(3,3,3,3,3,2,1,1,0,0) - required_candidates = 5 - weight = 5 - cost = 7 - minimum_round_time = 70 MINUTES - requirements = REQUIREMENTS_VERY_HIGH_THREAT_NEEDED - ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_NUKIEBASE) - flags = HIGH_IMPACT_RULESET - - var/list/operative_cap = list(2,2,3,3,4,5,5,5,5,5) - -/datum/dynamic_ruleset/midround/from_ghosts/nuclear/acceptable(population=0, threat_level=0) - if (locate(/datum/dynamic_ruleset/roundstart/nuclear) in SSdynamic.executed_rules) - return FALSE // Unavailable if nuke ops were already sent at roundstart - indice_pop = min(operative_cap.len, round(living_players.len/5)+1) - required_candidates = operative_cap[indice_pop] - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/nuclear/ready(forced = FALSE) - if (!check_candidates()) - return FALSE - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/nuclear/finish_applications() - var/mob/leader = get_most_experienced(assigned, ROLE_NUCLEAR_OPERATIVE) - if(leader) - assigned.Remove(leader) - assigned.Insert(1, leader) - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/nuclear/finish_setup(mob/new_character, index) - new_character.mind.set_assigned_role(SSjob.GetJobType(/datum/job/nuclear_operative)) - new_character.mind.special_role = ROLE_NUCLEAR_OPERATIVE - if(index == 1) - var/datum/antagonist/nukeop/leader/leader_antag_datum = new() - new_character.mind.add_antag_datum(leader_antag_datum) - return - return ..() - -/// Midround Blob Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/blob - name = "Blob" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_datum = /datum/antagonist/blob - antag_flag = ROLE_BLOB - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - minimum_round_time = 35 MINUTES - weight = 3 - cost = 8 - minimum_players = 25 - repeatable = TRUE - -/datum/dynamic_ruleset/midround/from_ghosts/blob/generate_ruleset_body(mob/applicant) - var/body = applicant.become_overmind() - return body - -/// Midround Blob Infection Ruleset (From Living) -/datum/dynamic_ruleset/midround/from_living/blob_infection - name = "Blob Infection" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_datum = /datum/antagonist/blob/infection - antag_flag = ROLE_BLOB_INFECTION - antag_flag_override = ROLE_BLOB - protected_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ROLE_POSITRONIC_BRAIN, - ) - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - minimum_round_time = 35 MINUTES - weight = 3 - cost = 10 - minimum_players = 25 - repeatable = TRUE - -/datum/dynamic_ruleset/midround/from_living/blob_infection/trim_candidates() - ..() - candidates = living_players - for(var/mob/living/player as anything in candidates) - var/turf/player_turf = get_turf(player) - if(!player_turf || !is_station_level(player_turf.z)) - candidates -= player - continue - - if(player.mind && (player.mind.special_role || length(player.mind.antag_datums) > 0)) - candidates -= player - -/datum/dynamic_ruleset/midround/from_living/blob_infection/execute() - if(!candidates || !candidates.len) - return FALSE - var/mob/living/carbon/human/blob_antag = pick_n_take(candidates) - assigned += blob_antag.mind - blob_antag.mind.special_role = antag_flag - return ..() - -/// Midround Xenomorph Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/xenomorph - name = "Alien Infestation" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_datum = /datum/antagonist/xeno - antag_flag = ROLE_ALIEN - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - minimum_round_time = 40 MINUTES - weight = 5 - cost = 10 - minimum_players = 25 - repeatable = TRUE - var/list/vents = list() - -/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/forget_startup() - vents = list() - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/execute() - // 50% chance of being incremented by one - required_candidates += prob(50) - var/list/vent_pumps = SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/atmospherics/components/unary/vent_pump) - for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent as anything in vent_pumps) - if(QDELETED(temp_vent)) - continue - if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) - var/datum/pipeline/temp_vent_parent = temp_vent.parents[1] - if(!temp_vent_parent) - continue // No parent vent - // Stops Aliens getting stuck in small networks. - // See: Security, Virology - if(temp_vent_parent.other_atmos_machines.len > 20) - vents += temp_vent - if(!vents.len) - return FALSE - . = ..() - -/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/generate_ruleset_body(mob/applicant) - var/obj/vent = pick_n_take(vents) - var/mob/living/carbon/alien/larva/new_xeno = new(vent.loc) - new_xeno.key = applicant.key - new_xeno.move_into_vent(vent) - message_admins("[ADMIN_LOOKUPFLW(new_xeno)] has been made into an alien by the midround ruleset.") - log_dynamic("[key_name(new_xeno)] was spawned as an alien by the midround ruleset.") - return new_xeno - -/// Midround Nightmare Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/nightmare - name = "Nightmare" - midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT - antag_datum = /datum/antagonist/nightmare - antag_flag = ROLE_NIGHTMARE - antag_flag_override = ROLE_ALIEN - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - weight = 3 - cost = 5 - minimum_players = 15 - repeatable = TRUE - -/datum/dynamic_ruleset/midround/from_ghosts/nightmare/acceptable(population = 0, threat_level = 0) - var/turf/spawn_loc = find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE) //Checks if there's a single safe, dark tile on station. - if(!spawn_loc) - return FALSE - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/nightmare/generate_ruleset_body(mob/applicant) - var/datum/mind/player_mind = new /datum/mind(applicant.key) - player_mind.active = TRUE - - var/mob/living/carbon/human/new_nightmare = new (find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE)) - player_mind.transfer_to(new_nightmare) - player_mind.set_assigned_role(SSjob.GetJobType(/datum/job/nightmare)) - player_mind.special_role = ROLE_NIGHTMARE - player_mind.add_antag_datum(/datum/antagonist/nightmare) - new_nightmare.set_species(/datum/species/shadow/nightmare) - - playsound(new_nightmare, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) - message_admins("[ADMIN_LOOKUPFLW(new_nightmare)] has been made into a Nightmare by the midround ruleset.") - log_dynamic("[key_name(new_nightmare)] was spawned as a Nightmare by the midround ruleset.") - return new_nightmare - -/// Midround Space Dragon Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/space_dragon - name = "Space Dragon" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_datum = /datum/antagonist/space_dragon - antag_flag = ROLE_SPACE_DRAGON - antag_flag_override = ROLE_SPACE_DRAGON - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - weight = 4 - cost = 7 - minimum_players = 25 - repeatable = TRUE - var/list/spawn_locs = list() - -/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/forget_startup() - spawn_locs = list() - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/execute() - for(var/obj/effect/landmark/carpspawn/C in GLOB.landmarks_list) - spawn_locs += (C.loc) - if(!spawn_locs.len) - message_admins("No valid spawn locations found, aborting...") - return MAP_ERROR - . = ..() - -/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/generate_ruleset_body(mob/applicant) - var/datum/mind/player_mind = new /datum/mind(applicant.key) - player_mind.active = TRUE - - var/mob/living/basic/space_dragon/S = new (pick(spawn_locs)) - player_mind.transfer_to(S) - player_mind.add_antag_datum(/datum/antagonist/space_dragon) - - playsound(S, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) - message_admins("[ADMIN_LOOKUPFLW(S)] has been made into a Space Dragon by the midround ruleset.") - log_dynamic("[key_name(S)] was spawned as a Space Dragon by the midround ruleset.") - priority_announce("A large organic energy flux has been recorded near of [station_name()], please stand-by.", "Lifesign Alert") - return S - -/datum/dynamic_ruleset/midround/from_ghosts/abductors - name = "Abductors" - midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT - antag_datum = /datum/antagonist/abductor - antag_flag = ROLE_ABDUCTOR - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 2 - required_applicants = 2 - weight = 4 - cost = 7 - minimum_players = 25 - repeatable = TRUE - ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_ABDUCTOR_SHIPS) - - var/datum/team/abductor_team/new_team - -/datum/dynamic_ruleset/midround/from_ghosts/abductors/forget_startup() - new_team = null - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/abductors/ready(forced = FALSE) - if (required_candidates > (dead_players.len + list_observers.len)) - return FALSE - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/abductors/finish_setup(mob/new_character, index) - if (index == 1) // Our first guy is the scientist. We also initialize the team here as well since this should only happen once per pair of abductors. - new_team = new - if(new_team.team_number > ABDUCTOR_MAX_TEAMS) - return MAP_ERROR - var/datum/antagonist/abductor/scientist/new_role = new - new_character.mind.add_antag_datum(new_role, new_team) - else // Our second guy is the agent, team is already created, don't need to make another one. - var/datum/antagonist/abductor/agent/new_role = new - new_character.mind.add_antag_datum(new_role, new_team) - -/// Midround Space Ninja Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/space_ninja - name = "Space Ninja" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_datum = /datum/antagonist/ninja - antag_flag = ROLE_NINJA - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - weight = 4 - cost = 8 - minimum_players = 30 - repeatable = TRUE - ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_NINJA_HOLDING_FACILITY) // I mean, no one uses the nets anymore but whateva - - var/list/spawn_locs = list() - -/datum/dynamic_ruleset/midround/from_ghosts/space_ninja/forget_startup() - spawn_locs = list() - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/space_ninja/execute() - for(var/obj/effect/landmark/carpspawn/carp_spawn in GLOB.landmarks_list) - if(!isturf(carp_spawn.loc)) - stack_trace("Carp spawn found not on a turf: [carp_spawn.type] on [isnull(carp_spawn.loc) ? "null" : carp_spawn.loc.type]") - continue - spawn_locs += carp_spawn.loc - if(!spawn_locs.len) - message_admins("No valid spawn locations found, aborting...") - return MAP_ERROR - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/space_ninja/generate_ruleset_body(mob/applicant) - var/mob/living/carbon/human/ninja = create_space_ninja(pick(spawn_locs)) - ninja.key = applicant.key - ninja.mind.add_antag_datum(/datum/antagonist/ninja) - - message_admins("[ADMIN_LOOKUPFLW(ninja)] has been made into a Space Ninja by the midround ruleset.") - log_dynamic("[key_name(ninja)] was spawned as a Space Ninja by the midround ruleset.") - return ninja - -/// Midround Spiders Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/spiders - name = "Spiders" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_flag = ROLE_SPIDER - required_type = /mob/dead/observer - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 0 - weight = 3 - cost = 8 - minimum_players = 27 - repeatable = TRUE - var/spawncount = 2 - -/datum/dynamic_ruleset/midround/spiders/execute() - create_midwife_eggs(spawncount) - return ..() - -/// Midround Revenant Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/revenant - name = "Revenant" - midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT - antag_datum = /datum/antagonist/revenant - antag_flag = ROLE_REVENANT - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - weight = 4 - cost = 5 - minimum_players = 15 - repeatable = TRUE - var/dead_mobs_required = 20 - var/need_extra_spawns_value = 15 - var/list/spawn_locs = list() - -/datum/dynamic_ruleset/midround/from_ghosts/revenant/forget_startup() - spawn_locs = list() - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/revenant/acceptable(population=0, threat_level=0) - if(GLOB.dead_mob_list.len < dead_mobs_required) - return FALSE - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/revenant/execute() - for(var/mob/living/corpse in GLOB.dead_mob_list) //look for any dead bodies - var/turf/corpse_turf = get_turf(corpse) - if(corpse_turf && is_station_level(corpse_turf.z)) - spawn_locs += corpse_turf - if(!spawn_locs.len || spawn_locs.len < need_extra_spawns_value) //look for any morgue trays, crematoriums, ect if there weren't alot of dead bodies on the station to pick from - for(var/obj/structure/bodycontainer/corpse_container in GLOB.bodycontainers) - var/turf/container_turf = get_turf(corpse_container) - if(container_turf && is_station_level(container_turf.z)) - spawn_locs += container_turf - if(!spawn_locs.len) //If we can't find any valid spawnpoints, try the carp spawns - for(var/obj/effect/landmark/carpspawn/carp_spawnpoint in GLOB.landmarks_list) - if(isturf(carp_spawnpoint.loc)) - spawn_locs += carp_spawnpoint.loc - if(!spawn_locs.len) //If we can't find THAT, then just give up and cry - return FALSE - . = ..() - -/datum/dynamic_ruleset/midround/from_ghosts/revenant/generate_ruleset_body(mob/applicant) - var/mob/living/basic/revenant/revenant = new(pick(spawn_locs)) - revenant.key = applicant.key - message_admins("[ADMIN_LOOKUPFLW(revenant)] has been made into a revenant by the midround ruleset.") - log_game("[key_name(revenant)] was spawned as a revenant by the midround ruleset.") - return revenant - -/// Midround Sentient Disease Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/sentient_disease - name = "Sentient Disease" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_datum = /datum/antagonist/disease - antag_flag = ROLE_SENTIENT_DISEASE - required_candidates = 1 - minimum_players = 25 - weight = 4 - cost = 8 - repeatable = TRUE - -/datum/dynamic_ruleset/midround/from_ghosts/sentient_disease/generate_ruleset_body(mob/applicant) - var/mob/camera/disease/virus = new /mob/camera/disease(SSmapping.get_station_center()) - virus.key = applicant.key - INVOKE_ASYNC(virus, TYPE_PROC_REF(/mob/camera/disease, pick_name)) - message_admins("[ADMIN_LOOKUPFLW(virus)] has been made into a sentient disease by the midround ruleset.") - log_game("[key_name(virus)] was spawned as a sentient disease by the midround ruleset.") - return virus - -/// Midround Space Pirates Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/pirates - name = "Space Pirates" - midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT - antag_flag = "Space Pirates" - required_type = /mob/dead/observer - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 0 - weight = 3 - cost = 8 - minimum_players = 20 - repeatable = TRUE - -/datum/dynamic_ruleset/midround/pirates/acceptable(population=0, threat_level=0) - if (SSmapping.is_planetary() || GLOB.light_pirate_gangs.len == 0) - return FALSE - return ..() - -/datum/dynamic_ruleset/midround/pirates/execute() - send_pirate_threat(GLOB.light_pirate_gangs) - return ..() - -/// Dangerous Space Pirates ruleset -/datum/dynamic_ruleset/midround/dangerous_pirates - name = "Dangerous Space Pirates" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_flag = "Space Pirates" - required_type = /mob/dead/observer - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 0 - weight = 3 - cost = 8 - minimum_players = 25 - repeatable = TRUE - -/datum/dynamic_ruleset/midround/dangerous_pirates/acceptable(population=0, threat_level=0) - if (SSmapping.is_planetary() || GLOB.heavy_pirate_gangs.len == 0) - return FALSE - return ..() - -/datum/dynamic_ruleset/midround/dangerous_pirates/execute() - send_pirate_threat(GLOB.heavy_pirate_gangs) - return ..() - -/// Midround Obsessed Ruleset (From Living) -/datum/dynamic_ruleset/midround/from_living/obsessed - name = "Obsessed" - midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT - antag_datum = /datum/antagonist/obsessed - antag_flag = ROLE_OBSESSED - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ROLE_POSITRONIC_BRAIN, - ) - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - weight = 4 - cost = 3 // Doesn't have the same impact on rounds as revenants, dragons, sentient disease (10) or syndicate infiltrators (5). - repeatable = TRUE - -/datum/dynamic_ruleset/midround/from_living/obsessed/trim_candidates() - ..() - candidates = living_players - for(var/mob/living/carbon/human/candidate in candidates) - if( \ - !candidate.get_organ_by_type(/obj/item/organ/internal/brain) \ - || candidate.mind.has_antag_datum(/datum/antagonist/obsessed) \ - || candidate.stat == DEAD \ - || !(ROLE_OBSESSED in candidate.client?.prefs?.be_special) \ - || !candidate.mind.assigned_role \ - ) - candidates -= candidate - -/datum/dynamic_ruleset/midround/from_living/obsessed/execute() - var/mob/living/carbon/human/obsessed = pick_n_take(candidates) - obsessed.gain_trauma(/datum/brain_trauma/special/obsessed) - message_admins("[ADMIN_LOOKUPFLW(obsessed)] has been made Obsessed by the midround ruleset.") - log_game("[key_name(obsessed)] was made Obsessed by the midround ruleset.") - return TRUE - -/// Midround Space Changeling Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/changeling_midround - name = "Space Changeling" - midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT - antag_datum = /datum/antagonist/changeling/space - antag_flag = ROLE_CHANGELING_MIDROUND - antag_flag_override = ROLE_CHANGELING - required_type = /mob/dead/observer - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - weight = 3 - cost = 7 - minimum_players = 15 - repeatable = TRUE - -/datum/dynamic_ruleset/midround/from_ghosts/changeling_midround/generate_ruleset_body(mob/applicant) - var/body = generate_changeling_meteor(applicant) - message_admins("[ADMIN_LOOKUPFLW(body)] has been made into a space changeling by the midround ruleset.") - log_dynamic("[key_name(body)] was spawned as a space changeling by the midround ruleset.") - return body - -/// Midround Paradox Clone Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone - name = "Paradox Clone" - midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT - antag_datum = /datum/antagonist/paradox_clone - antag_flag = ROLE_PARADOX_CLONE - enemy_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_SECURITY, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - required_enemies = list(2, 2, 1, 1, 1, 1, 1, 0, 0, 0) - required_candidates = 1 - weight = 4 - cost = 3 - repeatable = TRUE - var/list/possible_spawns = list() ///places the antag can spawn - -/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/forget_startup() - possible_spawns = list() - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/execute() - possible_spawns += find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE) - if(!possible_spawns.len) - return MAP_ERROR - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/generate_ruleset_body(mob/applicant) - var/datum/mind/player_mind = new /datum/mind(applicant.key) - player_mind.active = TRUE - - var/mob/living/carbon/human/clone_victim = find_original() - var/mob/living/carbon/human/clone = clone_victim.make_full_human_copy(pick(possible_spawns)) - player_mind.transfer_to(clone) - - var/datum/antagonist/paradox_clone/new_datum = player_mind.add_antag_datum(/datum/antagonist/paradox_clone) - new_datum.original_ref = WEAKREF(clone_victim.mind) - new_datum.setup_clone() - - playsound(clone, 'sound/weapons/zapbang.ogg', 30, TRUE) - new /obj/item/storage/toolbox/mechanical(clone.loc) //so they dont get stuck in maints - - message_admins("[ADMIN_LOOKUPFLW(clone)] has been made into a Paradox Clone by the midround ruleset.") - clone.log_message("was spawned as a Paradox Clone of [key_name(clone)] by the midround ruleset.", LOG_GAME) - - return clone - -/** - * Trims through GLOB.player_list and finds a target - * Returns a single human victim, if none is possible then returns null. - */ -/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/proc/find_original() - var/list/possible_targets = list() - - for(var/mob/living/carbon/human/player in GLOB.player_list) - if(!player.client || !player.mind || player.stat) - continue - if(!(player.mind.assigned_role.job_flags & JOB_CREW_MEMBER)) - continue - possible_targets += player - - if(possible_targets.len) - return pick(possible_targets) - return FALSE - -#undef MALF_ION_PROB -#undef REPLACE_LAW_WITH_ION_PROB diff --git a/code/controllers/subsystem/dynamic/dynamic_rulesets_roundstart.dm b/code/controllers/subsystem/dynamic/dynamic_rulesets_roundstart.dm deleted file mode 100644 index 290b71161625..000000000000 --- a/code/controllers/subsystem/dynamic/dynamic_rulesets_roundstart.dm +++ /dev/null @@ -1,701 +0,0 @@ -GLOBAL_VAR_INIT(revolutionary_win, FALSE) - -////////////////////////////////////////////// -// // -// SYNDICATE TRAITORS // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/traitor - name = "Traitors" - antag_flag = ROLE_TRAITOR - antag_datum = /datum/antagonist/traitor - minimum_required_age = 0 - protected_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ) - required_candidates = 1 - weight = 5 - cost = 8 // Avoid raising traitor threat above this, as it is the default low cost ruleset. - scaling_cost = 9 - requirements = list(8,8,8,8,8,8,8,8,8,8) - antag_cap = list("denominator" = 38) - var/autotraitor_cooldown = (15 MINUTES) - -/datum/dynamic_ruleset/roundstart/traitor/pre_execute(population) - . = ..() - var/num_traitors = get_antag_cap(population) * (scaled_times + 1) - for (var/i = 1 to num_traitors) - if(candidates.len <= 0) - break - var/mob/M = pick_n_take(candidates) - assigned += M.mind - M.mind.special_role = ROLE_TRAITOR - M.mind.restricted_roles = restricted_roles - GLOB.pre_setup_antags += M.mind - return TRUE - -////////////////////////////////////////////// -// // -// MALFUNCTIONING AI // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/malf_ai - name = "Malfunctioning AI" - antag_flag = ROLE_MALF - antag_datum = /datum/antagonist/malf_ai - minimum_required_age = 14 - exclusive_roles = list(JOB_AI) - required_candidates = 1 - weight = 3 - cost = 18 - requirements = list(101,101,101,80,60,50,30,20,10,10) - antag_cap = 1 - flags = HIGH_IMPACT_RULESET - -/datum/dynamic_ruleset/roundstart/malf_ai/ready(forced) - var/datum/job/ai_job = SSjob.GetJobType(/datum/job/ai) - - // If we're not forced, we're going to make sure we can actually have an AI in this shift, - if(!forced && min(ai_job.total_positions - ai_job.current_positions, ai_job.spawn_positions) <= 0) - log_dynamic("FAIL: [src] could not run, because there is nobody who wants to be an AI") - return FALSE - - return ..() - -/datum/dynamic_ruleset/roundstart/malf_ai/pre_execute(population) - . = ..() - - var/datum/job/ai_job = SSjob.GetJobType(/datum/job/ai) - // Maybe a bit too pedantic, but there should never be more malf AIs than there are available positions, spawn positions or antag cap allocations. - var/num_malf = min(get_antag_cap(population), min(ai_job.total_positions - ai_job.current_positions, ai_job.spawn_positions)) - for (var/i in 1 to num_malf) - if(candidates.len <= 0) - break - var/mob/new_malf = pick_n_take(candidates) - assigned += new_malf.mind - new_malf.mind.special_role = ROLE_MALF - GLOB.pre_setup_antags += new_malf.mind - // We need an AI for the malf roundstart ruleset to execute. This means that players who get selected as malf AI get priority, because antag selection comes before role selection. - LAZYADDASSOC(SSjob.dynamic_forced_occupations, new_malf, "AI") - return TRUE - -////////////////////////////////////////// -// // -// BLOOD BROTHERS // -// // -////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/traitorbro - name = "Blood Brothers" - antag_flag = ROLE_BROTHER - antag_datum = /datum/antagonist/brother - protected_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ) - weight = 5 - cost = 8 - scaling_cost = 15 - requirements = list(40,30,30,20,20,15,15,15,10,10) - antag_cap = 1 - -/datum/dynamic_ruleset/roundstart/traitorbro/pre_execute(population) - . = ..() - - for (var/_ in 1 to get_antag_cap(population) * (scaled_times + 1)) - var/mob/candidate = pick_n_take(candidates) - if (isnull(candidate)) - break - - assigned += candidate.mind - candidate.mind.restricted_roles = restricted_roles - candidate.mind.special_role = ROLE_BROTHER - GLOB.pre_setup_antags += candidate.mind - - return TRUE - -/datum/dynamic_ruleset/roundstart/traitorbro/execute() - for (var/datum/mind/mind in assigned) - var/datum/team/brother_team/team = new - team.add_member(mind) - team.forge_brother_objectives() - mind.add_antag_datum(/datum/antagonist/brother, team) - GLOB.pre_setup_antags -= mind - - return TRUE - -////////////////////////////////////////////// -// // -// CHANGELINGS // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/changeling - name = "Changelings" - antag_flag = ROLE_CHANGELING - antag_datum = /datum/antagonist/changeling - protected_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ) - required_candidates = 1 - weight = 3 - cost = 16 - scaling_cost = 10 - requirements = list(70,70,60,50,40,20,20,10,10,10) - antag_cap = list("denominator" = 29) - -/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) - if(candidates.len <= 0) - break - var/mob/M = pick_n_take(candidates) - assigned += M.mind - M.mind.restricted_roles = restricted_roles - M.mind.special_role = ROLE_CHANGELING - GLOB.pre_setup_antags += M.mind - return TRUE - -/datum/dynamic_ruleset/roundstart/changeling/execute() - for(var/datum/mind/changeling in assigned) - var/datum/antagonist/changeling/new_antag = new antag_datum() - changeling.add_antag_datum(new_antag) - GLOB.pre_setup_antags -= changeling - return TRUE - -////////////////////////////////////////////// -// // -// HERETICS // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/heretics - name = "Heretics" - antag_flag = ROLE_HERETIC - antag_datum = /datum/antagonist/heretic - protected_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ) - required_candidates = 1 - weight = 3 - cost = 10 - scaling_cost = 9 - requirements = list(101,101,60,30,30,25,20,15,10,10) - antag_cap = list("denominator" = 24) - ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_HERETIC_SACRIFICE) - - -/datum/dynamic_ruleset/roundstart/heretics/pre_execute(population) - . = ..() - var/num_ecult = get_antag_cap(population) * (scaled_times + 1) - - for (var/i = 1 to num_ecult) - if(candidates.len <= 0) - break - var/mob/picked_candidate = pick_n_take(candidates) - 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() - - for(var/c in assigned) - 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 - - -////////////////////////////////////////////// -// // -// WIZARDS // -// // -////////////////////////////////////////////// - -// Dynamic is a wonderful thing that adds wizards to every round and then adds even more wizards during the round. -/datum/dynamic_ruleset/roundstart/wizard - name = "Wizard" - antag_flag = ROLE_WIZARD - antag_datum = /datum/antagonist/wizard - flags = HIGH_IMPACT_RULESET - minimum_required_age = 14 - restricted_roles = list( - JOB_CAPTAIN, - JOB_HEAD_OF_SECURITY, - ) // Just to be sure that a wizard getting picked won't ever imply a Captain or HoS not getting drafted - required_candidates = 1 - weight = 2 - cost = 20 - requirements = list(90,90,90,80,60,40,30,20,10,10) - ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_WIZARDDEN) - -/datum/dynamic_ruleset/roundstart/wizard/ready(forced = FALSE) - if(!check_candidates()) - return FALSE - if(!length(GLOB.wizardstart)) - 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/round_result() - for(var/datum/antagonist/wizard/wiz in GLOB.antagonists) - var/mob/living/real_wiz = wiz.owner?.current - if(isnull(real_wiz)) - continue - - var/turf/wiz_location = get_turf(real_wiz) - // If this wiz is alive AND not in an away level, then we know not all wizards are dead and can leave entirely - if(considered_alive(wiz.owner) && wiz_location && !is_away_level(wiz_location.z)) - return - - SSticker.news_report = WIZARD_KILLED - -/datum/dynamic_ruleset/roundstart/wizard/pre_execute() - . = ..() - if(GLOB.wizardstart.len == 0) - return FALSE - var/mob/M = pick_n_take(candidates) - if (M) - assigned += M.mind - M.mind.set_assigned_role(SSjob.GetJobType(/datum/job/space_wizard)) - M.mind.special_role = ROLE_WIZARD - - return TRUE - -/datum/dynamic_ruleset/roundstart/wizard/execute() - for(var/datum/mind/M in assigned) - M.current.forceMove(pick(GLOB.wizardstart)) - M.add_antag_datum(new antag_datum()) - return TRUE - -////////////////////////////////////////////// -// // -// BLOOD CULT // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/bloodcult - name = "Blood Cult" - antag_flag = ROLE_CULTIST - antag_datum = /datum/antagonist/cult - minimum_required_age = 14 - restricted_roles = list( - JOB_AI, - JOB_CAPTAIN, - JOB_CHAPLAIN, - JOB_CYBORG, - JOB_DETECTIVE, - JOB_HEAD_OF_PERSONNEL, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - required_candidates = 2 - weight = 3 - cost = 20 - 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 - -/datum/dynamic_ruleset/roundstart/bloodcult/ready(population, forced = FALSE) - required_candidates = get_antag_cap(population) - return ..() - -/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 - var/mob/M = pick_n_take(candidates) - assigned += M.mind - M.mind.special_role = ROLE_CULTIST - M.mind.restricted_roles = restricted_roles - GLOB.pre_setup_antags += M.mind - return TRUE - -/datum/dynamic_ruleset/roundstart/bloodcult/execute() - main_cult = new - for(var/datum/mind/M in assigned) - var/datum/antagonist/cult/new_cultist = new antag_datum() - new_cultist.cult_team = main_cult - new_cultist.give_equipment = TRUE - M.add_antag_datum(new_cultist) - GLOB.pre_setup_antags -= M - main_cult.setup_objectives() - return TRUE - -/datum/dynamic_ruleset/roundstart/bloodcult/round_result() - if(main_cult.check_cult_victory()) - SSticker.mode_result = "win - cult win" - SSticker.news_report = CULT_SUMMON - return - - SSticker.mode_result = "loss - staff stopped the cult" - - if(main_cult.size_at_maximum == 0) - CRASH("Cult team existed with a size_at_maximum of 0 at round end!") - - // If more than a certain ratio of our cultists have escaped, give the "cult escape" resport. - // Otherwise, give the "cult failure" report. - var/ratio_to_be_considered_escaped = 0.5 - var/escaped_cultists = 0 - for(var/datum/mind/escapee as anything in main_cult.members) - if(considered_escaped(escapee)) - escaped_cultists++ - - SSticker.news_report = (escaped_cultists / main_cult.size_at_maximum) >= ratio_to_be_considered_escaped ? CULT_ESCAPE : CULT_FAILURE - -////////////////////////////////////////////// -// // -// NUCLEAR OPERATIVES // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/nuclear - name = "Nuclear Emergency" - antag_flag = ROLE_OPERATIVE - antag_datum = /datum/antagonist/nukeop - var/datum/antagonist/antag_leader_datum = /datum/antagonist/nukeop/leader - minimum_required_age = 14 - restricted_roles = list( - JOB_CAPTAIN, - JOB_HEAD_OF_SECURITY, - ) // 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 - requirements = list(90,90,90,80,60,40,30,20,10,10) - flags = HIGH_IMPACT_RULESET - antag_cap = list("denominator" = 18, "offset" = 1) - ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_NUKIEBASE) - var/required_role = ROLE_NUCLEAR_OPERATIVE - var/datum/team/nuclear/nuke_team - -/datum/dynamic_ruleset/roundstart/nuclear/ready(population, forced = FALSE) - required_candidates = get_antag_cap(population) - return ..() - -/datum/dynamic_ruleset/roundstart/nuclear/pre_execute(population) - . = ..() - // If ready() did its job, candidates should have 5 or more members in it - var/operatives = get_antag_cap(population) - for(var/operatives_number = 1 to operatives) - if(candidates.len <= 0) - break - var/mob/M = pick_n_take(candidates) - assigned += M.mind - M.mind.set_assigned_role(SSjob.GetJobType(/datum/job/nuclear_operative)) - M.mind.special_role = ROLE_NUCLEAR_OPERATIVE - return TRUE - -/datum/dynamic_ruleset/roundstart/nuclear/execute() - var/datum/mind/most_experienced = get_most_experienced(assigned, required_role) - if(!most_experienced) - most_experienced = assigned[1] - var/datum/antagonist/nukeop/leader/leader = most_experienced.add_antag_datum(antag_leader_datum) - nuke_team = leader.nuke_team - for(var/datum/mind/assigned_player in assigned) - if(assigned_player == most_experienced) - continue - var/datum/antagonist/nukeop/new_op = new antag_datum() - assigned_player.add_antag_datum(new_op) - return TRUE - -/datum/dynamic_ruleset/roundstart/nuclear/round_result() - var/result = nuke_team.get_result() - switch(result) - if(NUKE_RESULT_FLUKE) - SSticker.mode_result = "loss - syndicate nuked - disk secured" - SSticker.news_report = NUKE_SYNDICATE_BASE - if(NUKE_RESULT_NUKE_WIN) - SSticker.mode_result = "win - syndicate nuke" - SSticker.news_report = STATION_DESTROYED_NUKE - if(NUKE_RESULT_NOSURVIVORS) - SSticker.mode_result = "halfwin - syndicate nuke - did not evacuate in time" - SSticker.news_report = STATION_DESTROYED_NUKE - if(NUKE_RESULT_WRONG_STATION) - SSticker.mode_result = "halfwin - blew wrong station" - SSticker.news_report = NUKE_MISS - if(NUKE_RESULT_WRONG_STATION_DEAD) - SSticker.mode_result = "halfwin - blew wrong station - did not evacuate in time" - SSticker.news_report = NUKE_MISS - if(NUKE_RESULT_CREW_WIN_SYNDIES_DEAD) - SSticker.mode_result = "loss - evacuation - disk secured - syndi team dead" - SSticker.news_report = OPERATIVES_KILLED - if(NUKE_RESULT_CREW_WIN) - SSticker.mode_result = "loss - evacuation - disk secured" - SSticker.news_report = OPERATIVES_KILLED - if(NUKE_RESULT_DISK_LOST) - SSticker.mode_result = "halfwin - evacuation - disk not secured" - SSticker.news_report = OPERATIVE_SKIRMISH - if(NUKE_RESULT_DISK_STOLEN) - SSticker.mode_result = "halfwin - detonation averted" - SSticker.news_report = OPERATIVE_SKIRMISH - else - SSticker.mode_result = "halfwin - interrupted" - SSticker.news_report = OPERATIVE_SKIRMISH - -////////////////////////////////////////////// -// // -// REVS // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/revs - name = "Revolution" - persistent = TRUE - antag_flag = ROLE_REV_HEAD - antag_flag_override = ROLE_REV_HEAD - antag_datum = /datum/antagonist/rev/head - minimum_required_age = 14 - restricted_roles = list( - JOB_AI, - JOB_CAPTAIN, - JOB_CHIEF_ENGINEER, - JOB_CHIEF_MEDICAL_OFFICER, - JOB_CYBORG, - JOB_DETECTIVE, - JOB_HEAD_OF_PERSONNEL, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_QUARTERMASTER, - JOB_RESEARCH_DIRECTOR, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - required_candidates = 3 - weight = 3 - delay = 7 MINUTES - cost = 20 - requirements = list(101,101,70,40,30,20,10,10,10,10) - 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(population) - . = ..() - var/max_candidates = get_antag_cap(population) - for(var/i = 1 to max_candidates) - if(candidates.len <= 0) - break - var/mob/M = pick_n_take(candidates) - assigned += M.mind - M.mind.restricted_roles = restricted_roles - M.mind.special_role = antag_flag - GLOB.pre_setup_antags += M.mind - return TRUE - -/datum/dynamic_ruleset/roundstart/revs/execute() - revolution = new() - for(var/datum/mind/M in assigned) - GLOB.pre_setup_antags -= M - if(check_eligible(M)) - var/datum/antagonist/rev/head/new_head = new antag_datum() - new_head.give_flash = TRUE - new_head.give_hud = TRUE - new_head.remove_clumsy = TRUE - M.add_antag_datum(new_head,revolution) - else - assigned -= M - log_dynamic("[ruletype] [name] discarded [M.name] from head revolutionary due to ineligibility.") - if(revolution.members.len) - revolution.update_objectives() - revolution.update_rev_heads() - SSshuttle.registerHostileEnvironment(revolution) - return TRUE - log_dynamic("[ruletype] [name] failed to get any eligible headrevs. Refunding [cost] threat.") - return FALSE - -/datum/dynamic_ruleset/roundstart/revs/clean_up() - qdel(revolution) - ..() - -/datum/dynamic_ruleset/roundstart/revs/rule_process() - var/winner = revolution.process_victory() - if (isnull(winner)) - return - - finished = winner - - if(winner == REVOLUTION_VICTORY) - GLOB.revolutionary_win = TRUE - - return RULESET_STOP_PROCESSING - -/// Checks for revhead loss conditions and other antag datums. -/datum/dynamic_ruleset/roundstart/revs/proc/check_eligible(datum/mind/M) - var/turf/T = get_turf(M.current) - if(!considered_afk(M) && considered_alive(M) && is_station_level(T.z) && !M.antag_datums?.len && !HAS_TRAIT(M, TRAIT_MINDSHIELD)) - return TRUE - return FALSE - -/datum/dynamic_ruleset/roundstart/revs/round_result() - revolution.round_result(finished) - -// Admin only rulesets. The threat requirement is 101 so it is not possible to roll them. - -////////////////////////////////////////////// -// // -// EXTENDED // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/extended - name = "Extended" - antag_flag = null - antag_datum = null - restricted_roles = list() - required_candidates = 0 - weight = 3 - cost = 0 - requirements = list(101,101,101,101,101,101,101,101,101,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.") - SSdynamic.spend_roundstart_budget(SSdynamic.round_start_budget) - SSdynamic.spend_midround_budget(SSdynamic.mid_round_budget) - SSdynamic.threat_log += "[worldtime2text()]: Extended ruleset set threat to 0." - return TRUE - -////////////////////////////////////////////// -// // -// CLOWN OPS // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/nuclear/clown_ops - name = "Clown Operatives" - antag_datum = /datum/antagonist/nukeop/clownop - antag_flag = ROLE_CLOWN_OPERATIVE - antag_flag_override = ROLE_OPERATIVE - antag_leader_datum = /datum/antagonist/nukeop/leader/clownop - requirements = list(101,101,101,101,101,101,101,101,101,101) - required_role = ROLE_CLOWN_OPERATIVE - -/datum/dynamic_ruleset/roundstart/nuclear/clown_ops/pre_execute() - . = ..() - if(!.) - return - - var/list/nukes = SSmachines.get_machines_by_type(/obj/machinery/nuclearbomb/syndicate) - for(var/obj/machinery/nuclearbomb/syndicate/nuke as anything in nukes) - new /obj/machinery/nuclearbomb/syndicate/bananium(nuke.loc) - qdel(nuke) - - for(var/datum/mind/clowns in assigned) - clowns.set_assigned_role(SSjob.GetJobType(/datum/job/clown_operative)) - clowns.special_role = ROLE_CLOWN_OPERATIVE - -////////////////////////////////////////////// -// // -// METEOR // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/meteor - name = "Meteor" - persistent = TRUE - required_candidates = 0 - weight = 3 - cost = 0 - requirements = list(101,101,101,101,101,101,101,101,101,101) - flags = LONE_RULESET - var/meteordelay = 2000 - var/nometeors = FALSE - var/rampupdelta = 5 - -/datum/dynamic_ruleset/roundstart/meteor/rule_process() - if(nometeors || meteordelay > world.time - SSticker.round_start_time) - return - - var/list/wavetype = GLOB.meteors_normal - var/meteorminutes = (world.time - SSticker.round_start_time - meteordelay) / 10 / 60 - - if (prob(meteorminutes)) - wavetype = GLOB.meteors_threatening - - if (prob(meteorminutes/2)) - wavetype = GLOB.meteors_catastrophic - - var/ramp_up_final = clamp(round(meteorminutes/rampupdelta), 1, 10) - - spawn_meteors(ramp_up_final, wavetype) - -/// Ruleset for Nations -/datum/dynamic_ruleset/roundstart/nations - name = "Nations" - required_candidates = 0 - weight = 0 //admin only (and for good reason) - cost = 0 - flags = LONE_RULESET | ONLY_RULESET - -/datum/dynamic_ruleset/roundstart/nations/execute() - . = ..() - //notably assistant is not in this list to prevent the round turning into BARBARISM instantly, and silicon is in this list for UN - var/list/department_types = list( - /datum/job_department/silicon, //united nations - /datum/job_department/cargo, - /datum/job_department/engineering, - /datum/job_department/medical, - /datum/job_department/science, - /datum/job_department/security, - /datum/job_department/service, - ) - - for(var/department_type in department_types) - create_separatist_nation(department_type, announcement = FALSE, dangerous = FALSE, message_admins = FALSE) - - GLOB.round_default_lawset = /datum/ai_laws/united_nations diff --git a/code/controllers/subsystem/dynamic/dynamic_unfavorable_situation.dm b/code/controllers/subsystem/dynamic/dynamic_unfavorable_situation.dm deleted file mode 100644 index 994f2e3f5de7..000000000000 --- a/code/controllers/subsystem/dynamic/dynamic_unfavorable_situation.dm +++ /dev/null @@ -1,74 +0,0 @@ -/// An easy interface to make...*waves hands* bad things happen. -/// This is used for impactful events like traitors hacking and creating more threat, or a revolutions victory. -/// It tries to spawn a heavy midround if possible, otherwise it will trigger a "bad" random event after a short period. -/// Calling this function will not use up any threat. -/datum/controller/subsystem/dynamic/proc/unfavorable_situation() - SHOULD_NOT_SLEEP(TRUE) - - INVOKE_ASYNC(src, PROC_REF(_unfavorable_situation)) - -/datum/controller/subsystem/dynamic/proc/_unfavorable_situation() - var/static/list/unfavorable_random_events = list() - if (!length(unfavorable_random_events)) - unfavorable_random_events = generate_unfavourable_events() - var/list/possible_heavies = generate_unfavourable_heavy_rulesets() - if (!length(possible_heavies)) - var/datum/round_event_control/round_event_control_type = pick(unfavorable_random_events) - var/delay = rand(20 SECONDS, 1 MINUTES) - - log_dynamic_and_announce("An unfavorable situation was requested, but no heavy rulesets could be drafted. Spawning [initial(round_event_control_type.name)] in [DisplayTimeText(delay)] instead.") - force_event_after(round_event_control_type, "an unfavorable situation", delay) - else - var/datum/dynamic_ruleset/midround/heavy_ruleset = pick_weight(possible_heavies) - log_dynamic_and_announce("An unfavorable situation was requested, spawning [initial(heavy_ruleset.name)]") - picking_specific_rule(heavy_ruleset, forced = TRUE, ignore_cost = TRUE) - -/// Return a valid heavy dynamic ruleset, or an empty list if there's no time to run any rulesets -/datum/controller/subsystem/dynamic/proc/generate_unfavourable_heavy_rulesets() - if (EMERGENCY_PAST_POINT_OF_NO_RETURN) - return list() - - var/list/possible_heavies = list() - for (var/datum/dynamic_ruleset/midround/ruleset as anything in midround_rules) - if (ruleset.midround_ruleset_style != MIDROUND_RULESET_STYLE_HEAVY) - continue - - if (ruleset.weight == 0) - continue - - if (ruleset.cost > max_threat_level) - continue - - if (!ruleset.acceptable(GLOB.alive_player_list.len, threat_level)) - continue - - if (ruleset.minimum_round_time > world.time - SSticker.round_start_time) - continue - - if(istype(ruleset, /datum/dynamic_ruleset/midround/from_ghosts) && !(GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT)) - continue - - ruleset.trim_candidates() - - ruleset.load_templates() - if (!ruleset.ready()) - continue - - possible_heavies[ruleset] = ruleset.get_weight() - return possible_heavies - -/// Filter the below list by which events can actually run on this map -/datum/controller/subsystem/dynamic/proc/generate_unfavourable_events() - var/static/list/unfavorable_random_events = list( - /datum/round_event_control/earthquake, - /datum/round_event_control/immovable_rod, - /datum/round_event_control/meteor_wave, - /datum/round_event_control/portal_storm_syndicate, - ) - var/list/picked_events = list() - for(var/type in unfavorable_random_events) - var/datum/round_event_control/event = new type() - if(!event.valid_for_map()) - continue - picked_events += type - return picked_events diff --git a/code/controllers/subsystem/dynamic/readme.md b/code/controllers/subsystem/dynamic/readme.md deleted file mode 100644 index ec21626945ea..000000000000 --- a/code/controllers/subsystem/dynamic/readme.md +++ /dev/null @@ -1,194 +0,0 @@ -# Dynamic Mode - -## Roundstart - -Dynamic rolls threat based on a special sauce formula: - -> [dynamic_curve_width][/datum/controller/global_vars/var/dynamic_curve_width] \* tan((3.1416 \* (rand() - 0.5) \* 57.2957795)) + [dynamic_curve_centre][/datum/controller/global_vars/var/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/controller/subsystem/dynamic/proc/generate_threat` (for creating the threat level) and `/datum/controller/subsystem/dynamic/proc/generate_budgets` (for splitting the threat level into budgets). - -## Deciding roundstart threats -In `/datum/controller/subsystem/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/controller/subsystem/dynamic/pre_setup()`). - -## Deciding midround threats - -### Frequency - -The frequency of midround threats is based on the midround threat of the round. The number of midround threats that will roll is `threat_level` / `threat_per_midround_roll` (configurable), rounded up. For example, if `threat_per_midround_roll` is set to 5, then for every 5 threat, one midround roll will be added. If you have 6 threat, with this configuration, you will get 2 midround rolls. - -These midround roll points are then equidistantly spaced across the round, starting from `midround_lower_bound` (configurable) to `midround_upper_bound` (configurable), with a +/- of `midround_roll_distance` (configurable). - -For example, if: -1. `midround_lower_bound` is `10 MINUTES` -2. `midround_upper_bound` is `100 MINUTES` -3. `midround_roll_distance` is `3 MINUTES` -4. You have 5 midround rolls for the round - -...then those 5 midround rolls will be placed equidistantly (meaning equally apart) across the first 10-100 minutes of the round. Every individual roll will then be adjusted to either be 3 minutes earlier, or 3 minutes later. - -### Threat variety - -Threats are split between **heavy** rulesets and **light** rulesets. A heavy ruleset includes major threats like space dragons or blobs, while light rulesets are ones that don't often cause shuttle calls when rolled, such as revenants or traitors (sleeper agents). - -When a midround roll occurs, the decision to choose between light or heavy depends on the current round time. If it is less than `midround_light_upper_bound` (configurable), then it is guaranteed to be a light ruleset. If it is more than `midround_heavy_lower_bound`, then it is guaranteed to be a heavy ruleset. If it is any point in between, it will interpolate the value between those. This means that the longer the round goes on, the more likely you are to get a heavy ruleset. - -If no heavy ruleset can run, such as not having enough threat, then a light ruleset is guaranteed to run. - -## Rule Processing - -Calls [rule_process][/datum/dynamic_ruleset/proc/rule_process] on every rule which is in the current_rules list. -Every sixty seconds, update_playercounts() -Midround injection time is checked against world.time to see if an injection should happen. -If midround injection time is lower than world.time, it updates playercounts again, then tries to inject and generates a new cooldown regardless of whether a rule is picked. - -## Latejoin - -make_antag_chance(newPlayer) -> (For each latespawn rule...) --> acceptable(living players, threat_level) -> trim_candidates() -> ready(forced=FALSE) -**If true, add to drafted rules -**NOTE that acceptable uses threat_level not threat! -**NOTE Latejoin timer is ONLY reset if at least one rule was drafted. -**NOTE the new_player.dm AttemptLateSpawn() calls OnPostSetup for all roles (unless assigned role is MODE) - -(After collecting all draftble rules...) --> picking_latejoin_ruleset(drafted_rules) -> spend threat -> ruleset.execute() - -## Midround - -process() -> (For each midround rule... --> acceptable(living players, threat_level) -> trim_candidates() -> ready(forced=FALSE) -(After collecting all draftble rules...) --> picking_midround_ruleset(drafted_rules) -> spend threat -> ruleset.execute() - -## Forced - -For latejoin, it simply sets forced_latejoin_rule -make_antag_chance(newPlayer) -> trim_candidates() -> ready(forced=TRUE) **NOTE no acceptable() call - -For midround, calls the below proc with forced = TRUE -picking_specific_rule(ruletype,forced) -> forced OR acceptable(living_players, threat_level) -> trim_candidates() -> ready(forced) -> spend threat -> execute() -**NOTE specific rule can be called by RS traitor->MR autotraitor w/ forced=FALSE -**NOTE that due to short circuiting acceptable() need not be called if forced. - -## Ruleset - -acceptable(population,threat) just checks if enough threat_level for population indice. -**NOTE that we currently only send threat_level as the second arg, not threat. -ready(forced) checks if enough candidates and calls the map's map_ruleset(dynamic_ruleset) at the parent level - -trim_candidates() varies significantly according to the ruleset type -Roundstart: All candidates are new_player mobs. Check them for standard stuff: connected, desire role, not banned, etc. -**NOTE Roundstart deals with both candidates (trimmed list of valid players) and mode.candidates (everyone readied up). Don't confuse them! -Latejoin: Only one candidate, the latejoiner. Standard checks. -Midround: Instead of building a single list candidates, candidates contains four lists: living, dead, observing, and living antags. Standard checks in trim_list(list). - -Midround - Rulesets have additional types -/from_ghosts: execute() -> send_applications() -> review_applications() -> finish_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/controller/subsystem/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/controller/subsystem/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. */ - }, - - "Station": { - /* Special threat reductions for dangerous station traits. Traits are selected before dynamic, so traits will always */ - /* reduce threat even if there's no threat for it available. Only "cost" can be modified */ - } -} -``` - -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. - -This is not a complete list--search "configurable" in this README to learn more. - -### 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/controller/subsystem/dynamic/proc/get_injection_chance()`). Regardless of its decision, a new timer will be started within the range of 1 to 3 minutes, repeatedly. -- `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)) -- `max_threat_level` - Sets the maximum amount of threat that can be rolled. Defaults to 100. You should only use this to *lower* the maximum threat, as raising it higher will not do anything. - -## 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/controller/subsystem/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://github.com/tgstation/documentation-assets/blob/main/dynamic/random_event_hijacking.png) - -`n` is a random value between `random_event_hijack_minimum` and `random_event_hijack_maximum`. Heavy injection chance, should it need to be raised, is increased by `hijacked_random_event_injection_chance_modifier`. diff --git a/code/controllers/subsystem/dynamic/ruleset_picking.dm b/code/controllers/subsystem/dynamic/ruleset_picking.dm deleted file mode 100644 index e3de3289899f..000000000000 --- a/code/controllers/subsystem/dynamic/ruleset_picking.dm +++ /dev/null @@ -1,139 +0,0 @@ -#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. - * - * * max_allowed_attempts - Allows you to configure how many times the proc will attempt to pick a ruleset before giving up. - */ -/datum/controller/subsystem/dynamic/proc/pick_ruleset(list/drafted_rules, max_allowed_attempts = INFINITY) - if (only_ruleset_executed) - log_dynamic("FAIL: only_ruleset_executed") - return null - - if(!length(drafted_rules)) - log_dynamic("FAIL: pick ruleset supplied with an empty list of drafted rules.") - return null - - var/attempts = 0 - while (attempts < max_allowed_attempts) - attempts++ - var/datum/dynamic_ruleset/rule = pick_weight(drafted_rules) - if (!rule) - var/list/leftover_rules = list() - for (var/leftover_rule in drafted_rules) - leftover_rules += "[leftover_rule]" - - log_dynamic("FAIL: No rulesets left to pick. Leftover rules: [leftover_rules.Join(", ")]") - return null - - if (check_blocking(rule.blocking_rules, executed_rules)) - log_dynamic("FAIL: [rule] can't execute as another rulset is blocking it.") - 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 \ - ) - log_dynamic("FAIL: [rule] can't execute as a high impact ruleset was already executed.") - drafted_rules -= rule - if(drafted_rules.len <= 0) - return null - continue - - return rule - - return null - -/// Executes a random midround ruleset from the list of drafted rules. -/datum/controller/subsystem/dynamic/proc/pick_midround_rule(list/drafted_rules, description) - log_dynamic("Rolling [drafted_rules.len] [description]") - - var/datum/dynamic_ruleset/rule = pick_ruleset(drafted_rules) - if (isnull(rule)) - return null - - current_midround_rulesets = drafted_rules - rule - - midround_injection_timer_id = addtimer( - CALLBACK(src, PROC_REF(execute_midround_rule), rule), \ - ADMIN_CANCEL_MIDROUND_TIME, \ - TIMER_STOPPABLE, \ - ) - - log_dynamic("[rule] ruleset executing...") - message_admins("DYNAMIC: Executing midround ruleset [rule] in [DisplayTimeText(ADMIN_CANCEL_MIDROUND_TIME)]. \ - CANCEL | \ - SOMETHING ELSE") - - return rule - -/// Fired after admins do not cancel a midround injection. -/datum/controller/subsystem/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_REF(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/controller/subsystem/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]") - rule.pre_execute(GLOB.alive_player_list.len) - if (rule.execute()) - log_dynamic("Injected a [rule.ruletype] 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_RULESET) - var/mob/M = pick(rule.candidates) - message_admins("[key_name(M)] joined the station, and was selected by the [rule.name] ruleset.") - log_dynamic("[key_name(M)] joined the station, and was selected by the [rule.name] ruleset.") - executed_rules += rule - if (rule.persistent) - current_rules += rule - new_snapshot(rule) - rule.forget_startup() - return TRUE - rule.forget_startup() - 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/controller/subsystem/dynamic/proc/admin_cancel_midround(mob/user, timer_id) - if (midround_injection_timer_id != timer_id || !deltimer(midround_injection_timer_id)) - to_chat(user, span_notice("Too late!")) - return - - log_admin("[key_name(user)] cancelled the next midround injection.") - message_admins("[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/controller/subsystem/dynamic/proc/admin_different_midround(mob/user, timer_id) - if (midround_injection_timer_id != timer_id || !deltimer(midround_injection_timer_id)) - to_chat(user, span_notice("Too late!")) - return - - midround_injection_timer_id = null - - if (isnull(current_midround_rulesets) || current_midround_rulesets.len == 0) - log_admin("[key_name(user)] asked for a different midround injection, but there were none left.") - message_admins("[key_name(user)] asked for a different midround injection, but there were none left.") - return - - log_admin("[key_name(user)] asked for a different midround injection.") - message_admins("[key_name(user)] asked for a different midround injection.") - pick_midround_rule(current_midround_rulesets, "different midround rulesets") - -#undef ADMIN_CANCEL_MIDROUND_TIME diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 8e46450debd0..6fabc2909ad5 100644 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -229,8 +229,8 @@ SUBSYSTEM_DEF(ticker) return TRUE if(GLOB.station_was_nuked) return TRUE - if(GLOB.revolutionary_win) - return TRUE +// if(GLOB.revolutionary_win) +// return TRUE return FALSE /datum/controller/subsystem/ticker/proc/setup() @@ -240,7 +240,8 @@ SUBSYSTEM_DEF(ticker) CHECK_TICK //Configure mode and assign player to antagonists var/can_continue = FALSE - can_continue = SSdynamic.pre_setup() //Choose antagonists + // This is where you assign antags if you want that + can_continue = TRUE CHECK_TICK SEND_GLOBAL_SIGNAL(COMSIG_GLOB_PRE_JOBS_ASSIGNED, src) can_continue = can_continue && SSjob.DivideOccupations() //Distribute jobs @@ -306,7 +307,8 @@ SUBSYSTEM_DEF(ticker) /datum/controller/subsystem/ticker/proc/PostSetup() set waitfor = FALSE - SSdynamic.post_setup() + + SScommunications.send_roundstart_report() GLOB.start_state = new /datum/station_state() GLOB.start_state.count() diff --git a/code/datums/station_traits/_station_trait.dm b/code/datums/station_traits/_station_trait.dm index 8bdfd5745c9e..7c54abb25291 100644 --- a/code/datums/station_traits/_station_trait.dm +++ b/code/datums/station_traits/_station_trait.dm @@ -31,8 +31,6 @@ GLOBAL_LIST_EMPTY(lobby_station_traits) var/list/lobby_buttons = list() /// The ID that we look for in dynamic.json. Not synced with 'name' because I can already see this go wrong var/dynamic_threat_id - /// If ran during dynamic, do we reduce the total threat? Will be overriden by config if set - var/threat_reduction = 0 /// Trait should not be instantiated in a round if its type matches this type var/abstract_type = /datum/station_trait @@ -41,8 +39,6 @@ GLOBAL_LIST_EMPTY(lobby_station_traits) RegisterSignal(SSticker, COMSIG_TICKER_ROUND_STARTING, PROC_REF(on_round_start)) - if(threat_reduction) - GLOB.dynamic_station_traits[src] = threat_reduction if(sign_up_button) GLOB.lobby_station_traits += src if(trait_processes) @@ -52,7 +48,6 @@ GLOBAL_LIST_EMPTY(lobby_station_traits) /datum/station_trait/Destroy() SSstation.station_traits -= src - GLOB.dynamic_station_traits.Remove(src) destroy_lobby_buttons() return ..() diff --git a/code/datums/station_traits/negative_traits.dm b/code/datums/station_traits/negative_traits.dm index 1f140fd1eda1..3631f8af7928 100644 --- a/code/datums/station_traits/negative_traits.dm +++ b/code/datums/station_traits/negative_traits.dm @@ -556,7 +556,6 @@ trait_to_give = STATION_TRAIT_RADIOACTIVE_NEBULA blacklist = list(/datum/station_trait/random_event_weight_modifier/rad_storms) - threat_reduction = 30 dynamic_threat_id = "Radioactive Nebula" intensity_increment_time = 5 MINUTES diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm index 9e833e497f9a..2ab098d756bc 100644 --- a/code/game/machinery/computer/communications.dm +++ b/code/game/machinery/computer/communications.dm @@ -858,6 +858,8 @@ * hacker - the mob that caused the hack */ /obj/machinery/computer/communications/proc/hack_console(mob/living/hacker) + to_chat(hacker, span_notice("You hack into the communications console. Nothing actually happens.")) +/* // All hack results we'll choose from. var/list/hack_options = list(HACK_THREAT) @@ -934,6 +936,7 @@ "Attention crew, it appears that someone on your station has hijacked your telecommunications and broadcasted an unknown signal.", "[command_name()] High-Priority Update", ) +*/ #undef HACK_PIRATE #undef HACK_FUGITIVES diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 10b9a58b8700..9caa06833cfc 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -20,15 +20,6 @@ return var/dat = "
Game Panel

" - if(SSticker.current_state <= GAME_STATE_PREGAME) - dat += "(Manage Dynamic Rulesets)
" - dat += "(Force Roundstart Rulesets)
" - if (GLOB.dynamic_forced_roundstart_ruleset.len > 0) - for(var/datum/dynamic_ruleset/roundstart/rule in GLOB.dynamic_forced_roundstart_ruleset) - dat += {"-> [rule.name] <-
"} - dat += "(Clear Rulesets)
" - dat += "(Dynamic mode options)
" - dat += "
" if(SSticker.IsRoundInProgress()) dat += "(Game Mode Panel)
" dat += "(Manage Dynamic Rulesets)
" @@ -103,130 +94,6 @@ ADMIN_VERB(spawn_cargo, R_SPAWN, "Spawn Cargo", "Spawn a cargo crate.", ADMIN_CA log_admin("[key_name(user)] spawned cargo pack [chosen] at [AREACOORD(user.mob)]") BLACKBOX_LOG_ADMIN_VERB("Spawn Cargo") -/datum/admins/proc/dynamic_mode_options(mob/user) - var/dat = {" -

Dynamic Mode Options


-
-

Common options

- All these options can be changed midround.
-
- Force extended: - Option is [GLOB.dynamic_forced_extended ? "ON" : "OFF"]. -
This will force the round to be extended. No rulesets will be drafted.
-
- 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.
-
- Forced threat level: Current value : [GLOB.dynamic_forced_threat_level]. -
The value threat is set to if it is higher than -1.
-
-
- 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.
- "} - - user << browse(dat, "window=dyn_mode_options;size=900x650") - -/datum/admins/proc/dynamic_ruleset_manager(mob/user) - var/dat = "

Dynamic Ruleset Management


\ - Change these options to forcibly enable or disable dynamic rulesets.
\ - Disabled rulesets will never run, even if they would otherwise be valid.
\ - Enabled rulesets will run even if the qualifying minimum of threat or player count is not present, this does not guarantee that they will necessarily be chosen (for example their weight may be set to 0 in config).
\ - \[force enable all / \ - force disable all / \ - reset all\]" - - if (SSticker.current_state <= GAME_STATE_PREGAME) // Don't bother displaying after the round has started - var/static/list/rulesets_by_context = list() - if (!length(rulesets_by_context)) - for (var/datum/dynamic_ruleset/rule as anything in subtypesof(/datum/dynamic_ruleset)) - if (initial(rule.name) == "") - continue - LAZYADD(rulesets_by_context[initial(rule.ruletype)], rule) - - dat += dynamic_ruleset_category_pre_start_display("Roundstart", rulesets_by_context[ROUNDSTART_RULESET]) - dat += dynamic_ruleset_category_pre_start_display("Latejoin", rulesets_by_context[LATEJOIN_RULESET]) - dat += dynamic_ruleset_category_pre_start_display("Midround", rulesets_by_context[MIDROUND_RULESET]) - user << browse(dat, "window=dyn_mode_options;size=900x650") - return - - var/pop_count = length(GLOB.alive_player_list) - var/threat_level = SSdynamic.threat_level - dat += dynamic_ruleset_category_during_round_display("Latejoin", SSdynamic.latejoin_rules, pop_count, threat_level) - dat += dynamic_ruleset_category_during_round_display("Midround", SSdynamic.midround_rules, pop_count, threat_level) - user << browse(dat, "window=dyn_mode_options;size=900x650") - -/datum/admins/proc/dynamic_ruleset_category_pre_start_display(title, list/rules) - var/dat = "

[title]

" - for (var/datum/dynamic_ruleset/rule as anything in rules) - var/forced = GLOB.dynamic_forced_rulesets[rule] || RULESET_NOT_FORCED - var/color = COLOR_BLACK - switch (forced) - if (RULESET_FORCE_ENABLED) - color = COLOR_GREEN - if (RULESET_FORCE_DISABLED) - color = COLOR_RED - dat += "" - dat += "
[initial(rule.name)]\[[forced]\]\[\ - force enabled /\ - force disabled /\ - reset\]
" - return dat - -/datum/admins/proc/dynamic_ruleset_category_during_round_display(title, list/rules, pop_count, threat_level) - var/dat = "

[title]

" - for (var/datum/dynamic_ruleset/rule as anything in rules) - var/active = rule.acceptable(population = pop_count, threat_level = threat_level) && rule.weight > 0 - var/forced = GLOB.dynamic_forced_rulesets[rule.type] || RULESET_NOT_FORCED - var/color = (active) ? COLOR_GREEN : COLOR_RED - var/explanation = "" - if (!active) - if (rule.weight <= 0) - explanation = " - Weight is zero" - else if (forced == RULESET_FORCE_DISABLED) - explanation = " - Forcibly disabled" - else if (forced == RULESET_FORCE_ENABLED) - explanation = " - Failed spawn conditions" - else if (!rule.is_valid_population(pop_count)) - explanation = " - Invalid player count" - else if (!rule.is_valid_threat(pop_count, threat_level)) - explanation = " - Insufficient threat" - else - explanation = " - Failed spawn conditions" - else if (forced == RULESET_FORCE_ENABLED) - explanation = " - Forcibly enabled" - active = active ? "Active" : "Inactive" - - dat += "\ - \ - " - dat += "
[rule.name]\[Weight : [rule.weight]\]\ - \[[active][explanation]\]\[\ - force enabled /\ - force disabled /\ - reset\]\[VV\]
" - return dat - - -/datum/admins/proc/force_all_rulesets(mob/user, force_value) - if (force_value == RULESET_NOT_FORCED) - GLOB.dynamic_forced_rulesets = list() - else - for (var/datum/dynamic_ruleset/rule as anything in subtypesof(/datum/dynamic_ruleset)) - GLOB.dynamic_forced_rulesets[rule] = force_value - var/logged_message = "[key_name(user)] set all dynamic rulesets to [force_value]." - log_admin(logged_message) - message_admins(logged_message) - dynamic_ruleset_manager(user) - -/datum/admins/proc/set_dynamic_ruleset_forced(mob/user, datum/dynamic_ruleset/type, force_value) - if (isnull(type)) - return - GLOB.dynamic_forced_rulesets[type] = force_value - dynamic_ruleset_manager(user) - var/logged_message = "[key_name(user)] set '[initial(type.name)] ([initial(type.ruletype)])' to [GLOB.dynamic_forced_rulesets[type]]." - log_admin(logged_message) - message_admins(logged_message) - ADMIN_VERB(create_or_modify_area, R_DEBUG, "Create Or Modify Area", "Create of modify an area. wow.", ADMIN_CATEGORY_DEBUG) create_area(user.mob) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index edcad85134a5..76e1d7ac647a 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -66,11 +66,6 @@ else if(href_list["editrights"]) edit_rights_topic(href_list) - else if(href_list["gamemode_panel"]) - if(!check_rights(R_ADMIN)) - return - SSdynamic.admin_panel() - else if(href_list["call_shuttle"]) if(!check_rights(R_ADMIN)) return @@ -388,122 +383,6 @@ return cmd_admin_mute(href_list["mute"], text2num(href_list["mute_type"])) - else if(href_list["f_dynamic_roundstart"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker.HasRoundStarted()) - return tgui_alert(usr, "The game has already started.") - var/roundstart_rules = list() - for (var/rule in subtypesof(/datum/dynamic_ruleset/roundstart)) - var/datum/dynamic_ruleset/roundstart/newrule = new rule() - roundstart_rules[newrule.name] = newrule - var/added_rule = input(usr,"What ruleset do you want to force? This will bypass threat level and population restrictions.", "Rigging Roundstart", null) as null|anything in sort_list(roundstart_rules) - if (added_rule) - GLOB.dynamic_forced_roundstart_ruleset += roundstart_rules[added_rule] - log_admin("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.") - message_admins("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.", 1) - Game() - - else if(href_list["f_dynamic_roundstart_clear"]) - if(!check_rights(R_ADMIN)) - return - GLOB.dynamic_forced_roundstart_ruleset = list() - Game() - log_admin("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.") - message_admins("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.", 1) - - else if(href_list["f_dynamic_roundstart_remove"]) - if(!check_rights(R_ADMIN)) - return - var/datum/dynamic_ruleset/roundstart/rule = locate(href_list["f_dynamic_roundstart_remove"]) - GLOB.dynamic_forced_roundstart_ruleset -= rule - Game() - log_admin("[key_name(usr)] removed [rule] from the forced roundstart rulesets.") - message_admins("[key_name(usr)] removed [rule] from the forced roundstart rulesets.", 1) - - else if (href_list["f_dynamic_ruleset_manage"]) - if(!check_rights(R_ADMIN)) - return - dynamic_ruleset_manager(usr) - else if (href_list["f_dynamic_ruleset_force_all_on"]) - if(!check_rights(R_ADMIN)) - return - force_all_rulesets(usr, RULESET_FORCE_ENABLED) - else if (href_list["f_dynamic_ruleset_force_all_off"]) - if(!check_rights(R_ADMIN)) - return - force_all_rulesets(usr, RULESET_FORCE_DISABLED) - else if (href_list["f_dynamic_ruleset_force_all_reset"]) - if(!check_rights(R_ADMIN)) - return - force_all_rulesets(usr, RULESET_NOT_FORCED) - else if (href_list["f_dynamic_ruleset_force_on"]) - if(!check_rights(R_ADMIN)) - return - set_dynamic_ruleset_forced(usr, locate(href_list["f_dynamic_ruleset_force_on"]), RULESET_FORCE_ENABLED) - else if (href_list["f_dynamic_ruleset_force_off"]) - if(!check_rights(R_ADMIN)) - return - set_dynamic_ruleset_forced(usr, locate(href_list["f_dynamic_ruleset_force_off"]), RULESET_FORCE_DISABLED) - else if (href_list["f_dynamic_ruleset_force_reset"]) - if(!check_rights(R_ADMIN)) - return - set_dynamic_ruleset_forced(usr, locate(href_list["f_dynamic_ruleset_force_reset"]), RULESET_NOT_FORCED) - else if (href_list["f_inspect_ruleset"]) - if(!check_rights(R_ADMIN)) - return - usr.client.debug_variables(locate(href_list["f_inspect_ruleset"])) - - else if (href_list["f_dynamic_options"]) - if(!check_rights(R_ADMIN)) - return - - if(SSticker.HasRoundStarted()) - return tgui_alert(usr, "The game has already started.") - - dynamic_mode_options(usr) - else if(href_list["f_dynamic_force_extended"]) - if(!check_rights(R_ADMIN)) - return - - GLOB.dynamic_forced_extended = !GLOB.dynamic_forced_extended - log_admin("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].") - message_admins("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_no_stacking"]) - if(!check_rights(R_ADMIN)) - return - - GLOB.dynamic_no_stacking = !GLOB.dynamic_no_stacking - log_admin("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].") - message_admins("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].") - dynamic_mode_options(usr) - else if(href_list["f_dynamic_stacking_limit"]) - if(!check_rights(R_ADMIN)) - return - - 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 - log_admin("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].") - message_admins("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_forced_threat"]) - if(!check_rights(R_ADMIN)) - return - - if(SSticker.HasRoundStarted()) - return tgui_alert(usr, "The game has already started.") - - var/new_value = input(usr, "Enter the forced threat level for dynamic mode.", "Forced threat level") as num - if (new_value > 100) - return tgui_alert(usr, "The value must be be under 100.") - GLOB.dynamic_forced_threat_level = new_value - - log_admin("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].") - message_admins("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].") - dynamic_mode_options(usr) - else if(href_list["forcespeech"]) if(!check_rights(R_FUN)) return diff --git a/code/modules/antagonists/changeling/fallen_changeling.dm b/code/modules/antagonists/changeling/fallen_changeling.dm index c44c1b66cd3d..5d3a1b49f925 100644 --- a/code/modules/antagonists/changeling/fallen_changeling.dm +++ b/code/modules/antagonists/changeling/fallen_changeling.dm @@ -3,11 +3,10 @@ name = "\improper Fallen Changeling" roundend_category = "changelings" antagpanel_category = "Changeling" - job_rank = ROLE_CHANGELING + // job_rank = ROLE_CHANGELING antag_moodlet = /datum/mood_event/fallen_changeling antag_hud_name = "changeling" /datum/mood_event/fallen_changeling description = "My powers! Where are my powers?!" mood_change = -4 - diff --git a/code/modules/antagonists/disease/disease_datum.dm b/code/modules/antagonists/disease/disease_datum.dm index 9d5af7ab83e7..75cc7d647154 100644 --- a/code/modules/antagonists/disease/disease_datum.dm +++ b/code/modules/antagonists/disease/disease_datum.dm @@ -1,6 +1,7 @@ /datum/antagonist/disease name = "Sentient Disease" roundend_category = "diseases" + job_rank = ROLE_SENTIENT_DISEASE antagpanel_category = ANTAG_GROUP_BIOHAZARDS show_to_ghosts = TRUE var/disease_name = "" diff --git a/code/modules/antagonists/heretic/heretic_monsters.dm b/code/modules/antagonists/heretic/heretic_monsters.dm index 3f3dd3203572..b216c46402d8 100644 --- a/code/modules/antagonists/heretic/heretic_monsters.dm +++ b/code/modules/antagonists/heretic/heretic_monsters.dm @@ -4,7 +4,7 @@ roundend_category = "Heretics" antagpanel_category = ANTAG_GROUP_HORRORS antag_moodlet = /datum/mood_event/heretics - job_rank = ROLE_HERETIC + // job_rank = ROLE_SENTIENCE // NON-MODULE CHANGE / Was ROLE_HERETIC antag_hud_name = "heretic_beast" suicide_cry = "MY MASTER SMILES UPON ME!!" show_in_antagpanel = FALSE diff --git a/code/modules/antagonists/pirate/pirate.dm b/code/modules/antagonists/pirate/pirate.dm index 15a028b24d74..41ed300fe89b 100644 --- a/code/modules/antagonists/pirate/pirate.dm +++ b/code/modules/antagonists/pirate/pirate.dm @@ -1,6 +1,6 @@ /datum/antagonist/pirate name = "\improper Space Pirate" - job_rank = ROLE_TRAITOR + job_rank = ROLE_SPACE_PIRATE // NON-MODULE CHANGE / Was ROLE_TRAITOR roundend_category = "space pirates" antagpanel_category = ANTAG_GROUP_PIRATES show_in_antagpanel = FALSE diff --git a/code/modules/antagonists/revenant/revenant_antag.dm b/code/modules/antagonists/revenant/revenant_antag.dm index 8f15508989c5..fdb5f246c943 100644 --- a/code/modules/antagonists/revenant/revenant_antag.dm +++ b/code/modules/antagonists/revenant/revenant_antag.dm @@ -4,6 +4,7 @@ show_name_in_check_antagonists = TRUE show_to_ghosts = TRUE antagpanel_category = ANTAG_GROUP_HORRORS + job_rank = ROLE_REVENANT /datum/antagonist/revenant/greet() . = ..() diff --git a/code/modules/antagonists/sentient_creature/sentient_creature.dm b/code/modules/antagonists/sentient_creature/sentient_creature.dm index d1197265ced5..10b5b99cbd7a 100644 --- a/code/modules/antagonists/sentient_creature/sentient_creature.dm +++ b/code/modules/antagonists/sentient_creature/sentient_creature.dm @@ -3,6 +3,7 @@ show_in_antagpanel = FALSE show_in_roundend = FALSE count_against_dynamic_roll_chance = FALSE + job_rank = ROLE_SENTIENCE ui_name = "AntagInfoSentient" /datum/antagonist/sentient_creature/get_preview_icon() diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/summons.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/summons.dm index fe5f69fd9fa5..9ea1a1b87717 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook_entries/summons.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/summons.dm @@ -20,9 +20,6 @@ There is a good chance that they will shoot each other first." /datum/spellbook_entry/summon/guns/can_be_purchased() - // Summon Guns requires 98 threat. - if(SSdynamic.threat_level < MINIMUM_THREAT_FOR_RITUALS) - return FALSE // Also must be config enabled return !CONFIG_GET(flag/no_summon_guns) @@ -37,9 +34,6 @@ why they aren't to be trusted with it at the same time." /datum/spellbook_entry/summon/magic/can_be_purchased() - // Summon Magic requires 98 threat. - if(SSdynamic.threat_level < MINIMUM_THREAT_FOR_RITUALS) - return FALSE // Also must be config enabled return !CONFIG_GET(flag/no_summon_magic) @@ -57,9 +51,6 @@ limit = 5 // Each purchase can intensify it. /datum/spellbook_entry/summon/events/can_be_purchased() - // Summon Events requires 98 threat. - if(SSdynamic.threat_level < MINIMUM_THREAT_FOR_RITUALS) - return FALSE // Also, must be config enabled return !CONFIG_GET(flag/no_summon_events) @@ -135,8 +126,6 @@ return ..() /datum/spellbook_entry/summon/specific_spell/can_be_purchased() - if(SSdynamic.threat_level < MINIMUM_THREAT_FOR_RITUALS) - return FALSE if(GLOB.mass_teaching) return FALSE return ..() diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm b/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm index a951a5daf422..8cb2e913be9c 100644 --- a/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm +++ b/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm @@ -38,9 +38,10 @@ var/static/list/doom_options = list() if (!length(doom_options)) doom_options = list(DOOM_SINGULARITY, DOOM_TESLA) +/* if (!SSmapping.config.planetary) doom_options += DOOM_METEORS - +*/ switch(pick(doom_options)) if (DOOM_SINGULARITY) var/obj/singularity/singulo = new(current_location) @@ -48,11 +49,13 @@ if (DOOM_TESLA) var/obj/energy_ball/tesla = new (current_location) tesla.energy = 200 +/* if (DOOM_METEORS) var/datum/dynamic_ruleset/roundstart/meteor/meteors = new() meteors.meteordelay = 0 SSdynamic.execute_roundstart_rule(meteors) // Meteors will continue until morale is crushed. priority_announce("Meteors have been detected on collision course with the station.", "Meteor Alert", ANNOUNCER_METEORS) +*/ #undef DOOM_SINGULARITY #undef DOOM_TESLA diff --git a/code/modules/antagonists/wizard/slaughter_antag.dm b/code/modules/antagonists/wizard/slaughter_antag.dm index c9aea4478c39..d4804097d864 100644 --- a/code/modules/antagonists/wizard/slaughter_antag.dm +++ b/code/modules/antagonists/wizard/slaughter_antag.dm @@ -2,7 +2,7 @@ name = "\improper Slaughter Demon" show_name_in_check_antagonists = TRUE ui_name = "AntagInfoDemon" - job_rank = ROLE_ALIEN + job_rank = ROLE_SLAUGHTER_DEMON // NON-MODULE CHANGE / Was ROLE_ALIEN show_in_antagpanel = FALSE show_to_ghosts = TRUE antagpanel_category = ANTAG_GROUP_WIZARDS diff --git a/code/modules/client/preferences/middleware/antags.dm b/code/modules/client/preferences/middleware/antags.dm index 13e3bfa718f7..f139805d291d 100644 --- a/code/modules/client/preferences/middleware/antags.dm +++ b/code/modules/client/preferences/middleware/antags.dm @@ -59,14 +59,8 @@ /datum/preference_middleware/antags/proc/get_antag_bans() var/list/antag_bans = list() - for (var/datum/dynamic_ruleset/dynamic_ruleset as anything in subtypesof(/datum/dynamic_ruleset)) - var/antag_flag = initial(dynamic_ruleset.antag_flag) - var/antag_flag_override = initial(dynamic_ruleset.antag_flag_override) - - if (isnull(antag_flag)) - continue - - if (is_banned_from(preferences.parent.ckey, list(antag_flag_override || antag_flag, ROLE_SYNDICATE))) + for (var/antag_flag in get_selectable_antags()) + if (is_banned_from(preferences.parent.ckey, list(antag_flag, ROLE_SYNDICATE))) antag_bans += serialize_antag_name(antag_flag) return antag_bans @@ -77,24 +71,15 @@ var/list/antag_days_left = list() - for (var/datum/dynamic_ruleset/dynamic_ruleset as anything in subtypesof(/datum/dynamic_ruleset)) - var/antag_flag = initial(dynamic_ruleset.antag_flag) - var/antag_flag_override = initial(dynamic_ruleset.antag_flag_override) - - if (isnull(antag_flag)) - continue - - var/days_needed = preferences.parent?.get_remaining_days( - GLOB.special_roles[antag_flag_override || antag_flag] - ) - + for (var/antag_flag in get_selectable_antags()) + var/days_needed = preferences.parent?.get_remaining_days(GLOB.special_roles[antag_flag]) if (days_needed > 0) antag_days_left[serialize_antag_name(antag_flag)] = days_needed return antag_days_left /datum/preference_middleware/antags/proc/get_serialized_antags() - var/list/serialized_antags + var/static/list/serialized_antags if (isnull(serialized_antags)) serialized_antags = list() @@ -113,26 +98,8 @@ var/list/antag_icons = list() /datum/asset/spritesheet/antagonists/create_spritesheets() - // Antagonists that don't have a dynamic ruleset, but do have a preference - var/static/list/non_ruleset_antagonists = list( - ROLE_GLITCH = /datum/antagonist/bitrunning_glitch, - ROLE_FUGITIVE = /datum/antagonist/fugitive, - ROLE_LONE_OPERATIVE = /datum/antagonist/nukeop/lone, - ROLE_SENTIENCE = /datum/antagonist/sentient_creature, - ) - - var/list/antagonists = non_ruleset_antagonists.Copy() - - for (var/datum/dynamic_ruleset/ruleset as anything in subtypesof(/datum/dynamic_ruleset)) - var/datum/antagonist/antagonist_type = initial(ruleset.antag_datum) - if (isnull(antagonist_type)) - continue - - // antag_flag is guaranteed to be unique by unit tests. - antagonists[initial(ruleset.antag_flag)] = antagonist_type - + var/list/antagonists = get_selectable_antags() var/list/generated_icons = list() - for (var/antag_flag in antagonists) var/datum/antagonist/antagonist_type = antagonists[antag_flag] @@ -164,3 +131,32 @@ /proc/serialize_antag_name(antag_name) // These are sent through CSS, so they need to be safe to use as class names. return lowertext(sanitize_css_class_name(antag_name)) + +/// Returns a list of all antags that can be selected in prefs indexed by job_rank +/proc/get_selectable_antags() + var/static/list/antagonists + + if(isnull(antagonists)) + antagonists = list( + // These antags don't have a job rank / are based on an existing antag in a different context, but they still count + ROLE_CHANGELING_MIDROUND = /datum/antagonist/changeling, + ROLE_CLOWN_OPERATIVE = /datum/antagonist/nukeop/clownop, + ROLE_HERETIC_SMUGGLER = /datum/antagonist/heretic, + ROLE_LONE_OPERATIVE = /datum/antagonist/nukeop/lone, + ROLE_MALF_MIDROUND = /datum/antagonist/malf_ai, + ROLE_OPERATIVE_MIDROUND = /datum/antagonist/nukeop, + ROLE_PROVOCATEUR = /datum/antagonist/rev/head, + ROLE_SLEEPER_AGENT = /datum/antagonist/traitor, + ROLE_STOWAWAY_CHANGELING = /datum/antagonist/changeling, + ROLE_SYNDICATE_INFILTRATOR = /datum/antagonist/traitor, + ROLE_WIZARD_MIDROUND = /datum/antagonist/wizard, + ) + + for (var/datum/antagonist/antagonist_type as anything in subtypesof(/datum/antagonist)) + var/antag_rank = initial(antagonist_type.job_rank) + if(!antag_rank || antagonists[antag_rank]) + continue + // antag_flag is guaranteed to be unique by unit tests. + antagonists[antag_rank] = antagonist_type + + return antagonists diff --git a/code/modules/events/_event.dm b/code/modules/events/_event.dm index a90cd6a4d7bf..06760fbd4509 100644 --- a/code/modules/events/_event.dm +++ b/code/modules/events/_event.dm @@ -86,10 +86,6 @@ return FALSE if(ispath(typepath, /datum/round_event/ghost_role) && !(GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT)) return FALSE - - if (dynamic_should_hijack && SSdynamic.random_event_hijacked != HIJACKED_NOTHING) - return FALSE - return TRUE /datum/round_event_control/proc/preRunEvent() diff --git a/code/modules/events/ghost_role/abductor.dm b/code/modules/events/ghost_role/abductor.dm index 65fe4a142f5a..9463bfe01622 100644 --- a/code/modules/events/ghost_role/abductor.dm +++ b/code/modules/events/ghost_role/abductor.dm @@ -24,7 +24,7 @@ var/mob/living/carbon/human/scientist = make_body(pick_n_take(candidates)) var/datum/team/abductor_team/T = new - if(T.team_number > ABDUCTOR_MAX_TEAMS) + if(T.team_number > 2) return MAP_ERROR scientist.log_message("has been selected as [T.name] abductor scientist.", LOG_GAME) diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index 582f119bb037..43c0e5afe496 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -227,15 +227,6 @@ GLOB.joined_player_list += character.ckey - if(CONFIG_GET(flag/allow_latejoin_antagonists) && humanc) //Borgs aren't allowed to be antags. Will need to be tweaked if we get true latejoin ais. - if(SSshuttle.emergency) - switch(SSshuttle.emergency.mode) - if(SHUTTLE_RECALL, SHUTTLE_IDLE) - SSdynamic.make_antag_chance(humanc) - if(SHUTTLE_CALL) - if(SSshuttle.emergency.timeLeft(1) > initial(SSshuttle.emergency_call_time)*0.5) - SSdynamic.make_antag_chance(humanc) - if((job.job_flags & JOB_ASSIGN_QUIRKS) && humanc && CONFIG_GET(flag/roundstart_traits)) SSquirks.AssignQuirks(humanc, humanc.client) if(humanc) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index aa7b8aabba90..f23b725e597f 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -133,7 +133,6 @@ #include "dragon_expiration.dm" #include "drink_icons.dm" #include "dummy_spawn.dm" -#include "dynamic_ruleset_sanity.dm" #include "egg_glands.dm" #include "emoting.dm" #include "ensure_subtree_operational_datum.dm" diff --git a/code/modules/unit_tests/dynamic_ruleset_sanity.dm b/code/modules/unit_tests/dynamic_ruleset_sanity.dm deleted file mode 100644 index 552959220c80..000000000000 --- a/code/modules/unit_tests/dynamic_ruleset_sanity.dm +++ /dev/null @@ -1,43 +0,0 @@ -/// 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/datum/dynamic_ruleset/roundstart/ruleset as anything in subtypesof(/datum/dynamic_ruleset/roundstart)) - 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) - TEST_FAIL("[ruleset] has a scaling_cost, but is also a lone/highlander ruleset.") - else if (!has_scaling_cost && !is_lone) - TEST_FAIL("[ruleset] has no scaling cost, but is also not a lone/highlander ruleset.") - - for (var/datum/dynamic_ruleset/midround/ruleset as anything in subtypesof(/datum/dynamic_ruleset/midround)) - if(initial(ruleset.abstract_type) == ruleset) - if(initial(ruleset.weight)) - TEST_FAIL("[ruleset] is abstract and should never run, it should also have 0 weight set.") - continue - var/midround_ruleset_style = initial(ruleset.midround_ruleset_style) - if (midround_ruleset_style != MIDROUND_RULESET_STYLE_HEAVY && midround_ruleset_style != MIDROUND_RULESET_STYLE_LIGHT) - TEST_FAIL("[ruleset] has an invalid midround_ruleset_style, it should be MIDROUND_RULESET_STYLE_HEAVY or MIDROUND_RULESET_STYLE_LIGHT") - -/// Verifies that dynamic rulesets have unique antag_flag. -/datum/unit_test/dynamic_unique_antag_flags - -/datum/unit_test/dynamic_unique_antag_flags/Run() - var/list/known_antag_flags = list() - - for (var/datum/dynamic_ruleset/ruleset as anything in subtypesof(/datum/dynamic_ruleset)) - if (isnull(initial(ruleset.antag_datum))) - continue - - var/antag_flag = initial(ruleset.antag_flag) - - if (isnull(antag_flag)) - TEST_FAIL("[ruleset] has a null antag_flag!") - continue - - if (antag_flag in known_antag_flags) - TEST_FAIL("[ruleset] has a non-unique antag_flag [antag_flag] (used by [known_antag_flags[antag_flag]])!") - continue - - known_antag_flags[antag_flag] = ruleset diff --git a/code/modules/unit_tests/traitor.dm b/code/modules/unit_tests/traitor.dm index 16098a9227c4..f603c5c0d680 100644 --- a/code/modules/unit_tests/traitor.dm +++ b/code/modules/unit_tests/traitor.dm @@ -1,7 +1,9 @@ /datum/unit_test/traitor/Run() - var/datum/dynamic_ruleset/roundstart/traitor/traitor_ruleset = allocate(/datum/dynamic_ruleset/roundstart/traitor) var/list/possible_jobs = list() - var/list/restricted_roles = traitor_ruleset.restricted_roles + var/list/restricted_roles = list( + JOB_AI, + JOB_CYBORG, + ) for(var/datum/job/job as anything in SSjob.joinable_occupations) if(!(job.job_flags & JOB_CREW_MEMBER)) continue diff --git a/config/dynamic.json b/config/dynamic.json deleted file mode 100644 index 27fb01ec6a28..000000000000 --- a/config/dynamic.json +++ /dev/null @@ -1,163 +0,0 @@ -{ - "Dynamic": {}, - "Roundstart": { - "Traitors": { - "cost": 101, - "scaling_cost": 9, - "weight": 0, - "required_candidates": 1, - "minimum_required_age": 0, - "requirements": [ - 10, - 10, - 10, - 10, - 10, - 10, - 10, - 10, - 10, - 10 - ], - "antag_cap": { - "denominator": 24 - }, - "protected_roles": [ - "Prisoner", - "Security Officer", - "Warden", - "Detective", - "Head of Security", - "Captain" - ], - "restricted_roles": [ - "Cyborg" - ] - }, - - "Blood Brothers": { - "weight": 0 - }, - - "Changelings": { - "weight": 0 - }, - - "Heretics": { - "weight": 0 - }, - - "Wizard": { - "weight": 0 - }, - - "Blood Cult": { - "weight": 0 - }, - - "Nuclear Emergency": { - "weight": 0 - }, - - "Revolution": { - "weight": 0 - } - }, - - "Midround": { - "Syndicate Sleeper Agent": { - "weight": 0 - }, - - "Malfunctioning AI": { - "weight": 0 - }, - - "Wizard": { - "weight": 0 - }, - - "Nuclear Assault": { - "weight": 0 - }, - - "Blob": { - "weight": 0 - }, - - "Blob Infection": { - "weight": 0 - }, - - "Alien Infestation": { - "weight": 0 - }, - - "Nightmare": { - "weight": 0 - }, - - "Space Dragon": { - "weight": 0 - }, - - "Abductors": { - "weight": 0 - }, - - "Space Ninja": { - "weight": 0 - }, - - "Spiders": { - "weight": 0 - }, - - "Revenant": { - "weight": 0 - }, - - "Sentient Disease": { - "weight": 0 - }, - - "Space Pirates": { - "weight": 0 - }, - - "Obsessed": { - "weight": 0 - }, - - "Space Changeling": { - "weight": 0 - }, - - "Paradox Clone": { - "weight": 0 - } - }, - - "Latejoin": { - "Syndicate Infiltrator": { - "weight": 0 - }, - - "Provocateur": { - "weight": 0 - }, - - "Heretic Smuggler": { - "weight": 0 - }, - - "Stowaway Changeling": { - "weight": 0 - } - }, - - "Station": { - "Radioactive Nebula": { - } - } -} diff --git a/maplestation.dme b/maplestation.dme index de8e3c4cc387..aa8bbda3525e 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -84,7 +84,6 @@ #include "code\__DEFINES\do_afters.dm" #include "code\__DEFINES\drone.dm" #include "code\__DEFINES\dye_keys.dm" -#include "code\__DEFINES\dynamic.dm" #include "code\__DEFINES\economy.dm" #include "code\__DEFINES\electrified_buckle.dm" #include "code\__DEFINES\events.dm" @@ -477,7 +476,6 @@ #include "code\__HELPERS\logging\atmos.dm" #include "code\__HELPERS\logging\attack.dm" #include "code\__HELPERS\logging\debug.dm" -#include "code\__HELPERS\logging\dynamic.dm" #include "code\__HELPERS\logging\economy.dm" #include "code\__HELPERS\logging\game.dm" #include "code\__HELPERS\logging\manifest.dm" @@ -701,16 +699,6 @@ #include "code\controllers\subsystem\wardrobe.dm" #include "code\controllers\subsystem\weather.dm" #include "code\controllers\subsystem\wiremod_composite.dm" -#include "code\controllers\subsystem\dynamic\dynamic.dm" -#include "code\controllers\subsystem\dynamic\dynamic_hijacking.dm" -#include "code\controllers\subsystem\dynamic\dynamic_logging.dm" -#include "code\controllers\subsystem\dynamic\dynamic_midround_rolling.dm" -#include "code\controllers\subsystem\dynamic\dynamic_rulesets.dm" -#include "code\controllers\subsystem\dynamic\dynamic_rulesets_latejoin.dm" -#include "code\controllers\subsystem\dynamic\dynamic_rulesets_midround.dm" -#include "code\controllers\subsystem\dynamic\dynamic_rulesets_roundstart.dm" -#include "code\controllers\subsystem\dynamic\dynamic_unfavorable_situation.dm" -#include "code\controllers\subsystem\dynamic\ruleset_picking.dm" #include "code\controllers\subsystem\movement\ai_movement.dm" #include "code\controllers\subsystem\movement\cliff_falling.dm" #include "code\controllers\subsystem\movement\hyperspace_drift.dm" diff --git a/tgstation.dme b/tgstation.dme index 0784eba69b71..242700e5ca95 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -84,7 +84,6 @@ #include "code\__DEFINES\do_afters.dm" #include "code\__DEFINES\drone.dm" #include "code\__DEFINES\dye_keys.dm" -#include "code\__DEFINES\dynamic.dm" #include "code\__DEFINES\economy.dm" #include "code\__DEFINES\electrified_buckle.dm" #include "code\__DEFINES\events.dm" @@ -477,7 +476,6 @@ #include "code\__HELPERS\logging\atmos.dm" #include "code\__HELPERS\logging\attack.dm" #include "code\__HELPERS\logging\debug.dm" -#include "code\__HELPERS\logging\dynamic.dm" #include "code\__HELPERS\logging\economy.dm" #include "code\__HELPERS\logging\game.dm" #include "code\__HELPERS\logging\manifest.dm" @@ -701,16 +699,6 @@ #include "code\controllers\subsystem\wardrobe.dm" #include "code\controllers\subsystem\weather.dm" #include "code\controllers\subsystem\wiremod_composite.dm" -#include "code\controllers\subsystem\dynamic\dynamic.dm" -#include "code\controllers\subsystem\dynamic\dynamic_hijacking.dm" -#include "code\controllers\subsystem\dynamic\dynamic_logging.dm" -#include "code\controllers\subsystem\dynamic\dynamic_midround_rolling.dm" -#include "code\controllers\subsystem\dynamic\dynamic_rulesets.dm" -#include "code\controllers\subsystem\dynamic\dynamic_rulesets_latejoin.dm" -#include "code\controllers\subsystem\dynamic\dynamic_rulesets_midround.dm" -#include "code\controllers\subsystem\dynamic\dynamic_rulesets_roundstart.dm" -#include "code\controllers\subsystem\dynamic\dynamic_unfavorable_situation.dm" -#include "code\controllers\subsystem\dynamic\ruleset_picking.dm" #include "code\controllers\subsystem\movement\ai_movement.dm" #include "code\controllers\subsystem\movement\cliff_falling.dm" #include "code\controllers\subsystem\movement\hyperspace_drift.dm" From 6816b4f92728d8ea60f35127160644790f1e8d2f Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 9 Apr 2025 03:04:46 -0500 Subject: [PATCH 2/3] 4 --- code/modules/events/ghost_role/abductor.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/events/ghost_role/abductor.dm b/code/modules/events/ghost_role/abductor.dm index 9463bfe01622..14b9a57ab2c7 100644 --- a/code/modules/events/ghost_role/abductor.dm +++ b/code/modules/events/ghost_role/abductor.dm @@ -24,7 +24,7 @@ var/mob/living/carbon/human/scientist = make_body(pick_n_take(candidates)) var/datum/team/abductor_team/T = new - if(T.team_number > 2) + if(T.team_number > 4) return MAP_ERROR scientist.log_message("has been selected as [T.name] abductor scientist.", LOG_GAME) From 16ffa45b3b40119e2a9a22f5b41451baf238e600 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 9 Apr 2025 03:21:04 -0500 Subject: [PATCH 3/3] Yep --- code/modules/client/preferences/middleware/antags.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/client/preferences/middleware/antags.dm b/code/modules/client/preferences/middleware/antags.dm index f139805d291d..5f6befebba4b 100644 --- a/code/modules/client/preferences/middleware/antags.dm +++ b/code/modules/client/preferences/middleware/antags.dm @@ -139,7 +139,7 @@ if(isnull(antagonists)) antagonists = list( // These antags don't have a job rank / are based on an existing antag in a different context, but they still count - ROLE_CHANGELING_MIDROUND = /datum/antagonist/changeling, + ROLE_CHANGELING_MIDROUND = /datum/antagonist/changeling/space, ROLE_CLOWN_OPERATIVE = /datum/antagonist/nukeop/clownop, ROLE_HERETIC_SMUGGLER = /datum/antagonist/heretic, ROLE_LONE_OPERATIVE = /datum/antagonist/nukeop/lone,