diff --git a/code/__DEFINES/dynamic.dm b/code/__DEFINES/dynamic.dm index f07b7e20b0f..07c833cdc49 100644 --- a/code/__DEFINES/dynamic.dm +++ b/code/__DEFINES/dynamic.dm @@ -32,3 +32,9 @@ #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/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index 6208df36de1..ce64f200458 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -17,6 +17,8 @@ GLOBAL_LIST_EMPTY(dynamic_forced_roundstart_ruleset) 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) /datum/game_mode/dynamic // Threat logging vars diff --git a/code/game/gamemodes/dynamic/dynamic_midround_rolling.dm b/code/game/gamemodes/dynamic/dynamic_midround_rolling.dm index 5079035834b..c3e295ae875 100644 --- a/code/game/gamemodes/dynamic/dynamic_midround_rolling.dm +++ b/code/game/gamemodes/dynamic/dynamic_midround_rolling.dm @@ -45,7 +45,11 @@ continue if (!ruleset.acceptable(GLOB.alive_player_list.len, threat_level)) - log_dynamic("FAIL: [ruleset] is not acceptable with the current parameters. Alive players: [GLOB.alive_player_list.len], threat level: [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) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm index 1fe34273fb9..0af18e0b0aa 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm @@ -106,23 +106,39 @@ /// 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) - pop_per_requirement = pop_per_requirement > 0 ? pop_per_requirement : mode.pop_per_requirement - indice_pop = min(requirements.len,round(population/pop_per_requirement)+1) + 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(minimum_players > population) - log_dynamic("FAIL: [src] failed acceptable: minimum_players ([minimum_players]) > population ([population])") + 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(maximum_players > 0 && population > maximum_players) - log_dynamic("FAIL: [src] failed acceptable: maximum_players ([maximum_players]) < population ([population])") - return FALSE - - if (threat_level < requirements[indice_pop]) + 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 : mode.pop_per_requirement + indice_pop = min(requirements.len,round(population/pop_per_requirement)+1) + return threat_level >= requirements[indice_pop] + /// When picking rulesets, if dynamic picks the same one multiple times, it will "scale up". /// However, doing this blindly would result in lowpop rounds (think under 10 people) where over 80% of the crew is antags! /// This function is here to ensure the antag ratio is kept under control while scaling up. @@ -175,7 +191,7 @@ 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! diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index 3610d8aad91..7b125439d53 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -38,8 +38,8 @@ job_check++ // Checking for "enemies" (such as sec officers). To be counters, they must either not be candidates to that rule, or have a job that restricts them from it var/threat = round(mode.threat_level/10) - - if (job_check < required_enemies[threat]) + 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 diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index 0634c44a4f2..b7c66873aee 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -116,8 +116,8 @@ job_check++ // Checking for "enemies" (such as sec officers). To be counters, they must either not be candidates to that rule, or have a job that restricts them from it var/threat = round(mode.threat_level/10) - - if (job_check < required_enemies[threat]) + 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 @@ -387,7 +387,7 @@ 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=0) +/datum/dynamic_ruleset/midround/from_ghosts/nuclear/acceptable(population=0, threat_level=0) if (locate(/datum/dynamic_ruleset/roundstart/nuclear) in mode.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) @@ -542,7 +542,7 @@ minimum_players = 15 repeatable = TRUE -/datum/dynamic_ruleset/midround/from_ghosts/nightmare/acceptable(population = 0, threat = 0) +/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 @@ -719,7 +719,7 @@ spawn_locs = list() return ..() -/datum/dynamic_ruleset/midround/from_ghosts/revenant/acceptable(population=0, threat=0) +/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 ..() @@ -782,7 +782,7 @@ minimum_players = 20 repeatable = TRUE -/datum/dynamic_ruleset/midround/pirates/acceptable(population=0, threat=0) +/datum/dynamic_ruleset/midround/pirates/acceptable(population=0, threat_level=0) if (SSmapping.is_planetary() || GLOB.light_pirate_gangs.len == 0) return FALSE return ..() @@ -804,7 +804,7 @@ minimum_players = 25 repeatable = TRUE -/datum/dynamic_ruleset/midround/dangerous_pirates/acceptable(population=0, threat=0) +/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 ..() diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index bc6942a9a11..2d2c692ed5f 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -21,6 +21,7 @@ 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) @@ -30,6 +31,8 @@ dat += "
" if(SSticker.IsRoundInProgress()) dat += "(Game Mode Panel)
" + if(istype(SSticker.mode, /datum/game_mode/dynamic)) + dat += "(Manage Dynamic Rulesets)
" dat += {"
Create Object
@@ -143,6 +146,111 @@ user << browse(dat, "window=dyn_mode_options;size=900x650") +/datum/admins/proc/dynamic_ruleset_manager(mob/user) + if (SSticker.current_state > GAME_STATE_PREGAME && !istype(SSticker.mode, /datum/game_mode/dynamic)) + return // Not running dynamic + + 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/datum/game_mode/dynamic/current_mode = SSticker.mode + var/pop_count = length(GLOB.alive_player_list) + var/threat_level = current_mode.threat_level + dat += dynamic_ruleset_category_during_round_display("Latejoin", current_mode.latejoin_rules, pop_count, threat_level) + dat += dynamic_ruleset_category_during_round_display("Midround", current_mode.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) + /datum/admins/proc/create_or_modify_area() set category = "Debug" set name = "Create or modify area" diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 9db6585799f..c4c09b51adf 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -465,6 +465,39 @@ 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