Files
vgstation13/code/datums/gamemode/dynamic/dynamic_rulesets_roundstart.dm
Anthony "Shifty Rail 189f77cbb7 Refactors player preferences for modularity + SQLite Unit Test (#37615)
* Pref code refactor

* Empty database reference

* Unit testing SQLite

* Everything else

* Disable unit testing.

* Equivalent

* more robust unit tests
2025-06-07 09:54:09 -04:00

1010 lines
42 KiB
Plaintext

/*
* Syndicate Traitors
* Syndicate Challengers
* Changelings
* Vampires
* Wizard
* Civil War of Casters
* Blood Cult
* Nuclear Emergency
* Malfunctioning AI
* Blob Conglomerate
* Extended
* Revolution
* The Grinch
* Tag mode
* Antag Madness
*/
//////////////////////////////////////////////
// //
// SYNDICATE TRAITORS ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/roundstart/traitor
name = "Syndicate Traitors"
role_category = /datum/role/traitor
protected_from_jobs = list("Security Officer", "Merchant", "Warden", "Head of Personnel", "Cyborg", "Detective",
"Head of Security", "Captain", "Chief Engineer", "Chief Medical Officer", "Research Director", "Brig Medic")
restricted_from_jobs = list("AI","Mobile MMI")
required_candidates = 1
weight = BASE_RULESET_WEIGHT
weight_category = "Traitor"
cost = 10
var/traitor_threshold = 3
var/additional_cost = 5
requirements = list(10,10,10,10,10,10,10,10,10,10)
high_population_requirement = 10
/datum/dynamic_ruleset/roundstart/traitor/choose_candidates()
var/traitor_scaling_coeff = 10 - max(0,round(mode.threat_level/10)-5)//above 50 threat level, coeff goes down by 1 for every 10 levels
var/num_traitors = min(round(mode.roundstart_pop_ready / traitor_scaling_coeff) + 1, candidates.len)
for (var/i = 1 to num_traitors)
var/mob/M = pick(candidates)
assigned += M
candidates -= M
if (i > traitor_threshold)
if ((mode.threat > additional_cost))
mode.spend_threat(additional_cost)
else
break
return (assigned.len > 0)
/datum/dynamic_ruleset/roundstart/traitor/execute()
for (var/mob/M in assigned)
var/datum/role/traitor/newTraitor = new
newTraitor.AssignToRole(M.mind,1)
newTraitor.Greet(GREET_ROUNDSTART)
// Above 3 traitors, we start to cost a bit more.
return 1
/datum/dynamic_ruleset/roundstart/traitor/previous_rounds_odds_reduction(var/result)
return result
//////////////////////////////////////////////
// //
// CHALLENGERS ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/roundstart/challengers
name = "Syndicate Challengers"
role_category = /datum/role/traitor/challenger
role_category_override = TRAITOR
protected_from_jobs = list("Security Officer", "Merchant", "Warden", "Head of Personnel", "Detective",
"Head of Security", "Captain", "Chief Engineer", "Chief Medical Officer", "Research Director", "Brig Medic")
restricted_from_jobs = list("AI","Cyborg","Mobile MMI")
required_candidates = 3
weight = BASE_RULESET_WEIGHT
weight_category = "Traitor"
cost = 15
var/traitor_threshold = 4
var/additional_cost = 5
requirements = list(101,101,101,101,10,10,10,10,10,10)
high_population_requirement = 15
// -- Currently a copypaste of traitors. Could be fixed to be less copy & paste.
/datum/dynamic_ruleset/roundstart/challengers/choose_candidates()
var/traitor_scaling_coeff = 10 - max(0,round(mode.threat_level/10)-5)//above 50 threat level, coeff goes down by 1 for every 10 levels
var/num_traitors = clamp(round(mode.roundstart_pop_ready / traitor_scaling_coeff) + 1, required_candidates, candidates.len)
for (var/i = 1 to num_traitors)
var/mob/M = pick(candidates)
assigned += M
candidates -= M
if (i > traitor_threshold)
if ((mode.threat > additional_cost))
mode.spend_threat(additional_cost)
else
break
return (assigned.len > 0)
/datum/dynamic_ruleset/roundstart/challengers/execute()
var/traitor_scaling_coeff = 10 - max(0,round(mode.threat_level/10)-5)//above 50 threat level, coeff goes down by 1 for every 10 levels
var/num_traitors = min(round(mode.roundstart_pop_ready / traitor_scaling_coeff) + 1, candidates.len)
num_traitors = max(required_candidates,num_traitors)
var/list/double_agents = list()
for (var/mob/M in assigned)
var/datum/role/traitor/challenger/newTraitor = new
double_agents += newTraitor
newTraitor.AssignToRole(M.mind,1)
newTraitor.Greet(GREET_ROUNDSTART)
if (double_agents.len > 1)
for (var/i = 1 to (double_agents.len - 1))
var/datum/role/traitor/challenger/myAgent = double_agents[i]
var/datum/role/traitor/challenger/myTarget = double_agents[i+1]
myAgent.assassination_target = myTarget
var/datum/role/traitor/challenger/myAgent = double_agents[double_agents.len]
var/datum/role/traitor/challenger/myTarget = double_agents[1]
myAgent.assassination_target = myTarget
//the objectives are properly created during ForgeObjectives() on the mode's PostSetup()
return 1
//////////////////////////////////////////////
// //
// CHANGELINGS ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/roundstart/changeling
name = "Changelings"
role_category = /datum/role/changeling
protected_from_jobs = list("Security Officer", "Warden","Merchant", "Head of Personnel", "Detective",
"Head of Security", "Captain", "Chief Engineer", "Chief Medical Officer", "Research Director", "Brig Medic")
restricted_from_jobs = list("AI","Cyborg","Mobile MMI")
enemy_jobs = list("Security Officer","Detective", "Warden", "Head of Security", "Captain")
required_pop = list(15,15,15,10,10,10,10,5,5,0)
required_candidates = 1
weight = BASE_RULESET_WEIGHT
weight_category = "Changeling"
cost = 18
requirements = list(80,70,60,60,30,20,10,10,10,10)
high_population_requirement = 30
// -- Currently a copypaste of traitors. Could be fixed to be less copy & paste.
/datum/dynamic_ruleset/roundstart/changeling/choose_candidates()
var/mob/M = pick(candidates)
assigned += M
candidates -= M
return (assigned.len > 0)
/datum/dynamic_ruleset/roundstart/changeling/execute()
for (var/mob/M in assigned)
var/datum/role/changeling/newChangeling = new
newChangeling.AssignToRole(M.mind,1)
//Assign to the hivemind faction
var/datum/faction/changeling/hivemind = find_active_faction_by_type(/datum/faction/changeling)
if(!hivemind)
hivemind = ticker.mode.CreateFaction(/datum/faction/changeling)
hivemind.OnPostSetup()
hivemind.HandleRecruitedRole(newChangeling)
newChangeling.ForgeObjectives()
newChangeling.Greet(GREET_ROUNDSTART)
return 1
//////////////////////////////////////////////
// //
// VAMPIRES ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/roundstart/vampire
name = "Vampires"
role_category = /datum/role/vampire
protected_from_jobs = list("Security Officer", "Warden","Merchant", "Head of Personnel", "Detective",
"Head of Security", "Captain", "Chief Engineer", "Chief Medical Officer", "Research Director", "Brig Medic")
restricted_from_jobs = list("AI","Cyborg","Mobile MMI", "Chaplain")
enemy_jobs = list("Security Officer","Detective", "Warden", "Head of Security", "Captain", "Chaplain")
required_pop = list(15,15,15,10,10,10,10,5,5,0)
required_candidates = 1
weight = BASE_RULESET_WEIGHT
weight_category = "Vampire"
cost = 15
requirements = list(80,70,60,60,30,20,10,10,10,10)
high_population_requirement = 30
var/vampire_threshold = 2
// -- Currently a copypaste of traitors. Could be fixed to be less copy & paste.
/datum/dynamic_ruleset/roundstart/vampire/choose_candidates()
var/num_vampires = min(round(mode.roundstart_pop_ready / 10) + 1, candidates.len)
for (var/i = 1 to num_vampires)
var/mob/M = pick(candidates)
assigned += M
candidates -= M
// Above 2 vampires, we start to cost a bit more.
if (i > vampire_threshold)
if ((mode.threat > cost))
mode.spend_threat(cost)
else
break
return (assigned.len > 0)
/datum/dynamic_ruleset/roundstart/vampire/execute()
for (var/mob/M in assigned)
var/datum/faction/vampire/fac = ticker.mode.CreateFaction(/datum/faction/vampire, null, 1)
var/datum/role/vampire/newVampire = new(M.mind, fac, override = TRUE)
newVampire.Greet(GREET_MASTER)
newVampire.AnnounceObjectives()
update_faction_icons()
return 1
//////////////////////////////////////////////
// //
// WIZARDS ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/roundstart/wizard
name = "Wizard"
role_category = /datum/role/wizard
enemy_jobs = list("Security Officer","Detective","Head of Security", "Captain")
required_pop = list(15,15,15,10,10,10,10,5,5,0)
required_candidates = 1
weight = BASE_RULESET_WEIGHT/2
weight_category = "Wizard"
cost = 30
requirements = list(90,90,70,40,30,20,10,10,10,10)
high_population_requirement = 40
var/list/roundstart_wizards = list()
/datum/dynamic_ruleset/roundstart/wizard/execute()
var/mob/new_player/M = pick(assigned)
if (M)
var/datum/role/wizard/newWizard = new
var/datum/faction/wizard/federation = find_active_faction_by_type(/datum/faction/wizard)
if (!federation)
federation = ticker.mode.CreateFaction(/datum/faction/wizard, null, 1)
var/mob/living/carbon/human/H = M.create_human(M.client.prefs)
H.forceMove(pick(wizardstart))
H.key = M.client.ckey
qdel(M)
newWizard.AssignToRole(H.mind,1)
roundstart_wizards += newWizard
federation.HandleRecruitedRole(newWizard)//this will give the wizard their icon
newWizard.Greet(GREET_ROUNDSTART)
return 1
//////////////////////////////////////////////
// //
// CIVIL WAR OF CASTERS ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/roundstart/cwc
name = "Civil War of Casters"
role_category = /datum/role/wizard
restricted_from_jobs = list("Head of Security", "Captain")//just to be sure that a wizard getting picked won't ever imply a Captain or HoS not getting drafted
enemy_jobs = list("Security Officer","Detective","Warden","Head of Security", "Captain")
required_pop = list(25,25,20,20,20,20,15,15,15,5)
required_candidates = 4
weight = BASE_RULESET_WEIGHT/2
weight_category = "CWC"
cost = 45
requirements = list(90,90,70,40,30,20,10,10,10,10)
high_population_requirement = 40
flags = HIGHLANDER_RULESET
// var/wizard_cd = 210 //7 minutes
var/total_wizards = 4
/datum/dynamic_ruleset/roundstart/cwc/choose_candidates()
for(var/wizards_number = 1 to total_wizards)
var/mob/new_player/M = pick(candidates)
assigned += M
candidates -= M
return (assigned.len > 0)
/datum/dynamic_ruleset/roundstart/cwc/execute()
var/datum/faction/wizard/civilwar/wpf/WPF = ticker.mode.CreateFaction(/datum/faction/wizard/civilwar/wpf, null, 1)
var/datum/faction/wizard/civilwar/pfw/PFW = ticker.mode.CreateFaction(/datum/faction/wizard/civilwar/pfw, null, 1)
for(var/mob/new_player/M in assigned)
var/datum/role/wizard/newWizard = new
if (WPF.members.len < PFW.members.len)
WPF.HandleRecruitedRole(newWizard)
else if (WPF.members.len > PFW.members.len)
PFW.HandleRecruitedRole(newWizard)
else if(prob(50))
WPF.HandleRecruitedRole(newWizard)
else
PFW.HandleRecruitedRole(newWizard)
var/mob/living/carbon/human/H = M.create_human(M.client.prefs)
H.forceMove(pick(wizardstart))
H.key = M.client.ckey
qdel(M)
newWizard.AssignToRole(H.mind,1)
newWizard.Greet(GREET_MIDROUND)
return 1
//////////////////////////////////////////////
// //
// BLOOD CULT ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/roundstart/bloodcult
name = "Blood Cult"
role_category = /datum/role/cultist
restricted_from_jobs = list("Merchant","AI", "Cyborg", "Mobile MMI", "Security Officer", "Warden", "Detective",
"Head of Security", "Captain", "Chaplain", "Head of Personnel", "Internal Affairs Agent",
"Chief Engineer", "Chief Medical Officer", "Research Director", "Brig Medic")
enemy_jobs = list("Security Officer","Warden", "Detective","Head of Security", "Captain")
required_pop = list(25,25,20,20,20,20,20,15,15,10)
required_candidates = 4
required_enemies = list(2,2,2,2,2,2,2,2,2,2)
weight = BASE_RULESET_WEIGHT
weight_category = "Cult"
cost = 30
requirements = list(90,80,60,30,20,10,10,10,10,10)
high_population_requirement = 40
var/cultist_cap = list(2,2,3,4,4,4,4,4,4,4)
//Readd this once proper round ending rituals are added
//flags = HIGHLANDER_RULESET
/datum/dynamic_ruleset/roundstart/bloodcult/ready(var/forced = 0)
var/indice_pop = min(10,round(mode.roundstart_pop_ready/5)+1)
required_candidates = cultist_cap[indice_pop]
if (forced)
required_candidates = 1
. = ..()
/datum/dynamic_ruleset/roundstart/bloodcult/choose_candidates()
var/indice_pop = min(10,round(mode.roundstart_pop_ready/5)+1)
var/cultists = cultist_cap[indice_pop]
for (var/i = 1 to cultists)
if(candidates.len <= 0)
break
var/mob/M = pick(candidates)
assigned += M
candidates -= M
return (assigned.len > 0)
/datum/dynamic_ruleset/roundstart/bloodcult/execute()
//if ready() did its job, candidates should have 4 or more members in it
var/datum/faction/bloodcult/cult = find_active_faction_by_type(/datum/faction/bloodcult)
if (!cult)
cult = ticker.mode.CreateFaction(/datum/faction/bloodcult, null, 1)
for(var/mob/M in assigned)
var/datum/role/cultist/newCultist = new
newCultist.AssignToRole(M.mind,1)
cult.HandleRecruitedRole(newCultist)
newCultist.Greet(GREET_ROUNDSTART)
return 1
//////////////////////////////////////////////
// //
// CULT (LEGACY) ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
//////////////////////////////////////////////
/*
NB IF UNCOMMENTING : setting `assigned` in execute() should not be done.
Assign your candidates in choose_candidates() instead.
/datum/dynamic_ruleset/roundstart/cult_legacy
name = "Cult (Legacy)"
role_category = /datum/role/legacy_cultist
role_category_override = CULTIST // H-ha
protected_from_jobs = list("Merchant")
restricted_from_jobs = list("AI", "Cyborg", "Mobile MMI", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Chaplain", "Head of Personnel", "Internal Affairs Agent", "Chaplain")
enemy_jobs = list("AI", "Cyborg", "Security Officer","Detective","Head of Security", "Captain", "Chaplain")
required_pop = list(25,25,20,20,20,20,20,15,15,10)
required_candidates = 4
weight = BASE_RULESET_WEIGHT
cost = 25
requirements = list(90,90,70,40,30,20,10,10,10,10)
high_population_requirement = 40
/datum/dynamic_ruleset/roundstart/cult_legacy/execute()
//if ready() did its job, candidates should have 4 or more members in it
var/datum/faction/cult/narsie/legacy = find_active_faction_by_type(/datum/faction/cult/narsie)
if (!legacy)
legacy = ticker.mode.CreateFaction(/datum/faction/cult/narsie, null, 1)
for(var/cultists_number = 1 to required_candidates)
if(candidates.len <= 0)
break
var/mob/M = pick(candidates)
assigned += M
candidates -= M
var/datum/role/legacy_cultist/newCultist = new
newCultist.AssignToRole(M.mind,1)
legacy.HandleRecruitedRole(newCultist)
newCultist.Greet(GREET_ROUNDSTART)
return 1
*/
//////////////////////////////////////////////
// //
// NUCLEAR OPERATIVES ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/roundstart/nuclear
name = "Nuclear Emergency"
role_category = /datum/role/nuclear_operative
role_category_override = "Nuke Operative" // this is what is used on the ban page
restricted_from_jobs = list("Head of Security", "Captain") //Just to be sure that a nukie getting picked won't ever imply a Captain or HoS not getting drafted
enemy_jobs = list("AI", "Cyborg", "Security Officer", "Warden","Detective","Head of Security", "Captain")
required_pop = list(25,25,20,20,20,20,20,15,15,10)
required_candidates = 5 //This value is useless, see operative_cap
required_enemies = list(2,2,2,2,2,2,2,2,2,2)
weight = BASE_RULESET_WEIGHT
weight_category = "Nuke"
cost = 30
requirements = list(90, 80, 60, 30, 20, 10, 10, 10, 10, 10)
high_population_requirement = 40
var/operative_cap = list(2, 2, 3, 3, 4, 5, 5, 5, 5, 5)
flags = HIGHLANDER_RULESET
/datum/dynamic_ruleset/roundstart/nuclear/ready(var/forced = 0)
var/indice_pop = min(10, round(mode.roundstart_pop_ready/5) + 1)
required_candidates = operative_cap[indice_pop]
if (forced)
required_candidates = 1
. = ..()
/datum/dynamic_ruleset/roundstart/nuclear/choose_candidates()
var/indice_pop = min(10, round(mode.roundstart_pop_ready/5) + 1)
var/operatives = operative_cap[indice_pop]
message_admins("[name]: indice_pop = [indice_pop], operatives = [operatives]")
for(var/operatives_number = 1 to operatives)
if(candidates.len <= 0)
break
var/mob/new_player/M = pick(candidates)
assigned += M
candidates -= M
return (assigned.len > 0)
/datum/dynamic_ruleset/roundstart/nuclear/execute()
//If ready() did its job, candidates should have 5 or more members in it
var/datum/faction/syndicate/nuke_op/nuclear = find_active_faction_by_type(/datum/faction/syndicate/nuke_op)
if(!nuclear)
nuclear = ticker.mode.CreateFaction(/datum/faction/syndicate/nuke_op, null, 1)
var/list/turf/synd_spawn = list()
for(var/obj/effect/landmark/A in landmarks_list)
if(A.name == "Syndicate-Spawn")
synd_spawn += get_turf(A)
continue
var/spawnpos = 1
var/leader = 1
for(var/mob/new_player/M in assigned)
if(spawnpos > synd_spawn.len)
spawnpos = 1
var/mob/living/carbon/human/H = M.create_human(M.client.prefs)
H.forceMove(synd_spawn[spawnpos])
H.key = M.client.ckey
qdel(M)
if(leader)
leader = 0
var/datum/role/nuclear_operative/leader/newCop = new
newCop.AssignToRole(H.mind, 1)
nuclear.HandleRecruitedRole(newCop)
newCop.Greet(GREET_ROUNDSTART)
else
var/datum/role/nuclear_operative/newCop = new
newCop.AssignToRole(H.mind, 1)
nuclear.HandleRecruitedRole(newCop)
newCop.Greet(GREET_ROUNDSTART)
spawnpos++
for(var/obj/effect/spawner/newbomb/timer/syndicate/bomb in syndicate_bomb_spawners)
bomb.spawnbomb()
return 1
//////////////////////////////////////////////
// //
// AI MALFUNCTION ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/roundstart/malf
name = "Malfunctioning AI"
role_category = /datum/role/malfAI
enemy_jobs = list("Security Officer", "Warden","Detective","Head of Security", "Captain", "Scientist", "Chemist", "Research Director", "Chief Engineer")
restricted_from_jobs = list("Captain","Head of Personnel","Head of Security","Chief Engineer","Research Director","Chief Medical Officer","Station Engineer","Atmospheric Technician","Mechanic","Medical Doctor","Geneticist","Virologist","Paramedic","Chemist","Orderly","Research Director","Scientist","Roboticist","Bartender","Botanist","Chef","Janitor","Librarian","Internal Affairs Agent","Chaplain","Clown","Mime","Assistant","Quartermaster","Cargo Technician","Shaft Miner","Warden","Detective","Security Officer","Cyborg", "Mobile MMI")
required_pop = list(25,25,25,20,20,20,15,15,15,15)
required_candidates = 1
weight = BASE_RULESET_WEIGHT
weight_category = "Malf"
cost = 40
requirements = list(90,80,70,60,50,40,40,30,30,20)
high_population_requirement = 60
flags = HIGHLANDER_RULESET
// NB : `M` will never be empty as `ready` made sure we have at least one candidate with malf AI on.
// This candidate will become an AI upon roundstart, eventually replacing other AIs candidates who do not have the preference.
// You should `not` perform any null checks on M. M being null is a sign of a problem and should runtime.
/datum/dynamic_ruleset/roundstart/malf/choose_candidates()
var/mob/M = progressive_job_search() //dynamic_rulesets.dm. Handles adding the guy to assigned.
if(!M)
return 0
if(M.mind.assigned_role != "AI")
for(var/mob/living/silicon/ai/player in player_list) //mode.candidates is everyone readied up, not to be confused with candidates
if(player != M) // This should always be true but in case something goes terribly terribly wrong we definitely do not want to end up displacing the malf AI
displace_AI(player)
break // There will only be one roundstart AI normally. In case of a triple-AI round we only need to displace one AI anyway.
//Now that we've replaced the eventual other AIs, we make sure this chosen candidate has the proper roles.
M.mind.assigned_role = "AI"
if(!isAI(M))
assigned.Remove(M)
M = M.AIize()
assigned.Add(M)
return (assigned.len > 0)
/datum/dynamic_ruleset/roundstart/malf/progressive_job_search()
for(var/job in job_priority)
for(var/mob/M in candidates)
if(M.mind.assigned_role == job && !jobban_isbanned(M, "AI"))
assigned += M
candidates -= M
return M
while(candidates.len)
var/mob/M = pick_n_take(candidates)
if(!jobban_isbanned(M, "AI"))
assigned += M
return M
/datum/dynamic_ruleset/roundstart/malf/execute()
var/datum/faction/malf/unction = find_active_faction_by_type(/datum/faction/malf)
if (!unction)
unction = ticker.mode.CreateFaction(/datum/faction/malf, null, 1)
var/mob/M = pick(assigned)
unction.HandleNewMind(M.mind)
var/datum/role/malfAI/MAI = M.mind.GetRole(MALF)
MAI.Greet()
return 1
/datum/dynamic_ruleset/roundstart/malf/proc/displace_AI(var/mob/displaced)
var/mob/new_player/old_AI = new
old_AI.ckey = displaced.ckey
old_AI.name = displaced.ckey
qdel(displaced)
old_AI.mind.assigned_role = null
var/list/shuffledoccupations = shuffle(job_master.occupations)
for(var/level = 3 to 1 step -1)
if(old_AI.mind.assigned_role)
break
for(var/datum/job/job in shuffledoccupations)
if(job_master.TryAssignJob(old_AI,level,job))
break
if(!old_AI.mind.assigned_role) // still no job
if(old_AI.client.prefs.get_pref(/datum/preference_setting/enum/alternate_option) == GET_RANDOM_JOB)
job_master.GiveRandomJob(old_AI)
else if(old_AI.client.prefs.get_pref(/datum/preference_setting/enum/alternate_option) == BE_ASSISTANT)
job_master.AssignRole(old_AI, "Assistant")
if(!old_AI.mind.assigned_role)
to_chat(old_AI, "<span class='danger'>You have been returned to lobby due to your job preferences being filled.")
log_admin("([old_AI.ckey]) was displaced by a malf AI and sent back to lobby.")
message_admins("([old_AI.ckey]) was displaced by a malf AI and started the game as a [old_AI.mind.assigned_role].")
old_AI.ready = 0
return
if(old_AI.mind.assigned_role=="AI" || old_AI.mind.assigned_role=="Cyborg" || old_AI.mind.assigned_role=="Mobile MMI")
old_AI.create_roundstart_silicon(old_AI.mind.assigned_role)
else
var/mob/living/carbon/human/new_character = old_AI.create_human(old_AI.client.prefs)
job_master.PostJobSetup(new_character)
EquipCustomItems(new_character)
log_admin("([old_AI.ckey]) was displaced by a malf AI and started the game as a [old_AI.mind.assigned_role].")
message_admins("([old_AI.ckey]) was displaced by a malf AI and started the game as a [old_AI.mind.assigned_role].")
qdel(old_AI)
//////////////////////////////////////////////
// //
// BLOB ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
//////////////////////////////////////////////
///////////////
//Currently disabled from rolling roundstart.
//Major source of grievances, as it would either end the round too fast or cause "blobstended" when it got killed.
//Even though it hasn't won a single round in months due to Liberator guns.
//Roundstart blob got axed so that midround blob may be buffed (and more fun).
///////////////
// /datum/dynamic_ruleset/roundstart/blob
// name = "Blob Conglomerate"
// role_category = /datum/role/blob_overmind/
// restricted_from_jobs = list("AI", "Cyborg", "Mobile MMI", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel")
// enemy_jobs = list("AI", "Cyborg", "Warden", "Head of Security", "Captain", "Quartermaster", "Head of Personnel", "Station Engineer", "Chief Engineer", "Atmospheric Technician")
// required_pop = list(30,25,25,20,20,20,15,15,15,15)
// required_enemies = list(4,4,4,4,4,4,4,3,2,1)
// required_candidates = 1
// weight = BASE_RULESET_WEIGHT
// weight_category = "Blob"
// weekday_rule_boost = list("Tue")
// cost = 45
// requirements = list(90,90,90,80,60,40,30,20,10,10)
// high_population_requirement = 70
// flags = HIGHLANDER_RULESET
// /datum/dynamic_ruleset/roundstart/blob/execute()
// var/datum/faction/blob_conglomerate/blob_fac = find_active_faction_by_type(/datum/faction/blob_conglomerate)
// if (!blob_fac)
// blob_fac = ticker.mode.CreateFaction(/datum/faction/blob_conglomerate, null, 1)
// var/blob_number = 1 + round(mode.roundstart_pop_ready/25) // + 1 Blob per 25 pop. ready.
// for (var/i = 1 to min(blob_number, assigned.len))
// var/mob/M = pick(assigned)
// blob_fac.HandleNewMind(M.mind)
// var/datum/role/blob = M.mind.GetRole(BLOBOVERMIND)
// blob.Greet(GREET_ROUNDSTART)
// switch(M.mind.assigned_role)
// if("Clown")
// blob_looks_player["clownscape"] = 32
// if("Station Engineer","Atmospheric Technician","Chief Engineer")
// blob_looks_player["AME"] = 32
// blob_looks_player["AME_new"] = 64
// if("Chaplain")
// blob_looks_player["skelleton"] = 64
// if("Security Officer","Detective","Head of Security","Warden")
// blob_looks_player["secblob"] = 32
// if (calledBy == "antag madness")//one core is plenty on antag madness
// break
// return 1
//////////////////////////////////////////////
// //
// EXTENDED ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/roundstart/extended
name = "Extended"
role_category = null
restricted_from_jobs = list()
enemy_jobs = list()
required_candidates = 0
weight = BASE_RULESET_WEIGHT * 0.5
weight_category = "Extended"
cost = 0
requirements = list(0,0,0,0,0,0,0,0,0,0)
high_population_requirement = 101
// 70% chance of allowing extended at 0-30 threat, then (100-threat)% chance. Requires 30 pop still.
/datum/dynamic_ruleset/roundstart/extended/ready(var/forced=0)
if(forced)
return TRUE
if (mode.roundstart_pop_ready < 30)
return FALSE
var/probability = clamp(mode.threat_level, 30, 100)
return !prob(probability)
/datum/dynamic_ruleset/roundstart/extended/choose_candidates()
return TRUE
/datum/dynamic_ruleset/roundstart/extended/trim_candidates()
return
/datum/dynamic_ruleset/roundstart/extended/execute()
message_admins("Starting a round of extended.")
log_admin("Starting a round of extended.")
admin_disable_rulesets = TRUE
log_admin("Dynamic rulesets are disabled in Extended.")
message_admins("Dynamic rulesets are disabled in Extended.")
return TRUE
//////////////////////////////////////////////
// //
// REVS ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/roundstart/delayed/revs
name = "Revolution"
role_category = /datum/role/revolutionary
restricted_from_jobs = list("Merchant","AI", "Cyborg", "Mobile MMI", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Internal Affairs Agent", "Brig Medic")
enemy_jobs = list("Security Officer","Detective","Head of Security", "Captain", "Warden")
required_pop = list(25,25,25,20,20,20,15,15,15,15)
required_candidates = 3
weight = BASE_RULESET_WEIGHT
weight_category = "Revolution"
cost = 40
requirements = list(101,101,70,40,30,20,10,10,10,10)
high_population_requirement = 50
delay = 5 MINUTES
var/required_heads = 3
flags = HIGHLANDER_RULESET
/datum/dynamic_ruleset/roundstart/delayed/revs/ready(var/forced = 0)
if (forced)
required_heads = 1
required_candidates = 1
if (!..())
return FALSE
var/head_check = 0
for (var/mob/player in player_list)
if (player.mind.assigned_role in command_positions)
head_check++
return (head_check >= required_heads)
/datum/dynamic_ruleset/roundstart/delayed/revs/choose_candidates()
var/max_canditates = 4
for(var/i = 1 to max_canditates)
if(candidates.len <= 0)
break
var/mob/M = pick(candidates)
assigned += M
candidates -= M
assigned_ckeys += M.ckey
return (assigned.len > 0)
/datum/dynamic_ruleset/roundstart/delayed/revs/execute()
var/datum/faction/revolution/R = find_active_faction_by_type(/datum/faction/revolution)
if (!R)
R = ticker.mode.CreateFaction(/datum/faction/revolution, null, 1)
for (var/rev_ckey in assigned_ckeys)
var/mob/M = find_player_by_ckey(rev_ckey)
var/datum/role/revolutionary/leader/lenin = new
lenin.AssignToRole(M.mind, 1, 1)
R.HandleRecruitedRole(lenin)
lenin.Greet(GREET_ROUNDSTART)
update_faction_icons()
R.OnPostSetup()
return 1
//////////////////////////////////////////////
// //
// THE GRINCH (holidays) ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/roundstart/grinch
name = "The Grinch"
role_category = /datum/role/grinch
restricted_from_jobs = list()
enemy_jobs = list()
required_pop = list(0,0,0,0,0,0,0,0,0,0)
required_candidates = 1
weight = BASE_RULESET_WEIGHT
weight_category = "Special"//Admin only
cost = 10
requirements = list(101,101,101,101,101,101,101,101,101,101) // So that's not possible to roll it naturally
high_population_requirement = 10
flags = MINOR_RULESET
/datum/dynamic_ruleset/roundstart/grinch/ready(var/forced=0)
if(grinchstart.len == 0)
log_admin("Cannot accept Grinch ruleset. Couldn't find any grinch spawn points.")
message_admins("Cannot accept Grinch ruleset. Couldn't find any grinch spawn points.")
return 0
if (!..())
return FALSE
var/MM = text2num(time2text(world.timeofday, "MM")) // get the current month
var/DD = text2num(time2text(world.timeofday, "DD")) // get the current day
var/accepted = (MM == 12 && DD > 15) || (MM == 1 && DD < 9) // Between the 15th of December and the 9th of January
return (accepted || forced)
/datum/dynamic_ruleset/roundstart/grinch/execute()
var/mob/new_player/M = pick(assigned)
if (M)
var/datum/role/grinch/newGrinch = new
var/mob/living/simple_animal/hostile/gremlin/grinch/G = new (pick(grinchstart))
G.key = M.client.ckey
qdel(M)
newGrinch.AssignToRole(G.mind,1)
newGrinch.Greet(GREET_ROUNDSTART)
G << sound(null, repeat = 0, wait = 0, volume = 85, channel = CHANNEL_LOBBY)// MAD JAMS cant last forever yo
return 1
//////////////////////////////////////////////
// //
// TAG MODE (speical) ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/roundstart/tag_mode
name = "Tag mode"
role_category = /datum/role/changeling/changeling_clown
restricted_from_jobs = list()
enemy_jobs = list()
required_pop = list(0,0,0,0,0,0,0,0,0,0)
required_candidates = 1
weight = BASE_RULESET_WEIGHT
weight_category = "Special"//Admin only
cost = 10
requirements = list(101,101,101,101,101,101,101,101,101,101) // So that's not possible to roll it naturally
high_population_requirement = 101
flags = MINOR_RULESET
/datum/dynamic_ruleset/roundstart/tag_mode/execute()
// Populate tagmode spawn list
for(var/obj/effect/landmark/A in landmarks_list)
if(A.name in valid_landmark_lists)
tag_mode_spawns += get_turf(A)
QDEL_NULL(A)
continue
init_tag_mode_spawns()
// Spawn the clown...
var/mob/M = pick(assigned)
var/datum/role/changeling/changeling_clown/clown = new
clown.AssignToRole(M.mind,1)
clown.Greet(GREET_ROUNDSTART)
// And everyone else as mimes.
for (var/mob/M2 in (living_mob_list - M))
if (!M2.mind || !M2.client)
continue
var/datum/role/tag_mode_mime/mime = new
mime.AssignToRole(M2.mind,1)
mime.Greet(GREET_ROUNDSTART)
return 1
//////////////////////////////////////////////
// //
// ANTAG MADNESS ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
//////////////////////////////////////////////
var/antag_madness = ANTAG_MADNESS_OFF
/datum/dynamic_ruleset/roundstart/antag_madness
name = "Antag Madness"
role_category = /datum/role/nanotrasen_official
role_category_override = HEAD
protected_from_jobs = list()
restricted_from_jobs = list()
cost = 0
weight_category = "Special"//Admin only
requirements = list(101,101,101,101,101,101,101,101,101,101) // Adminbus only
high_population_requirement = 101
persistent = TRUE//latejoiners will either be heads of staff or traitors (unless traitor is deactivated/antagbanned)
var/list/nanotrasen_staff = list("Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Internal Affairs Agent")
var/escalation_delay = 18 MINUTES
/datum/dynamic_ruleset/roundstart/antag_madness/trim_candidates()//All the heads of staff get the role, the rest of the players will get trimmed by the other rulesets
for(var/mob/P in candidates)
if (P.mind.assigned_role && (P.mind.assigned_role in nanotrasen_staff))
assigned.Add(P)
candidates.Remove(P)
/datum/dynamic_ruleset/roundstart/antag_madness/choose_candidates()
message_admins("DYNAMIC MODE: starting [name] with [assigned.len] heads of staff out of [candidates.len] players.")
log_admin("DYNAMIC MODE: starting [name] with [assigned.len] heads of staff out of [candidates.len] players.")
return TRUE
/datum/dynamic_ruleset/roundstart/antag_madness/ready(var/forced = 0)
return TRUE
/datum/dynamic_ruleset/roundstart/antag_madness/execute()
antag_madness = ANTAG_MADNESS_EARLY
//first we initialize the nanotrasen faction, even if there are no heads currently there
var/datum/faction/nanotrasen/nanotrasen = find_active_faction_by_type(/datum/faction/nanotrasen)
if (!nanotrasen)
nanotrasen = ticker.mode.CreateFaction(/datum/faction/nanotrasen, null, 1)
//next we throw every head currently there in it
for(var/mob/M in assigned)
var/datum/role/nanotrasen_official/head = new
head.name = M.mind.assigned_role
head.AssignToRole(M.mind,1)
nanotrasen.HandleRecruitedRole(head)
head.Greet(GREET_ROUNDSTART)
//and now we manipulate Dynamic to force a bunch of other rulesets. It just works...
var/list/the_madness = list(
"Malfunctioning AI",
"Blob Conglomerate",
"Blood Cult",
"Nuclear Emergency",
"Wizard",
"Revolution",
"Vampires",
"Changelings",
"Syndicate Traitors",
)//The order matters. As a rule of thumb, rarer rulesets should have their chance come first, while more common ones will fill the spots
if (assigned.len < 5)
the_madness -= "Revolution"//No revs unless there's at least 5 roundstart heads, because that'd be overkill
for (var/madness_ruleset in the_madness)
for (var/datum/dynamic_ruleset/roundstart/rule in mode.roundstart_rules)
if (madness_ruleset == rule.name)
rule.candidates = candidates.Copy()
rule.trim_candidates()
if (rule.ready(TRUE))
rule.calledBy = "antag madness"
rule.required_candidates = 1//because we're funny
if (madness_ruleset == "Syndicate Traitors")//no traitor limits
rule.protected_from_jobs = list()
rule.restricted_from_jobs = list("Mobile MMI")
for (var/mob/M in candidates)
rule.assigned += M
rule.candidates -= M
else
rule.choose_candidates()
if (rule.assigned.len)
message_admins("ANTAG MADNESS: <font size='3'>[rule.name]</font> OK!")
log_admin("ANTAG MADNESS: <font size='3'>[rule.name]</font> OK!")
mode.threat_log += "[worldtime2text()]: Roundstart [rule.name] forced by antag madness"
if (istype(rule, /datum/dynamic_ruleset/roundstart/delayed/))
var/datum/dynamic_ruleset/roundstart/delayed/delayed_ruleset = rule
message_admins("ANTAG MADNESS: with a delay of [delayed_ruleset.delay/10] seconds.")
log_admin("ANTAG MADNESS: with a delay of [delayed_ruleset.delay/10] seconds.")
mode.pick_delay(rule)
if (rule.execute())
rule.stillborn = IsRoundAboutToEnd()
mode.executed_rules += rule
if (rule.persistent)
mode.current_rules += rule
for(var/mob/M in rule.assigned)
candidates -= M
else
message_admins("ANTAG MADNESS: <font size='3'>[rule.name]</font> FAILED!")
log_admin("ANTAG MADNESS: <font size='3'>[rule.name]</font> FAILED!")
else
message_admins("ANTAG MADNESS: <font size='3'>[rule.name]</font> FAILED!")
log_admin("ANTAG MADNESS: <font size='3'>[rule.name]</font> FAILED!")
spawn(10)
message_admins("<span class='danger'>Antag Madness is now underway. In this very chaotic mode, admins are encouraged to proactively interfere with the round to keep things interesting, and help it reach a conclusion if necessary. Rev may no longer cause the round to suddenly end if all heads are dead, Malf, Blob and Nuke Ops still can if they destroy the station. If things get stale after 30 minutes and there are lots of dead players, consider bringing the round to a conclusion by either forcing a shuttle call, sending either the deathsquad or elite syndies to destroy the station, or trigger a supermatter cascade (or combination of those).</span>")
log_admin("<span class='danger'>Antag Madness is now underway. In this very chaotic mode, admins are encouraged to proactively interfere with the round to keep things interesting, and help it reach a conclusion if necessary. Rev may no longer cause the round to suddenly end if all heads are dead, Malf, Blob and Nuke Ops still can if they destroy the station. If things get stale after 30 minutes and there are lots of dead players, consider bringing the round to a conclusion by either forcing a shuttle call, sending either the deathsquad or elite syndies to destroy the station, or trigger a supermatter cascade (or combination of those).</span>")
spawn(escalation_delay)//ERT calling is automated after 18 minutes. Any further developments will have to be manually enacted by the badmin who forced this terrible ruleset in the first place. have fun!
antag_madness = ANTAG_MADNESS_LATE
var/datum/striketeam/ert/response_team = new()
response_team.trigger_strike(null,"Nanotrasen officials have been misled to a dummy Space Station filled with antagonistic forces. You must find, protect, and retrieve the various Heads of Staff and Internal Affair Agents aboard the station. Anyone else is a potential threat that must be dealt with extreme prejudice.")
for(var/mob/living/player in player_list)
if (!player.client)
continue
if (player.stat == DEAD)
continue
if (isMoMMI(player))
continue
if (!isanyantag(player))
var/datum/role/survivor/R = new
R.AssignToRole(player.mind,1)
R.Greet(GREET_MADNESSSURVIVOR)
R.OnPostSetup()
R.ForgeObjectives()
R.AnnounceObjectives()
return 1
/datum/dynamic_ruleset/roundstart/antag_madness/latespawn_interaction(var/mob/living/newPlayer)
var/datum/faction/nanotrasen/nanotrasen = find_active_faction_by_type(/datum/faction/nanotrasen)
if (!nanotrasen)
nanotrasen = mode.CreateFaction(/datum/faction/nanotrasen, null, 1)
if (newPlayer.mind.assigned_role && (newPlayer.mind.assigned_role in nanotrasen_staff))//latejoining as a head? better start running
var/datum/role/nanotrasen_official/head = new
head.name = newPlayer.mind.assigned_role
head.AssignToRole(newPlayer.mind,1)
nanotrasen.HandleRecruitedRole(head)
switch(nanotrasen.delta)
if (0)
head.Greet(GREET_LATEJOIN)
if (1)
head.Greet(GREET_LATEJOIN_ERT_COMING)
if (2)
head.Greet(GREET_LATEJOIN_ERT_NOT_COMING)
return TRUE
else
for (var/datum/dynamic_ruleset/latejoin/rule in mode.latejoin_rules)//otherwise, you may become a traitor
if (rule.name == "Syndicate Infiltrator")
rule.candidates = list(newPlayer)
rule.protected_from_jobs = list()
rule.restricted_from_jobs = list("Mobile MMI")
rule.trim_candidates()//if you have it enabled/aren't traitorbanned
for (var/mob/M in rule.candidates)
var/datum/role/traitor/newTraitor = new
newTraitor.AssignToRole(M.mind,1)
if (nanotrasen.delta)
newTraitor.Greet(GREET_LATEJOINMADNESS)
else
newTraitor.Greet(GREET_LATEJOIN)
return TRUE
//tator disabled? tator banned? whatever you'll be a survivor if the round has gone on long enough
if (antag_madness == ANTAG_MADNESS_LATE)
var/datum/role/survivor/R = new
R.AssignToRole(newPlayer.mind,1)
R.Greet(GREET_MADNESSSURVIVOR)
R.OnPostSetup()
R.ForgeObjectives()
R.AnnounceObjectives()
return TRUE
return FALSE