From ae9ff92d330a2ca1be9a0f7c847352724d327d83 Mon Sep 17 00:00:00 2001 From: Ghommie <42542238+Ghommie@users.noreply.github.com> Date: Tue, 5 Nov 2019 18:03:50 +0100 Subject: [PATCH 1/5] Fixes free real estate paraplegic trait and bolas. --- code/modules/surgery/bodyparts/helpers.dm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/code/modules/surgery/bodyparts/helpers.dm b/code/modules/surgery/bodyparts/helpers.dm index d520fbb917..704a377fa8 100644 --- a/code/modules/surgery/bodyparts/helpers.dm +++ b/code/modules/surgery/bodyparts/helpers.dm @@ -112,14 +112,14 @@ /mob/proc/get_leg_ignore() return FALSE -/mob/living/carbon/alien/larva/get_leg_ignore() - return TRUE - -/mob/living/carbon/human/get_leg_ignore() - if(movement_type & FLYING|FLOATING) +/mob/living/carbon/get_leg_ignore() + if(movement_type & (FLYING|FLOATING)) return TRUE return FALSE +/mob/living/carbon/alien/larva/get_leg_ignore() + return TRUE + /mob/living/proc/get_missing_limbs() return list() From c6550291b93ef4e59ba2f3c9207da0e277e7a57a Mon Sep 17 00:00:00 2001 From: CitadelStationBot Date: Sat, 9 Nov 2019 22:10:48 -0600 Subject: [PATCH 2/5] Automatic changelog generation for PR #9724 [ci skip] --- html/changelogs/AutoChangeLog-pr-9724.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-9724.yml diff --git a/html/changelogs/AutoChangeLog-pr-9724.yml b/html/changelogs/AutoChangeLog-pr-9724.yml new file mode 100644 index 0000000000..853d38b041 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-9724.yml @@ -0,0 +1,4 @@ +author: "Ghommie" +delete-after: True +changes: + - bugfix: "Fixed free real estate paraplegic trait and bolas." From 3ddbee88786a3e84a6a94ae90a4d12e4fd514201 Mon Sep 17 00:00:00 2001 From: Ghommie <42542238+Ghommie@users.noreply.github.com> Date: Sun, 10 Nov 2019 06:30:47 +0100 Subject: [PATCH 3/5] Initial stuff. --- code/game/gamemodes/dynamic/dynamic.dm | 64 ++++++----------- .../gamemodes/dynamic/dynamic_rulesets.dm | 57 ++++----------- .../dynamic/dynamic_rulesets_latejoin.dm | 11 ++- .../dynamic/dynamic_rulesets_midround.dm | 34 ++++----- .../dynamic/dynamic_rulesets_roundstart.dm | 70 ++++++++----------- 5 files changed, 87 insertions(+), 149 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index ca009f76ad..473e2ae4a5 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -118,7 +118,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) /datum/game_mode/dynamic/admin_panel() var/list/dat = list("Game Mode Panel

Game Mode Panel

