mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-07 23:42:44 +00:00
## About The Pull Request Repo default max traitors upped from 1 per 38 players (up to 2 traitors at 50 pop, up to 3 traitors at 80 pop) to 1 per 24 players (up to 3 traitors at 50 pop, up to 4 traitors at 80 pop) Note: The ruleset can be executed twice or even thrice depending on RNG, meaning a particularly chaotic round may spawn with up to 6/9 traitors at 50 pop (and more may be added via midrounds and latejoins). ## Why It's Good For The Game #67823 changed this number This was done because dynamic would force spawn midround traitor IF any midround ruleset failed to execute. But dynamic no longer does that. So this value should be reverted.
434 lines
16 KiB
Plaintext
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 = list(
|
|
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 = list(
|
|
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 = list(
|
|
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 = list(
|
|
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 = list(
|
|
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 = list(
|
|
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
|