Files
Bubberstation/code/controllers/subsystem/dynamic/dynamic_ruleset_roundstart.dm
Roxy d84a87f668 Rewrites to fix compiler errors on 516.1670+ (#93801)
## About The Pull Request

Fixes all instances of numbers being used as assoc list keys in things
that aren't alists, either by turning them into alists or changing the
keys to something else. Also adds new macros to support creating global
alists, as a few global lists became alists. Most of these are pretty
simple and self-explanatory but
- The GLOB.huds one necessitated rewriting because code depended on it
being a non-assoc list, which it technically was because the defines it
used as keys were numbers so BYOND turned it into a regular list, most
of this was for loops through all the subtypes of
`/datum/atom_hud/data/diagnostic` of which there's only one, so I just
changed it to get that type directly by key
- NT Frontier used number indexes which it looped through for some
reason and also passed to TGUI, changed these to strings and adjusted
the TGUI to match, I tested this and it works fine

## Why It's Good For The Game

Makes the code compile, I couldn't test everything but I tried to check
all usages of affected vars to make sure they wouldn't break from being
switched to alists, a TM might be in order just to be sure nothing's
fucked

## Changelog
🆑
refactor: rewrote all cases of numbers being used as keys in non-alist
associative lists
/🆑
2025-11-08 14:19:55 -08:00

434 lines
16 KiB
Plaintext

/datum/dynamic_ruleset/roundstart
// We can pick multiple of a roundstart ruleset to "scale up" (spawn more of the same type of antag)
// Set this to FALSE if you DON'T want this ruleset to "scale up"
repeatable = TRUE
/// If TRUE, the ruleset will be the only one selected for roundstart
var/solo = FALSE
/datum/dynamic_ruleset/roundstart/is_valid_candidate(mob/candidate, client/candidate_client)
if(isnull(candidate.mind))
return FALSE
// Checks that any other roundstart ruleset hasn't already picked this guy
for(var/datum/dynamic_ruleset/roundstart/ruleset as anything in SSdynamic.queued_rulesets)
if(candidate.mind in ruleset.selected_minds)
return FALSE
return ..()
/// Helpful proc - to use if your ruleset forces a job - which ensures a candidate can play the passed job typepath
/datum/dynamic_ruleset/roundstart/proc/ruleset_forced_job_check(mob/candidate, client/candidate_client, datum/job/job_typepath)
// Malf AI can only go to people who want to be AI
if(!candidate_client.prefs.job_preferences[job_typepath::title])
return FALSE
// And only to people who can actually be AI this round
if(SSjob.check_job_eligibility(candidate, SSjob.get_job_type(job_typepath), "[name] Candidacy") != JOB_AVAILABLE)
return FALSE
// (Something else forced us to play a job that isn't AI)
var/forced_job = LAZYACCESS(SSjob.forced_occupations, candidate)
if(forced_job && forced_job != job_typepath)
return FALSE
// (Something else forced us NOT to play AI)
if(job_typepath::title in LAZYACCESS(SSjob.prevented_occupations, candidate))
return FALSE
return TRUE
/datum/dynamic_ruleset/roundstart/traitor
name = "Traitors"
config_tag = "Roundstart Traitor"
preview_antag_datum = /datum/antagonist/traitor
pref_flag = ROLE_TRAITOR
weight = 10
min_pop = 3
max_antag_cap = list("denominator" = 24)
/datum/dynamic_ruleset/roundstart/traitor/assign_role(datum/mind/candidate)
candidate.add_antag_datum(/datum/antagonist/traitor)
/datum/dynamic_ruleset/roundstart/malf_ai
name = "Malfunctioning AI"
config_tag = "Roundstart Malfunctioning AI"
pref_flag = ROLE_MALF
preview_antag_datum = /datum/antagonist/malf_ai
ruleset_flags = RULESET_HIGH_IMPACT
weight = alist(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 1,
DYNAMIC_TIER_MEDIUMHIGH = 3,
DYNAMIC_TIER_HIGH = 3,
)
min_pop = 30
max_antag_cap = 1
repeatable = FALSE
/datum/dynamic_ruleset/roundstart/malf_ai/get_always_blacklisted_roles()
return list()
/datum/dynamic_ruleset/roundstart/malf_ai/is_valid_candidate(mob/candidate, client/candidate_client)
return ..() && ruleset_forced_job_check(candidate, candidate_client, /datum/job/ai)
/datum/dynamic_ruleset/roundstart/malf_ai/prepare_for_role(datum/mind/candidate)
LAZYSET(SSjob.forced_occupations, candidate, /datum/job/ai)
/datum/dynamic_ruleset/roundstart/malf_ai/assign_role(datum/mind/candidate)
candidate.add_antag_datum(/datum/antagonist/malf_ai)
/datum/dynamic_ruleset/roundstart/malf_ai/can_be_selected()
return ..() && !HAS_TRAIT(SSstation, STATION_TRAIT_HUMAN_AI)
/datum/dynamic_ruleset/roundstart/blood_brother
name = "Blood Brothers"
config_tag = "Roundstart Blood Brothers"
preview_antag_datum = /datum/antagonist/brother
pref_flag = ROLE_BROTHER
weight = 5
max_antag_cap = list("denominator" = 29)
min_pop = 10
/datum/dynamic_ruleset/roundstart/blood_brother/assign_role(datum/mind/candidate)
candidate.add_antag_datum(/datum/antagonist/brother)
/datum/dynamic_ruleset/roundstart/changeling
name = "Changelings"
config_tag = "Roundstart Changeling"
preview_antag_datum = /datum/antagonist/changeling
pref_flag = ROLE_CHANGELING
weight = 3
min_pop = 15
max_antag_cap = list("denominator" = 29)
/datum/dynamic_ruleset/roundstart/changeling/assign_role(datum/mind/candidate)
candidate.add_antag_datum(/datum/antagonist/changeling)
/datum/dynamic_ruleset/roundstart/heretic
name = "Heretics"
config_tag = "Roundstart Heretics"
preview_antag_datum = /datum/antagonist/heretic
pref_flag = ROLE_HERETIC
weight = 3
max_antag_cap = list("denominator" = 24)
min_pop = 30 // Ensures good spread of sacrifice targets
/datum/dynamic_ruleset/roundstart/heretic/assign_role(datum/mind/candidate)
candidate.add_antag_datum(/datum/antagonist/heretic)
/datum/dynamic_ruleset/roundstart/wizard
name = "Wizard"
config_tag = "Roundstart Wizard"
preview_antag_datum = /datum/antagonist/wizard
pref_flag = ROLE_WIZARD
ruleset_flags = RULESET_INVADER|RULESET_HIGH_IMPACT
weight = alist(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 0,
DYNAMIC_TIER_MEDIUMHIGH = 1,
DYNAMIC_TIER_HIGH = 2,
)
max_antag_cap = 1
min_pop = 30
ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_WIZARDDEN)
repeatable = FALSE
/datum/dynamic_ruleset/roundstart/wizard/prepare_for_role(datum/mind/candidate)
LAZYSET(SSjob.forced_occupations, candidate, /datum/job/space_wizard)
/datum/dynamic_ruleset/roundstart/wizard/assign_role(datum/mind/candidate)
candidate.add_antag_datum(/datum/antagonist/wizard) // moves to lair for us
/datum/dynamic_ruleset/roundstart/wizard/round_result()
for(var/datum/mind/wiz as anything in selected_minds)
if(considered_alive(wiz) && !considered_exiled(wiz))
return FALSE
SSticker.news_report = WIZARD_KILLED
return TRUE
/datum/dynamic_ruleset/roundstart/blood_cult
name = "Blood Cult"
config_tag = "Roundstart Blood Cult"
preview_antag_datum = /datum/antagonist/cult
pref_flag = ROLE_CULTIST
ruleset_flags = RULESET_HIGH_IMPACT
weight = alist(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 1,
DYNAMIC_TIER_MEDIUMHIGH = 3,
DYNAMIC_TIER_HIGH = 3,
)
min_pop = 30
blacklisted_roles = list(
JOB_HEAD_OF_PERSONNEL,
)
min_antag_cap = list("denominator" = 20, "offset" = 1)
repeatable = FALSE
/// Ratio of cultists getting on the shuttle to be considered a minor win
var/ratio_to_be_considered_escaped = 0.5
/datum/dynamic_ruleset/roundstart/blood_cult/get_always_blacklisted_roles()
return ..() | JOB_CHAPLAIN
/datum/dynamic_ruleset/roundstart/blood_cult/create_execute_args()
return list(
new /datum/team/cult(),
get_most_experienced(selected_minds, pref_flag),
)
/datum/dynamic_ruleset/roundstart/blood_cult/execute()
. = ..()
// future todo, find a cleaner way to get this from execute args
var/datum/team/cult/main_cult = locate() in GLOB.antagonist_teams
main_cult.setup_objectives()
/datum/dynamic_ruleset/roundstart/blood_cult/assign_role(datum/mind/candidate, datum/team/cult/cult, datum/mind/most_experienced)
var/datum/antagonist/cult/cultist = new()
cultist.give_equipment = TRUE
candidate.add_antag_datum(cultist, cult)
if(most_experienced == candidate)
cultist.make_cult_leader()
/datum/dynamic_ruleset/roundstart/blood_cult/round_result()
var/datum/team/cult/main_cult = locate() in GLOB.antagonist_teams
if(main_cult.check_cult_victory())
SSticker.mode_result = "win - cult win"
SSticker.news_report = CULT_SUMMON
return TRUE
var/num_cultists = main_cult.size_at_maximum || 100
var/ratio_to_be_considered_escaped = 0.5
var/escaped_cultists = 0
for(var/datum/mind/escapee as anything in main_cult.members)
if(considered_escaped(escapee))
escaped_cultists++
SSticker.mode_result = "loss - staff stopped the cult"
SSticker.news_report = (escaped_cultists / num_cultists) >= ratio_to_be_considered_escaped ? CULT_ESCAPE : CULT_FAILURE
return TRUE
/datum/dynamic_ruleset/roundstart/nukies
name = "Nuclear Operatives"
config_tag = "Roundstart Nukeops"
preview_antag_datum = /datum/antagonist/nukeop
pref_flag = ROLE_OPERATIVE
ruleset_flags = RULESET_INVADER|RULESET_HIGH_IMPACT
weight = alist(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 1,
DYNAMIC_TIER_MEDIUMHIGH = 3,
DYNAMIC_TIER_HIGH = 3,
)
min_pop = 30
min_antag_cap = list("denominator" = 18, "offset" = 1)
ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_NUKIEBASE)
repeatable = FALSE
/datum/dynamic_ruleset/roundstart/nukies/prepare_for_role(datum/mind/candidate)
LAZYSET(SSjob.forced_occupations, candidate, /datum/job/nuclear_operative)
/datum/dynamic_ruleset/roundstart/nukies/create_execute_args()
return list(
new /datum/team/nuclear(),
get_most_experienced(selected_minds, pref_flag),
)
/datum/dynamic_ruleset/roundstart/nukies/assign_role(datum/mind/candidate, datum/team/nuke_team, datum/mind/most_experienced)
if(most_experienced == candidate)
candidate.add_antag_datum(/datum/antagonist/nukeop/leader, nuke_team)
else
candidate.add_antag_datum(/datum/antagonist/nukeop, nuke_team)
/datum/dynamic_ruleset/roundstart/nukies/round_result()
var/datum/antagonist/nukeop/nukie = selected_minds[1].has_antag_datum(/datum/antagonist/nukeop)
var/datum/team/nuclear/nuke_team = nukie.get_team()
var/result = nuke_team.get_result()
switch(result)
if(NUKE_RESULT_FLUKE)
SSticker.mode_result = "loss - syndicate nuked - disk secured"
SSticker.news_report = NUKE_SYNDICATE_BASE
if(NUKE_RESULT_NUKE_WIN)
SSticker.mode_result = "win - syndicate nuke"
SSticker.news_report = STATION_DESTROYED_NUKE
if(NUKE_RESULT_NOSURVIVORS)
SSticker.mode_result = "halfwin - syndicate nuke - did not evacuate in time"
SSticker.news_report = STATION_DESTROYED_NUKE
if(NUKE_RESULT_WRONG_STATION)
SSticker.mode_result = "halfwin - blew wrong station"
SSticker.news_report = NUKE_MISS
if(NUKE_RESULT_WRONG_STATION_DEAD)
SSticker.mode_result = "halfwin - blew wrong station - did not evacuate in time"
SSticker.news_report = NUKE_MISS
if(NUKE_RESULT_CREW_WIN_SYNDIES_DEAD)
SSticker.mode_result = "loss - evacuation - disk secured - syndi team dead"
SSticker.news_report = OPERATIVES_KILLED
if(NUKE_RESULT_CREW_WIN)
SSticker.mode_result = "loss - evacuation - disk secured"
SSticker.news_report = OPERATIVES_KILLED
if(NUKE_RESULT_DISK_LOST)
SSticker.mode_result = "halfwin - evacuation - disk not secured"
SSticker.news_report = OPERATIVE_SKIRMISH
if(NUKE_RESULT_DISK_STOLEN)
SSticker.mode_result = "halfwin - detonation averted"
SSticker.news_report = OPERATIVE_SKIRMISH
else
SSticker.mode_result = "halfwin - interrupted"
SSticker.news_report = OPERATIVE_SKIRMISH
/datum/dynamic_ruleset/roundstart/nukies/clown
name = "Clown Operatives"
config_tag = "Roundstart Clownops"
preview_antag_datum = /datum/antagonist/nukeop/clownop
pref_flag = ROLE_CLOWN_OPERATIVE
weight = 0
/datum/dynamic_ruleset/roundstart/nukies/clown/prepare_for_role(datum/mind/candidate)
LAZYSET(SSjob.forced_occupations, candidate, /datum/job/nuclear_operative/clown_operative)
/datum/dynamic_ruleset/roundstart/nukies/clown/assign_role(datum/mind/candidate, datum/team/nuke_team, datum/mind/most_experienced)
if(most_experienced == candidate)
candidate.add_antag_datum(/datum/antagonist/nukeop/leader/clownop)
else
candidate.add_antag_datum(/datum/antagonist/nukeop/clownop)
/datum/dynamic_ruleset/roundstart/revolution
name = "Revolution"
config_tag = "Roundstart Revolution"
preview_antag_datum = /datum/antagonist/rev/head
pref_flag = ROLE_REV_HEAD
ruleset_flags = RULESET_HIGH_IMPACT
weight = alist(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 1,
DYNAMIC_TIER_MEDIUMHIGH = 3,
DYNAMIC_TIER_HIGH = 3,
)
min_pop = 30
min_antag_cap = 1
max_antag_cap = 3
repeatable = FALSE
/// If we have fewer heads of staff than this 7 minutes into the round, we'll cancel the revolution
var/heads_necessary = 2
/datum/dynamic_ruleset/roundstart/revolution/get_always_blacklisted_roles()
. = ..()
for(var/datum/job/job as anything in SSjob.all_occupations)
if(job.job_flags & JOB_HEAD_OF_STAFF)
. |= job.title
/datum/dynamic_ruleset/roundstart/revolution/assign_role(datum/mind/candidate)
LAZYADD(candidate.special_roles, "Dormant Head Revolutionary")
addtimer(CALLBACK(src, PROC_REF(reveal_head), candidate), 7 MINUTES, TIMER_DELETE_ME)
/// Reveals the headrev after a set amount of time
/datum/dynamic_ruleset/roundstart/revolution/proc/reveal_head(datum/mind/candidate)
LAZYREMOVE(candidate.special_roles, "Dormant Head Revolutionary")
var/head_check = 0
for(var/mob/player as anything in get_active_player_list(alive_check = TRUE, afk_check = TRUE))
if(player.mind?.assigned_role.job_flags & JOB_HEAD_OF_STAFF)
head_check++
if(head_check < heads_necessary)
log_dynamic("[config_tag]: Not enough heads of staff were present to start a revolution.")
addtimer(CALLBACK(src, PROC_REF(revs_execution_failed)), 1 MINUTES, TIMER_UNIQUE|TIMER_DELETE_ME)
return
if(!can_be_headrev(candidate))
log_dynamic("[config_tag]: [key_name(candidate)] was not eligible to be a headrev after the timer expired - finding a replacement.")
find_another_headrev()
return
GLOB.revolution_handler ||= new()
var/datum/antagonist/rev/head/new_head = new()
new_head.give_flash = TRUE
new_head.give_hud = TRUE
new_head.remove_clumsy = TRUE
candidate.add_antag_datum(new_head, GLOB.revolution_handler.revs)
GLOB.revolution_handler.start_revolution()
/datum/dynamic_ruleset/roundstart/revolution/proc/find_another_headrev()
for(var/mob/living/carbon/human/upstanding_citizen in GLOB.player_list)
if(!can_be_headrev(upstanding_citizen.mind))
continue
reveal_head(upstanding_citizen.mind)
log_dynamic("[config_tag]: [key_name(upstanding_citizen)] was selected as a replacement headrev.")
return
log_dynamic("[config_tag]: Failed to find a replacement headrev.")
addtimer(CALLBACK(src, PROC_REF(revs_execution_failed)), 1 MINUTES, TIMER_UNIQUE|TIMER_DELETE_ME)
/datum/dynamic_ruleset/roundstart/revolution/proc/revs_execution_failed()
if(GLOB.revolution_handler)
return
// Execution is effectively cancelled by this point, but it's not like we can go back and refund it
SSdynamic.unreported_rulesets += src
name += " (Canceled)"
log_dynamic("[config_tag]: All headrevs were ineligible after the timer expired, and no replacements could be found. Ruleset canceled.")
message_admins("[config_tag]: All headrevs were ineligible after the timer expired, and no replacements could be found. Ruleset canceled.")
/datum/dynamic_ruleset/roundstart/spies
name = "Spies"
config_tag = "Roundstart Spies"
preview_antag_datum = /datum/antagonist/spy
pref_flag = ROLE_SPY
weight = alist(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 1,
DYNAMIC_TIER_MEDIUMHIGH = 3,
DYNAMIC_TIER_HIGH = 3,
)
min_pop = 10
min_antag_cap = list("denominator" = 20, "offset" = 1)
/datum/dynamic_ruleset/roundstart/spies/assign_role(datum/mind/candidate)
candidate.add_antag_datum(/datum/antagonist/spy)
/datum/dynamic_ruleset/roundstart/extended
name = "Extended"
config_tag = "Extended"
weight = 0
min_antag_cap = 0
repeatable = FALSE
solo = TRUE
/datum/dynamic_ruleset/roundstart/extended/execute()
// No midrounds no latejoins
for(var/category in SSdynamic.rulesets_to_spawn)
SSdynamic.rulesets_to_spawn[category] = 0
/datum/dynamic_ruleset/roundstart/meteor
name = "Meteor"
config_tag = "Meteor"
weight = 0
min_antag_cap = 0
repeatable = FALSE
/datum/dynamic_ruleset/roundstart/meteor/execute()
GLOB.meteor_mode ||= new()
GLOB.meteor_mode.start_meteor()
/datum/dynamic_ruleset/roundstart/nations
name = "Nations"
config_tag = "Nations"
weight = 0
min_antag_cap = 0
repeatable = FALSE
solo = TRUE
/datum/dynamic_ruleset/roundstart/nations/execute()
// No midrounds no latejoins
for(var/category in SSdynamic.rulesets_to_spawn)
SSdynamic.rulesets_to_spawn[category] = 0
//notably assistant is not in this list to prevent the round turning into BARBARISM instantly, and silicon is in this list for UN
var/list/department_types = list(
/datum/job_department/silicon, //united nations
/datum/job_department/cargo,
/datum/job_department/engineering,
/datum/job_department/medical,
/datum/job_department/science,
/datum/job_department/security,
/datum/job_department/service,
)
for(var/department_type in department_types)
create_separatist_nation(department_type, announcement = FALSE, dangerous = FALSE, message_admins = FALSE)
GLOB.round_default_lawset = /datum/ai_laws/united_nations