mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-09 00:13:55 +00:00
## 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>
952 lines
35 KiB
Plaintext
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
|