Merge pull request #13602 from Putnam3145/better_dynamic

Adds a new "minor antag" roundstart to dynamic
This commit is contained in:
silicons
2020-11-07 00:18:51 -07:00
committed by GitHub
9 changed files with 365 additions and 41 deletions
+7 -5
View File
@@ -1,7 +1,7 @@
#define CURRENT_LIVING_PLAYERS 1
#define CURRENT_LIVING_ANTAGS 2
#define CURRENT_DEAD_PLAYERS 3
#define CURRENT_OBSERVERS 4
#define CURRENT_OBSERVERS 4
#define NO_ASSASSIN (1<<0)
#define WAROPS_ALWAYS_ALLOWED (1<<1)
@@ -9,9 +9,11 @@
#define FORCE_IF_WON (1<<3)
#define USE_PREV_ROUND_WEIGHTS (1<<4)
#define ONLY_RULESET (1<<0)
#define HIGHLANDER_RULESET (1<<1)
#define TRAITOR_RULESET (1<<2)
#define MINOR_RULESET (1<<3)
#define ONLY_RULESET (1<<0)
#define HIGHLANDER_RULESET (1<<1)
#define TRAITOR_RULESET (1<<2)
#define MINOR_RULESET (1<<3)
#define FAKE_ANTAG_RULESET (1<<4)
#define ALWAYS_MAX_WEIGHT_RULESET (1<<5)
#define RULESET_STOP_PROCESSING 1
+53 -16
View File
@@ -72,6 +72,8 @@ GLOBAL_VAR_INIT(dynamic_forced_storyteller, null)
var/list/threat_log_verbose = list()
/// List of roundstart rules used for selecting the rules.
var/list/roundstart_rules = list()
/// List of minor roundstart rules used for selecting the rules.
var/list/minor_rules = list()
/// List of latejoin rules used for selecting the rules.
var/list/latejoin_rules = list()
/// List of midround rules used for selecting the rules.
@@ -121,10 +123,12 @@ GLOBAL_VAR_INIT(dynamic_forced_storyteller, null)
var/pop_last_updated = 0
/// How many percent of the rounds are more peaceful.
var/peaceful_percentage = 50
/// If a highlander executed.
/// If a highlander executed. No other highlander rulesets will be run.
var/highlander_executed = FALSE
/// If a only ruleset has been executed.
/// If a only ruleset has been executed. No other rulesets will be run.
var/only_ruleset_executed = FALSE
/// If the first picked ruleset was a minor ruleset. Minor antagonists will be weighted higher.
var/minor_ruleset_start = 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
// Arbitrary threat addition, for fudging purposes.
@@ -372,6 +376,8 @@ GLOBAL_VAR_INIT(dynamic_forced_storyteller, null)
if(ruleset.name == "")
continue
switch(ruleset.ruletype)
if("Minor")
minor_rules += ruleset
if("Roundstart")
roundstart_rules += ruleset
if ("Latejoin")
@@ -396,21 +402,47 @@ GLOBAL_VAR_INIT(dynamic_forced_storyteller, null)
rigged_roundstart()
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]")
if(minor_ruleset_start)
log_game("DYNAMIC: Starting a minor ruleset round.")
else
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
/datum/game_mode/dynamic/post_setup(report)
update_playercounts()
for(var/datum/dynamic_ruleset/roundstart/rule in executed_rules)
addtimer(CALLBACK(src, /datum/game_mode/dynamic/.proc/execute_roundstart_rule, rule), rule.delay)
if(minor_ruleset_start)
addtimer(CALLBACK(src, /datum/game_mode/dynamic/.proc/minor_roundstart),rand(1 MINUTES,5 MINUTES))
else
for(var/datum/dynamic_ruleset/roundstart/rule in executed_rules)
addtimer(CALLBACK(src, /datum/game_mode/dynamic/.proc/execute_roundstart_rule, rule), rule.delay)
..()
/datum/game_mode/dynamic/proc/minor_roundstart()
message_admins("Dynamic beginning minor antag roundstart rolls.")
var/list/potential_minor_rulesets = storyteller.minor_rule_draft()
var/iterations = 0
var/num_rulesets_executed = 0
while(threat < threat_level && potential_minor_rulesets.len && (!CHECK_TICK || iterations < 100))
var/datum/dynamic_ruleset/minor/rule = pickweight(potential_minor_rulesets)
rule.candidates = current_players[CURRENT_LIVING_PLAYERS].Copy()
rule.trim_candidates()
if(!check_blocking(rule.blocking_rules, executed_rules) && rule.ready())
rule.execute()
executed_rules |= rule
log_threat("[rule.ruletype] - <b>[rule.name]</b> [rule.cost] threat", verbose = TRUE)
num_rulesets_executed++
else
potential_minor_rulesets -= rule
update_playercounts()
iterations++
message_admins("Minor antag roundstart rolls completed, with [num_rulesets_executed] antags or antag teams made.")
log_game("DYNAMIC: Minor antag roundstart made [num_rulesets_executed] antags or antag teams.")
/// A simple roundstart proc used when dynamic_forced_roundstart_ruleset has rules in it.
/datum/game_mode/dynamic/proc/rigged_roundstart()
message_admins("[GLOB.dynamic_forced_roundstart_ruleset.len] rulesets being forced. Will now attempt to draft players for them.")
@@ -429,12 +461,17 @@ GLOBAL_VAR_INIT(dynamic_forced_storyteller, null)
if (GLOB.dynamic_forced_extended)
log_game("DYNAMIC: Starting a round of forced extended.")
return TRUE
if(prob(storyteller.minor_start_chance()))
minor_ruleset_start = TRUE
message_admins("Dynamic has initialized a minor antag start. Antags will be assigned in 1-5 minutes.")
log_game("DYNAMIC: Minor start initialized.")
return TRUE
var/list/drafted_rules = storyteller.roundstart_draft()
if(!drafted_rules.len)
message_admins("Not enough threat level for roundstart antags!")
log_game("DYNAMIC: Not enough threat level for roundstart antags!")
midround_injection_cooldown = round((midround_injection_cooldown + world.time) / 2, 1)
latejoin_injection_cooldown = round((latejoin_injection_cooldown + world.time) / 2, 1)
message_admins("No roundstart antags drafted! Falling back to minor ruleset start.")
log_game("DYNAMIC: No roundstart antags drafted! Falling back to minor ruleset start.")
minor_ruleset_start = TRUE
return FALSE
var/indice_pop = min(10,round(roundstart_pop_ready/pop_per_requirement)+1)
extra_rulesets_amount = 0
if (GLOB.dynamic_classic_secret)
@@ -468,8 +505,9 @@ GLOBAL_VAR_INIT(dynamic_forced_storyteller, null)
else
if(threat_level >= 50)
message_admins("DYNAMIC: Picking first roundstart ruleset failed. You should report this.")
message_admins("DYNAMIC: Picking first roundstart ruleset failed. You should report this. Falling back to minor antag start.")
log_game("DYNAMIC: Picking first roundstart ruleset failed. drafted_rules.len = [drafted_rules.len] and threat = [threat]/[threat_level]")
minor_ruleset_start = TRUE
return FALSE
return TRUE
@@ -513,7 +551,6 @@ GLOBAL_VAR_INIT(dynamic_forced_storyteller, null)
drafted_rules -= starting_rule
starting_rule.trim_candidates()
starting_rule.scale_up(extra_rulesets_amount, threat_level-added_threat)
if (starting_rule.pre_execute())
log_threat("[starting_rule.ruletype] - <b>[starting_rule.name]</b> [starting_rule.cost + starting_rule.scaled_times * starting_rule.scaling_cost] threat", verbose = TRUE)
if(starting_rule.flags & HIGHLANDER_RULESET)
@@ -50,8 +50,12 @@
/// Used for the roundend report
var/total_cost = 0
/// A flag that determines how the ruleset is handled
/// ONLY_RULESET are rulesets that prevent ALL other rulesets from rolling.
/// HIGHLANDER_RULESET are rulesets can end the round.
/// TRAITOR_RULESET and MINOR_RULESET can't end the round and have no difference right now.
/// TRAITOR_RULESET are the "default" ruleset--they should always be addable to a round, if the round type allows antags and dynamic thinks there should be another.
/// MINOR_RULESET is for rulesets whose antags can have multiple instances without causing too much issue. As roundstarts, they have their weights reduced based on the storyteller's minor-antag-round chance.
/// FAKE_ANTAG_RULESET is for rulesets whose antags aren't actually antagonistic--essentially just flavor meant to spice the round up.
/// ALWAYS_MAX_WEIGHT_RULESET means that the ruleset doesn't have its weight reduced based on recency.
var/flags = 0
/// Pop range per requirement. If zero defaults to mode's pop_per_requirement.
var/pop_per_requirement = 0
@@ -82,9 +86,6 @@
var/delay = 0
/// List of tags for use in storytellers.
var/list/property_weights = list()
/// Whether or not recent-round weight values are taken into account for this ruleset.
/// Weight reduction uses the same values as secret's recent-round mode weight reduction.
var/always_max_weight = FALSE
/// Weight reduction by recent-rounds. Saved on new.
var/weight_mult = 1
@@ -100,7 +101,7 @@
var/high_population_requirements = CONFIG_GET(keyed_list/dynamic_high_population_requirement)
var/list/repeated_mode_adjust = CONFIG_GET(number_list/repeated_mode_adjust)
if(config_tag in weights)
if(!always_max_weight && SSpersistence.saved_dynamic_rules.len == 3 && repeated_mode_adjust.len == 3)
if(!(flags & ALWAYS_MAX_WEIGHT_RULESET) && SSpersistence.saved_dynamic_rules.len == 3 && repeated_mode_adjust.len == 3)
var/saved_dynamic_rules = SSpersistence.saved_dynamic_rules
for(var/i in 1 to 3)
if(config_tag in saved_dynamic_rules[i])
@@ -119,6 +120,9 @@
/datum/dynamic_ruleset/roundstart // One or more of those drafted at roundstart
ruletype = "Roundstart"
/datum/dynamic_ruleset/minor // drafted at roundstart in minor rounds, one antag at a time, for a "mixed" round
ruletype = "Minor"
// Can be drafted when a player joins the server
/datum/dynamic_ruleset/latejoin
ruletype = "Latejoin"
@@ -175,7 +179,7 @@
/// This is called if persistent variable is true everytime SSTicker ticks.
/datum/dynamic_ruleset/proc/rule_process()
return TRUE
/// 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
@@ -70,9 +70,8 @@
requirements = list(40,30,20,15,15,15,15,15,15,15)
high_population_requirement = 15
repeatable = TRUE
flags = TRAITOR_RULESET
flags = TRAITOR_RULESET | MINOR_RULESET | ALWAYS_MAX_WEIGHT_RULESET
property_weights = list("story_potential" = 2, "trust" = -1, "extended" = 1)
always_max_weight = TRUE
//////////////////////////////////////////////
// //
@@ -207,6 +206,7 @@
weight = 4
cost = 25
requirements = list(60,60,60,55,50,50,50,50,50,50)
flags = MINOR_RULESET
high_population_requirement = 50
property_weights = list("story_potential" = 1, "trust" = -1, "chaos" = 2, "extended" = -1, "valid" = 2)
repeatable = TRUE
@@ -229,6 +229,7 @@
cost = 10
property_weights = list("story_potential" = 2, "extended" = 2, "trust" = -2, "valid" = 1)
requirements = list(70,65,60,55,50,45,40,35,30,30)
flags = MINOR_RULESET
high_population_requirement = 30
repeatable = TRUE
@@ -258,6 +259,7 @@
required_candidates = 1
weight = 3
cost = 15
flags = MINOR_RULESET
requirements = list(101,101,101,101,101,101,101,101,101,101)
property_weights = list("trust" = -2, "valid" = 2)
high_population_requirement = 101
@@ -281,5 +283,5 @@
requirements = list(10,10,10,10,10,10,10,10,10,10)
high_population_requirement = 10
repeatable = TRUE
flags = TRAITOR_RULESET | MINOR_RULESET
property_weights = list("story_potential" = 2, "trust" = -1, "extended" = 2)
flags = TRAITOR_RULESET | MINOR_RULESET | FAKE_ANTAG_RULESET
property_weights = list("story_potential" = 1, "trust" = -1, "extended" = 2)
@@ -207,9 +207,8 @@
requirements = list(30,25,20,15,15,15,15,15,15,15)
repeatable = TRUE
high_population_requirement = 15
flags = TRAITOR_RULESET
flags = TRAITOR_RULESET | MINOR_RULESET | ALWAYS_MAX_WEIGHT_RULESET
property_weights = list("story_potential" = 2, "trust" = -1, "extended" = 1)
always_max_weight = TRUE
/datum/dynamic_ruleset/midround/autotraitor/acceptable(population = 0, threat = 0)
var/player_count = mode.current_players[CURRENT_LIVING_PLAYERS].len
@@ -494,6 +493,7 @@
required_candidates = 1
weight = 3
cost = 10
flags = MINOR_RULESET
requirements = list(101,101,101,70,50,40,20,15,15,15)
high_population_requirement = 50
repeatable_weight_decrease = 2
@@ -630,6 +630,7 @@
required_candidates = 1
weight = 4
cost = 15
flags = MINOR_RULESET
requirements = list(101,101,101,90,80,70,60,50,40,30)
high_population_requirement = 30
property_weights = list("story_potential" = 1, "extended" = -2, "valid" = 2)
@@ -0,0 +1,212 @@
//////////////////////////////////////////////
// //
// SYNDICATE TRAITORS //
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/minor/traitor
name = "Traitors"
config_tag = "traitor" // these having identical config tags to the roundstart modes is 100% intentional, so that config edits are simpler
persistent = TRUE
antag_flag = ROLE_TRAITOR
antag_datum = /datum/antagonist/traitor/
minimum_required_age = 0
protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster", "Cyborg")
restricted_roles = list("Cyborg", "AI")
required_candidates = 1
weight = 5
flags = TRAITOR_RULESET | ALWAYS_MAX_WEIGHT_RULESET
cost = 10 // Avoid raising traitor threat above 10, as it is the default low cost ruleset.
requirements = list(50,50,50,50,50,50,50,50,50,50)
high_population_requirement = 40
property_weights = list("story_potential" = 2, "trust" = -1, "extended" = 1, "valid" = 1)
/datum/dynamic_ruleset/minor/traitor/execute()
var/mob/M = pick_n_take(candidates)
assigned += M
var/datum/antagonist/traitor/newTraitor = new
M.mind.add_antag_datum(newTraitor)
log_admin("[M] was made into a traitor by dynamic.")
message_admins("[M] was made into a traitor by dynamic.")
return TRUE
//////////////////////////////////////////
// //
// BLOOD BROTHERS //
// //
//////////////////////////////////////////
/datum/dynamic_ruleset/minor/traitorbro
name = "Blood Brothers"
config_tag = "traitorbro"
antag_flag = ROLE_BROTHER
antag_datum = /datum/antagonist/brother
restricted_roles = list("AI", "Cyborg")
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
requirements = list(101,101,101,101,101,101,101,101,101,101)
high_population_requirement = 101
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.
property_weights = list("story_potential" = 1, "trust" = -1, "extended" = 1, "valid" = 1)
var/list/datum/team/brother_team/pre_brother_teams = list()
var/const/min_team_size = 2
/datum/dynamic_ruleset/minor/traitorbro/execute()
if(candidates.len < min_team_size || candidates.len < required_candidates)
return FALSE
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_n_take(candidates)
assigned += bro.mind
team.add_member(bro.mind)
bro.mind.special_role = "brother"
bro.mind.restricted_roles = restricted_roles
team.pick_meeting_area()
team.forge_brother_objectives()
for(var/datum/mind/M in team.members)
M.add_antag_datum(/datum/antagonist/brother, team)
team.update_name()
mode.brother_teams += team
//////////////////////////////////////////////
// //
// CHANGELINGS //
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/minor/changeling
name = "Changelings"
config_tag = "changeling"
antag_flag = ROLE_CHANGELING
antag_datum = /datum/antagonist/changeling
restricted_roles = list("AI", "Cyborg")
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 = 15
scaling_cost = 15
requirements = list(101,101,101,101,101,101,101,101,101,101)
property_weights = list("trust" = -2, "valid" = 2)
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/minor/changeling/execute()
var/mob/M = pick_n_take(candidates)
assigned += M.mind
M.mind.restricted_roles = restricted_roles
M.mind.special_role = ROLE_CHANGELING
var/datum/antagonist/changeling/new_antag = new antag_datum()
M.mind.add_antag_datum(new_antag)
return TRUE
//////////////////////////////////////////////
// //
// ELDRITCH CULT //
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/minor/heretics
name = "Heretic"
antag_flag = "heretic"
antag_datum = /datum/antagonist/heretic
protected_roles = list("Prisoner","Security Officer", "Warden", "Detective", "Head of Security", "Captain")
restricted_roles = list("AI", "Cyborg")
required_candidates = 1
weight = 3
cost = 25
scaling_cost = 15
requirements = list(60,60,60,55,50,50,50,50,50,50)
property_weights = list("story_potential" = 1, "trust" = -1, "chaos" = 2, "extended" = -1, "valid" = 2)
antag_cap = list(1,1,1,1,2,2,2,2,3,3)
high_population_requirement = 50
/datum/dynamic_ruleset/minor/heretics/pre_execute()
var/mob/picked_candidate = pick_n_take(candidates)
assigned += picked_candidate.mind
picked_candidate.mind.restricted_roles = restricted_roles
picked_candidate.mind.special_role = ROLE_HERETIC
var/datum/antagonist/heretic/new_antag = new antag_datum()
picked_candidate.mind.add_antag_datum(new_antag)
return TRUE
//////////////////////////////////////////////
// //
// DEVIL //
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/minor/devil
name = "Devil"
config_tag = "devil"
antag_flag = ROLE_DEVIL
antag_datum = /datum/antagonist/devil
restricted_roles = list("Lawyer", "Curator", "Chaplain", "Head of Security", "Captain", "AI")
required_candidates = 1
weight = 3
cost = 0
requirements = list(101,101,101,101,101,101,101,101,101,101)
high_population_requirement = 101
antag_cap = list(1,1,1,2,2,2,3,3,3,4)
property_weights = list("extended" = 1)
/datum/dynamic_ruleset/minor/devil/pre_execute()
var/mob/devil = pick_n_take(candidates)
assigned += devil.mind
devil.mind.special_role = ROLE_DEVIL
devil.mind.restricted_roles = restricted_roles
log_game("[key_name(devil)] has been selected as a devil")
add_devil(devil, ascendable = TRUE)
add_devil_objectives(devil.mind,2)
return TRUE
/datum/dynamic_ruleset/minor/devil/proc/add_devil_objectives(datum/mind/devil_mind, quantity)
var/list/validtypes = list(/datum/objective/devil/soulquantity, /datum/objective/devil/soulquality, /datum/objective/devil/sintouch, /datum/objective/devil/buy_target)
var/datum/antagonist/devil/D = devil_mind.has_antag_datum(/datum/antagonist/devil)
for(var/i = 1 to quantity)
var/type = pick(validtypes)
var/datum/objective/devil/objective = new type(null)
objective.owner = devil_mind
D.objectives += objective
if(!istype(objective, /datum/objective/devil/buy_target))
validtypes -= type
else
objective.find_target()
//////////////////////////////////////////////
// //
// BLOODSUCKERS //
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/minor/bloodsucker
name = "Bloodsuckers"
config_tag = "bloodsucker"
antag_flag = ROLE_BLOODSUCKER
antag_datum = ANTAG_DATUM_BLOODSUCKER
minimum_required_age = 0
protected_roles = list("Chaplain", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
restricted_roles = list("Cyborg", "AI")
required_candidates = 1
weight = 2
cost = 15
scaling_cost = 10
property_weights = list("story_potential" = 1, "extended" = 1, "trust" = -2, "valid" = 1)
requirements = list(70,65,60,55,50,50,50,50,50,50)
high_population_requirement = 50
/datum/dynamic_ruleset/minor/bloodsucker/execute()
var/mob/M = pick_n_take(candidates)
assigned += M.mind
M.mind.special_role = ROLE_BLOODSUCKER
M.mind.restricted_roles = restricted_roles
mode.check_start_sunlight()
if(mode.make_bloodsucker(M.mind))
mode.bloodsuckers += M.mind
return TRUE
@@ -15,6 +15,7 @@
protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster", "Cyborg")
restricted_roles = list("Cyborg", "AI")
required_candidates = 1
flags = TRAITOR_RULESET | MINOR_RULESET | ALWAYS_MAX_WEIGHT_RULESET
weight = 5
cost = 10 // Avoid raising traitor threat above 10, as it is the default low cost ruleset.
scaling_cost = 10
@@ -22,7 +23,6 @@
high_population_requirement = 40
antag_cap = list(1,1,1,1,2,2,2,2,3,3)
property_weights = list("story_potential" = 2, "trust" = -1, "extended" = 1, "valid" = 1)
always_max_weight = TRUE
var/autotraitor_cooldown = 450 // 15 minutes (ticks once per 2 sec)
/datum/dynamic_ruleset/roundstart/traitor/pre_execute()
@@ -57,6 +57,7 @@
restricted_roles = list("AI", "Cyborg")
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
flags = MINOR_RULESET
weight = 4
cost = 10
requirements = list(101,101,101,101,101,101,101,101,101,101)
@@ -106,6 +107,7 @@
restricted_roles = list("AI", "Cyborg")
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
flags = MINOR_RULESET
weight = 3
cost = 15
scaling_cost = 15
@@ -156,6 +158,7 @@
protected_roles = list("Prisoner","Security Officer", "Warden", "Detective", "Head of Security", "Captain")
restricted_roles = list("AI", "Cyborg")
required_candidates = 1
flags = MINOR_RULESET
weight = 3
cost = 25
scaling_cost = 15
@@ -694,6 +697,7 @@
antag_datum = /datum/antagonist/devil
restricted_roles = list("Lawyer", "Curator", "Chaplain", "Head of Security", "Captain", "AI")
required_candidates = 1
flags = MINOR_RULESET
weight = 3
cost = 0
requirements = list(101,101,101,101,101,101,101,101,101,101)
@@ -850,6 +854,7 @@
protected_roles = list("Chaplain", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
restricted_roles = list("Cyborg", "AI")
required_candidates = 1
flags = MINOR_RULESET
weight = 2
cost = 15
scaling_cost = 10
@@ -32,6 +32,9 @@ Property weights are added to the config weight of the ruleset. They are:
"conversion" -- Basically a bool. Conversion antags, well, convert. It's in its own class 'cause people kinda hate conversion.
*/
/datum/dynamic_storyteller/proc/minor_start_chance()
return clamp(60 - mode.threat_level,0,100) // by default higher threat = lower chance of minor round
/datum/dynamic_storyteller/proc/start_injection_cooldowns()
var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_first_latejoin_delay_max + GLOB.dynamic_first_latejoin_delay_min)
mode.latejoin_injection_cooldown = round(clamp(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_first_latejoin_delay_min, GLOB.dynamic_first_latejoin_delay_max)) + world.time
@@ -104,8 +107,27 @@ Property weights are added to the config weight of the ruleset. They are:
/datum/dynamic_storyteller/proc/roundstart_draft()
var/list/drafted_rules = list()
var/minor_round_weight_mult = (100-minor_start_chance()) / 100
for (var/datum/dynamic_ruleset/roundstart/rule in mode.roundstart_rules)
if (rule.acceptable(mode.roundstart_pop_ready, mode.threat_level)) // If we got the population and threat required
rule.candidates = mode.candidates.Copy()
rule.trim_candidates()
if (rule.ready() && rule.candidates.len > 0)
var/property_weight = 0
for(var/property in property_weights)
if(property in rule.property_weights) // just treat it as 0 if it's not in there
property_weight += rule.property_weights[property] * property_weights[property]
var/calced_weight = (rule.get_weight() + property_weight) * rule.weight_mult
if(CHECK_BITFIELD(rule.flags, MINOR_RULESET))
calced_weight *= minor_round_weight_mult
if(calced_weight > 0) // negatives in the list might cause problems
drafted_rules[rule] = calced_weight
return drafted_rules
/datum/dynamic_storyteller/proc/minor_rule_draft()
var/list/drafted_rules = list()
for (var/datum/dynamic_ruleset/minor/rule in mode.minor_rules)
if (rule.acceptable(mode.current_players[CURRENT_LIVING_PLAYERS].len, mode.threat_level))
rule.candidates = mode.candidates.Copy()
rule.trim_candidates()
if (rule.ready() && rule.candidates.len > 0)
@@ -124,7 +146,7 @@ Property weights are added to the config weight of the ruleset. They are:
// if there are antags OR the rule is an antag rule, antag_acceptable will be true.
if (rule.acceptable(mode.current_players[CURRENT_LIVING_PLAYERS].len, mode.threat_level))
// Classic secret : only autotraitor/minor roles
if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET)))
continue
rule.trim_candidates()
if (rule.ready())
@@ -133,7 +155,7 @@ Property weights are added to the config weight of the ruleset. They are:
if(property in rule.property_weights) // just treat it as 0 if it's not in there
property_weight += rule.property_weights[property] * property_weights[property]
var/threat_weight = 1
if(!(rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)) // makes the traitor rulesets always possible anyway
if(!(rule.flags & TRAITOR_RULESET)) // makes the traitor rulesets always possible anyway
var/cost_difference = rule.cost-(mode.threat_level-mode.threat)
/* Basically, the closer the cost is to the current threat-level-away-from-threat, the more likely it is to
pick this particular ruleset.
@@ -157,7 +179,7 @@ Property weights are added to the config weight of the ruleset. They are:
for (var/datum/dynamic_ruleset/latejoin/rule in mode.latejoin_rules)
if (rule.acceptable(mode.current_players[CURRENT_LIVING_PLAYERS].len, mode.threat_level - mode.threat))
// Classic secret : only autotraitor/minor roles
if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET)))
continue
// No stacking : only one round-ender, unless threat level > stacking_limit.
if (mode.threat_level > GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
@@ -172,7 +194,7 @@ Property weights are added to the config weight of the ruleset. They are:
if(property in rule.property_weights)
property_weight += rule.property_weights[property] * property_weights[property]
var/threat_weight = 1
if(!(rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET))
if(!(rule.flags & TRAITOR_RULESET))
var/cost_difference = rule.cost-(mode.threat_level-mode.threat)
threat_weight = 1-abs(1-(LOGISTIC_FUNCTION(2,0.05,abs(cost_difference),0)))
if(cost_difference > 0)
@@ -195,6 +217,9 @@ Property weights are added to the config weight of the ruleset. They are:
min_players = 30
var/refund_cooldown = 0
/datum/dynamic_storyteller/chaotic/minor_start_chance()
return 0
/datum/dynamic_storyteller/chaotic/do_process()
if(refund_cooldown < world.time)
mode.create_threat(20)
@@ -215,6 +240,9 @@ Property weights are added to the config weight of the ruleset. They are:
flags = WAROPS_ALWAYS_ALLOWED | USE_PREV_ROUND_WEIGHTS
property_weights = list("valid" = 3, "trust" = 5)
/datum/dynamic_storyteller/chaotic/minor_start_chance()
return 0
/datum/dynamic_storyteller/team/should_inject_antag(dry_run = FALSE)
return (mode.current_players[CURRENT_LIVING_ANTAGS].len ? FALSE : ..())
@@ -228,6 +256,9 @@ Property weights are added to the config weight of the ruleset. They are:
flags = WAROPS_ALWAYS_ALLOWED
property_weights = list("valid" = 1, "conversion" = 20)
/datum/dynamic_storyteller/chaotic/minor_start_chance()
return 0
/datum/dynamic_storyteller/random
name = "Random"
config_tag = "random"
@@ -244,6 +275,9 @@ Property weights are added to the config weight of the ruleset. They are:
/datum/dynamic_storyteller/random/should_inject_antag()
return prob(50)
/datum/dynamic_storyteller/chaotic/minor_start_chance()
return 20
/datum/dynamic_storyteller/random/roundstart_draft()
var/list/drafted_rules = list()
for (var/datum/dynamic_ruleset/roundstart/rule in mode.roundstart_rules)
@@ -254,12 +288,22 @@ Property weights are added to the config weight of the ruleset. They are:
drafted_rules[rule] = 1
return drafted_rules
/datum/dynamic_storyteller/random/minor_rule_draft()
var/list/drafted_rules = list()
for (var/datum/dynamic_ruleset/minor/rule in mode.minor_rules)
if (rule.acceptable(mode.current_players[CURRENT_LIVING_PLAYERS].len, mode.threat_level))
rule.candidates = mode.candidates.Copy()
rule.trim_candidates()
if (rule.ready() && rule.candidates.len > 0)
drafted_rules[rule] = 1
return drafted_rules
/datum/dynamic_storyteller/random/midround_draft()
var/list/drafted_rules = list()
for (var/datum/dynamic_ruleset/midround/rule in mode.midround_rules)
if (rule.acceptable(mode.current_players[CURRENT_LIVING_PLAYERS].len, mode.threat_level))
// Classic secret : only autotraitor/minor roles
if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET)))
continue
rule.trim_candidates()
if (rule.ready())
@@ -271,7 +315,7 @@ Property weights are added to the config weight of the ruleset. They are:
for (var/datum/dynamic_ruleset/latejoin/rule in mode.latejoin_rules)
if (rule.acceptable(mode.current_players[CURRENT_LIVING_PLAYERS].len, mode.threat_level))
// Classic secret : only autotraitor/minor roles
if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET)))
continue
// No stacking : only one round-ender, unless threat level > stacking_limit.
if (mode.threat_level > GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
@@ -286,7 +330,7 @@ Property weights are added to the config weight of the ruleset. They are:
/datum/dynamic_storyteller/story
name = "Story"
config_tag = "story"
desc = "Antags with options for loadouts and gimmicks. Traitor, wizard, nukies. Has a buildup-climax-falling action threat curve."
desc = "Antags with options for loadouts and gimmicks. Traitor, wizard, nukies."
weight = 2
curve_width = 2
flags = USE_PREV_ROUND_WEIGHTS
@@ -308,6 +352,19 @@ Property weights are added to the config weight of the ruleset. They are:
flags = USE_PREV_ROUND_WEIGHTS
property_weights = list("trust" = -2)
/datum/dynamic_storyteller/intrigue/minor_start_chance()
return 100 - mode.threat_level
/datum/dynamic_storyteller/grabbag
name = "Grab Bag"
config_tag = "grabbag"
desc = "Crew antags (e.g. traitor, changeling, bloodsucker, heretic) only, all mixed together."
weight = 2
flags = USE_PREF_WEIGHTS | USE_PREV_ROUND_WEIGHTS
/datum/dynamic_storyteller/grabbag/minor_start_chance()
return 100
/datum/dynamic_storyteller/liteextended
name = "Calm"
config_tag = "calm"
@@ -319,6 +376,9 @@ Property weights are added to the config weight of the ruleset. They are:
dead_player_weight = 5
property_weights = list("extended" = 2, "chaos" = -1, "valid" = -1, "conversion" = -10)
/datum/dynamic_storyteller/liteextended/minor_start_chance()
return 100
/datum/dynamic_storyteller/no_antag
name = "Extended"
config_tag = "semiextended"