mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-02-04 05:18:12 +00:00
## About The Previous Pull Request
#85308 reverted by #85929

~~Causes the round to not start when a player isn't eligible for any
jobs at a specific priority level due to runtimes trying to `pick()`
from an empty list aborting the entire job assignment stack.~~
(Fixed???? by
e0e9f2f430)
Maybe we should test merge this for a mo just to make sure no more
cheeky runtimes pop up before merging.
## About The Pull Request
This PR does a couple of minor things:
Makes the job debug logging a bit easier to follow.
Minorly brings some SSjob code up to code standards, converting proc
names to snake_case and doing some otherm is cleanup.
Refactored some stuff into different procs, updated some comments.
And some major things:
Changes the job assignment logic.
Old behaviour
> Assign dynamic priority roles
> Force one Head of Staff (if possible)
> Assign all AIs
> Assign overflow roles (bugged in 2 ways)
> Shuffle the available jobs list once, at the start of the random job
assignment loop
> Pick and assign random jobs for random players from High prefs down,
with a priority on Head of Staff roles
> Handle everyone that couldn't be assigned a random job
New behaviour
> Assign dynamic priority roles
> Assign all Head of Staff roles to players with High prefs
> If no Head of Staff was made in the above way, force one Head of Staff
(if possible)
> Assign all AIs
> Assign overflow roles (fixed)
> Prioritise and fill unfilled head roles at each job priority pref
level, from High prefs down.
> Build a list of all jobs that each unassigned player could be eligible
for at the above pref level.
> Pick a job from that list at random and assign it to the player.
> Handle everyone that couldn't be assigned a random job.
In reality there should be little impact on overall job assignment, the
code changes read more as semantics. For example, the priority check for
filling Head slots will have the same candidate pool in both old and new
versions, but in the new version we're more clearly saying that Heads
are important and we want to prioritise filling them for the sake of
round progression even though the outcome in new and old is the same.
A key change will lead to an increase in assistants - Overflow fixes.
Currently the code block to do early assignments to the Overflow role
doesn't work - or works but not as you'd expect. The idea was is that
because enabling the Overflow role in the prefs menu is an On/Off toggle
that sets the job to High priority when enabled and prevents any other
High priority pref, players that have the Overflow role enabled will
**always** get it. It's their highest priority job with infinite slots.
So we do a pass right at the start to give everyone with the Overflow
role enabled that role and save us wasting time later on in random job
code giving them that same role but with more work.
The problem is the code for this only assigns the Overflow role to
people with it set to Low priority in their prefs, resulting in log
readouts like:
```
[2024-07-27 09:49:43.469] DEBUG-JOB: DO, Running Overflow Check 1
[2024-07-27 09:49:43.469] DEBUG-JOB: Running FOC, Job: /datum/job/assistant, Level: Low Priority
[2024-07-27 09:49:43.472] DEBUG-JOB: FOC player job enabled at wrong level, Player: Radioprague, TheirLevel: Medium Priority, ReqLevel: Low Priority
[2024-07-27 09:49:43.472] DEBUG-JOB: FOC player job enabled at wrong level, Player: Caluan, TheirLevel: High Priority, ReqLevel: Low Priority
[2024-07-27 09:49:43.473] DEBUG-JOB: FOC player job enabled at wrong level, Player: Caractaser, TheirLevel: High Priority, ReqLevel: Low Priority
[2024-07-27 09:49:43.473] DEBUG-JOB: FOC player job enabled at wrong level, Player: Apsua, TheirLevel: High Priority, ReqLevel: Low Priority
[2024-07-27 09:49:43.475] DEBUG-JOB: FOC player job enabled at wrong level, Player: Bebrus2, TheirLevel: Medium Priority, ReqLevel: Low Priority
[2024-07-27 09:49:43.475] DEBUG-JOB: AC1, Candidates: 0
```
Where nobody gets pre-assigned the overflow role because their prefs are
all set to the High priority from being toggled... Except wait a second,
some people have it at Medium priority when it should just be a No
Role/High Priority Role toggle?
And herein we meet a problem. My hypothesis is that traits and stuff
that change the overflow have allowed players to set the "ordinary"
overflow role of Assistant to Medium and/or Low priority.
This still shows as enabled in the prefs menu, but leads to an outcome
where a player with assistant enabled is assigned Cook instead.
```
[2024-07-27 09:49:47.775] DEBUG-JOB: DO, Running Overflow Check 1
[2024-07-27 09:49:47.775] DEBUG-JOB: Running FOC, Job: /datum/job/assistant, Level: Low Priority
...
[2024-07-27 09:49:43.475] DEBUG-JOB: FOC player job enabled at wrong level, Player: Bebrus2, TheirLevel: Medium Priority, ReqLevel: Low Priority
...
[2024-07-27 09:49:47.987] DEBUG-JOB: Running AR, Player: Bebrus2, Job: /datum/job/cook, LateJoin: 0
```
So players with the Overflow job pref set to Low (an unexpected state,
should be disabled or High) would be guaranteed to get that role if none
of the higher priority Head of Staff/AI/Dynamic roles took over via the
bugged "force overflow for people with the pref enabled" proc.
Players with the Overflow job pref set to High would be guaranteed to
get that role if none of the higher priority Head of Staff/AI/Dynamic
roles took over via the random job assignment code giving them their
Highest priority role thanks to the infinite job slots of the Overflow.
And players with the Overflow job pref set to Medium (an unexpected
state, should be disabled or High) would get Assistant if the shuffle
step of the available jobs list put Assisstant before any of the other
jobs they had prefs enabled for at Medium that weren't already filled,
otherwise they'd get another random job.
This code is now changed to ignore the priority the player has set when
looking for people to fill the overflow role. As long as it **is**
enabled, the player will get it unless they're forced into a dynamic
ruleset role (AI when malf rolls) or a Head of Staff role due to their
other prefs (they have RD set to med or low, and no other player has a
Head of Staff at high so they get randomly picked and miss the overflow
role).
This will increase the number of assistants in shifts where their pref
state has Assisstant in the bugged Medium priority, but doesn't change
it for bugged Low and not-bugged High/On priority.
On the other side of the coin, we have how the random jobs are picked.
They're kinda not random, and I noticed this reading the logs then
reading the code.
The list of available jobs to pick from is randomly shuffled - but only
**once**. All players pull from a list of jobs in the same order. So you
end up with a log block like this:
```
[2024-07-27 09:49:47.985] DEBUG-JOB: DO pass, Player: Pierow, Level:3, Job:Botanist
[2024-07-27 09:49:47.985] DEBUG-JOB: Running AR, Player: Pierow, Job: /datum/job/botanist, LateJoin: 0
[2024-07-27 09:49:47.985] DEBUG-JOB: Player: Pierow is now Rank: Botanist, JCP:0, JPL:2
[2024-07-27 09:49:47.986] DEBUG-JOB: DO pass, Player: Daddos, Level:3, Job:Botanist
[2024-07-27 09:49:47.986] DEBUG-JOB: Running AR, Player: Daddos, Job: /datum/job/botanist, LateJoin: 0
[2024-07-27 09:49:47.986] DEBUG-JOB: Player: Daddos is now Rank: Botanist, JCP:1, JPL:2
[2024-07-27 09:49:47.986] DEBUG-JOB: FOC job filled and not overflow, Player: Bebrus2, Job: /datum/job/botanist, Current: 2, Limit: 2
[2024-07-27 09:49:47.987] DEBUG-JOB: FOC player job not enabled, Player: Bebrus2
[2024-07-27 09:49:47.987] DEBUG-JOB: DO pass, Player: Bebrus2, Level:3, Job:Cook
[2024-07-27 09:49:47.987] DEBUG-JOB: Running AR, Player: Bebrus2, Job: /datum/job/cook, LateJoin: 0
[2024-07-27 09:49:47.988] DEBUG-JOB: Player: Bebrus2 is now Rank: Cook, JCP:0, JPL:1
[2024-07-27 09:49:47.988] DEBUG-JOB: FOC player job not enabled, Player: Redwizz
[2024-07-27 09:49:47.988] DEBUG-JOB: FOC job filled and not overflow, Player: Redwizz, Job: /datum/job/cook, Current: 1, Limit: 1
```
The list is shuffled into an order of something like `list("Scientist",
"Botanist", "Cook", "Sec Officer", ...)` then iterated over for each
player. So every random job selection goes:
> "Does Player1 have Scientist enabled and at the right priority? No?
Okay, Botanist? Yes? You get botanist."
> "Does Player2 have Scientist enabled and at the right priority? No?
Okay, Botanist? Yes? You get botanist."
> "Does Player3 have Scientist enabled and at the right priority? No?
Okay, Botanist has no slots left so we'll remove it from the list. Okay,
Cook? Yes? You get cook."
> "Does Player4 have Scientist enabled and at the right priority? No?
Okay, Cook has no slots left so we'll remove it from the list. Okay, Sec
Officer? ..."
This can lead to stacked individual departments if it gets randomly
rolled to the start of the list in the shuffle, and completely empty
departments if they end up at the end.
On high pop shifts this is probably less of an issue. Player prefs add
noise to this and as departments at the front fill up, those at the back
pick up some of the lower pref players.
But have you ever had a shift where there's just like... No fucking sec
even though there's tons of players? The logging (before I made changes
in this PR) was a bit ass, but my hypothesis there is that sec officer
was shuffled right at the end of the random job list, so every other
department was filled up before sec officers were picked.
To mitigate this, I made the list shuffle every single time the game
picks a random available job for the player. This should lead to a more
balanced selection of available jobs by avoiding situations where the
code is biased towards packing some departments by accident.
## Why It's Good For The Game
Overflow fixes mean people who go to their prefs and see the Overflow
Role is On will all have the same experience - They will be the Overflow
role.
More random random job selection should prevent individual departments
having a jobs be stacked when it would have otherwise been possible for
a more balanced selection but the code unintentially biased random
departments to be overstaffed and understaffed each shift.
## Changelog
🆑
fix: Having the Overflow Role set to On will properly ensure you get
that role at a High priority as intended by the game code.
fix: Job selection is now a little bit more random. Fixes an
unintentional bias in random job assignment that could lead to
feast-or-famine for roles where everyone is assigned one job and nobody
is assigned another job.
/🆑
301 lines
14 KiB
Plaintext
301 lines
14 KiB
Plaintext
/datum/dynamic_ruleset
|
|
/// For admin logging and round end screen.
|
|
// If you want to change this variable name, the force latejoin/midround rulesets
|
|
// to not use sort_names.
|
|
var/name = ""
|
|
/// For admin logging and round end screen, do not change this unless making a new rule type.
|
|
var/ruletype = ""
|
|
/// If set to TRUE, the rule won't be discarded after being executed, and dynamic will call rule_process() every time it ticks.
|
|
var/persistent = FALSE
|
|
/// If set to TRUE, dynamic will be able to draft this ruleset again later on. (doesn't apply for roundstart rules)
|
|
var/repeatable = FALSE
|
|
/// If set higher than 0 decreases weight by itself causing the ruleset to appear less often the more it is repeated.
|
|
var/repeatable_weight_decrease = 2
|
|
/// List of players that are being drafted for this rule
|
|
var/list/mob/candidates = list()
|
|
/// List of players that were selected for this rule. This can be minds, or mobs.
|
|
var/list/assigned = list()
|
|
/// Preferences flag such as ROLE_WIZARD that need to be turned on for players to be antag.
|
|
var/antag_flag = null
|
|
/// The antagonist datum that is assigned to the mobs mind on ruleset execution.
|
|
var/datum/antagonist/antag_datum = null
|
|
/// The required minimum account age for this ruleset.
|
|
var/minimum_required_age = 7
|
|
/// If set, and config flag protect_roles_from_antagonist is false, then the rule will not pick players from these roles.
|
|
var/list/protected_roles = list()
|
|
/// If set, rule will deny candidates from those roles always.
|
|
var/list/restricted_roles = list()
|
|
/// If set, rule will only accept candidates from those roles. If on a roundstart ruleset, requires the player to have the correct antag pref enabled and any of the possible roles enabled.
|
|
var/list/exclusive_roles = list()
|
|
/// If set, there needs to be a certain amount of players doing those roles (among the players who won't be drafted) for the rule to be drafted IMPORTANT: DOES NOT WORK ON ROUNDSTART RULESETS.
|
|
var/list/enemy_roles = list(
|
|
JOB_CAPTAIN,
|
|
JOB_DETECTIVE,
|
|
JOB_HEAD_OF_SECURITY,
|
|
JOB_SECURITY_OFFICER,
|
|
JOB_WARDEN,
|
|
)
|
|
/// If enemy_roles was set, this is the amount of enemy job workers needed per threat_level range (0-10,10-20,etc) IMPORTANT: DOES NOT WORK ON ROUNDSTART RULESETS.
|
|
var/required_enemies = list(1,1,0,0,0,0,0,0,0,0)
|
|
/// The rule needs this many candidates (post-trimming) to be executed (example: Cult needs 4 players at round start)
|
|
var/required_candidates = 0
|
|
/// 0 -> 9, probability for this rule to be picked against other rules. If zero this will effectively disable the rule.
|
|
var/weight = 5
|
|
/// Threat cost for this rule, this is decreased from the threat level when the rule is executed.
|
|
var/cost = 0
|
|
/// Cost per level the rule scales up.
|
|
var/scaling_cost = 0
|
|
/// How many times a rule has scaled up upon getting picked.
|
|
var/scaled_times = 0
|
|
/// Used for the roundend report
|
|
var/total_cost = 0
|
|
/// A flag that determines how the ruleset is handled. Check __DEFINES/dynamic.dm for an explanation of the accepted values.
|
|
var/flags = NONE
|
|
/// Pop range per requirement. If zero defaults to dynamic's pop_per_requirement.
|
|
var/pop_per_requirement = 0
|
|
/// Requirements are the threat level requirements per pop range.
|
|
/// With the default values, The rule will never get drafted below 10 threat level (aka: "peaceful extended"), and it requires a higher threat level at lower pops.
|
|
var/list/requirements = list(40,30,20,10,10,10,10,10,10,10)
|
|
/// If a role is to be considered another for the purpose of banning.
|
|
var/antag_flag_override = null
|
|
/// If set, will check this preference instead of antag_flag.
|
|
var/antag_preference = null
|
|
/// If a ruleset type which is in this list has been executed, then the ruleset will not be executed.
|
|
var/list/blocking_rules = list()
|
|
/// The minimum amount of players required for the rule to be considered.
|
|
var/minimum_players = 0
|
|
/// The maximum amount of players required for the rule to be considered.
|
|
/// Anything below zero or exactly zero is ignored.
|
|
var/maximum_players = 0
|
|
/// Calculated during acceptable(), used in scaling and team sizes.
|
|
var/indice_pop = 0
|
|
/// Base probability used in scaling. The higher it is, the more likely to scale. Kept as a var to allow for config editing._SendSignal(sigtype, list/arguments)
|
|
var/base_prob = 60
|
|
/// Delay for when execute will get called from the time of post_setup (roundstart) or process (midround/latejoin).
|
|
/// Make sure your ruleset works with execute being called during the game when using this, and that the clean_up proc reverts it properly in case of faliure.
|
|
var/delay = 0
|
|
|
|
/// Judges the amount of antagonists to apply, for both solo and teams.
|
|
/// Note that some antagonists (such as traitors, lings, heretics, etc) will add more based on how many times they've been scaled.
|
|
/// Written as a linear equation--ceil(x/denominator) + offset, or as a fixed constant.
|
|
/// If written as a linear equation, will be in the form of `list("denominator" = denominator, "offset" = offset).
|
|
var/antag_cap = 0
|
|
|
|
/// A list, or null, of templates that the ruleset depends on to function correctly
|
|
var/list/ruleset_lazy_templates
|
|
/// In what categories is this ruleset allowed to run? Used by station traits
|
|
var/ruleset_category = RULESET_CATEGORY_DEFAULT
|
|
|
|
/datum/dynamic_ruleset/New()
|
|
// Rulesets can be instantiated more than once, such as when an admin clicks
|
|
// "Execute Midround Ruleset". Thus, it would be wrong to perform any
|
|
// side effects here. Dynamic rulesets should be stateless anyway.
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
|
|
..()
|
|
|
|
/datum/dynamic_ruleset/roundstart // One or more of those drafted at roundstart
|
|
ruletype = ROUNDSTART_RULESET
|
|
|
|
// Can be drafted when a player joins the server
|
|
/datum/dynamic_ruleset/latejoin
|
|
ruletype = LATEJOIN_RULESET
|
|
|
|
/// By default, a rule is acceptable if it satisfies the threat level/population requirements.
|
|
/// If your rule has extra checks, such as counting security officers, do that in ready() instead
|
|
/datum/dynamic_ruleset/proc/acceptable(population = 0, threat_level = 0)
|
|
var/ruleset_forced = GLOB.dynamic_forced_rulesets[type] || RULESET_NOT_FORCED
|
|
if (ruleset_forced != RULESET_NOT_FORCED)
|
|
if (ruleset_forced == RULESET_FORCE_ENABLED)
|
|
return TRUE
|
|
else
|
|
log_dynamic("FAIL: [src] was disabled in admin panel.")
|
|
return FALSE
|
|
|
|
if(!is_valid_population(population))
|
|
var/range = maximum_players > 0 ? "([minimum_players] - [maximum_players])" : "(minimum: [minimum_players])"
|
|
log_dynamic("FAIL: [src] failed acceptable: min/max players out of range [range] vs population ([population])")
|
|
return FALSE
|
|
|
|
if (!is_valid_threat(population, threat_level))
|
|
log_dynamic("FAIL: [src] failed acceptable: threat_level ([threat_level]) < requirement ([requirements[indice_pop]])")
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/// Returns true if we have enough players to run
|
|
/datum/dynamic_ruleset/proc/is_valid_population(population)
|
|
if(minimum_players > population)
|
|
return FALSE
|
|
if(maximum_players > 0 && population > maximum_players)
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/// Sets the current threat indices and returns true if we're inside of them
|
|
/datum/dynamic_ruleset/proc/is_valid_threat(population, threat_level)
|
|
pop_per_requirement = pop_per_requirement > 0 ? pop_per_requirement : SSdynamic.pop_per_requirement
|
|
indice_pop = min(requirements.len,round(population/pop_per_requirement)+1)
|
|
return threat_level >= requirements[indice_pop]
|
|
|
|
/// When picking rulesets, if dynamic picks the same one multiple times, it will "scale up".
|
|
/// However, doing this blindly would result in lowpop rounds (think under 10 people) where over 80% of the crew is antags!
|
|
/// This function is here to ensure the antag ratio is kept under control while scaling up.
|
|
/// Returns how much threat to actually spend in the end.
|
|
/datum/dynamic_ruleset/proc/scale_up(population, max_scale)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
if (!scaling_cost)
|
|
return 0
|
|
|
|
var/antag_fraction = 0
|
|
for(var/datum/dynamic_ruleset/ruleset as anything in (SSdynamic.executed_rules + list(src))) // we care about the antags we *will* assign, too
|
|
antag_fraction += ruleset.get_antag_cap_scaling_included(population) / SSdynamic.roundstart_pop_ready
|
|
|
|
for(var/i in 1 to max_scale)
|
|
if(antag_fraction < 0.25)
|
|
scaled_times += 1
|
|
antag_fraction += get_scaling_antag_cap(population) / SSdynamic.roundstart_pop_ready // we added new antags, gotta update the %
|
|
|
|
return scaled_times * scaling_cost
|
|
|
|
/// Returns how many more antags to add while scaling with a given population.
|
|
/// By default rulesets scale linearly, but you can override this to make them scale differently.
|
|
/datum/dynamic_ruleset/proc/get_scaling_antag_cap(population)
|
|
return get_antag_cap(population)
|
|
|
|
/// Returns what the antag cap with the given population is.
|
|
/datum/dynamic_ruleset/proc/get_antag_cap(population)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
if (isnum(antag_cap))
|
|
return antag_cap
|
|
|
|
return CEILING(population / antag_cap["denominator"], 1) + (antag_cap["offset"] || 0)
|
|
|
|
/// Gets the 'final' antag cap for this ruleset, which is the base cap plus the scaled cap.
|
|
/datum/dynamic_ruleset/proc/get_antag_cap_scaling_included(population)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
var/base_cap = get_antag_cap(population)
|
|
var/modded_cap = scaled_times * get_scaling_antag_cap(population)
|
|
return base_cap + modded_cap
|
|
|
|
/// This is called if persistent variable is true everytime SSTicker ticks.
|
|
/datum/dynamic_ruleset/proc/rule_process()
|
|
return
|
|
|
|
/// Called on pre_setup for roundstart rulesets.
|
|
/// Do everything you need to do before job is assigned here.
|
|
/// IMPORTANT: ASSIGN special_role HERE
|
|
/datum/dynamic_ruleset/proc/pre_execute()
|
|
return TRUE
|
|
|
|
/// Called on post_setup on roundstart and when the rule executes on midround and latejoin.
|
|
/// Give your candidates or assignees equipment and antag datum here.
|
|
/datum/dynamic_ruleset/proc/execute()
|
|
for(var/datum/mind/M in assigned)
|
|
M.add_antag_datum(antag_datum)
|
|
GLOB.pre_setup_antags -= M
|
|
return TRUE
|
|
|
|
/// Rulesets can be reused, so when we're done setting one up we want to wipe its memory of the people it was selecting over
|
|
/// This isn't Destroy we aren't deleting it here, rulesets free when nothing holds a ref. This is just to prevent hung refs.
|
|
/datum/dynamic_ruleset/proc/forget_startup()
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
candidates = list()
|
|
assigned = list()
|
|
antag_datum = null
|
|
|
|
/// Here you can perform any additional checks you want. (such as checking the map etc)
|
|
/// Remember that on roundstart no one knows what their job is at this point.
|
|
/// IMPORTANT: If ready() returns TRUE, that means pre_execute() or execute() should never fail!
|
|
/datum/dynamic_ruleset/proc/ready(forced = 0)
|
|
return check_candidates()
|
|
|
|
/// This should always be called before ready is, to ensure that the ruleset can locate map/template based landmarks as needed
|
|
/datum/dynamic_ruleset/proc/load_templates()
|
|
for(var/template in ruleset_lazy_templates)
|
|
SSmapping.lazy_load_template(template)
|
|
|
|
/// Runs from gamemode process() if ruleset fails to start, like delayed rulesets not getting valid candidates.
|
|
/// This one only handles refunding the threat, override in ruleset to clean up the rest.
|
|
/datum/dynamic_ruleset/proc/clean_up()
|
|
SSdynamic.refund_threat(cost + (scaled_times * scaling_cost))
|
|
SSdynamic.threat_log += "[worldtime2text()]: [ruletype] [name] refunded [cost + (scaled_times * scaling_cost)]. Failed to execute."
|
|
|
|
/// Gets weight of the ruleset
|
|
/// Note that this decreases weight if repeatable is TRUE and repeatable_weight_decrease is higher than 0
|
|
/// Note: If you don't want repeatable rulesets to decrease their weight use the weight variable directly
|
|
/datum/dynamic_ruleset/proc/get_weight()
|
|
if(repeatable && weight > 1 && repeatable_weight_decrease > 0)
|
|
for(var/datum/dynamic_ruleset/DR in SSdynamic.executed_rules)
|
|
if(istype(DR, type))
|
|
weight = max(weight-repeatable_weight_decrease,1)
|
|
return weight
|
|
|
|
/// Checks if there are enough candidates to run, and logs otherwise
|
|
/datum/dynamic_ruleset/proc/check_candidates()
|
|
if (required_candidates <= candidates.len)
|
|
return TRUE
|
|
|
|
log_dynamic("FAIL: [src] does not have enough candidates ([required_candidates] needed, [candidates.len] found)")
|
|
return FALSE
|
|
|
|
/// Here you can remove candidates that do not meet your requirements.
|
|
/// This means if their job is not correct or they have disconnected you can remove them from candidates here.
|
|
/// Usually this does not need to be changed unless you need some specific requirements from your candidates.
|
|
/datum/dynamic_ruleset/proc/trim_candidates()
|
|
return
|
|
|
|
/// Set mode_result and news report here.
|
|
/// Only called if ruleset is flagged as HIGH_IMPACT_RULESET
|
|
/datum/dynamic_ruleset/proc/round_result()
|
|
|
|
//////////////////////////////////////////////
|
|
// //
|
|
// ROUNDSTART RULESETS //
|
|
// //
|
|
//////////////////////////////////////////////
|
|
|
|
/// Checks if candidates are connected and if they are banned or don't want to be the antagonist.
|
|
/datum/dynamic_ruleset/roundstart/trim_candidates()
|
|
for(var/mob/dead/new_player/candidate_player in candidates)
|
|
var/client/candidate_client = GET_CLIENT(candidate_player)
|
|
if (!candidate_client || !candidate_player.mind) // Are they connected?
|
|
candidates.Remove(candidate_player)
|
|
continue
|
|
|
|
if(candidate_client.get_remaining_days(minimum_required_age) > 0)
|
|
candidates.Remove(candidate_player)
|
|
continue
|
|
|
|
if(candidate_player.mind.special_role) // We really don't want to give antag to an antag.
|
|
candidates.Remove(candidate_player)
|
|
continue
|
|
|
|
if (!((antag_preference || antag_flag) in candidate_client.prefs.be_special))
|
|
candidates.Remove(candidate_player)
|
|
continue
|
|
|
|
if (is_banned_from(candidate_player.ckey, list(antag_flag_override || antag_flag, ROLE_SYNDICATE)))
|
|
candidates.Remove(candidate_player)
|
|
continue
|
|
|
|
// If this ruleset has exclusive_roles set, we want to only consider players who have those
|
|
// job prefs enabled and are eligible to play that job. Otherwise, continue as before.
|
|
if(length(exclusive_roles))
|
|
var/exclusive_candidate = FALSE
|
|
for(var/role in exclusive_roles)
|
|
var/datum/job/job = SSjob.get_job(role)
|
|
|
|
if((role in candidate_client.prefs.job_preferences) && SSjob.check_job_eligibility(candidate_player, job, "Dynamic Roundstart TC", add_job_to_log = TRUE) == JOB_AVAILABLE)
|
|
exclusive_candidate = TRUE
|
|
break
|
|
|
|
// If they didn't have any of the required job prefs enabled or were banned from all enabled prefs,
|
|
// they're not eligible for this antag type.
|
|
if(!exclusive_candidate)
|
|
candidates.Remove(candidate_player)
|
|
|
|
/// Do your checks if the ruleset is ready to be executed here.
|
|
/// Should ignore certain checks if forced is TRUE
|
|
/datum/dynamic_ruleset/roundstart/ready(population, forced = FALSE)
|
|
return ..()
|