") - dat += "Dynamic Mode \[VV\]
" + dat += "Dynamic Mode \[VV\]\[Refresh\]
" dat += "Threat Level: [threat_level]
" dat += "Threat to Spend: [threat] \[Adjust\] \[View Log\]
" @@ -273,15 +273,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) threat = threat_level /datum/game_mode/dynamic/can_start() - /* Disabled for now, had some changes that need to be tested and this might interfere with that. - if(GLOB.dynamic_curve_centre == 0) - // 10 is when the centre starts to decrease - // 6 is just 1 + 5 (from the maximum value and the one decreased) - // 1 just makes the curve look better, I don't know. - // Limited between 1 and 5 then inverted and rounded - // With this you get a centre curve that stays at -5 until 10 then first rapidly decreases but slows down at the end - GLOB.dynamic_curve_centre = round(-CLAMP((10*6/GLOB.player_list.len)-1, 0, 5), 0.5) - */ message_admins("Dynamic mode parameters for the round:") message_admins("Centre is [GLOB.dynamic_curve_centre], Width is [GLOB.dynamic_curve_width], Forced extended is [GLOB.dynamic_forced_extended ? "Enabled" : "Disabled"], No stacking is [GLOB.dynamic_no_stacking ? "Enabled" : "Disabled"].") message_admins("Stacking limit is [GLOB.dynamic_stacking_limit], Classic secret is [GLOB.dynamic_classic_secret ? "Enabled" : "Disabled"], High population limit is [GLOB.dynamic_high_pop_limit].") @@ -342,8 +333,11 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) for(var/datum/dynamic_ruleset/roundstart/rule in executed_rules) rule.candidates.Cut() // The rule should not use candidates at this point as they all are null. - if(!rule.execute()) - stack_trace("The starting rule \"[rule.name]\" failed to execute.") + if(rule.delay > 0) + addtimer(CALLBACK(rule, /datum/dynamic_ruleset/roundstart.proc/execute), rule.delay) + else + if(!rule.execute()) + stack_trace("The starting rule \"[rule.name]\" failed to execute.") ..() /// A simple roundstart proc used when dynamic_forced_roundstart_ruleset has rules in it. @@ -427,16 +421,18 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if(drafted_rules.len <= 0) return FALSE starting_rule = pickweight(drafted_rules) + // With low pop and high threat there might be rulesets that get executed with no valid candidates. + else if(starting_rule.ready()) + drafted_rules -= starting_rule + if(drafted_rules.len <= 0) + return FALSE + starting_rule = pickweight(drafted_rules) - log_game("DYNAMIC: Picking a [istype(starting_rule, /datum/dynamic_ruleset/roundstart/delayed/) ? " delayed " : ""] ruleset [starting_rule.name]") + log_game("DYNAMIC: Picking a ruleset [starting_rule.name]") roundstart_rules -= starting_rule drafted_rules -= starting_rule - if (istype(starting_rule, /datum/dynamic_ruleset/roundstart/delayed/)) - var/datum/dynamic_ruleset/roundstart/delayed/rule = starting_rule - addtimer(CALLBACK(src, .proc/execute_delayed, rule), rule.delay) - starting_rule.trim_candidates() if (starting_rule.pre_execute()) spend_threat(starting_rule.cost) @@ -448,29 +444,14 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) executed_rules += starting_rule if (starting_rule.persistent) current_rules += starting_rule - for(var/mob/M in starting_rule.assigned) - for (var/datum/dynamic_ruleset/roundstart/rule in roundstart_rules) - if (!rule.ready()) - drafted_rules -= rule // And removing rules that are no longer elligible + for (var/datum/dynamic_ruleset/roundstart/rule in drafted_rules) + if (!rule.ready()) + drafted_rules -= rule // And removing rules that are no longer elligible return TRUE else stack_trace("The starting rule \"[starting_rule.name]\" failed to pre_execute.") return FALSE -/// Executes delayed roundstart rules and has a hack in it. -/datum/game_mode/dynamic/proc/execute_delayed(datum/dynamic_ruleset/roundstart/delayed/rule) - update_playercounts() - rule.candidates = current_players[CURRENT_LIVING_PLAYERS].Copy() - rule.trim_candidates() - if(rule.execute()) - executed_rules += rule - if (rule.persistent) - current_rules += rule - return TRUE - else - stack_trace("The delayed roundstart rule \"[rule.name]\" failed to execute.") - return FALSE - /// Picks a random midround OR latejoin rule from the list given as an argument and executes it. /// Also this could be named better. /datum/game_mode/dynamic/proc/picking_midround_latejoin_rule(list/drafted_rules = list(), forced = FALSE) @@ -550,7 +531,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) update_playercounts() if ((forced || (new_rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && new_rule.cost <= threat))) - new_rule.candidates = current_players.Copy() new_rule.trim_candidates() if (new_rule.ready(forced)) spend_threat(new_rule.cost) @@ -582,7 +562,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if (GLOB.dynamic_forced_extended) return - // Somehow it manages to trigger midround multiple times so this was moved here. + // Somehow it managed to trigger midround multiple times so this was moved here. // There is no way this should be able to trigger an injection twice now. var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_midround_delay_max + GLOB.dynamic_midround_delay_min) midround_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_midround_delay_min, GLOB.dynamic_midround_delay_max)) + world.time) @@ -591,21 +571,19 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if(EMERGENCY_ESCAPED_OR_ENDGAMED) // Unless the shuttle is gone return - log_game("DYNAMIC: Checking state of the round.") + message_admins("DYNAMIC: Checking for midround injection.") + log_game("DYNAMIC: Checking for midround injection.") update_playercounts() - - if (prob(get_injection_chance())) + if (get_injection_chance()) var/list/drafted_rules = list() for (var/datum/dynamic_ruleset/midround/rule in midround_rules) if (rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost) // Classic secret : only autotraitor/minor roles if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET))) continue - rule.candidates = list() - rule.candidates = current_players.Copy() rule.trim_candidates() - if (rule.ready() && rule.candidates.len > 0) + if (rule.ready()) drafted_rules[rule] = rule.get_weight() if (drafted_rules.len > 0) picking_midround_latejoin_rule(drafted_rules) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm index 011b6ec251..b77bfcc4a0 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm @@ -86,10 +86,9 @@ /datum/dynamic_ruleset/roundstart // One or more of those drafted at roundstart ruletype = "Roundstart" - -/datum/dynamic_ruleset/roundstart/delayed/ // Executed with a 30 seconds delay - var/delay = 30 SECONDS - var/required_type = /mob/living/carbon/human // No ghosts, new players or silicons allowed. + /// Delay for when execute will get called from the time of post_setup. + /// Make sure your ruleset works with execute being called during the game when using this. + var/delay = 0 // Can be drafted when a player joins the server /datum/dynamic_ruleset/latejoin @@ -113,7 +112,7 @@ /datum/dynamic_ruleset/proc/rule_process() return -/// Called on game mode pre_setup, used for non-delayed roundstart rulesets only. +/// Called on game mode 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() @@ -126,12 +125,6 @@ M.add_antag_datum(antag_datum) return TRUE -/// Called after delay set in ruleset. -/// Give your candidates or assignees equipment and antag datum here. -/datum/dynamic_ruleset/roundstart/delayed/execute() - if (SSticker && SSticker.current_state < GAME_STATE_PLAYING) - CRASH("The delayed ruleset [name] executed before the round started.") - /// 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! @@ -156,14 +149,6 @@ /datum/dynamic_ruleset/proc/trim_candidates() return -/// Counts how many players are ready at roundstart. -/// Used only by non-delayed roundstart rulesets. -/datum/dynamic_ruleset/proc/num_players() - . = 0 - for(var/mob/dead/new_player/P in GLOB.player_list) - if(P.client && P.ready == PLAYER_READY_TO_PLAY) - . ++ - /// Set mode result and news report here. /// Only called if ruleset is flagged as HIGHLANDER_RULESET /datum/dynamic_ruleset/proc/round_result() @@ -191,32 +176,14 @@ if(P.mind.special_role) // We really don't want to give antag to an antag. candidates.Remove(P) continue - if (!(antag_flag in P.client.prefs.be_special) || jobban_isbanned(P.ckey, list(antag_flag, ROLE_SYNDICATE)) || (antag_flag_override && jobban_isbanned(P.ckey, list(antag_flag_override, ROLE_SYNDICATE))))//are they willing and not antag-banned? - candidates.Remove(P) - continue - -/// Checks if candidates are required mob type, connected, banned and if the job is exclusive to the role. -/datum/dynamic_ruleset/roundstart/delayed/trim_candidates() - . = ..() - for (var/mob/P in candidates) - if (!istype(P, required_type)) - candidates.Remove(P) // Can be a new_player, etc. - continue - if(!mode.check_age(P.client, minimum_required_age)) - candidates.Remove(P) - continue - if (!P.client || !P.mind || !P.mind.assigned_role) // Are they connected? - candidates.Remove(P) - continue - if(P.mind.special_role || P.mind.antag_datums?.len > 0) // Are they an antag already? - candidates.Remove(P) - continue - if (!(antag_flag in P.client.prefs.be_special) || jobban_isbanned(P.ckey, list(antag_flag, ROLE_SYNDICATE)) || (antag_flag_override && jobban_isbanned(P.ckey, list(antag_flag_override, ROLE_SYNDICATE))))//are they willing and not antag-banned? - candidates.Remove(P) - continue - if ((exclusive_roles.len > 0) && !(P.mind.assigned_role in exclusive_roles)) // Is the rule exclusive to their job? - candidates.Remove(P) - continue + if(antag_flag_override) + if(!(antag_flag_override in P.client.prefs.be_special) || is_banned_from(P.ckey, list(antag_flag_override, ROLE_SYNDICATE))) + candidates.Remove(P) + continue + else + if(!(antag_flag in P.client.prefs.be_special) || is_banned_from(P.ckey, list(antag_flag, ROLE_SYNDICATE))) + candidates.Remove(P) + continue /// Do your checks if the ruleset is ready to be executed here. /// Should ignore certain checks if forced is TRUE diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index fde020a3ae..a560e03f6e 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -12,9 +12,14 @@ if(!mode.check_age(P.client, minimum_required_age)) candidates.Remove(P) continue - if (!(antag_flag in P.client.prefs.be_special) || jobban_isbanned(P.ckey, list(antag_flag, ROLE_SYNDICATE)) || (antag_flag_override && jobban_isbanned(P.ckey, list(antag_flag_override))))//are they willing and not antag-banned? - candidates.Remove(P) - continue + if(antag_flag_override) + if(!(antag_flag_override in P.client.prefs.be_special) || is_banned_from(P.ckey, list(antag_flag_override, ROLE_SYNDICATE))) + candidates.Remove(P) + continue + else + if(!antag_flag in P.client.prefs.be_special || is_banned_from(P.ckey, list(antag_flag, ROLE_SYNDICATE))) + candidates.Remove(P) + continue if (P.mind.assigned_role in restricted_roles) // Does their job allow for it? candidates.Remove(P) continue diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index f24301d716..006a80cca6 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -8,7 +8,7 @@ ruletype = "Midround" /// If the ruleset should be restricted from ghost roles. var/restrict_ghost_roles = TRUE - /// What type the ruleset is restricted to. + /// 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() @@ -17,26 +17,18 @@ /datum/dynamic_ruleset/midround/from_ghosts weight = 0 + required_type = /mob/dead/observer /// Whether the ruleset should call generate_ruleset_body or not. var/makeBody = TRUE /datum/dynamic_ruleset/midround/trim_candidates() - // Unlike the previous two types, these rulesets are not meant for /mob/dead/new_player - // And since I want those rulesets to be as flexible as possible, I'm not gonna put much here, - // - // All you need to know is that here, the candidates list contains 4 lists itself, indexed with the following defines: - // Candidates = list(CURRENT_LIVING_PLAYERS, CURRENT_LIVING_ANTAGS, CURRENT_DEAD_PLAYERS, CURRENT_OBSERVERS) - // So for example you can get the list of all current dead players with var/list/dead_players = candidates[CURRENT_DEAD_PLAYERS] - // Make sure to properly typecheck the mobs in those lists, as the dead_players list could contain ghosts, or dead players still in their bodies. - // We're still gonna trim the obvious (mobs without clients, jobbanned players, etc) - living_players = trim_list(mode.current_players[CURRENT_LIVING_PLAYERS]) - living_antags = trim_list(mode.current_players[CURRENT_LIVING_ANTAGS]) - dead_players = trim_list(mode.current_players[CURRENT_DEAD_PLAYERS]) - list_observers = trim_list(mode.current_players[CURRENT_OBSERVERS]) + living_players = trim_list(mode.current_players[CURRENT_LIVING_PLAYERS]) + living_antags = trim_list(mode.current_players[CURRENT_LIVING_ANTAGS]) + dead_players = trim_list(mode.current_players[CURRENT_DEAD_PLAYERS]) + list_observers = trim_list(mode.current_players[CURRENT_OBSERVERS]) /datum/dynamic_ruleset/midround/proc/trim_list(list/L = list()) var/list/trimmed_list = L.Copy() - var/antag_name = initial(antag_flag) for(var/mob/M in trimmed_list) if (!istype(M, required_type)) trimmed_list.Remove(M) @@ -47,9 +39,14 @@ if(!mode.check_age(M.client, minimum_required_age)) trimmed_list.Remove(M) continue - if (!(antag_name in M.client.prefs.be_special) || jobban_isbanned(M.ckey, list(antag_name, ROLE_SYNDICATE)))//are they willing and not antag-banned? - trimmed_list.Remove(M) - continue + if(antag_flag_override) + if(!(antag_flag_override in M.client.prefs.be_special) || is_banned_from(M.ckey, list(antag_flag_override, ROLE_SYNDICATE))) + candidates.Remove(M) + continue + else + if(!antag_flag in M.client.prefs.be_special || is_banned_from(M.ckey, list(antag_flag, ROLE_SYNDICATE))) + candidates.Remove(M) + continue if (M.mind) if (restrict_ghost_roles && M.mind.assigned_role in GLOB.exp_specialmap[EXP_TYPE_SPECIAL]) // Are they playing a ghost role? trimmed_list.Remove(M) @@ -256,8 +253,7 @@ /datum/dynamic_ruleset/midround/malf/execute() if(!candidates || !candidates.len) return FALSE - var/mob/living/silicon/ai/M = pick(candidates) - candidates -= M + var/mob/living/silicon/ai/M = pick_n_take(candidates) assigned += M.mind var/datum/antagonist/traitor/AI = new M.mind.special_role = antag_flag diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index 6a9aefa996..f515ccc443 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -25,8 +25,7 @@ var/traitor_scaling_coeff = 10 - max(0,round(mode.threat_level/10)-5) // Above 50 threat level, coeff goes down by 1 for every 10 levels var/num_traitors = min(round(mode.candidates.len / traitor_scaling_coeff) + 1, candidates.len) for (var/i = 1 to num_traitors) - var/mob/M = pick(candidates) - candidates -= M + var/mob/M = pick_n_take(candidates) assigned += M.mind M.mind.special_role = ROLE_TRAITOR M.mind.restricted_roles = restricted_roles @@ -67,7 +66,7 @@ var/num_teams = team_amount var/bsc = CONFIG_GET(number/brother_scaling_coeff) if(bsc) - num_teams = max(1, round(num_players() / bsc)) + num_teams = max(1, round(mode.roundstart_pop_ready / bsc)) for(var/j = 1 to num_teams) if(candidates.len < min_team_size || candidates.len < required_candidates) @@ -75,8 +74,7 @@ var/datum/team/brother_team/team = new var/team_size = prob(10) ? min(3, candidates.len) : 2 for(var/k = 1 to team_size) - var/mob/bro = pick(candidates) - candidates -= bro + var/mob/bro = pick_n_take(candidates) assigned += bro.mind team.add_member(bro.mind) bro.mind.special_role = "brother" @@ -117,8 +115,7 @@ /datum/dynamic_ruleset/roundstart/changeling/pre_execute() var/num_changelings = min(round(mode.candidates.len / 10) + 1, candidates.len) for (var/i = 1 to num_changelings) - var/mob/M = pick(candidates) - candidates -= M + var/mob/M = pick_n_take(candidates) assigned += M.mind M.mind.restricted_roles = restricted_roles M.mind.special_role = ROLE_CHANGELING @@ -175,9 +172,8 @@ if(GLOB.wizardstart.len == 0) return FALSE - var/mob/M = pick(candidates) + var/mob/M = pick_n_take(candidates) if (M) - candidates -= M assigned += M.mind M.mind.assigned_role = ROLE_WIZARD M.mind.special_role = ROLE_WIZARD @@ -225,8 +221,7 @@ for(var/cultists_number = 1 to cultists) if(candidates.len <= 0) break - var/mob/M = pick(candidates) - candidates -= M + var/mob/M = pick_n_take(candidates) assigned += M.mind M.mind.special_role = ROLE_CULTIST M.mind.restricted_roles = restricted_roles @@ -288,8 +283,7 @@ for(var/operatives_number = 1 to operatives) if(candidates.len <= 0) break - var/mob/M = pick(candidates) - candidates -= M + var/mob/M = pick_n_take(candidates) assigned += M.mind M.mind.assigned_role = "Nuclear Operative" M.mind.special_role = "Nuclear Operative" @@ -347,7 +341,7 @@ // // ////////////////////////////////////////////// -/datum/dynamic_ruleset/roundstart/delayed/revs +/datum/dynamic_ruleset/roundstart/revs name = "Revolution" config_tag = "revolution" persistent = TRUE @@ -358,47 +352,48 @@ restricted_roles = list("AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") required_candidates = 3 weight = 2 + delay = 7 MINUTES cost = 35 requirements = list(101,101,70,40,30,20,10,10,10,10) high_population_requirement = 10 - delay = 5 MINUTES flags = HIGHLANDER_RULESET // I give up, just there should be enough heads with 35 players... minimum_players = 35 var/datum/team/revolution/revolution var/finished = 0 -/datum/dynamic_ruleset/roundstart/delayed/revs/execute() - var/max_canditates = 4 - revolution = new() +/datum/dynamic_ruleset/roundstart/revs/pre_execute() + var/max_canditates = 3 for(var/i = 1 to max_canditates) if(candidates.len <= 0) break - var/mob/M = pick(candidates) - candidates -= M + var/mob/M = pick_n_take(candidates) assigned += M.mind M.mind.restricted_roles = restricted_roles M.mind.special_role = antag_flag + return TRUE + +/datum/dynamic_ruleset/roundstart/revs/execute() + revolution = new() + for(var/datum/mind/M in assigned) var/datum/antagonist/rev/head/new_head = new antag_datum() new_head.give_flash = TRUE new_head.give_hud = TRUE new_head.remove_clumsy = TRUE - M.mind.add_antag_datum(new_head,revolution) - + M.add_antag_datum(new_head,revolution) revolution.update_objectives() revolution.update_heads() SSshuttle.registerHostileEnvironment(src) - return TRUE -/datum/dynamic_ruleset/roundstart/delayed/revs/rule_process() +/datum/dynamic_ruleset/roundstart/revs/rule_process() if(check_rev_victory()) finished = 1 else if(check_heads_victory()) finished = 2 return -/datum/dynamic_ruleset/roundstart/delayed/revs/check_finished() +/datum/dynamic_ruleset/roundstart/revs/check_finished() if(CONFIG_GET(keyed_list/continuous)["revolution"]) if(finished) SSshuttle.clearHostileEnvironment(src) @@ -408,13 +403,13 @@ else return ..() -/datum/dynamic_ruleset/roundstart/delayed/revs/proc/check_rev_victory() +/datum/dynamic_ruleset/roundstart/revs/proc/check_rev_victory() for(var/datum/objective/mutiny/objective in revolution.objectives) if(!(objective.check_completion())) return FALSE return TRUE -/datum/dynamic_ruleset/roundstart/delayed/revs/proc/check_heads_victory() +/datum/dynamic_ruleset/roundstart/revs/proc/check_heads_victory() for(var/datum/mind/rev_mind in revolution.head_revolutionaries()) var/turf/T = get_turf(rev_mind.current) if(!considered_afk(rev_mind) && considered_alive(rev_mind) && is_station_level(T.z)) @@ -422,7 +417,7 @@ return FALSE return TRUE -/datum/dynamic_ruleset/roundstart/delayed/revs/round_result() +/datum/dynamic_ruleset/roundstart/revs/round_result() if(finished == 1) SSticker.mode_result = "win - heads killed" SSticker.news_report = REVS_WIN @@ -487,14 +482,13 @@ PM.initTemplateBounds() var/starter_servants = 4 - var/number_players = num_players() + var/number_players = mode.roundstart_pop_ready if(number_players > 30) number_players -= 30 starter_servants += round(number_players / 10) starter_servants = min(starter_servants, 8) for (var/i in 1 to starter_servants) - var/mob/servant = pick(candidates) - candidates -= servant + var/mob/servant = pick_n_take(candidates) assigned += servant.mind servant.mind.assigned_role = ROLE_SERVANT_OF_RATVAR servant.mind.special_role = ROLE_SERVANT_OF_RATVAR @@ -610,16 +604,15 @@ var/num_devils = 1 if(tsc) - num_devils = max(required_candidates, min(round(num_players() / (tsc * 3)) + 2, round(num_players() / (tsc * 1.5)))) + num_devils = max(required_candidates, min(round(mode.roundstart_pop_ready / (tsc * 3)) + 2, round(mode.roundstart_pop_ready / (tsc * 1.5)))) else - num_devils = max(required_candidates, min(num_players(), devil_limit)) + num_devils = max(required_candidates, min(mode.roundstart_pop_ready, devil_limit)) for(var/j = 0, j < num_devils, j++) if (!candidates.len) break - var/mob/devil = pick(candidates) - assigned += devil - candidates -= devil + var/mob/devil = pick_n_take(candidates) + assigned += devil.mind devil.mind.special_role = ROLE_DEVIL devil.mind.restricted_roles = restricted_roles @@ -668,13 +661,12 @@ var/datum/team/monkey/monkey_team /datum/dynamic_ruleset/roundstart/monkey/pre_execute() - var/carriers_to_make = max(round(num_players()/players_per_carrier, 1), 1) + var/carriers_to_make = max(round(mode.roundstart_pop_ready / players_per_carrier, 1), 1) for(var/j = 0, j < carriers_to_make, j++) if (!candidates.len) break - var/mob/carrier = pick(candidates) - candidates -= carrier + var/mob/carrier = pick_n_take(candidates) assigned += carrier.mind carrier.mind.special_role = "Monkey Leader" carrier.mind.restricted_roles = restricted_roles From 03da6d6960e8c5e7e7015390021e14f5854ef0c9 Mon Sep 17 00:00:00 2001 From: Ghommie <42542238+Ghommie@users.noreply.github.com> Date: Sun, 10 Nov 2019 06:41:42 +0100 Subject: [PATCH 4/5] fixeronis. --- .../gamemodes/dynamic/dynamic_rulesets.dm | 42 +++++++++---------- .../dynamic/dynamic_rulesets_latejoin.dm | 2 +- .../dynamic/dynamic_rulesets_midround.dm | 6 +-- .../dynamic/dynamic_rulesets_roundstart.dm | 26 ++++++------ 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm index b77bfcc4a0..22a54d9b73 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm @@ -2,21 +2,21 @@ /// For admin logging and round end screen. var/name = "" /// For admin logging and round end screen, do not change this unless making a new rule type. - var/ruletype = "" + var/ruletype = "" /// For config purposes, similar to config_tag for secret game modes. var/config_tag = null /// If set to TRUE, the rule won't be discarded after being executed, and dynamic will call rule_process() every time it ticks. - var/persistent = FALSE + var/persistent = FALSE /// If set to TRUE, dynamic mode will be able to draft this ruleset again later on. (doesn't apply for roundstart rules) - var/repeatable = FALSE + var/repeatable = FALSE /// If set higher than 0 decreases weight by itself causing the ruleset to appear less often the more it is repeated. - var/repeatable_weight_decrease = 2 + var/repeatable_weight_decrease = 2 /// List of players that are being drafted for this rule - var/list/mob/candidates = list() + var/list/mob/candidates = list() /// List of players that were selected for this rule - var/list/datum/mind/assigned = list() + var/list/datum/mind/assigned = list() /// Preferences flag such as ROLE_WIZARD that need to be turned on for players to be antag - var/antag_flag = null + var/antag_flag = null /// The antagonist datum that is assigned to the mobs mind on ruleset execution. var/datum/antagonist/antag_datum = null /// The required minimum account age for this ruleset. @@ -24,19 +24,19 @@ /// If set, and config flag protect_roles_from_antagonist is false, then the rule will not pick players from these roles. var/list/protected_roles = list() /// If set, rule will deny candidates from those roles always. - var/list/restricted_roles = list() + var/list/restricted_roles = list() /// If set, rule will only accept candidates from those roles, IMPORTANT: DOES NOT WORK ON ROUNDSTART RULESETS. - var/list/exclusive_roles = list() + var/list/exclusive_roles = list() /// If set, there needs to be a certain amount of players doing those roles (among the players who won't be drafted) for the rule to be drafted IMPORTANT: DOES NOT WORK ON ROUNDSTART RULESETS. - var/list/enemy_roles = list() + var/list/enemy_roles = list() /// If enemy_roles was set, this is the amount of enemy job workers needed per threat_level range (0-10,10-20,etc) IMPORTANT: DOES NOT WORK ON ROUNDSTART RULESETS. - var/required_enemies = list(1,1,0,0,0,0,0,0,0,0) + var/required_enemies = list(1,1,0,0,0,0,0,0,0,0) /// The rule needs this many candidates (post-trimming) to be executed (example: Cult needs 4 players at round start) - var/required_candidates = 0 + var/required_candidates = 0 /// 1 -> 9, probability for this rule to be picked against other rules - var/weight = 5 + var/weight = 5 /// Threat cost for this rule, this is decreased from the mode's threat when the rule is executed. - var/cost = 0 + var/cost = 0 /// A flag that determines how the ruleset is handled /// HIGHLANDER_RULESET are rulesets can end the round. /// TRAITOR_RULESET and MINOR_RULESET can't end the round and have no difference right now. @@ -46,18 +46,18 @@ /// Requirements are the threat level requirements per pop range. /// With the default values, The rule will never get drafted below 10 threat level (aka: "peaceful extended"), and it requires a higher threat level at lower pops. var/list/requirements = list(40,30,20,10,10,10,10,10,10,10) - /// An alternative, static requirement used instead when pop is over mode's high_pop_limit. + /// An alternative, static requirement used instead when pop is over mode's high_pop_limit. var/high_population_requirement = 10 /// Reference to the mode, use this instead of SSticker.mode. var/datum/game_mode/dynamic/mode = null /// If a role is to be considered another for the purpose of banning. - var/antag_flag_override = null + var/antag_flag_override = null /// If a ruleset type which is in this list has been executed, then the ruleset will not be executed. var/list/blocking_rules = list() - /// The minimum amount of players required for the rule to be considered. + /// The minimum amount of players required for the rule to be considered. var/minimum_players = 0 /// The maximum amount of players required for the rule to be considered. - /// Anything below zero or exactly zero is ignored. + /// Anything below zero or exactly zero is ignored. var/maximum_players = 0 @@ -105,7 +105,7 @@ return (threat_level >= high_population_requirement) else pop_per_requirement = pop_per_requirement > 0 ? pop_per_requirement : mode.pop_per_requirement - var/indice_pop = min(10,round(population/pop_per_requirement)+1) + var/indice_pop = min(requirements.len,round(population/pop_per_requirement)+1) return (threat_level >= requirements[indice_pop]) /// This is called if persistent variable is true everytime SSTicker ticks. @@ -128,8 +128,8 @@ /// Here you can perform any additional checks you want. (such as checking the map etc) /// Remember that on roundstart no one knows what their job is at this point. /// IMPORTANT: If ready() returns TRUE, that means pre_execute() or execute() should never fail! -/datum/dynamic_ruleset/proc/ready(forced = 0) - if (required_candidates > candidates.len) +/datum/dynamic_ruleset/proc/ready(forced = 0) + if (required_candidates > candidates.len) return FALSE return TRUE diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index a560e03f6e..5c4728bc89 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -17,7 +17,7 @@ candidates.Remove(P) continue else - if(!antag_flag in P.client.prefs.be_special || is_banned_from(P.ckey, list(antag_flag, ROLE_SYNDICATE))) + if(!(antag_flag in P.client.prefs.be_special) || is_banned_from(P.ckey, list(antag_flag, ROLE_SYNDICATE))) candidates.Remove(P) continue if (P.mind.assigned_role in restricted_roles) // Does their job allow for it? diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index 006a80cca6..d26aa57856 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -44,7 +44,7 @@ candidates.Remove(M) continue else - if(!antag_flag in M.client.prefs.be_special || is_banned_from(M.ckey, list(antag_flag, ROLE_SYNDICATE))) + if(!(antag_flag in M.client.prefs.be_special) || is_banned_from(M.ckey, list(antag_flag, ROLE_SYNDICATE))) candidates.Remove(M) continue if (M.mind) @@ -317,14 +317,14 @@ cost = 35 requirements = list(90,90,90,80,60,40,30,20,10,10) high_population_requirement = 10 - var/operative_cap = list(2,2,3,3,4,5,5,5,5,5) + var/list/operative_cap = list(2,2,3,3,4,5,5,5,5,5) var/datum/team/nuclear/nuke_team flags = HIGHLANDER_RULESET /datum/dynamic_ruleset/midround/from_ghosts/nuclear/acceptable(population=0, threat=0) if (locate(/datum/dynamic_ruleset/roundstart/nuclear) in mode.executed_rules) return FALSE // Unavailable if nuke ops were already sent at roundstart - var/indice_pop = min(10,round(living_players.len/5)+1) + var/indice_pop = min(operative_cap.len, round(living_players.len/5)+1) required_candidates = operative_cap[indice_pop] return ..() diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index f515ccc443..d48153b222 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -123,7 +123,7 @@ /datum/dynamic_ruleset/roundstart/changeling/execute() var/team_mode = FALSE - if(prob(team_mode_probability)) + if(prob(team_mode_probability)) team_mode = TRUE var/list/team_objectives = subtypesof(/datum/objective/changeling_team_objective) var/list/possible_team_objectives = list() @@ -171,13 +171,13 @@ /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.assigned_role = ROLE_WIZARD M.mind.special_role = ROLE_WIZARD - + return TRUE /datum/dynamic_ruleset/roundstart/wizard/execute() @@ -185,7 +185,7 @@ M.current.forceMove(pick(GLOB.wizardstart)) M.add_antag_datum(new antag_datum()) return TRUE - + ////////////////////////////////////////////// // // // BLOOD CULT // @@ -207,16 +207,16 @@ high_population_requirement = 10 pop_per_requirement = 5 flags = HIGHLANDER_RULESET - var/cultist_cap = list(2,2,2,3,3,4,4,4,4,4) + var/list/cultist_cap = list(2,2,2,3,3,4,4,4,4,4) var/datum/team/cult/main_cult /datum/dynamic_ruleset/roundstart/bloodcult/ready(forced = FALSE) - var/indice_pop = min(10,round(mode.roundstart_pop_ready/pop_per_requirement)+1) + var/indice_pop = min(cultist_cap.len, round(mode.roundstart_pop_ready/pop_per_requirement)+1) required_candidates = cultist_cap[indice_pop] . = ..() /datum/dynamic_ruleset/roundstart/bloodcult/pre_execute() - var/indice_pop = min(10,round(mode.roundstart_pop_ready/pop_per_requirement)+1) + var/indice_pop = min(cultist_cap.len, round(mode.roundstart_pop_ready/pop_per_requirement)+1) var/cultists = cultist_cap[indice_pop] for(var/cultists_number = 1 to cultists) if(candidates.len <= 0) @@ -233,7 +233,7 @@ 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) + M.add_antag_datum(new_cultist) main_cult.setup_objectives() return TRUE @@ -267,18 +267,18 @@ high_population_requirement = 10 pop_per_requirement = 5 flags = HIGHLANDER_RULESET - var/operative_cap = list(2,2,2,3,3,3,4,4,5,5) + var/list/operative_cap = list(2,2,2,3,3,3,4,4,5,5) var/datum/team/nuclear/nuke_team /datum/dynamic_ruleset/roundstart/nuclear/ready(forced = FALSE) - var/indice_pop = min(10,round(mode.roundstart_pop_ready/pop_per_requirement)+1) + var/indice_pop = min(operative_cap.len,round(mode.roundstart_pop_ready/pop_per_requirement)+1) required_candidates = operative_cap[indice_pop] . = ..() /datum/dynamic_ruleset/roundstart/nuclear/pre_execute() // If ready() did its job, candidates should have 5 or more members in it - var/indice_pop = min(10,round(mode.roundstart_pop_ready/5)+1) + var/indice_pop = min(operative_cap.len, round(mode.roundstart_pop_ready/5)+1) var/operatives = operative_cap[indice_pop] for(var/operatives_number = 1 to operatives) if(candidates.len <= 0) @@ -385,7 +385,7 @@ revolution.update_heads() SSshuttle.registerHostileEnvironment(src) return TRUE - + /datum/dynamic_ruleset/roundstart/revs/rule_process() if(check_rev_victory()) finished = 1 @@ -599,7 +599,7 @@ high_population_requirement = 101 var/devil_limit = 4 // Hard limit on devils if scaling is turned off -/datum/dynamic_ruleset/roundstart/devil/pre_execute() +/datum/dynamic_ruleset/roundstart/devil/pre_execute() var/tsc = CONFIG_GET(number/traitor_scaling_coeff) var/num_devils = 1 From c6e44a7fed8d3687344c89a12144a6cf25405eca Mon Sep 17 00:00:00 2001 From: Ghommie <42542238+Ghommie@users.noreply.github.com> Date: Sun, 10 Nov 2019 17:59:40 +0100 Subject: [PATCH 5/5] That's it: #46027, (a little bit of) #46132, #47100, #47341, #47351, #47395, #47457, #47491. --- code/__DEFINES/traits.dm | 3 +- code/__HELPERS/roundend.dm | 2 +- code/game/gamemodes/dynamic/dynamic.dm | 192 +++++++++++------- .../gamemodes/dynamic/dynamic_rulesets.dm | 56 ++++- .../dynamic/dynamic_rulesets_latejoin.dm | 92 +++++++-- .../dynamic/dynamic_rulesets_midround.dm | 10 +- .../dynamic/dynamic_rulesets_roundstart.dm | 151 ++++++++------ .../antagonists/revolution/revolution.dm | 38 +++- code/modules/mob/living/carbon/human/death.dm | 9 +- 9 files changed, 381 insertions(+), 172 deletions(-) diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 1f37556d1d..f96337f691 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -213,4 +213,5 @@ #define LOCKED_HELMET_TRAIT "locked-helmet" #define NINJA_SUIT_TRAIT "ninja-suit" #define ANTI_DROP_IMPLANT_TRAIT "anti-drop-implant" -#define ABDUCTOR_ANTAGONIST "abductor-antagonist" \ No newline at end of file +#define ABDUCTOR_ANTAGONIST "abductor-antagonist" +#define MADE_UNCLONEABLE "made-uncloneable" \ No newline at end of file diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm index 8e59106d98..85b839c9a1 100644 --- a/code/__HELPERS/roundend.dm +++ b/code/__HELPERS/roundend.dm @@ -314,7 +314,7 @@ parts += "[FOURSPACES]Threat left: [mode.threat]" parts += "[FOURSPACES]Executed rules:" for(var/datum/dynamic_ruleset/rule in mode.executed_rules) - parts += "[FOURSPACES][FOURSPACES][rule.ruletype] - [rule.name]: -[rule.cost] threat" + parts += "[FOURSPACES][FOURSPACES][rule.ruletype] - [rule.name]: -[rule.cost + rule.scaled_times * rule.scaling_cost] threat" return parts.Join("
") /client/proc/roundend_report_file() diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index 473e2ae4a5..78febd0615 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -19,7 +19,7 @@ GLOBAL_VAR_INIT(dynamic_midround_delay_max, (35 MINUTES)) // Are HIGHLANDER_RULESETs allowed to stack? GLOBAL_VAR_INIT(dynamic_no_stacking, TRUE) -// A number between -5 and +5. +// A number between -5 and +5. // A negative value will give a more peaceful round and // a positive value will give a round with higher threat. GLOBAL_VAR_INIT(dynamic_curve_centre, 0) @@ -27,7 +27,7 @@ GLOBAL_VAR_INIT(dynamic_curve_centre, 0) // Higher value will favour extreme rounds and // lower value rounds closer to the average. GLOBAL_VAR_INIT(dynamic_curve_width, 1.8) -// If enabled only picks a single starting rule and executes only autotraitor midround ruleset. +// If enabled only picks a single starting rule and executes only autotraitor midround ruleset. GLOBAL_VAR_INIT(dynamic_classic_secret, FALSE) // How many roundstart players required for high population override to take effect. GLOBAL_VAR_INIT(dynamic_high_pop_limit, 55) @@ -38,7 +38,7 @@ GLOBAL_VAR_INIT(dynamic_forced_extended, FALSE) GLOBAL_VAR_INIT(dynamic_stacking_limit, 90) // List of forced roundstart rulesets. GLOBAL_LIST_EMPTY(dynamic_forced_roundstart_ruleset) -// Forced threat level, setting this to zero or higher forces the roundstart threat to the value. +// Forced threat level, setting this to zero or higher forces the roundstart threat to the value. GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) /datum/game_mode/dynamic @@ -49,14 +49,14 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) announce_text = "Dynamic mode!" // This needs to be changed maybe reroll_friendly = FALSE; - + // Threat logging vars /// The "threat cap", threat shouldn't normally go above this and is used in ruleset calculations - var/threat_level = 0 + var/threat_level = 0 /// Set at the beginning of the round. Spent by the mode to "purchase" rules. - var/threat = 0 + var/threat = 0 /// Running information about the threat. Can store text or datum entries. - var/list/threat_log = list() + var/list/threat_log = list() /// List of roundstart rules used for selecting the rules. var/list/roundstart_rules = list() /// List of latejoin rules used for selecting the rules. @@ -72,14 +72,20 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) * 0-6, 7-13, 14-20, 21-27, 28-34, 35-41, 42-48, 49-55, 56-62, 63+ */ var/pop_per_requirement = 6 - /// The requirement used for checking if a second rule should be selected. + /// The requirement used for checking if a second rule should be selected. Index based on pop_per_requirement. var/list/second_rule_req = list(100, 100, 80, 70, 60, 50, 30, 20, 10, 0) - /// The requirement used for checking if a third rule should be selected. + /// The probability for a second ruleset with index being every ten threat. + var/list/second_rule_prob = list(0,0,60,80,80,80,100,100,100,100) + /// The requirement used for checking if a third rule should be selected. Index based on pop_per_requirement. var/list/third_rule_req = list(100, 100, 100, 90, 80, 70, 60, 50, 40, 30) - /// Threat requirement for a second ruleset when high pop override is in effect. + /// The probability for a third ruleset with index being every ten threat. + var/list/third_rule_prob = list(0,0,0,0,60,60,80,90,100,100) + /// Threat requirement for a second ruleset when high pop override is in effect. var/high_pop_second_rule_req = 40 - /// Threat requirement for a third ruleset when high pop override is in effect. + /// Threat requirement for a third ruleset when high pop override is in effect. var/high_pop_third_rule_req = 60 + /// The amount of additional rulesets waiting to be picked. + var/extra_rulesets_amount = 0 /// Number of players who were ready on roundstart. var/roundstart_pop_ready = 0 /// List of candidates used on roundstart rulesets. @@ -106,6 +112,8 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) var/highlander_executed = FALSE /// If a only ruleset has been executed. var/only_ruleset_executed = FALSE + /// Antags rolled by rules so far, to keep track of and discourage scaling past a certain ratio of crew/antags especially on lowpop. + var/antags_rolled = 0 /datum/game_mode/dynamic/New() // i have NO IDEA if this is the proper way to do this. ..() @@ -175,7 +183,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) show_threatlog(usr) else if (href_list["stacking_limit"]) GLOB.dynamic_stacking_limit = input(usr,"Change the threat limit at which round-endings rulesets will start to stack.", "Change stacking limit", null) as num - + admin_panel() // Refreshes the window // Checks if there are HIGHLANDER_RULESETs and calls the rule's round_result() proc @@ -313,18 +321,21 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) candidates.Add(player) log_game("DYNAMIC: Listing [roundstart_rules.len] round start rulesets, and [candidates.len] players ready.") if (candidates.len <= 0) + log_game("DYNAMIC: [candidates.len] candidates.") return TRUE if (roundstart_rules.len <= 0) + log_game("DYNAMIC: [roundstart_rules.len] rules.") return TRUE - + if(GLOB.dynamic_forced_roundstart_ruleset.len > 0) rigged_roundstart() - else + else roundstart() var/starting_rulesets = "" for (var/datum/dynamic_ruleset/roundstart/DR in executed_rules) starting_rulesets += "[DR.name], " + log_game("DYNAMIC: Picked the following roundstart rules: [starting_rulesets]") candidates.Cut() return TRUE @@ -332,12 +343,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) update_playercounts() for(var/datum/dynamic_ruleset/roundstart/rule in executed_rules) - rule.candidates.Cut() // The rule should not use candidates at this point as they all are null. - if(rule.delay > 0) - addtimer(CALLBACK(rule, /datum/dynamic_ruleset/roundstart.proc/execute), rule.delay) - else - if(!rule.execute()) - stack_trace("The starting rule \"[rule.name]\" failed to execute.") + addtimer(CALLBACK(src, /datum/game_mode/dynamic/.proc/execute_roundstart_rule, rule), rule.delay) ..() /// A simple roundstart proc used when dynamic_forced_roundstart_ruleset has rules in it. @@ -348,6 +354,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) message_admins("Drafting players for forced ruleset [rule.name].") log_game("DYNAMIC: Drafting players for forced ruleset [rule.name].") rule.mode = src + rule.acceptable(roundstart_pop_ready, threat_level) // Assigns some vars in the modes, running it here for consistency rule.candidates = candidates.Copy() rule.trim_candidates() if (rule.ready(TRUE)) @@ -366,7 +373,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) drafted_rules[rule] = rule.weight var/indice_pop = min(10,round(roundstart_pop_ready/pop_per_requirement)+1) - var/extra_rulesets_amount = 0 + extra_rulesets_amount = 0 if (GLOB.dynamic_classic_secret) extra_rulesets_amount = 0 else @@ -378,23 +385,28 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if (threat_level > high_pop_third_rule_req) extra_rulesets_amount++ else - if (threat_level >= second_rule_req[indice_pop]) + var/threat_indice = min(10, max(round(threat_level ? threat_level/10 : 1), 1)) // 0-9 threat = 1, 10-19 threat = 2 ... + if (threat_level >= second_rule_req[indice_pop] && prob(second_rule_prob[threat_indice])) extra_rulesets_amount++ - if (threat_level >= third_rule_req[indice_pop]) + if (threat_level >= third_rule_req[indice_pop] && prob(third_rule_prob[threat_indice])) extra_rulesets_amount++ + log_game("DYNAMIC: Trying to roll [extra_rulesets_amount + 1] roundstart rulesets. Picking from [drafted_rules.len] eligible rulesets.") if (drafted_rules.len > 0 && picking_roundstart_rule(drafted_rules)) - if (extra_rulesets_amount > 0) // We've got enough population and threat for a second rulestart rule + log_game("DYNAMIC: First ruleset picked successfully. [extra_rulesets_amount] remaining.") + while(extra_rulesets_amount > 0 && drafted_rules.len > 0) // We had enough threat for one or two more rulesets for (var/datum/dynamic_ruleset/roundstart/rule in drafted_rules) if (rule.cost > threat) drafted_rules -= rule - if (drafted_rules.len > 0 && picking_roundstart_rule(drafted_rules)) - if (extra_rulesets_amount > 1) // We've got enough population and threat for a third rulestart rule - for (var/datum/dynamic_ruleset/roundstart/rule in drafted_rules) - if (rule.cost > threat) - drafted_rules -= rule - picking_roundstart_rule(drafted_rules) + if(drafted_rules.len) + picking_roundstart_rule(drafted_rules) + extra_rulesets_amount-- + log_game("DYNAMIC: Additional ruleset picked successfully, now [executed_rules.len] picked. [extra_rulesets_amount] remaining.") else + + if(threat >= 10) + message_admins("DYNAMIC: Picking first roundstart ruleset failed. You should report this.") + log_game("DYNAMIC: Picking first roundstart ruleset failed. drafted_rules.len = [drafted_rules.len] and threat = [threat]/[threat_level]") return FALSE return TRUE @@ -402,63 +414,83 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) /datum/game_mode/dynamic/proc/picking_roundstart_rule(list/drafted_rules = list(), forced = FALSE) var/datum/dynamic_ruleset/roundstart/starting_rule = pickweight(drafted_rules) if(!starting_rule) + log_game("DYNAMIC: Couldn't pick a starting ruleset. No rulesets available") return FALSE if(!forced) if(only_ruleset_executed) return FALSE // Check if a blocking ruleset has been executed. - else if(check_blocking(starting_rule.blocking_rules, executed_rules)) + else if(check_blocking(starting_rule.blocking_rules, executed_rules)) // Should already be filtered out, but making sure. Check filtering at end of proc if reported. drafted_rules -= starting_rule if(drafted_rules.len <= 0) + log_game("DYNAMIC: Picking [starting_rule.name] failed due to blocking_rules and no more rulesets available. Report this.") return FALSE starting_rule = pickweight(drafted_rules) // Check if the ruleset is highlander and if a highlander ruleset has been executed - else if(starting_rule.flags & HIGHLANDER_RULESET) - if(threat < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking) + else if(starting_rule.flags & HIGHLANDER_RULESET) // Should already be filtered out, but making sure. Check filtering at end of proc if reported. + if(threat_level > GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking) if(highlander_executed) drafted_rules -= starting_rule if(drafted_rules.len <= 0) + log_game("DYNAMIC: Picking [starting_rule.name] failed due to no highlander stacking and no more rulesets available. Report this.") return FALSE starting_rule = pickweight(drafted_rules) // With low pop and high threat there might be rulesets that get executed with no valid candidates. - else if(starting_rule.ready()) + else if(starting_rule.ready()) // Should already be filtered out, but making sure. Check filtering at end of proc if reported. drafted_rules -= starting_rule if(drafted_rules.len <= 0) + log_game("DYNAMIC: Picking [starting_rule.name] failed because there were not enough candidates and no more rulesets available. Report this.") return FALSE starting_rule = pickweight(drafted_rules) - log_game("DYNAMIC: Picking a ruleset [starting_rule.name]") + log_game("DYNAMIC: Picked a ruleset: [starting_rule.name]") roundstart_rules -= starting_rule drafted_rules -= starting_rule starting_rule.trim_candidates() - if (starting_rule.pre_execute()) - spend_threat(starting_rule.cost) + + var/added_threat = starting_rule.scale_up(extra_rulesets_amount, threat) + if(starting_rule.pre_execute()) + spend_threat(starting_rule.cost + added_threat) threat_log += "[worldtime2text()]: Roundstart [starting_rule.name] spent [starting_rule.cost]" if(starting_rule.flags & HIGHLANDER_RULESET) highlander_executed = TRUE else if(starting_rule.flags & ONLY_RULESET) only_ruleset_executed = TRUE executed_rules += starting_rule - if (starting_rule.persistent) - current_rules += starting_rule - for (var/datum/dynamic_ruleset/roundstart/rule in drafted_rules) - if (!rule.ready()) - drafted_rules -= rule // And removing rules that are no longer elligible + for(var/datum/dynamic_ruleset/roundstart/rule in drafted_rules) + if(check_blocking(rule.blocking_rules, executed_rules)) + drafted_rules -= rule + if(highlander_executed && rule.flags & HIGHLANDER_RULESET) + drafted_rules -= rule + if(!rule.ready()) + drafted_rules -= rule // And removing rules that are no longer eligible return TRUE else stack_trace("The starting rule \"[starting_rule.name]\" failed to pre_execute.") return FALSE +/// Mainly here to facilitate delayed rulesets. All roundstart rulesets are executed with a timered callback to this proc. +/datum/game_mode/dynamic/proc/execute_roundstart_rule(sent_rule) + var/datum/dynamic_ruleset/rule = sent_rule + if(rule.execute()) + if(rule.persistent) + current_rules += rule + return TRUE + rule.clean_up() // Refund threat, delete teams and so on. + executed_rules -= rule + stack_trace("The starting rule \"[rule.name]\" failed to execute.") + return FALSE + /// Picks a random midround OR latejoin rule from the list given as an argument and executes it. /// Also this could be named better. /datum/game_mode/dynamic/proc/picking_midround_latejoin_rule(list/drafted_rules = list(), forced = FALSE) var/datum/dynamic_ruleset/rule = pickweight(drafted_rules) if(!rule) return FALSE - + if(!forced) if(only_ruleset_executed) return FALSE @@ -470,39 +502,21 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) rule = pickweight(drafted_rules) // Check if the ruleset is highlander and if a highlander ruleset has been executed else if(rule.flags & HIGHLANDER_RULESET) - if(threat < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking) + if(threat_level > GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking) if(highlander_executed) drafted_rules -= rule if(drafted_rules.len <= 0) return FALSE rule = pickweight(drafted_rules) - + if(!rule.repeatable) if(rule.ruletype == "Latejoin") latejoin_rules = remove_from_list(latejoin_rules, rule.type) else if(rule.type == "Midround") midround_rules = remove_from_list(midround_rules, rule.type) - - if (rule.execute()) - log_game("DYNAMIC: Injected a [rule.ruletype == "latejoin" ? "latejoin" : "midround"] ruleset [rule.name].") - spend_threat(rule.cost) - threat_log += "[worldtime2text()]: [rule.ruletype] [rule.name] spent [rule.cost]" - if(rule.flags & HIGHLANDER_RULESET) - highlander_executed = TRUE - else if(rule.flags & ONLY_RULESET) - only_ruleset_executed = TRUE - if(rule.ruletype == "Latejoin") - var/mob/M = pick(rule.candidates) - message_admins("[key_name(M)] joined the station, and was selected by the [rule.name] ruleset.") - log_game("DYNAMIC: [key_name(M)] joined the station, and was selected by the [rule.name] ruleset.") - executed_rules += rule - rule.candidates.Cut() - if (rule.persistent) - current_rules += rule - return TRUE - else - stack_trace("The [rule.ruletype] rule \"[rule.name]\" failed to execute.") - return FALSE + + addtimer(CALLBACK(src, /datum/game_mode/dynamic/.proc/execute_midround_latejoin_rule, rule), rule.delay) + return TRUE /// An experimental proc to allow admins to call rules on the fly or have rules call other rules. /datum/game_mode/dynamic/proc/picking_specific_rule(ruletype, forced = FALSE) @@ -513,10 +527,10 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) new_rule = ruletype else return FALSE - + if(!new_rule) return FALSE - + if(!forced) if(only_ruleset_executed) return FALSE @@ -525,10 +539,10 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) return FALSE // Check if the ruleset is highlander and if a highlander ruleset has been executed else if(new_rule.flags & HIGHLANDER_RULESET) - if(threat < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking) + if(threat_level > GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking) if(highlander_executed) return FALSE - + update_playercounts() if ((forced || (new_rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && new_rule.cost <= threat))) new_rule.trim_candidates() @@ -549,6 +563,30 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) log_game("DYNAMIC: The ruleset [new_rule.name] couldn't be executed due to lack of elligible players.") return FALSE +/// Mainly here to facilitate delayed rulesets. All midround/latejoin rulesets are executed with a timered callback to this proc. +/datum/game_mode/dynamic/proc/execute_midround_latejoin_rule(sent_rule) + var/datum/dynamic_ruleset/rule = sent_rule + if (rule.execute()) + log_game("DYNAMIC: Injected a [rule.ruletype == "latejoin" ? "latejoin" : "midround"] ruleset [rule.name].") + spend_threat(rule.cost) + threat_log += "[worldtime2text()]: [rule.ruletype] [rule.name] spent [rule.cost]" + if(rule.flags & HIGHLANDER_RULESET) + highlander_executed = TRUE + else if(rule.flags & ONLY_RULESET) + only_ruleset_executed = TRUE + if(rule.ruletype == "Latejoin") + var/mob/M = pick(rule.candidates) + message_admins("[key_name(M)] joined the station, and was selected by the [rule.name] ruleset.") + log_game("DYNAMIC: [key_name(M)] joined the station, and was selected by the [rule.name] ruleset.") + executed_rules += rule + rule.candidates.Cut() + if (rule.persistent) + current_rules += rule + return TRUE + rule.clean_up() + stack_trace("The [rule.ruletype] rule \"[rule.name]\" failed to execute.") + return FALSE + /datum/game_mode/dynamic/process() if (pop_last_updated < world.time - (60 SECONDS)) pop_last_updated = world.time @@ -561,19 +599,19 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if (midround_injection_cooldown < world.time) if (GLOB.dynamic_forced_extended) return - + // Somehow it managed to trigger midround multiple times so this was moved here. // There is no way this should be able to trigger an injection twice now. var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_midround_delay_max + GLOB.dynamic_midround_delay_min) midround_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_midround_delay_min, GLOB.dynamic_midround_delay_max)) + world.time) - + // Time to inject some threat into the round if(EMERGENCY_ESCAPED_OR_ENDGAMED) // Unless the shuttle is gone return message_admins("DYNAMIC: Checking for midround injection.") log_game("DYNAMIC: Checking for midround injection.") - + update_playercounts() if (get_injection_chance()) var/list/drafted_rules = list() @@ -587,7 +625,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) drafted_rules[rule] = rule.get_weight() if (drafted_rules.len > 0) picking_midround_latejoin_rule(drafted_rules) - + /// Updates current_players. /datum/game_mode/dynamic/proc/update_playercounts() current_players[CURRENT_LIVING_PLAYERS] = list() @@ -682,11 +720,11 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) // Classic secret : only autotraitor/minor roles if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET))) continue - // No stacking : only one round-enter, unless > stacking_limit threat. - if (threat < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking) + // No stacking : only one round-ender, unless threat level > stacking_limit. + if (threat_level > GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking) if(rule.flags & HIGHLANDER_RULESET && highlander_executed) continue - + rule.candidates = list(newPlayer) rule.trim_candidates() if (rule.ready()) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm index 22a54d9b73..266d313a06 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm @@ -1,3 +1,9 @@ +#define EXTRA_RULESET_PENALTY 20 // Changes how likely a gamemode is to scale based on how many other roundstart rulesets are waiting to be rolled. +#define POP_SCALING_PENALTY 50 // Discourages scaling up rulesets if ratio of antags to crew is high. + +#define REVOLUTION_VICTORY 1 +#define STATION_VICTORY 2 + /datum/dynamic_ruleset /// For admin logging and round end screen. var/name = "" @@ -37,6 +43,12 @@ var/weight = 5 /// Threat cost for this rule, this is decreased from the mode's threat when the rule is executed. var/cost = 0 + /// Cost per level the rule scales up. + var/scaling_cost = 0 + /// How many times a rule has scaled up upon getting picked. + var/scaled_times = 0 + /// Used for the roundend report + var/total_cost = 0 /// A flag that determines how the ruleset is handled /// HIGHLANDER_RULESET are rulesets can end the round. /// TRAITOR_RULESET and MINOR_RULESET can't end the round and have no difference right now. @@ -59,7 +71,15 @@ /// The maximum amount of players required for the rule to be considered. /// Anything below zero or exactly zero is ignored. var/maximum_players = 0 - + /// Calculated during acceptable(), used in scaling and team sizes. + var/indice_pop = 0 + /// Population scaling. Used by team antags and scaling for solo antags. + var/list/antag_cap = list() + /// Base probability used in scaling. The higher it is, the more likely to scale. Kept as a var to allow for config editing._SendSignal(sigtype, list/arguments) + var/base_prob = 60 + /// Delay for when execute will get called from the time of post_setup (roundstart) or process (midround/latejoin). + /// Make sure your ruleset works with execute being called during the game when using this, and that the clean_up proc reverts it properly in case of faliure. + var/delay = 0 /datum/dynamic_ruleset/New() ..() @@ -86,9 +106,6 @@ /datum/dynamic_ruleset/roundstart // One or more of those drafted at roundstart ruletype = "Roundstart" - /// Delay for when execute will get called from the time of post_setup. - /// Make sure your ruleset works with execute being called during the game when using this. - var/delay = 0 // Can be drafted when a player joins the server /datum/dynamic_ruleset/latejoin @@ -102,12 +119,35 @@ if(maximum_players > 0 && population > maximum_players) return FALSE if (population >= GLOB.dynamic_high_pop_limit) + indice_pop = 10 return (threat_level >= high_population_requirement) else pop_per_requirement = pop_per_requirement > 0 ? pop_per_requirement : mode.pop_per_requirement - var/indice_pop = min(requirements.len,round(population/pop_per_requirement)+1) + if(antag_cap.len && requirements.len != antag_cap.len) + message_admins("DYNAMIC: requirements and antag_cap lists have different lengths in ruleset [name]. Likely config issue, report this.") + log_game("DYNAMIC: requirements and antag_cap lists have different lengths in ruleset [name]. Likely config issue, report this.") + indice_pop = min(requirements.len,round(population/pop_per_requirement)+1) return (threat_level >= requirements[indice_pop]) +/// Called when a suitable rule is picked during roundstart(). Will some times attempt to scale a rule up when there is threat remaining. Returns the amount of scaled steps. +/datum/dynamic_ruleset/proc/scale_up(extra_rulesets = 0, remaining_threat_level = 0) + remaining_threat_level -= cost + if(scaling_cost && scaling_cost <= remaining_threat_level) // Only attempts to scale the modes with a scaling cost explicitly set. + var/new_prob + var/pop_to_antags = (mode.antags_rolled + (antag_cap[indice_pop] * (scaled_times + 1))) / mode.roundstart_pop_ready + log_game("DYNAMIC: [name] roundstart ruleset attempting to scale up with [extra_rulesets] rulesets waiting and [remaining_threat_level] threat remaining.") + for(var/i in 1 to 3) //Can scale a max of 3 times + if(remaining_threat_level >= scaling_cost && pop_to_antags < 0.25) + new_prob = base_prob + (remaining_threat_level) - (scaled_times * scaling_cost) - (extra_rulesets * EXTRA_RULESET_PENALTY) - (pop_to_antags * POP_SCALING_PENALTY) + if (!prob(new_prob)) + break + remaining_threat_level -= scaling_cost + scaled_times++ + pop_to_antags = (mode.antags_rolled + (antag_cap[indice_pop] * (scaled_times + 1))) / mode.roundstart_pop_ready + log_game("DYNAMIC: [name] roundstart ruleset failed scaling up at [new_prob ? new_prob : 0]% chance after [scaled_times]/3 successful scaleups. [remaining_threat_level] threat remaining, antag to crew ratio: [pop_to_antags*100]%.") + mode.antags_rolled += (1 + scaled_times) * antag_cap[indice_pop] + return scaled_times * scaling_cost + /// This is called if persistent variable is true everytime SSTicker ticks. /datum/dynamic_ruleset/proc/rule_process() return @@ -133,6 +173,12 @@ return FALSE return TRUE +/// Runs from gamemode process() if ruleset fails to start, like delayed rulesets not getting valid candidates. +/// This one only handles refunding the threat, override in ruleset to clean up the rest. +/datum/dynamic_ruleset/proc/clean_up() + mode.refund_threat(cost + (scaled_times * scaling_cost)) + mode.threat_log += "[worldtime2text()]: [ruletype] [name] refunded [cost + (scaled_times * scaling_cost)]" + /// Gets weight of the ruleset /// Note that this decreases weight if repeatable is TRUE and repeatable_weight_decrease is higher than 0 /// Note: If you don't want repeatable rulesets to decrease their weight use the weight variable directly diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index 5c4728bc89..6eb7fb37eb 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -78,6 +78,7 @@ /datum/dynamic_ruleset/latejoin/provocateur name = "Provocateur" + persistent = TRUE config_tag = "latejoin_revolution" antag_datum = /datum/antagonist/rev/head antag_flag = ROLE_REV_HEAD @@ -87,31 +88,98 @@ required_enemies = list(2,2,1,1,1,1,1,0,0,0) required_candidates = 1 weight = 2 + delay = 1 MINUTES // Prevents rule start while head is offstation. cost = 20 requirements = list(101,101,70,40,30,20,20,20,20,20) high_population_requirement = 50 flags = HIGHLANDER_RULESET - var/required_heads = 3 + var/required_heads_of_staff = 3 + var/finished = FALSE + var/datum/team/revolution/revolution /datum/dynamic_ruleset/latejoin/provocateur/ready(forced=FALSE) if (forced) - required_heads = 1 + required_heads_of_staff = 1 if(!..()) return FALSE var/head_check = 0 for(var/mob/player in mode.current_players[CURRENT_LIVING_PLAYERS]) if (player.mind.assigned_role in GLOB.command_positions) head_check++ - return (head_check >= required_heads) + return (head_check >= required_heads_of_staff) /datum/dynamic_ruleset/latejoin/provocateur/execute() - var/mob/M = pick(candidates) - assigned += M.mind - M.mind.special_role = antag_flag - var/datum/antagonist/rev/head/new_head = new() - new_head.give_flash = TRUE - new_head.give_hud = TRUE - new_head.remove_clumsy = TRUE - new_head = M.mind.add_antag_datum(new_head) - new_head.rev_team.max_headrevs = 1 // Only one revhead if it is latejoin. + var/mob/M = pick(candidates) // This should contain a single player, but in case. + if(check_eligible(M.mind)) // Didnt die/run off z-level/get implanted since leaving shuttle. + assigned += M.mind + M.mind.special_role = antag_flag + revolution = new() + var/datum/antagonist/rev/head/new_head = new() + new_head.give_flash = TRUE + new_head.give_hud = TRUE + new_head.remove_clumsy = TRUE + new_head = M.mind.add_antag_datum(new_head, revolution) + revolution.update_objectives() + revolution.update_heads() + SSshuttle.registerHostileEnvironment(src) + return TRUE + else + log_game("DYNAMIC: [ruletype] [name] discarded [M.name] from head revolutionary due to ineligibility.") + log_game("DYNAMIC: [ruletype] [name] failed to get any eligible headrevs. Refunding [cost] threat.") + return FALSE + +/datum/dynamic_ruleset/latejoin/provocateur/rule_process() + if(check_rev_victory()) + finished = REVOLUTION_VICTORY + return RULESET_STOP_PROCESSING + else if (check_heads_victory()) + finished = STATION_VICTORY + SSshuttle.clearHostileEnvironment(src) + priority_announce("It appears the mutiny has been quelled. Please return yourself and your colleagues to work. \ + We have remotely blacklisted the head revolutionaries from your cloning software to prevent accidental cloning.", null, "attention", null, "Central Command Loyalty Monitoring Division") + for(var/datum/mind/M in revolution.members) // Remove antag datums and prevent headrev cloning then restarting rebellions. + if(M.has_antag_datum(/datum/antagonist/rev/head)) + var/datum/antagonist/rev/head/R = M.has_antag_datum(/datum/antagonist/rev/head) + R.remove_revolutionary(FALSE, "gamemode") + var/mob/living/carbon/C = M.current + if(C.stat == DEAD) + C.makeUncloneable() + if(M.has_antag_datum(/datum/antagonist/rev)) + var/datum/antagonist/rev/R = M.has_antag_datum(/datum/antagonist/rev) + R.remove_revolutionary(FALSE, "gamemode") + return RULESET_STOP_PROCESSING + +/// Checks for revhead loss conditions and other antag datums. +/datum/dynamic_ruleset/latejoin/provocateur/proc/check_eligible(var/datum/mind/M) + var/turf/T = get_turf(M.current) + if(!considered_afk(M) && considered_alive(M) && is_station_level(T.z) && !M.antag_datums?.len && !HAS_TRAIT(M, TRAIT_MINDSHIELD)) + return TRUE + return FALSE + +/datum/dynamic_ruleset/latejoin/provocateur/check_finished() + if(finished == REVOLUTION_VICTORY) + return TRUE + else + return ..() + +/datum/dynamic_ruleset/latejoin/provocateur/proc/check_rev_victory() + for(var/datum/objective/mutiny/objective in revolution.objectives) + if(!(objective.check_completion())) + return FALSE return TRUE + +/datum/dynamic_ruleset/latejoin/provocateur/proc/check_heads_victory() + for(var/datum/mind/rev_mind in revolution.head_revolutionaries()) + var/turf/T = get_turf(rev_mind.current) + if(!considered_afk(rev_mind) && considered_alive(rev_mind) && is_station_level(T.z)) + if(ishuman(rev_mind.current) || ismonkey(rev_mind.current)) + return FALSE + return TRUE + +/datum/dynamic_ruleset/latejoin/provocateur/round_result() + if(finished == REVOLUTION_VICTORY) + SSticker.mode_result = "win - heads killed" + SSticker.news_report = REVS_WIN + else if(finished == STATION_VICTORY) + SSticker.mode_result = "loss - rev heads killed" + SSticker.news_report = REVS_LOSE \ No newline at end of file diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index d26aa57856..ef08b11739 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -41,17 +41,17 @@ continue if(antag_flag_override) if(!(antag_flag_override in M.client.prefs.be_special) || is_banned_from(M.ckey, list(antag_flag_override, ROLE_SYNDICATE))) - candidates.Remove(M) + trimmed_list.Remove(M) continue else if(!(antag_flag in M.client.prefs.be_special) || is_banned_from(M.ckey, list(antag_flag, ROLE_SYNDICATE))) - candidates.Remove(M) + trimmed_list.Remove(M) continue if (M.mind) if (restrict_ghost_roles && M.mind.assigned_role in GLOB.exp_specialmap[EXP_TYPE_SPECIAL]) // Are they playing a ghost role? trimmed_list.Remove(M) continue - if (M.mind.assigned_role in restricted_roles || HAS_TRAIT(M, TRAIT_MINDSHIELD)) // Does their job allow it or are they mindshielded? + if (M.mind.assigned_role in restricted_roles) // Does their job allow it? trimmed_list.Remove(M) continue if ((exclusive_roles.len > 0) && !(M.mind.assigned_role in exclusive_roles)) // Is the rule exclusive to their job? @@ -239,7 +239,7 @@ /datum/dynamic_ruleset/midround/malf/trim_candidates() ..() - candidates = candidates[CURRENT_LIVING_PLAYERS] + living_players = candidates[CURRENT_LIVING_PLAYERS] for(var/mob/living/player in candidates) if(!isAI(player)) candidates -= player @@ -324,7 +324,7 @@ /datum/dynamic_ruleset/midround/from_ghosts/nuclear/acceptable(population=0, threat=0) if (locate(/datum/dynamic_ruleset/roundstart/nuclear) in mode.executed_rules) return FALSE // Unavailable if nuke ops were already sent at roundstart - var/indice_pop = min(operative_cap.len, round(living_players.len/5)+1) + indice_pop = min(operative_cap.len, round(living_players.len/5)+1) required_candidates = operative_cap[indice_pop] return ..() diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index d48153b222..346cbe1e00 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -16,14 +16,15 @@ restricted_roles = list("Cyborg") required_candidates = 1 weight = 5 - cost = 10 + cost = 10 // Avoid raising traitor threat above 10, as it is the default low cost ruleset. + scaling_cost = 10 requirements = list(10,10,10,10,10,10,10,10,10,10) high_population_requirement = 10 + antag_cap = list(1,1,1,1,2,2,2,2,3,3) var/autotraitor_cooldown = 450 // 15 minutes (ticks once per 2 sec) /datum/dynamic_ruleset/roundstart/traitor/pre_execute() - var/traitor_scaling_coeff = 10 - max(0,round(mode.threat_level/10)-5) // Above 50 threat level, coeff goes down by 1 for every 10 levels - var/num_traitors = min(round(mode.candidates.len / traitor_scaling_coeff) + 1, candidates.len) + var/num_traitors = antag_cap[indice_pop] * (scaled_times + 1) for (var/i = 1 to num_traitors) var/mob/M = pick_n_take(candidates) assigned += M.mind @@ -55,19 +56,16 @@ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") required_candidates = 2 weight = 4 - cost = 10 + cost = 15 + scaling_cost = 15 requirements = list(40,30,30,20,20,15,15,15,10,10) high_population_requirement = 15 + antag_cap = list(2,2,2,2,2,2,2,2,2,2) // Can pick 3 per team, but rare enough it doesn't matter. var/list/datum/team/brother_team/pre_brother_teams = list() - var/const/team_amount = 2 // Hard limit on brother teams if scaling is turned off var/const/min_team_size = 2 /datum/dynamic_ruleset/roundstart/traitorbro/pre_execute() - var/num_teams = team_amount - var/bsc = CONFIG_GET(number/brother_scaling_coeff) - if(bsc) - num_teams = max(1, round(mode.roundstart_pop_ready / bsc)) - + var/num_teams = (antag_cap[indice_pop]/min_team_size) * (scaled_times + 1) // 1 team per scaling for(var/j = 1 to num_teams) if(candidates.len < min_team_size || candidates.len < required_candidates) break @@ -107,13 +105,15 @@ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") required_candidates = 1 weight = 3 - cost = 30 - requirements = list(80,70,60,50,40,20,20,10,10,10) + cost = 15 + scaling_cost = 15 + requirements = list(70,70,60,50,40,20,20,10,10,10) high_population_requirement = 10 + antag_cap = list(1,1,1,1,1,2,2,2,2,3) var/team_mode_probability = 30 /datum/dynamic_ruleset/roundstart/changeling/pre_execute() - var/num_changelings = min(round(mode.candidates.len / 10) + 1, candidates.len) + var/num_changelings = antag_cap[indice_pop] * (scaled_times + 1) for (var/i = 1 to num_changelings) var/mob/M = pick_n_take(candidates) assigned += M.mind @@ -171,7 +171,7 @@ /datum/dynamic_ruleset/roundstart/wizard/pre_execute() if(GLOB.wizardstart.len == 0) return FALSE - + mode.antags_rolled += 1 var/mob/M = pick_n_take(candidates) if (M) assigned += M.mind @@ -202,22 +202,20 @@ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") required_candidates = 2 weight = 3 - cost = 30 + cost = 35 requirements = list(100,90,80,60,40,30,10,10,10,10) high_population_requirement = 10 - pop_per_requirement = 5 flags = HIGHLANDER_RULESET - var/list/cultist_cap = list(2,2,2,3,3,4,4,4,4,4) + antag_cap = list(2,2,2,3,3,4,4,4,4,4) var/datum/team/cult/main_cult /datum/dynamic_ruleset/roundstart/bloodcult/ready(forced = FALSE) - var/indice_pop = min(cultist_cap.len, round(mode.roundstart_pop_ready/pop_per_requirement)+1) - required_candidates = cultist_cap[indice_pop] + required_candidates = antag_cap[indice_pop] . = ..() /datum/dynamic_ruleset/roundstart/bloodcult/pre_execute() - var/indice_pop = min(cultist_cap.len, round(mode.roundstart_pop_ready/pop_per_requirement)+1) - var/cultists = cultist_cap[indice_pop] + var/cultists = antag_cap[indice_pop] + mode.antags_rolled += cultists for(var/cultists_number = 1 to cultists) if(candidates.len <= 0) break @@ -263,23 +261,22 @@ required_candidates = 5 weight = 3 cost = 40 + pop_per_requirement = 5 requirements = list(90,90,90,80,60,40,30,20,10,10) high_population_requirement = 10 pop_per_requirement = 5 flags = HIGHLANDER_RULESET - var/list/operative_cap = list(2,2,2,3,3,3,4,4,5,5) + antag_cap = list(2,2,2,3,3,3,4,4,5,5) var/datum/team/nuclear/nuke_team /datum/dynamic_ruleset/roundstart/nuclear/ready(forced = FALSE) - var/indice_pop = min(operative_cap.len,round(mode.roundstart_pop_ready/pop_per_requirement)+1) - required_candidates = operative_cap[indice_pop] + required_candidates = antag_cap[indice_pop] . = ..() /datum/dynamic_ruleset/roundstart/nuclear/pre_execute() // If ready() did its job, candidates should have 5 or more members in it - - var/indice_pop = min(operative_cap.len, round(mode.roundstart_pop_ready/5)+1) - var/operatives = operative_cap[indice_pop] + var/operatives = antag_cap[indice_pop] + mode.antags_rolled += operatives for(var/operatives_number = 1 to operatives) if(candidates.len <= 0) break @@ -356,15 +353,17 @@ cost = 35 requirements = list(101,101,70,40,30,20,10,10,10,10) high_population_requirement = 10 + antag_cap = list(3,3,3,3,3,3,3,3,3,3) flags = HIGHLANDER_RULESET // I give up, just there should be enough heads with 35 players... minimum_players = 35 var/datum/team/revolution/revolution - var/finished = 0 + var/finished = FALSE /datum/dynamic_ruleset/roundstart/revs/pre_execute() - var/max_canditates = 3 - for(var/i = 1 to max_canditates) + var/max_candidates = antag_cap[indice_pop] + mode.antags_rolled += max_candidates + for(var/i = 1 to max_candidates) if(candidates.len <= 0) break var/mob/M = pick_n_take(candidates) @@ -374,31 +373,63 @@ return TRUE /datum/dynamic_ruleset/roundstart/revs/execute() + var/success = TRUE revolution = new() for(var/datum/mind/M in assigned) - var/datum/antagonist/rev/head/new_head = new antag_datum() - new_head.give_flash = TRUE - new_head.give_hud = TRUE - new_head.remove_clumsy = TRUE - M.add_antag_datum(new_head,revolution) - revolution.update_objectives() - revolution.update_heads() - SSshuttle.registerHostileEnvironment(src) - return TRUE + if(check_eligible(M)) + var/datum/antagonist/rev/head/new_head = new antag_datum() + new_head.give_flash = TRUE + new_head.give_hud = TRUE + new_head.remove_clumsy = TRUE + M.add_antag_datum(new_head,revolution) + else + assigned -= M + log_game("DYNAMIC: [ruletype] [name] discarded [M.name] from head revolutionary due to ineligibility.") + if(!revolution.members.len) + success = FALSE + log_game("DYNAMIC: [ruletype] [name] failed to get any eligible headrevs. Refunding [cost] threat.") + if(success) + revolution.update_objectives() + revolution.update_heads() + SSshuttle.registerHostileEnvironment(src) + return TRUE + return FALSE + +/datum/dynamic_ruleset/roundstart/revs/clean_up() + qdel(revolution) + ..() /datum/dynamic_ruleset/roundstart/revs/rule_process() if(check_rev_victory()) - finished = 1 - else if(check_heads_victory()) - finished = 2 - return + finished = REVOLUTION_VICTORY + return RULESET_STOP_PROCESSING + else if (check_heads_victory()) + finished = STATION_VICTORY + SSshuttle.clearHostileEnvironment(src) + priority_announce("It appears the mutiny has been quelled. Please return yourself and your incapacitated colleagues to work. \ + We have remotely blacklisted the head revolutionaries from your cloning software to prevent accidental cloning.", null, "attention", null, "Central Command Loyalty Monitoring Division") + + for(var/datum/mind/M in revolution.members) // Remove antag datums and prevents podcloned or exiled headrevs restarting rebellions. + if(M.has_antag_datum(/datum/antagonist/rev/head)) + var/datum/antagonist/rev/head/R = M.has_antag_datum(/datum/antagonist/rev/head) + R.remove_revolutionary(FALSE, "gamemode") + var/mob/living/carbon/C = M.current + if(C.stat == DEAD) + C.makeUncloneable() + if(M.has_antag_datum(/datum/antagonist/rev)) + var/datum/antagonist/rev/R = M.has_antag_datum(/datum/antagonist/rev) + R.remove_revolutionary(FALSE, "gamemode") + return RULESET_STOP_PROCESSING + +/// Checks for revhead loss conditions and other antag datums. +/datum/dynamic_ruleset/roundstart/revs/proc/check_eligible(var/datum/mind/M) + var/turf/T = get_turf(M.current) + if(!considered_afk(M) && considered_alive(M) && is_station_level(T.z) && !M.antag_datums?.len && !HAS_TRAIT(M, TRAIT_MINDSHIELD)) + return TRUE + return FALSE /datum/dynamic_ruleset/roundstart/revs/check_finished() - if(CONFIG_GET(keyed_list/continuous)["revolution"]) - if(finished) - SSshuttle.clearHostileEnvironment(src) - return ..() - if(finished != 0) + if(finished == REVOLUTION_VICTORY) return TRUE else return ..() @@ -418,10 +449,10 @@ return TRUE /datum/dynamic_ruleset/roundstart/revs/round_result() - if(finished == 1) + if(finished == REVOLUTION_VICTORY) SSticker.mode_result = "win - heads killed" SSticker.news_report = REVS_WIN - else if(finished == 2) + else if(finished == STATION_VICTORY) SSticker.mode_result = "loss - rev heads killed" SSticker.news_report = REVS_LOSE @@ -469,6 +500,7 @@ requirements = list(101,101,101,101,101,101,101,101,101,101) high_population_requirement = 101 flags = HIGHLANDER_RULESET + antag_cap = list(2,3,3,4,4,4,4,4,4,4) var/ark_time /datum/dynamic_ruleset/roundstart/clockcult/pre_execute() @@ -481,12 +513,12 @@ for(var/datum/parsed_map/PM in reebes) PM.initTemplateBounds() - var/starter_servants = 4 + var/starter_servants = antag_cap[indice_pop] var/number_players = mode.roundstart_pop_ready if(number_players > 30) number_players -= 30 - starter_servants += round(number_players / 10) - starter_servants = min(starter_servants, 8) + starter_servants += min(round(number_players / 10), 5) + mode.antags_rolled += starter_servants for (var/i in 1 to starter_servants) var/mob/servant = pick_n_take(candidates) assigned += servant.mind @@ -597,17 +629,11 @@ cost = 0 requirements = list(101,101,101,101,101,101,101,101,101,101) high_population_requirement = 101 - var/devil_limit = 4 // Hard limit on devils if scaling is turned off + antag_cap = list(1,1,1,2,2,2,3,3,3,4) /datum/dynamic_ruleset/roundstart/devil/pre_execute() - var/tsc = CONFIG_GET(number/traitor_scaling_coeff) - var/num_devils = 1 - - if(tsc) - num_devils = max(required_candidates, min(round(mode.roundstart_pop_ready / (tsc * 3)) + 2, round(mode.roundstart_pop_ready / (tsc * 1.5)))) - else - num_devils = max(required_candidates, min(mode.roundstart_pop_ready, devil_limit)) - + var/num_devils = antag_cap[indice_pop] + mode.antags_rolled += num_devils for(var/j = 0, j < num_devils, j++) if (!candidates.len) break @@ -662,6 +688,7 @@ /datum/dynamic_ruleset/roundstart/monkey/pre_execute() var/carriers_to_make = max(round(mode.roundstart_pop_ready / players_per_carrier, 1), 1) + mode.antags_rolled += carriers_to_make for(var/j = 0, j < carriers_to_make, j++) if (!candidates.len) diff --git a/code/modules/antagonists/revolution/revolution.dm b/code/modules/antagonists/revolution/revolution.dm index e10d83ffb7..b41731369b 100644 --- a/code/modules/antagonists/revolution/revolution.dm +++ b/code/modules/antagonists/revolution/revolution.dm @@ -207,9 +207,20 @@ owner.current.visible_message("The frame beeps contentedly, purging the hostile memory engram from the MMI before initalizing it.", null, null, null, owner.current) to_chat(owner, "The frame's firmware detects and deletes your neural reprogramming! You remember nothing of your time spent reprogrammed, you can't even remember the names or identities of anyone involved...") +/datum/antagonist/rev/head/farewell() + if((ishuman(owner.current) || ismonkey(owner.current))) + if(owner.current.stat != DEAD) + owner.current.visible_message("[owner.current] looks like [owner.current.p_theyve()] just remembered [owner.current.p_their()] real allegiance!", null, null, null, owner.current) + to_chat(owner, "You have given up your cause of overthrowing the command staff. You are no longer a Head Revolutionary.") + else + to_chat(owner, "The sweet release of death. You are no longer a Head Revolutionary.") + else if(issilicon(owner.current)) + owner.current.visible_message("The frame beeps contentedly, suppressing the disloyal personality traits from the MMI before initalizing it.", null, null, null, owner.current) + to_chat(owner, "The frame's firmware detects and suppresses your unwanted personality traits! You feel more content with the leadership around these parts.") + //blunt trauma deconversions call this through species.dm spec_attacked_by() /datum/antagonist/rev/proc/remove_revolutionary(borged, deconverter) - log_attack("[key_name(owner.current)] has been deconverted from the revolution by [key_name(deconverter)]!") + log_attack("[key_name(owner.current)] has been deconverted from the revolution by [ismob(deconverter) ? key_name(deconverter) : deconverter]!") if(borged) message_admins("[ADMIN_LOOKUPFLW(owner.current)] has been borged while being a [name]") owner.special_role = null @@ -219,9 +230,8 @@ owner.remove_antag_datum(type) /datum/antagonist/rev/head/remove_revolutionary(borged,deconverter) - if(!borged) - return - . = ..() + if(borged || deconverter == "gamemode") + . = ..() /datum/antagonist/rev/head/equip_rev() var/mob/living/carbon/human/H = owner.current @@ -253,6 +263,8 @@ /datum/team/revolution name = "Revolution" var/max_headrevs = 3 + var/list/ex_headrevs = list() // Dynamic removes revs on loss, used to keep a list for the roundend report. + var/list/ex_revs = list() /datum/team/revolution/proc/update_objectives(initial = FALSE) var/untracked_heads = SSjob.get_all_heads() @@ -295,9 +307,12 @@ addtimer(CALLBACK(src,.proc/update_heads),HEAD_UPDATE_PERIOD,TIMER_UNIQUE) +/datum/team/revolution/proc/save_members() + ex_headrevs = get_antag_minds(/datum/antagonist/rev/head, TRUE) + ex_revs = get_antag_minds(/datum/antagonist/rev, TRUE) /datum/team/revolution/roundend_report() - if(!members.len) + if(!members.len && !ex_headrevs.len) return var/list/result = list() @@ -317,8 +332,17 @@ var/list/targets = list() - var/list/datum/mind/headrevs = get_antag_minds(/datum/antagonist/rev/head) - var/list/datum/mind/revs = get_antag_minds(/datum/antagonist/rev,TRUE) + var/list/datum/mind/headrevs + var/list/datum/mind/revs + if(ex_headrevs.len) + headrevs = ex_headrevs + else + headrevs = get_antag_minds(/datum/antagonist/rev/head, TRUE) + + if(ex_revs.len) + revs = ex_revs + else + revs = get_antag_minds(/datum/antagonist/rev, TRUE) if(headrevs.len) var/list/headrev_part = list() headrev_part += "The head revolutionaries were:" diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index b0c3f61eec..2f13af95db 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -69,11 +69,16 @@ /mob/living/carbon/human/proc/makeSkeleton() ADD_TRAIT(src, TRAIT_DISFIGURED, TRAIT_GENERIC) set_species(/datum/species/skeleton) - return 1 + return TRUE /mob/living/carbon/proc/Drain() become_husk(CHANGELING_DRAIN) ADD_TRAIT(src, TRAIT_NOCLONE, CHANGELING_DRAIN) blood_volume = 0 - return 1 + return TRUE + +/mob/living/carbon/proc/makeUncloneable() + ADD_TRAIT(src, TRAIT_NOCLONE, MADE_UNCLONEABLE) + blood_volume = 0 + return TRUE \ No newline at end of file