Files
Bubberstation/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm
Time-Green 328d001047 STATION TRAIT GAMEMODE: Station-Wide Background Checks (#83307)
## About The Pull Request
Adds a new station trait: Station-Wide Background Checks!

It does two things: 
1. Blocks most crew-side antagonists. No traitors, changelings, spies,
heretics, etc. You won't be able to fully trust your crew though, as
Space changelings, Paradox Clones, Obsesseds and Blob Infected are
excempted crew-antags since a background check doesn't really help here.
Other antagonists still spawn: pirates, revenants, blobs, aliens,
nukies, wizards etc. Expect a LOT more of these, as Dynamic is gonna put
threat somewhere...

2. Reduces dynamics threat slightly, configurable per server, but
defaults to 15.

It is essentially the first "dynamic gamemode".

## Why It's Good For The Game

Blocking crew antagonists changes the shifts dynamic, similair to old
warops. Security can "trust" crew to not be antagonists, and instead can
focus more on petty crimes and hunting down external threats.

Due to the increased chance of external threats and reduced chance of
internal threats, the crew can focus its defenses outwards. Don't worry
about your coworker killing you (intentionally/probably), but do worry a
lot more about the pirates trying to break through your hull, or alien
nests growing in virology.

I've also reduced total threat count slightly because the idea of 90
threat being dumped into ghost spawns kinda terrifies me and I do want
people to be able to let their guard down a slight bit. It can be
reduced/disabled for servers that already tend to lower threats.

I think it's a lot of fun to change the paranoia dynamic, and a fun
deviation from a normal round of spaceman13.

## Changelog
🆑
add: Station-Wide Background Checks (station trait, rare): Disables crew
antagonists, but get a lot more non-crew antagonists
/🆑
I want to do more like these (this was just an example I threw into
discord to annoy @Mothblocks but I realized I kinda liked), and this is
a good opportunity to gather community feedback and see how it plays!

---------

Co-authored-by: carlarctg <53100513+carlarctg@users.noreply.github.com>
2024-05-31 10:08:15 -07:00

952 lines
35 KiB
Plaintext

/// Probability the AI going malf will be accompanied by an ion storm announcement and some ion laws.
#define MALF_ION_PROB 33
/// The probability to replace an existing law with an ion law instead of adding a new ion law.
#define REPLACE_LAW_WITH_ION_PROB 10
/// Midround Rulesets
/datum/dynamic_ruleset/midround // Can be drafted once in a while during a round
ruletype = MIDROUND_RULESET
var/midround_ruleset_style
/// If the ruleset should be restricted from ghost roles.
var/restrict_ghost_roles = TRUE
/// What mob type the ruleset is restricted to.
var/required_type = /mob/living/carbon/human
var/list/living_players = list()
var/list/living_antags = list()
var/list/dead_players = list()
var/list/list_observers = list()
/// The minimum round time before this ruleset will show up
var/minimum_round_time = 0
/// Abstract root value
var/abstract_type = /datum/dynamic_ruleset/midround
/datum/dynamic_ruleset/midround/forget_startup()
living_players = list()
living_antags = list()
dead_players = list()
list_observers = list()
return ..()
/datum/dynamic_ruleset/midround/from_ghosts
weight = 0
required_type = /mob/dead/observer
abstract_type = /datum/dynamic_ruleset/midround/from_ghosts
/// Whether the ruleset should call generate_ruleset_body or not.
var/makeBody = TRUE
/// The rule needs this many applicants to be properly executed.
var/required_applicants = 1
///Path of an item to show up in ghost polls for applicants to sign up.
var/signup_item_path = /obj/structure/sign/poster/contraband/syndicate_recruitment
/datum/dynamic_ruleset/midround/from_ghosts/check_candidates()
var/dead_count = dead_players.len + list_observers.len
if (required_candidates <= dead_count)
return TRUE
log_dynamic("FAIL: [src], a from_ghosts ruleset, did not have enough dead candidates: [required_candidates] needed, [dead_count] found")
return FALSE
/datum/dynamic_ruleset/midround/trim_candidates()
living_players = trim_list(GLOB.alive_player_list)
living_antags = trim_list(GLOB.current_living_antags)
dead_players = trim_list(GLOB.dead_player_list)
list_observers = trim_list(GLOB.current_observers_list)
/datum/dynamic_ruleset/midround/proc/trim_list(list/to_trim = list())
var/list/trimmed_list = to_trim.Copy()
for(var/mob/creature in trimmed_list)
if (!istype(creature, required_type))
trimmed_list.Remove(creature)
continue
if (isnull(creature.client)) // Are they connected?
trimmed_list.Remove(creature)
continue
if(creature.client.get_remaining_days(minimum_required_age) > 0)
trimmed_list.Remove(creature)
continue
if (!((antag_preference || antag_flag) in creature.client.prefs.be_special))
trimmed_list.Remove(creature)
continue
if (is_banned_from(creature.ckey, list(antag_flag_override || antag_flag, ROLE_SYNDICATE)))
trimmed_list.Remove(creature)
continue
if (isnull(creature.mind))
continue
if (restrict_ghost_roles && !(creature.mind.assigned_role.job_flags & JOB_CREW_MEMBER)) // Are they not playing a station role?
trimmed_list.Remove(creature)
continue
if (creature.mind.assigned_role.title in restricted_roles) // Does their job allow it?
trimmed_list.Remove(creature)
continue
if (length(exclusive_roles) && !(creature.mind.assigned_role.title in exclusive_roles)) // Is the rule exclusive to their job?
trimmed_list.Remove(creature)
continue
if(HAS_TRAIT(creature, TRAIT_MIND_TEMPORARILY_GONE)) // are they out of body?
trimmed_list.Remove(creature)
continue
if(HAS_TRAIT(creature, TRAIT_TEMPORARY_BODY)) // are they an avatar?
trimmed_list.Remove(creature)
continue
return trimmed_list
// You can then for example prompt dead players in execute() to join as strike teams or whatever
// Or autotator someone
// IMPORTANT, since /datum/dynamic_ruleset/midround may accept candidates from both living, dead, and even antag players
// subtype your midround with /from_ghosts or /from_living to get candidate checking. Or check yourself by subtyping from neither
/datum/dynamic_ruleset/midround/ready(forced = FALSE)
if (forced)
return TRUE
var/job_check = 0
if (enemy_roles.len > 0)
for (var/mob/M in GLOB.alive_player_list)
if (M.stat == DEAD || !M.client)
continue // Dead/disconnected players cannot count as opponents
if (M.mind && (M.mind.assigned_role.title in enemy_roles) && (!(M in candidates) || (M.mind.assigned_role.title in restricted_roles)))
job_check++ // Checking for "enemies" (such as sec officers). To be counters, they must either not be candidates to that rule, or have a job that restricts them from it
var/threat = round(SSdynamic.threat_level/10)
var/ruleset_forced = (GLOB.dynamic_forced_rulesets[type] || RULESET_NOT_FORCED) == RULESET_FORCE_ENABLED
if (!ruleset_forced && job_check < required_enemies[threat])
log_dynamic("FAIL: [src] is not ready, because there are not enough enemies: [required_enemies[threat]] needed, [job_check] found")
return FALSE
return TRUE
/datum/dynamic_ruleset/midround/from_ghosts/execute()
var/list/possible_candidates = list()
possible_candidates.Add(dead_players)
possible_candidates.Add(list_observers)
send_applications(possible_candidates)
if(assigned.len > 0)
return TRUE
else
return FALSE
/// This sends a poll to ghosts if they want to be a ghost spawn from a ruleset.
/datum/dynamic_ruleset/midround/from_ghosts/proc/send_applications(list/possible_volunteers = list())
if (possible_volunteers.len <= 0) // This shouldn't happen, as ready() should return FALSE if there is not a single valid candidate
message_admins("Possible volunteers was 0. This shouldn't appear, because of ready(), unless you forced it!")
return
SSdynamic.log_dynamic_and_announce("Polling [possible_volunteers.len] players to apply for the [name] ruleset.")
candidates = SSpolling.poll_ghost_candidates(
question = "Looking for volunteers to become [span_notice(antag_flag)] for [span_danger(name)]",
check_jobban = antag_flag_override,
role = antag_flag || antag_flag_override,
poll_time = 30 SECONDS,
alert_pic = signup_item_path,
role_name_text = antag_flag,
)
if(!candidates || candidates.len <= 0)
SSdynamic.log_dynamic_and_announce("The ruleset [name] received no applications.")
SSdynamic.executed_rules -= src
attempt_replacement()
return
SSdynamic.log_dynamic_and_announce("[candidates.len] players volunteered for [name].")
review_applications()
/// Here is where you can check if your ghost applicants are valid for the ruleset.
/// Called by send_applications().
/datum/dynamic_ruleset/midround/from_ghosts/proc/review_applications()
if(candidates.len < required_applicants)
SSdynamic.executed_rules -= src
return
for (var/i = 1, i <= required_candidates, i++)
if(candidates.len <= 0)
break
var/mob/applicant = pick(candidates)
candidates -= applicant
if(!isobserver(applicant))
if(applicant.stat == DEAD) // Not an observer? If they're dead, make them one.
applicant = applicant.ghostize(FALSE)
else // Not dead? Disregard them, pick a new applicant
i--
continue
if(!applicant)
i--
continue
assigned += applicant
finish_applications()
/// Here the accepted applications get generated bodies and their setup is finished.
/// Called by review_applications()
/datum/dynamic_ruleset/midround/from_ghosts/proc/finish_applications()
var/i = 0
for(var/mob/applicant as anything in assigned)
i++
var/mob/new_character = applicant
if(makeBody)
new_character = generate_ruleset_body(applicant)
SEND_GLOBAL_SIGNAL(COMSIG_RULESET_BODY_GENERATED_FROM_GHOSTS, applicant)
finish_setup(new_character, i)
notify_ghosts(
"[applicant.name] has been picked for the ruleset [name]!",
source = new_character,
)
/datum/dynamic_ruleset/midround/from_ghosts/proc/generate_ruleset_body(mob/applicant)
var/mob/living/carbon/human/new_character = make_body(applicant)
new_character.dna.remove_all_mutations()
return new_character
/datum/dynamic_ruleset/midround/from_ghosts/proc/finish_setup(mob/new_character, index)
var/datum/antagonist/new_role = new antag_datum()
setup_role(new_role)
new_character.mind.add_antag_datum(new_role)
new_character.mind.special_role = antag_flag
/datum/dynamic_ruleset/midround/from_ghosts/proc/setup_role(datum/antagonist/new_role)
return
/// Fired when there are no valid candidates. Will spawn a sleeper agent or latejoin traitor.
/datum/dynamic_ruleset/midround/from_ghosts/proc/attempt_replacement()
var/datum/dynamic_ruleset/midround/from_living/autotraitor/sleeper_agent = new
SSdynamic.configure_ruleset(sleeper_agent)
if (!SSdynamic.picking_specific_rule(sleeper_agent))
return
SSdynamic.picking_specific_rule(/datum/dynamic_ruleset/latejoin/infiltrator)
///subtype to handle checking players
/datum/dynamic_ruleset/midround/from_living
weight = 0
abstract_type = /datum/dynamic_ruleset/midround/from_living
/datum/dynamic_ruleset/midround/from_living/ready(forced)
if(!check_candidates())
return FALSE
return ..()
/// Midround Traitor Ruleset (From Living)
/datum/dynamic_ruleset/midround/from_living/autotraitor
name = "Syndicate Sleeper Agent"
midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT
antag_datum = /datum/antagonist/traitor/infiltrator/sleeper_agent
antag_flag = ROLE_SLEEPER_AGENT
antag_flag_override = ROLE_TRAITOR
protected_roles = list(
JOB_CAPTAIN,
JOB_DETECTIVE,
JOB_HEAD_OF_PERSONNEL,
JOB_HEAD_OF_SECURITY,
JOB_PRISONER,
JOB_SECURITY_OFFICER,
JOB_WARDEN,
)
restricted_roles = list(
JOB_AI,
JOB_CYBORG,
ROLE_POSITRONIC_BRAIN,
)
required_candidates = 1
weight = 35
cost = 3
requirements = list(3,3,3,3,3,3,3,3,3,3)
repeatable = TRUE
/datum/dynamic_ruleset/midround/from_living/autotraitor/trim_candidates()
..()
candidates = living_players
for(var/mob/living/player in candidates)
if(issilicon(player)) // Your assigned role doesn't change when you are turned into a silicon.
candidates -= player
else if(is_centcom_level(player.z))
candidates -= player // We don't autotator people in CentCom
else if(player.mind && (player.mind.special_role || player.mind.antag_datums?.len > 0))
candidates -= player // We don't autotator people with roles already
/datum/dynamic_ruleset/midround/from_living/autotraitor/execute()
var/mob/M = pick(candidates)
assigned += M
candidates -= M
var/datum/antagonist/traitor/infiltrator/sleeper_agent/newTraitor = new
M.mind.add_antag_datum(newTraitor)
message_admins("[ADMIN_LOOKUPFLW(M)] was selected by the [name] ruleset and has been made into a midround traitor.")
log_dynamic("[key_name(M)] was selected by the [name] ruleset and has been made into a midround traitor.")
return TRUE
/// Midround Malf AI Ruleset (From Living)
/datum/dynamic_ruleset/midround/malf
name = "Malfunctioning AI"
midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY
antag_datum = /datum/antagonist/malf_ai
antag_flag = ROLE_MALF_MIDROUND
antag_flag_override = ROLE_MALF
enemy_roles = list(
JOB_CHEMIST,
JOB_CHIEF_ENGINEER,
JOB_HEAD_OF_SECURITY,
JOB_RESEARCH_DIRECTOR,
JOB_SCIENTIST,
JOB_SECURITY_OFFICER,
JOB_WARDEN,
)
exclusive_roles = list(JOB_AI)
required_enemies = list(4,4,4,4,4,4,2,2,2,0)
required_candidates = 1
minimum_players = 25
weight = 2
cost = 10
required_type = /mob/living/silicon/ai
blocking_rules = list(/datum/dynamic_ruleset/roundstart/malf_ai)
// AIs are technically considered "Ghost roles" as far as candidate selection are concerned
// So we need to allow it here. We filter of actual ghost role AIs (charlie) via trim_candidates ourselves
restrict_ghost_roles = FALSE
/datum/dynamic_ruleset/midround/malf/trim_candidates()
..()
candidates = list()
for(var/mob/living/silicon/ai/player in living_players)
if(!is_station_level(player.z))
continue
if(isnull(player.mind))
continue
if(player.mind.special_role || length(player.mind.antag_datums))
continue
candidates += player
/datum/dynamic_ruleset/midround/malf/ready(forced)
if(!check_candidates())
log_dynamic("FAIL: No valid AI found for the Malfunctioning AI ruleset.")
return FALSE
return ..()
/datum/dynamic_ruleset/midround/malf/execute()
var/mob/living/silicon/ai/new_malf_ai = pick_n_take(candidates)
assigned += new_malf_ai.mind
var/datum/antagonist/malf_ai/malf_antag_datum = new
new_malf_ai.mind.special_role = antag_flag
new_malf_ai.mind.add_antag_datum(malf_antag_datum)
if(prob(MALF_ION_PROB))
priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", ANNOUNCER_IONSTORM)
if(prob(REPLACE_LAW_WITH_ION_PROB))
new_malf_ai.replace_random_law(generate_ion_law(), list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION), LAW_ION)
else
new_malf_ai.add_ion_law(generate_ion_law())
return TRUE
/// Midround Wizard Ruleset (From Ghosts)
/datum/dynamic_ruleset/midround/from_ghosts/wizard
name = "Wizard"
midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY
antag_datum = /datum/antagonist/wizard
antag_flag = ROLE_WIZARD_MIDROUND
antag_flag_override = ROLE_WIZARD
ruleset_category = parent_type::ruleset_category | RULESET_CATEGORY_NO_WITTING_CREW_ANTAGONISTS
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 1
weight = 1
cost = 10
requirements = REQUIREMENTS_VERY_HIGH_THREAT_NEEDED
flags = HIGH_IMPACT_RULESET
ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_WIZARDDEN)
/datum/dynamic_ruleset/midround/from_ghosts/wizard/ready(forced = FALSE)
if(!check_candidates())
return FALSE
if(!length(GLOB.wizardstart))
log_admin("Cannot accept Wizard ruleset. Couldn't find any wizard spawn points.")
message_admins("Cannot accept Wizard ruleset. Couldn't find any wizard spawn points.")
return FALSE
return ..()
/datum/dynamic_ruleset/midround/from_ghosts/wizard/finish_setup(mob/new_character, index)
..()
new_character.forceMove(pick(GLOB.wizardstart))
/// Midround Nuclear Operatives Ruleset (From Ghosts)
/datum/dynamic_ruleset/midround/from_ghosts/nuclear
name = "Nuclear Assault"
midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY
antag_flag = ROLE_OPERATIVE_MIDROUND
antag_flag_override = ROLE_OPERATIVE
antag_datum = /datum/antagonist/nukeop
ruleset_category = parent_type::ruleset_category | RULESET_CATEGORY_NO_WITTING_CREW_ANTAGONISTS
enemy_roles = list(
JOB_AI,
JOB_CYBORG,
JOB_CAPTAIN,
JOB_DETECTIVE,
JOB_HEAD_OF_SECURITY,
JOB_SECURITY_OFFICER,
JOB_WARDEN,
)
required_enemies = list(3,3,3,3,3,2,1,1,0,0)
required_candidates = 5
weight = 5
cost = 7
minimum_round_time = 70 MINUTES
requirements = REQUIREMENTS_VERY_HIGH_THREAT_NEEDED
ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_NUKIEBASE)
flags = HIGH_IMPACT_RULESET
var/list/operative_cap = list(2,2,3,3,4,5,5,5,5,5)
/datum/dynamic_ruleset/midround/from_ghosts/nuclear/acceptable(population=0, threat_level=0)
if (locate(/datum/dynamic_ruleset/roundstart/nuclear) in SSdynamic.executed_rules)
return FALSE // Unavailable if nuke ops were already sent at roundstart
indice_pop = min(operative_cap.len, round(living_players.len/5)+1)
required_candidates = operative_cap[indice_pop]
return ..()
/datum/dynamic_ruleset/midround/from_ghosts/nuclear/ready(forced = FALSE)
if (!check_candidates())
return FALSE
return ..()
/datum/dynamic_ruleset/midround/from_ghosts/nuclear/finish_applications()
var/mob/leader = get_most_experienced(assigned, ROLE_NUCLEAR_OPERATIVE)
if(leader)
assigned.Remove(leader)
assigned.Insert(1, leader)
return ..()
/datum/dynamic_ruleset/midround/from_ghosts/nuclear/finish_setup(mob/new_character, index)
new_character.mind.set_assigned_role(SSjob.GetJobType(/datum/job/nuclear_operative))
new_character.mind.special_role = ROLE_NUCLEAR_OPERATIVE
if(index == 1)
var/datum/antagonist/nukeop/leader/leader_antag_datum = new()
new_character.mind.add_antag_datum(leader_antag_datum)
return
return ..()
/// Midround Blob Ruleset (From Ghosts)
/datum/dynamic_ruleset/midround/from_ghosts/blob
name = "Blob"
midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY
antag_datum = /datum/antagonist/blob
antag_flag = ROLE_BLOB
ruleset_category = parent_type::ruleset_category | RULESET_CATEGORY_NO_WITTING_CREW_ANTAGONISTS
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 1
minimum_round_time = 35 MINUTES
weight = 3
cost = 8
minimum_players = 25
repeatable = TRUE
/datum/dynamic_ruleset/midround/from_ghosts/blob/generate_ruleset_body(mob/applicant)
var/body = applicant.become_overmind()
return body
/// Midround Blob Infection Ruleset (From Living)
/datum/dynamic_ruleset/midround/from_living/blob_infection
name = "Blob Infection"
midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY
antag_datum = /datum/antagonist/blob/infection
antag_flag = ROLE_BLOB_INFECTION
antag_flag_override = ROLE_BLOB
ruleset_category = parent_type::ruleset_category | RULESET_CATEGORY_NO_WITTING_CREW_ANTAGONISTS
protected_roles = list(
JOB_CAPTAIN,
JOB_DETECTIVE,
JOB_HEAD_OF_SECURITY,
JOB_PRISONER,
JOB_SECURITY_OFFICER,
JOB_WARDEN,
)
restricted_roles = list(
JOB_AI,
JOB_CYBORG,
ROLE_POSITRONIC_BRAIN,
)
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 1
minimum_round_time = 35 MINUTES
weight = 3
cost = 10
minimum_players = 25
repeatable = TRUE
/datum/dynamic_ruleset/midround/from_living/blob_infection/trim_candidates()
..()
candidates = living_players
for(var/mob/living/player as anything in candidates)
var/turf/player_turf = get_turf(player)
if(!player_turf || !is_station_level(player_turf.z))
candidates -= player
continue
if(player.mind && (player.mind.special_role || length(player.mind.antag_datums) > 0))
candidates -= player
/datum/dynamic_ruleset/midround/from_living/blob_infection/execute()
if(!candidates || !candidates.len)
return FALSE
var/mob/living/carbon/human/blob_antag = pick_n_take(candidates)
assigned += blob_antag.mind
blob_antag.mind.special_role = antag_flag
notify_ghosts(
"[blob_antag] has become a blob host!",
source = blob_antag,
header = "So Bulbous...",
)
return ..()
/// Midround Xenomorph Ruleset (From Ghosts)
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph
name = "Alien Infestation"
midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY
antag_datum = /datum/antagonist/xeno
antag_flag = ROLE_ALIEN
ruleset_category = parent_type::ruleset_category | RULESET_CATEGORY_NO_WITTING_CREW_ANTAGONISTS
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 1
minimum_round_time = 40 MINUTES
weight = 5
cost = 10
minimum_players = 25
repeatable = TRUE
var/list/vents = list()
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/forget_startup()
vents = list()
return ..()
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/execute()
// 50% chance of being incremented by one
required_candidates += prob(50)
var/list/vent_pumps = SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/atmospherics/components/unary/vent_pump)
for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent as anything in vent_pumps)
if(QDELETED(temp_vent))
continue
if(is_station_level(temp_vent.loc.z) && !temp_vent.welded)
var/datum/pipeline/temp_vent_parent = temp_vent.parents[1]
if(!temp_vent_parent)
continue // No parent vent
// Stops Aliens getting stuck in small networks.
// See: Security, Virology
if(temp_vent_parent.other_atmos_machines.len > 20)
vents += temp_vent
if(!vents.len)
return FALSE
. = ..()
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/generate_ruleset_body(mob/applicant)
var/obj/vent = pick_n_take(vents)
var/mob/living/carbon/alien/larva/new_xeno = new(vent.loc)
new_xeno.key = applicant.key
new_xeno.move_into_vent(vent)
message_admins("[ADMIN_LOOKUPFLW(new_xeno)] has been made into an alien by the midround ruleset.")
log_dynamic("[key_name(new_xeno)] was spawned as an alien by the midround ruleset.")
return new_xeno
/// Midround Nightmare Ruleset (From Ghosts)
/datum/dynamic_ruleset/midround/from_ghosts/nightmare
name = "Nightmare"
midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT
antag_datum = /datum/antagonist/nightmare
antag_flag = ROLE_NIGHTMARE
antag_flag_override = ROLE_ALIEN
ruleset_category = parent_type::ruleset_category | RULESET_CATEGORY_NO_WITTING_CREW_ANTAGONISTS
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 1
weight = 3
cost = 5
minimum_players = 15
repeatable = TRUE
/datum/dynamic_ruleset/midround/from_ghosts/nightmare/acceptable(population = 0, threat_level = 0)
var/turf/spawn_loc = find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE) //Checks if there's a single safe, dark tile on station.
if(!spawn_loc)
return FALSE
return ..()
/datum/dynamic_ruleset/midround/from_ghosts/nightmare/generate_ruleset_body(mob/applicant)
var/datum/mind/player_mind = new /datum/mind(applicant.key)
player_mind.active = TRUE
var/mob/living/carbon/human/new_nightmare = new (find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE))
player_mind.transfer_to(new_nightmare)
player_mind.set_assigned_role(SSjob.GetJobType(/datum/job/nightmare))
player_mind.special_role = ROLE_NIGHTMARE
player_mind.add_antag_datum(/datum/antagonist/nightmare)
new_nightmare.set_species(/datum/species/shadow/nightmare)
playsound(new_nightmare, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1)
message_admins("[ADMIN_LOOKUPFLW(new_nightmare)] has been made into a Nightmare by the midround ruleset.")
log_dynamic("[key_name(new_nightmare)] was spawned as a Nightmare by the midround ruleset.")
return new_nightmare
/// Midround Space Dragon Ruleset (From Ghosts)
/datum/dynamic_ruleset/midround/from_ghosts/space_dragon
name = "Space Dragon"
midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY
antag_datum = /datum/antagonist/space_dragon
antag_flag = ROLE_SPACE_DRAGON
ruleset_category = parent_type::ruleset_category | RULESET_CATEGORY_NO_WITTING_CREW_ANTAGONISTS
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 1
weight = 4
cost = 7
minimum_players = 25
repeatable = TRUE
var/list/spawn_locs = list()
/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/forget_startup()
spawn_locs = list()
return ..()
/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/execute()
for(var/obj/effect/landmark/carpspawn/C in GLOB.landmarks_list)
spawn_locs += (C.loc)
if(!spawn_locs.len)
message_admins("No valid spawn locations found, aborting...")
return MAP_ERROR
. = ..()
/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/generate_ruleset_body(mob/applicant)
var/datum/mind/player_mind = new /datum/mind(applicant.key)
player_mind.active = TRUE
var/mob/living/basic/space_dragon/S = new (pick(spawn_locs))
player_mind.transfer_to(S)
player_mind.add_antag_datum(/datum/antagonist/space_dragon)
playsound(S, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1)
message_admins("[ADMIN_LOOKUPFLW(S)] has been made into a Space Dragon by the midround ruleset.")
log_dynamic("[key_name(S)] was spawned as a Space Dragon by the midround ruleset.")
priority_announce("A large organic energy flux has been recorded near of [station_name()], please stand-by.", "Lifesign Alert")
return S
/datum/dynamic_ruleset/midround/from_ghosts/abductors
name = "Abductors"
midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT
antag_datum = /datum/antagonist/abductor
antag_flag = ROLE_ABDUCTOR
ruleset_category = parent_type::ruleset_category | RULESET_CATEGORY_NO_WITTING_CREW_ANTAGONISTS
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 2
required_applicants = 2
weight = 4
cost = 7
minimum_players = 25
repeatable = TRUE
ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_ABDUCTOR_SHIPS)
var/datum/team/abductor_team/new_team
/datum/dynamic_ruleset/midround/from_ghosts/abductors/forget_startup()
new_team = null
return ..()
/datum/dynamic_ruleset/midround/from_ghosts/abductors/ready(forced = FALSE)
if (required_candidates > (dead_players.len + list_observers.len))
return FALSE
return ..()
/datum/dynamic_ruleset/midround/from_ghosts/abductors/finish_setup(mob/new_character, index)
if (index == 1) // Our first guy is the scientist. We also initialize the team here as well since this should only happen once per pair of abductors.
new_team = new
if(new_team.team_number > ABDUCTOR_MAX_TEAMS)
return MAP_ERROR
var/datum/antagonist/abductor/scientist/new_role = new
new_character.mind.add_antag_datum(new_role, new_team)
else // Our second guy is the agent, team is already created, don't need to make another one.
var/datum/antagonist/abductor/agent/new_role = new
new_character.mind.add_antag_datum(new_role, new_team)
/// Midround Space Ninja Ruleset (From Ghosts)
/datum/dynamic_ruleset/midround/from_ghosts/space_ninja
name = "Space Ninja"
midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY
antag_datum = /datum/antagonist/ninja
antag_flag = ROLE_NINJA
ruleset_category = parent_type::ruleset_category | RULESET_CATEGORY_NO_WITTING_CREW_ANTAGONISTS
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 1
weight = 4
cost = 8
minimum_players = 30
repeatable = TRUE
ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_NINJA_HOLDING_FACILITY) // I mean, no one uses the nets anymore but whateva
var/list/spawn_locs = list()
/datum/dynamic_ruleset/midround/from_ghosts/space_ninja/forget_startup()
spawn_locs = list()
return ..()
/datum/dynamic_ruleset/midround/from_ghosts/space_ninja/execute()
for(var/obj/effect/landmark/carpspawn/carp_spawn in GLOB.landmarks_list)
if(!isturf(carp_spawn.loc))
stack_trace("Carp spawn found not on a turf: [carp_spawn.type] on [isnull(carp_spawn.loc) ? "null" : carp_spawn.loc.type]")
continue
spawn_locs += carp_spawn.loc
if(!spawn_locs.len)
message_admins("No valid spawn locations found, aborting...")
return MAP_ERROR
return ..()
/datum/dynamic_ruleset/midround/from_ghosts/space_ninja/generate_ruleset_body(mob/applicant)
var/mob/living/carbon/human/ninja = create_space_ninja(pick(spawn_locs))
ninja.key = applicant.key
ninja.mind.add_antag_datum(/datum/antagonist/ninja)
message_admins("[ADMIN_LOOKUPFLW(ninja)] has been made into a Space Ninja by the midround ruleset.")
log_dynamic("[key_name(ninja)] was spawned as a Space Ninja by the midround ruleset.")
return ninja
/// Midround Spiders Ruleset (From Ghosts)
/datum/dynamic_ruleset/midround/spiders
name = "Spiders"
midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY
antag_flag = ROLE_SPIDER
ruleset_category = parent_type::ruleset_category | RULESET_CATEGORY_NO_WITTING_CREW_ANTAGONISTS
required_type = /mob/dead/observer
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 0
weight = 3
cost = 8
minimum_players = 27
repeatable = TRUE
var/spawncount = 2
/datum/dynamic_ruleset/midround/spiders/execute()
create_midwife_eggs(spawncount)
return ..()
/// Midround Revenant Ruleset (From Ghosts)
/datum/dynamic_ruleset/midround/from_ghosts/revenant
name = "Revenant"
midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT
ruleset_category = parent_type::ruleset_category | RULESET_CATEGORY_NO_WITTING_CREW_ANTAGONISTS
antag_datum = /datum/antagonist/revenant
antag_flag = ROLE_REVENANT
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 1
weight = 4
cost = 5
minimum_players = 15
repeatable = TRUE
var/dead_mobs_required = 20
var/need_extra_spawns_value = 15
var/list/spawn_locs = list()
/datum/dynamic_ruleset/midround/from_ghosts/revenant/forget_startup()
spawn_locs = list()
return ..()
/datum/dynamic_ruleset/midround/from_ghosts/revenant/acceptable(population=0, threat_level=0)
if(GLOB.dead_mob_list.len < dead_mobs_required)
return FALSE
return ..()
/datum/dynamic_ruleset/midround/from_ghosts/revenant/execute()
for(var/mob/living/corpse in GLOB.dead_mob_list) //look for any dead bodies
var/turf/corpse_turf = get_turf(corpse)
if(corpse_turf && is_station_level(corpse_turf.z))
spawn_locs += corpse_turf
if(!spawn_locs.len || spawn_locs.len < need_extra_spawns_value) //look for any morgue trays, crematoriums, ect if there weren't alot of dead bodies on the station to pick from
for(var/obj/structure/bodycontainer/corpse_container in GLOB.bodycontainers)
var/turf/container_turf = get_turf(corpse_container)
if(container_turf && is_station_level(container_turf.z))
spawn_locs += container_turf
if(!spawn_locs.len) //If we can't find any valid spawnpoints, try the carp spawns
for(var/obj/effect/landmark/carpspawn/carp_spawnpoint in GLOB.landmarks_list)
if(isturf(carp_spawnpoint.loc))
spawn_locs += carp_spawnpoint.loc
if(!spawn_locs.len) //If we can't find THAT, then just give up and cry
return FALSE
. = ..()
/datum/dynamic_ruleset/midround/from_ghosts/revenant/generate_ruleset_body(mob/applicant)
var/mob/living/basic/revenant/revenant = new(pick(spawn_locs))
revenant.key = applicant.key
message_admins("[ADMIN_LOOKUPFLW(revenant)] has been made into a revenant by the midround ruleset.")
log_game("[key_name(revenant)] was spawned as a revenant by the midround ruleset.")
return revenant
/// Midround Space Pirates Ruleset (From Ghosts)
/datum/dynamic_ruleset/midround/pirates
name = "Space Pirates"
midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT
ruleset_category = parent_type::ruleset_category | RULESET_CATEGORY_NO_WITTING_CREW_ANTAGONISTS
antag_flag = "Space Pirates"
required_type = /mob/dead/observer
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 0
weight = 3
cost = 8
minimum_players = 20
repeatable = TRUE
/datum/dynamic_ruleset/midround/pirates/acceptable(population=0, threat_level=0)
if (SSmapping.is_planetary() || GLOB.light_pirate_gangs.len == 0)
return FALSE
return ..()
/datum/dynamic_ruleset/midround/pirates/execute()
send_pirate_threat(GLOB.light_pirate_gangs)
return ..()
/// Dangerous Space Pirates ruleset
/datum/dynamic_ruleset/midround/dangerous_pirates
name = "Dangerous Space Pirates"
midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY
ruleset_category = parent_type::ruleset_category | RULESET_CATEGORY_NO_WITTING_CREW_ANTAGONISTS
antag_flag = "Space Pirates"
required_type = /mob/dead/observer
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 0
weight = 3
cost = 8
minimum_players = 25
repeatable = TRUE
/datum/dynamic_ruleset/midround/dangerous_pirates/acceptable(population=0, threat_level=0)
if (SSmapping.is_planetary() || GLOB.heavy_pirate_gangs.len == 0)
return FALSE
return ..()
/datum/dynamic_ruleset/midround/dangerous_pirates/execute()
send_pirate_threat(GLOB.heavy_pirate_gangs)
return ..()
/// Midround Obsessed Ruleset (From Living)
/datum/dynamic_ruleset/midround/from_living/obsessed
name = "Obsessed"
midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT
antag_datum = /datum/antagonist/obsessed
antag_flag = ROLE_OBSESSED
ruleset_category = parent_type::ruleset_category | RULESET_CATEGORY_NO_WITTING_CREW_ANTAGONISTS
restricted_roles = list(
JOB_AI,
JOB_CYBORG,
ROLE_POSITRONIC_BRAIN,
)
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 1
weight = 4
cost = 3 // Doesn't have the same impact on rounds as revenants, dragons, sentient disease (10) or syndicate infiltrators (5).
repeatable = TRUE
/datum/dynamic_ruleset/midround/from_living/obsessed/trim_candidates()
..()
candidates = living_players
for(var/mob/living/carbon/human/candidate in candidates)
if( \
!candidate.get_organ_by_type(/obj/item/organ/internal/brain) \
|| candidate.mind.has_antag_datum(/datum/antagonist/obsessed) \
|| candidate.stat == DEAD \
|| !(ROLE_OBSESSED in candidate.client?.prefs?.be_special) \
|| !candidate.mind.assigned_role \
)
candidates -= candidate
/datum/dynamic_ruleset/midround/from_living/obsessed/execute()
var/mob/living/carbon/human/obsessed = pick_n_take(candidates)
obsessed.gain_trauma(/datum/brain_trauma/special/obsessed)
message_admins("[ADMIN_LOOKUPFLW(obsessed)] has been made Obsessed by the midround ruleset.")
log_game("[key_name(obsessed)] was made Obsessed by the midround ruleset.")
notify_ghosts(
"[obsessed] has developed an obsession with someone!",
source = obsessed,
header = "Love Can Bloom",
)
return TRUE
/// Midround Space Changeling Ruleset (From Ghosts)
/datum/dynamic_ruleset/midround/from_ghosts/changeling_midround
name = "Space Changeling"
midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT
antag_datum = /datum/antagonist/changeling/space
antag_flag = ROLE_CHANGELING_MIDROUND
antag_flag_override = ROLE_CHANGELING
ruleset_category = parent_type::ruleset_category | RULESET_CATEGORY_NO_WITTING_CREW_ANTAGONISTS
required_type = /mob/dead/observer
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 1
weight = 3
cost = 7
minimum_players = 15
repeatable = TRUE
/datum/dynamic_ruleset/midround/from_ghosts/changeling_midround/generate_ruleset_body(mob/applicant)
var/body = generate_changeling_meteor(applicant)
message_admins("[ADMIN_LOOKUPFLW(body)] has been made into a space changeling by the midround ruleset.")
log_dynamic("[key_name(body)] was spawned as a space changeling by the midround ruleset.")
return body
/// Midround Paradox Clone Ruleset (From Ghosts)
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone
name = "Paradox Clone"
midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT
antag_datum = /datum/antagonist/paradox_clone
antag_flag = ROLE_PARADOX_CLONE
ruleset_category = parent_type::ruleset_category | RULESET_CATEGORY_NO_WITTING_CREW_ANTAGONISTS
enemy_roles = list(
JOB_CAPTAIN,
JOB_DETECTIVE,
JOB_HEAD_OF_SECURITY,
JOB_SECURITY_OFFICER,
JOB_WARDEN,
)
required_enemies = list(2, 2, 1, 1, 1, 1, 1, 0, 0, 0)
required_candidates = 1
weight = 4
cost = 3
repeatable = TRUE
signup_item_path = /obj/effect/bluespace_stream
var/list/possible_spawns = list() ///places the antag can spawn
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/forget_startup()
possible_spawns = list()
return ..()
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/execute()
possible_spawns += find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE)
if(!possible_spawns.len)
return MAP_ERROR
return ..()
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/generate_ruleset_body(mob/applicant)
var/datum/mind/player_mind = new /datum/mind(applicant.key)
player_mind.active = TRUE
var/mob/living/carbon/human/clone_victim = find_original()
var/mob/living/carbon/human/clone = clone_victim.make_full_human_copy(pick(possible_spawns))
player_mind.transfer_to(clone)
var/datum/antagonist/paradox_clone/new_datum = player_mind.add_antag_datum(/datum/antagonist/paradox_clone)
new_datum.original_ref = WEAKREF(clone_victim.mind)
new_datum.setup_clone()
playsound(clone, 'sound/weapons/zapbang.ogg', 30, TRUE)
new /obj/item/storage/toolbox/mechanical(clone.loc) //so they dont get stuck in maints
message_admins("[ADMIN_LOOKUPFLW(clone)] has been made into a Paradox Clone by the midround ruleset.")
clone.log_message("was spawned as a Paradox Clone of [key_name(clone)] by the midround ruleset.", LOG_GAME)
return clone
/**
* Trims through GLOB.player_list and finds a target
* Returns a single human victim, if none is possible then returns null.
*/
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/proc/find_original()
var/list/possible_targets = list()
for(var/mob/living/carbon/human/player in GLOB.player_list)
if(!player.client || !player.mind || player.stat)
continue
if(!(player.mind.assigned_role.job_flags & JOB_CREW_MEMBER))
continue
possible_targets += player
if(possible_targets.len)
return pick(possible_targets)
return FALSE
#undef MALF_ION_PROB
#undef REPLACE_LAW_WITH_ION_PROB