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

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

## Why It's Good For The Game

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

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

1197 lines
46 KiB
Plaintext

/datum/dynamic_ruleset/midround
repeatable = TRUE
repeatable_weight_decrease = 2
/// LIGHT_MIDROUND or HEAVY_MIDROUND - determines which pool it enters
var/midround_type
/// If the false alarm event can pick this ruleset to trigger, well, a false alarm
var/false_alarm_able = FALSE
/**
* Collect candidates handles getting the broad pool of players we want to pick from
*
* You can sleep in this - say, if you wanted to poll players.
*/
/datum/dynamic_ruleset/midround/proc/collect_candidates()
return list()
/**
* Called when the ruleset is selected for false alarm
*/
/datum/dynamic_ruleset/midround/proc/false_alarm()
return
/datum/dynamic_ruleset/midround/spiders
name = "Spiders"
config_tag = "Spiders"
midround_type = HEAVY_MIDROUND
false_alarm_able = TRUE
ruleset_flags = RULESET_INVADER
weight = alist(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 0,
DYNAMIC_TIER_MEDIUMHIGH = 1,
DYNAMIC_TIER_HIGH = 2,
)
min_pop = 30
min_antag_cap = 0
/// Determines how many eggs to create - can take a formula like antag_cap
var/egg_count = 2
/datum/dynamic_ruleset/midround/spiders/can_be_selected()
return ..() && (GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT) && !isnull(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE))
/datum/dynamic_ruleset/midround/spiders/execute()
var/num_egg = get_antag_cap(length(GLOB.alive_player_list), egg_count)
while(num_egg > 0)
var/turf/spawn_loc = find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE)
if(isnull(spawn_loc))
break
var/obj/effect/mob_spawn/ghost_role/spider/midwife/new_eggs = new(spawn_loc)
new_eggs.amount_grown = 98
num_egg--
addtimer(CALLBACK(src, PROC_REF(announce_spiders)), rand(375, 600) SECONDS)
/datum/dynamic_ruleset/midround/spiders/proc/announce_spiders()
priority_announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", ANNOUNCER_ALIENS)
/datum/dynamic_ruleset/midround/spiders/false_alarm()
announce_spiders()
/datum/dynamic_ruleset/midround/pirates
name = "Pirates"
config_tag = "Light Pirates"
midround_type = LIGHT_MIDROUND
jobban_flag = ROLE_TRAITOR
ruleset_flags = RULESET_INVADER|RULESET_ADMIN_CONFIGURABLE
weight = 3
min_pop = 15
min_antag_cap = 0 // ship will spawn if there are no ghosts around
/// Pool to pick pirates from
var/list/datum/pirate_gang/pirate_pool
/datum/dynamic_ruleset/midround/pirates/New(list/dynamic_config)
. = ..()
pirate_pool = default_pirate_pool()
/datum/dynamic_ruleset/midround/pirates/can_be_selected()
return ..() && !SSmapping.is_planetary() && (GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT) && length(default_pirate_pool()) > 0
// An abornmal ruleset that selects no players, but just spawns a pirate ship
/datum/dynamic_ruleset/midround/pirates/execute()
send_pirate_threat(pirate_pool)
/// Returns what pool of pirates to drawn from
/// Returned list is mutated by the ruleset
/datum/dynamic_ruleset/midround/pirates/proc/default_pirate_pool()
return GLOB.light_pirate_gangs
/datum/dynamic_ruleset/midround/pirates/heavy
name = "Pirates"
config_tag = "Heavy Pirates"
midround_type = HEAVY_MIDROUND
jobban_flag = ROLE_TRAITOR
ruleset_flags = RULESET_INVADER
weight = 3
min_pop = 25
min_antag_cap = 0 // ship will spawn if there are no ghosts around
/datum/dynamic_ruleset/midround/pirates/heavy/default_pirate_pool()
return GLOB.heavy_pirate_gangs
#define RANDOM_PIRATE_POOL "Random"
/datum/dynamic_ruleset/midround/pirates/configure_ruleset(mob/admin)
var/list/admin_pool = list("[RULESET_CONFIG_CANCEL]" = TRUE, "[RANDOM_PIRATE_POOL]" = TRUE)
for(var/datum/pirate_gang/gang as anything in default_pirate_pool())
admin_pool[gang.name] = gang
var/picked = tgui_input_list(admin, "Select a pirate gang", "Pirate Gang Selection", admin_pool)
if(!picked || picked == RULESET_CONFIG_CANCEL)
return RULESET_CONFIG_CANCEL
if(picked == RANDOM_PIRATE_POOL)
return null
pirate_pool = list(admin_pool[picked])
return null
#undef RANDOM_PIRATE_POOL
#define NO_ANSWER 0
#define POSITIVE_ANSWER 1
#define NEGATIVE_ANSWER 2
/datum/dynamic_ruleset/midround/pirates/proc/send_pirate_threat(list/pirate_selection)
var/datum/pirate_gang/chosen_gang = pick_n_take(pirate_selection)
///If there was nothing to pull from our requested list, stop here.
if(!chosen_gang)
message_admins("Error attempting to run the space pirate event, as the given pirate gangs list was empty.")
return
//set payoff
var/payoff = 0
var/datum/bank_account/account = SSeconomy.get_dep_account(ACCOUNT_CAR)
if(account)
payoff = max(PAYOFF_MIN, FLOOR(account.account_balance * 0.80, 1000))
var/datum/comm_message/threat = chosen_gang.generate_message(payoff)
//send message
priority_announce("Incoming subspace communication. Secure channel opened at all communication consoles.", "Incoming Message", SSstation.announcer.get_rand_report_sound())
threat.answer_callback = CALLBACK(src, PROC_REF(pirates_answered), threat, chosen_gang, payoff, world.time)
addtimer(CALLBACK(src, PROC_REF(spawn_pirates), threat, chosen_gang), RESPONSE_MAX_TIME)
GLOB.communications_controller.send_message(threat, unique = TRUE)
/datum/dynamic_ruleset/midround/pirates/proc/pirates_answered(datum/comm_message/threat, datum/pirate_gang/chosen_gang, payoff, initial_send_time)
if(world.time > initial_send_time + RESPONSE_MAX_TIME)
priority_announce(chosen_gang.response_too_late, sender_override = chosen_gang.ship_name, color_override = chosen_gang.announcement_color)
return
if(!threat?.answered)
return
if(threat.answered == NEGATIVE_ANSWER)
priority_announce(chosen_gang.response_rejected, sender_override = chosen_gang.ship_name, color_override = chosen_gang.announcement_color)
return
var/datum/bank_account/plundered_account = SSeconomy.get_dep_account(ACCOUNT_CAR)
if(plundered_account)
if(plundered_account.adjust_money(-payoff))
chosen_gang.paid_off = TRUE
priority_announce(chosen_gang.response_received, sender_override = chosen_gang.ship_name, color_override = chosen_gang.announcement_color)
else
priority_announce(chosen_gang.response_not_enough, sender_override = chosen_gang.ship_name, color_override = chosen_gang.announcement_color)
/datum/dynamic_ruleset/midround/pirates/proc/spawn_pirates(datum/comm_message/threat, datum/pirate_gang/chosen_gang)
if(chosen_gang.paid_off)
return
var/list/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for a [span_notice("pirate crew of [chosen_gang.name]?")]", check_jobban = ROLE_TRAITOR, alert_pic = /obj/item/claymore/cutlass, role_name_text = "pirate crew")
shuffle_inplace(candidates)
var/template_key = "pirate_[chosen_gang.ship_template_id]"
var/datum/map_template/shuttle/pirate/ship = SSmapping.shuttle_templates[template_key]
var/x = rand(TRANSITIONEDGE,world.maxx - TRANSITIONEDGE - ship.width)
var/y = rand(TRANSITIONEDGE,world.maxy - TRANSITIONEDGE - ship.height)
var/z = SSmapping.empty_space.z_value
var/turf/T = locate(x,y,z)
if(!T)
CRASH("Pirate event found no turf to load in")
if(!ship.load(T))
CRASH("Loading pirate ship failed!")
for(var/turf/area_turf as anything in ship.get_affected_turfs(T))
for(var/obj/effect/mob_spawn/ghost_role/human/pirate/spawner in area_turf)
if(candidates.len > 0)
var/mob/our_candidate = candidates[1]
var/mob/spawned_mob = spawner.create_from_ghost(our_candidate)
candidates -= our_candidate
notify_ghosts(
"The [chosen_gang.ship_name] has an object of interest: [spawned_mob]!",
source = spawned_mob,
header = "Pirates!",
)
else
notify_ghosts(
"The [chosen_gang.ship_name] has an object of interest: [spawner]!",
source = spawner,
header = "Pirate Spawn Here!",
)
priority_announce(chosen_gang.arrival_announcement, sender_override = chosen_gang.ship_name)
#undef NO_ANSWER
#undef POSITIVE_ANSWER
#undef NEGATIVE_ANSWER
/**
* ### Ghost rulesets
*
* Rulesets which select an observer/ghost player to play as a new character
*
* Implementation notes:
* - prepare_role will handle making the body for the mob for you. Avoid touching it if not necessary.
* - create_ruleset_body is what makes the new /mob for the candidate. It handles putting the player in the body for you.
* You can override it entirely for to spawn a different mob type.
* You can also override it to spawn nothing, if you're doing special handling in assign_role, but you'll have to handle moving the player yourself.
* - assign_role is what gives the player their antag datum.
*/
/datum/dynamic_ruleset/midround/from_ghosts
///Path of an item to show up in ghost polls for applicants to sign up.
var/signup_atom_appearance = /obj/structure/sign/poster/contraband/syndicate_recruitment
/// Text shown in the candidate poll. Optional, if unset uses pref_flag. (Though required if pref_flag is unset)
var/candidate_role
/datum/dynamic_ruleset/midround/from_ghosts/can_be_selected()
SHOULD_CALL_PARENT(TRUE)
return ..() && (GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT)
/datum/dynamic_ruleset/midround/from_ghosts/get_candidate_mind(mob/dead/candidate)
// Ghost roles will always get a fresh mind
return new /datum/mind(candidate.key)
/datum/dynamic_ruleset/midround/from_ghosts/prepare_for_role(datum/mind/candidate)
var/mob/living/body = create_ruleset_body()
if(isnull(body))
return
candidate.transfer_to(body, force_key_move = TRUE) // yoinks the candidate's client
if(ishuman(body))
var/mob/living/carbon/human/human_body = body
body.client?.prefs.safe_transfer_prefs_to(body)
human_body.dna.remove_all_mutations()
human_body.dna.update_dna_identity()
/**
* Handles making the body for the candidate
*
* Handling loc is not necessary here - you can do it in assign_role
*
* Returning null will skip body creation entirely, though you will be expected to do it yourself in assign_role
*/
/datum/dynamic_ruleset/midround/from_ghosts/proc/create_ruleset_body()
return new /mob/living/carbon/human
/datum/dynamic_ruleset/midround/from_ghosts/collect_candidates()
var/readable_poll_role = candidate_role || pref_flag
if(isnull(readable_poll_role))
stack_trace("[config_tag]: No candidate role or pref_flag set, give it a human readable candidate roll at the bare minimum.")
readable_poll_role = "Some Midround Antagonist Without A Role Set (Yell At Coders)"
return SSpolling.poll_candidates(
group = trim_candidates(GLOB.dead_player_list | GLOB.current_observers_list),
question = "Looking for volunteers to become [span_notice(readable_poll_role)] for [span_danger(name)]",
// check_jobban = list(ROLE_SYNDICATE, jobban_flag || pref_flag), // Not necessary, handled in trim_candidates()
// role = pref_flag, // Not necessary, handled in trim_candidates()
poll_time = 30 SECONDS,
alert_pic = signup_atom_appearance,
role_name_text = readable_poll_role,
)
/datum/dynamic_ruleset/midround/from_ghosts/wizard
name = "Wizard"
config_tag = "Midround Wizard"
preview_antag_datum = /datum/antagonist/wizard
midround_type = HEAVY_MIDROUND
candidate_role = "Wizard"
pref_flag = ROLE_WIZARD_MIDROUND
jobban_flag = ROLE_WIZARD
ruleset_flags = RULESET_INVADER|RULESET_HIGH_IMPACT
weight = alist(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 0,
DYNAMIC_TIER_MEDIUMHIGH = 1,
DYNAMIC_TIER_HIGH = 2,
)
min_pop = 30
max_antag_cap = 1
ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_WIZARDDEN)
signup_atom_appearance = /obj/item/clothing/head/wizard
/datum/dynamic_ruleset/midround/from_ghosts/wizard/assign_role(datum/mind/candidate)
candidate.add_antag_datum(/datum/antagonist/wizard) // moves to lair for us
/datum/dynamic_ruleset/midround/from_ghosts/nukies
name = "Nuclear Operatives"
config_tag = "Midround Nukeops"
preview_antag_datum = /datum/antagonist/nukeop
midround_type = HEAVY_MIDROUND
candidate_role = "Operative"
pref_flag = ROLE_OPERATIVE_MIDROUND
jobban_flag = ROLE_OPERATIVE
ruleset_flags = RULESET_INVADER|RULESET_HIGH_IMPACT
weight = alist(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 1,
DYNAMIC_TIER_MEDIUMHIGH = 3,
DYNAMIC_TIER_HIGH = 3,
)
min_pop = 30
min_antag_cap = list("denominator" = 18, "offset" = 1)
repeatable = FALSE
ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_NUKIEBASE)
signup_atom_appearance = /obj/machinery/nuclearbomb/syndicate
/datum/dynamic_ruleset/midround/from_ghosts/nukies/create_execute_args()
return list(
new /datum/team/nuclear(),
get_most_experienced(selected_minds, pref_flag),
)
/datum/dynamic_ruleset/midround/from_ghosts/nukies/assign_role(datum/mind/candidate, datum/team/nuclear/nuke_team, datum/mind/most_experienced)
if(most_experienced == candidate)
candidate.add_antag_datum(/datum/antagonist/nukeop/leader, nuke_team) // moves to nuke base for us
else
candidate.add_antag_datum(/datum/antagonist/nukeop, nuke_team) // moves to nuke base for us
/datum/dynamic_ruleset/midround/from_ghosts/nukies/round_result()
var/datum/antagonist/nukeop/nukie = selected_minds[1].has_antag_datum(/datum/antagonist/nukeop)
var/datum/team/nuclear/nuke_team = nukie.get_team()
var/result = nuke_team.get_result()
switch(result)
if(NUKE_RESULT_FLUKE)
SSticker.mode_result = "loss - syndicate nuked - disk secured"
SSticker.news_report = NUKE_SYNDICATE_BASE
if(NUKE_RESULT_NUKE_WIN)
SSticker.mode_result = "win - syndicate nuke"
SSticker.news_report = STATION_DESTROYED_NUKE
if(NUKE_RESULT_NOSURVIVORS)
SSticker.mode_result = "halfwin - syndicate nuke - did not evacuate in time"
SSticker.news_report = STATION_DESTROYED_NUKE
if(NUKE_RESULT_WRONG_STATION)
SSticker.mode_result = "halfwin - blew wrong station"
SSticker.news_report = NUKE_MISS
if(NUKE_RESULT_WRONG_STATION_DEAD)
SSticker.mode_result = "halfwin - blew wrong station - did not evacuate in time"
SSticker.news_report = NUKE_MISS
if(NUKE_RESULT_CREW_WIN_SYNDIES_DEAD)
SSticker.mode_result = "loss - evacuation - disk secured - syndi team dead"
SSticker.news_report = OPERATIVES_KILLED
if(NUKE_RESULT_CREW_WIN)
SSticker.mode_result = "loss - evacuation - disk secured"
SSticker.news_report = OPERATIVES_KILLED
if(NUKE_RESULT_DISK_LOST)
SSticker.mode_result = "halfwin - evacuation - disk not secured"
SSticker.news_report = OPERATIVE_SKIRMISH
if(NUKE_RESULT_DISK_STOLEN)
SSticker.mode_result = "halfwin - detonation averted"
SSticker.news_report = OPERATIVE_SKIRMISH
else
SSticker.mode_result = "halfwin - interrupted"
SSticker.news_report = OPERATIVE_SKIRMISH
/datum/dynamic_ruleset/midround/from_ghosts/nukies/clown
name = "Clown Operatives"
config_tag = "Midround Clownops"
preview_antag_datum = /datum/antagonist/nukeop/clownop
candidate_role = "Operative"
pref_flag = ROLE_CLOWN_OPERATIVE_MIDROUND
jobban_flag = ROLE_CLOWN_OPERATIVE
weight = 0
signup_atom_appearance = /obj/machinery/nuclearbomb/syndicate/bananium
/datum/dynamic_ruleset/midround/from_ghosts/nukies/clown/assign_role(datum/mind/candidate, datum/team/nuclear/nuke_team, datum/mind/most_experienced)
if(most_experienced == candidate)
candidate.add_antag_datum(/datum/antagonist/nukeop/leader/clownop, nuke_team) // moves to nuke base for us
else
candidate.add_antag_datum(/datum/antagonist/nukeop/clownop, nuke_team) // moves to nuke base for us
/datum/dynamic_ruleset/midround/from_ghosts/blob
name = "Blob"
config_tag = "Blob"
preview_antag_datum = /datum/antagonist/blob
midround_type = HEAVY_MIDROUND
false_alarm_able = TRUE
pref_flag = ROLE_BLOB
ruleset_flags = RULESET_INVADER
weight = alist(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 1,
DYNAMIC_TIER_MEDIUMHIGH = 3,
DYNAMIC_TIER_HIGH = 3,
)
min_pop = 30
max_antag_cap = 1
repeatable_weight_decrease = 3
signup_atom_appearance = /obj/structure/blob/normal
/// How many points does the blob spawn with
var/starting_points = OVERMIND_STARTING_POINTS
/datum/dynamic_ruleset/midround/from_ghosts/blob/create_ruleset_body()
return new /mob/eye/blob(get_blobspawn(), starting_points)
/datum/dynamic_ruleset/midround/from_ghosts/blob/assign_role(datum/mind/candidate)
return // everything is handled by blob new()
/datum/dynamic_ruleset/midround/from_ghosts/blob/proc/get_blobspawn()
if(!length(GLOB.blobstart))
var/obj/effect/landmark/observer_start/default = locate() in GLOB.landmarks_list
return get_turf(default)
return pick(GLOB.blobstart)
/datum/dynamic_ruleset/midround/from_ghosts/blob/false_alarm()
priority_announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", ANNOUNCER_OUTBREAK5)
// Set status displays to biohazard alert even for false alarm
send_status_display_biohazard_alert()
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph
name = "Alien Infestation"
config_tag = "Xenomorph"
preview_antag_datum = /datum/antagonist/xeno
midround_type = HEAVY_MIDROUND
false_alarm_able = TRUE
pref_flag = ROLE_ALIEN
ruleset_flags = RULESET_INVADER
weight = alist(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 1,
DYNAMIC_TIER_MEDIUMHIGH = 5,
DYNAMIC_TIER_HIGH = 5,
)
min_pop = 30
max_antag_cap = 1
min_antag_cap = 1
repeatable_weight_decrease = 3
signup_atom_appearance = /mob/living/basic/alien
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/New(list/dynamic_config)
. = ..()
max_antag_cap += prob(50) // 50% chance to get a second xeno, free!
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/can_be_selected()
return ..() && length(find_vents()) > 0
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/execute()
. = ..()
addtimer(CALLBACK(src, PROC_REF(announce_xenos)), rand(375, 600) SECONDS)
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/proc/announce_xenos()
priority_announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", ANNOUNCER_ALIENS)
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/false_alarm()
announce_xenos()
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/create_ruleset_body()
return new /mob/living/carbon/alien/larva
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/create_execute_args()
return list(find_vents())
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/assign_role(datum/mind/candidate, list/vent_list)
// xeno login gives antag datums
var/obj/vent = length(vent_list) >= 2 ? pick_n_take(vent_list) : vent_list[1]
candidate.current.move_into_vent(vent)
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/proc/find_vents()
var/list/vents = list()
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)
continue
var/datum/pipeline/temp_vent_parent = temp_vent.parents[1]
if(!temp_vent_parent)
continue
// Stops Aliens getting stuck in small networks.
// See: Security, Virology
if(length(temp_vent_parent.other_atmos_machines) <= 20)
continue
vents += temp_vent
return vents
/datum/dynamic_ruleset/midround/from_ghosts/nightmare
name = "Nightmare"
config_tag = "Nightmare"
preview_antag_datum = /datum/antagonist/nightmare
midround_type = LIGHT_MIDROUND
pref_flag = ROLE_NIGHTMARE
ruleset_flags = RULESET_INVADER
weight = 5
min_pop = 15
max_antag_cap = 1
signup_atom_appearance = /obj/item/light_eater
/datum/dynamic_ruleset/midround/from_ghosts/nightmare/can_be_selected()
return ..() && !isnull(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE))
/datum/dynamic_ruleset/midround/from_ghosts/nightmare/assign_role(datum/mind/candidate)
candidate.add_antag_datum(/datum/antagonist/nightmare)
candidate.current.set_species(/datum/species/shadow/nightmare)
candidate.current.forceMove(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE))
playsound(candidate.current, 'sound/effects/magic/ethereal_exit.ogg', 50, TRUE, -1)
/datum/dynamic_ruleset/midround/from_ghosts/space_dragon
name = "Space Dragon"
config_tag = "Space Dragon"
preview_antag_datum = /datum/antagonist/space_dragon
midround_type = HEAVY_MIDROUND
false_alarm_able = TRUE
pref_flag = ROLE_SPACE_DRAGON
ruleset_flags = RULESET_INVADER
weight = alist(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 3,
DYNAMIC_TIER_MEDIUMHIGH = 5,
DYNAMIC_TIER_HIGH = 5,
)
min_pop = 30
max_antag_cap = 1
repeatable_weight_decrease = 3
signup_atom_appearance = /mob/living/basic/space_dragon
/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/can_be_selected()
return ..() && !isnull(find_space_spawn())
/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/create_ruleset_body()
return new /mob/living/basic/space_dragon
/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/assign_role(datum/mind/candidate)
candidate.add_antag_datum(/datum/antagonist/space_dragon)
candidate.current.forceMove(find_space_spawn())
playsound(candidate.current, 'sound/effects/magic/ethereal_exit.ogg', 50, TRUE, -1)
/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/execute()
. = ..()
addtimer(CALLBACK(src, PROC_REF(announce_space_dragon)), rand(5, 10) SECONDS)
/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/proc/announce_space_dragon()
priority_announce("A large organic energy flux has been recorded near of [station_name()], please stand-by.", "Lifesign Alert")
/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/false_alarm()
announce_space_dragon()
/datum/dynamic_ruleset/midround/from_ghosts/abductors
name = "Abductors"
config_tag = "Abductors"
preview_antag_datum = /datum/antagonist/abductor
midround_type = LIGHT_MIDROUND
pref_flag = ROLE_ABDUCTOR
ruleset_flags = RULESET_INVADER
weight = 5
min_pop = 20
min_antag_cap = 2
repeatable_weight_decrease = 3
ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_ABDUCTOR_SHIPS)
signup_atom_appearance = /obj/item/melee/baton/abductor
/datum/dynamic_ruleset/midround/from_ghosts/abductors/can_be_selected()
if(!..())
return FALSE
var/num_abductors = 0
for(var/datum/team/abductor_team/team in GLOB.antagonist_teams)
num_abductors++
return num_abductors < 4
/datum/dynamic_ruleset/midround/from_ghosts/abductors/create_execute_args()
return list(new /datum/team/abductor_team())
/datum/dynamic_ruleset/midround/from_ghosts/abductors/assign_role(datum/mind/candidate, datum/team/abductor_team/team)
if(candidate == selected_minds[1])
candidate.add_antag_datum(/datum/antagonist/abductor/scientist, team) // sets species and moves to spawn point
else
candidate.add_antag_datum(/datum/antagonist/abductor/agent, team) // sets species and moves to spawn point
/datum/dynamic_ruleset/midround/from_ghosts/space_ninja
name = "Space Ninja"
config_tag = "Space Ninja"
preview_antag_datum = /datum/antagonist/ninja
midround_type = HEAVY_MIDROUND
pref_flag = ROLE_NINJA
ruleset_flags = RULESET_INVADER
weight = alist(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 0,
DYNAMIC_TIER_MEDIUMHIGH = 1,
DYNAMIC_TIER_HIGH = 2,
)
min_pop = 30
max_antag_cap = 1
repeatable = FALSE
ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_NINJA_HOLDING_FACILITY)
signup_atom_appearance = /obj/item/energy_katana
/datum/dynamic_ruleset/midround/from_ghosts/space_ninja/can_be_selected()
return ..() && !isnull(find_space_spawn())
/datum/dynamic_ruleset/midround/from_ghosts/space_ninja/assign_role(datum/mind/candidate)
var/mob/living/carbon/human/new_ninja = candidate.current
new_ninja.forceMove(find_space_spawn()) // ninja antag datum needs the mob to be in place first
randomize_human_normie(new_ninja)
var/new_name = "[pick(GLOB.ninja_titles)] [pick(GLOB.ninja_names)]"
new_ninja.name = new_name
new_ninja.real_name = new_name
new_ninja.dna.update_dna_identity() // ninja antag datum needs dna to be set first
candidate.add_antag_datum(/datum/antagonist/ninja)
/datum/dynamic_ruleset/midround/from_ghosts/revenant
name = "Revenant"
config_tag = "Revenant"
preview_antag_datum = /datum/antagonist/revenant
midround_type = LIGHT_MIDROUND
pref_flag = ROLE_REVENANT
ruleset_flags = RULESET_INVADER
weight = 5
min_pop = 10
max_antag_cap = 1
repeatable = FALSE
signup_atom_appearance = /mob/living/basic/revenant
/// There must be this many dead mobs on the station for a revenant to spawn (of all mob types, not just humans)
/// Remember there's usually 2-3 that spawn in the Morgue roundstart, so adjust this accordingly
var/required_station_corpses = 10
/datum/dynamic_ruleset/midround/from_ghosts/revenant/can_be_selected()
if(!..())
return FALSE
var/num_station_corpses = 0
for(var/mob/deceased as anything in GLOB.dead_mob_list)
var/turf/deceased_turf = get_turf(deceased)
if(is_station_level(deceased_turf?.z))
num_station_corpses++
return num_station_corpses > required_station_corpses
/datum/dynamic_ruleset/midround/from_ghosts/revenant/create_ruleset_body()
return new /mob/living/basic/revenant(pick(get_revenant_spawns()))
/datum/dynamic_ruleset/midround/from_ghosts/revenant/assign_role(datum/mind/candidate)
return // revenant new() handles everything
/datum/dynamic_ruleset/midround/from_ghosts/revenant/proc/get_revenant_spawns()
var/list/spawn_locs = list()
for(var/mob/deceased in GLOB.dead_mob_list)
var/turf/deceased_turf = get_turf(deceased)
if(is_station_level(deceased_turf?.z))
spawn_locs += deceased_turf
if(!length(spawn_locs) || length(spawn_locs) < 12) // get a comfortably large pool of spawnpoints
for(var/obj/structure/bodycontainer/corpse_container in GLOB.bodycontainers)
var/turf/container_turf = get_turf(corpse_container)
if(is_station_level(container_turf?.z))
spawn_locs += container_turf
if(!length(spawn_locs) || length(spawn_locs) < 4) // get a comfortably large pool of spawnpoints
for(var/obj/effect/landmark/carpspawn/carpspawn in GLOB.landmarks_list)
spawn_locs += carpspawn.loc
return spawn_locs
/datum/dynamic_ruleset/midround/from_ghosts/space_changeling
name = "Space Changeling"
config_tag = "Midround Changeling"
preview_antag_datum = /datum/antagonist/changeling/space
midround_type = LIGHT_MIDROUND
candidate_role = "Changeling"
pref_flag = ROLE_CHANGELING_MIDROUND
jobban_flag = ROLE_CHANGELING
ruleset_flags = RULESET_INVADER|RULESET_VARIATION
weight = 5
min_pop = 15
max_antag_cap = 1
signup_atom_appearance = /obj/effect/meteor/meaty/changeling
/datum/dynamic_ruleset/midround/from_ghosts/space_changeling/create_ruleset_body()
return // handled by generate_changeling_meteor() entirely
/datum/dynamic_ruleset/midround/from_ghosts/space_changeling/assign_role(datum/mind/candidate)
generate_changeling_meteor(candidate)
/datum/dynamic_ruleset/midround/from_ghosts/space_changeling/mass
name = "Mass Space Changelings"
config_tag = "Mass Changelings"
midround_type = HEAVY_MIDROUND
min_pop = 25
min_antag_cap = 2
max_antag_cap = 3
repeatable_weight_decrease = 4
weight = alist(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 3,
DYNAMIC_TIER_MEDIUMHIGH = 4,
DYNAMIC_TIER_HIGH = 5,
)
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone
name = "Paradox Clone"
config_tag = "Paradox Clone"
preview_antag_datum = /datum/antagonist/paradox_clone
midround_type = LIGHT_MIDROUND
pref_flag = ROLE_PARADOX_CLONE
ruleset_flags = RULESET_INVADER
weight = 5
min_pop = 10
max_antag_cap = 1
signup_atom_appearance = /obj/effect/bluespace_stream
/// Chance of getting another clone for the price of free
var/bonus_clone_chance = 20
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/New(list/dynamic_config)
. = ..()
max_antag_cap += prob(bonus_clone_chance)
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/can_be_selected()
return ..() && !isnull(find_clone()) && !isnull(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE))
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/create_execute_args()
return list(find_clone())
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/create_ruleset_body()
return // handled by assign_role() entirely
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/assign_role(datum/mind/candidate, mob/living/carbon/human/good_version)
var/mob/living/carbon/human/bad_version = good_version.make_full_human_copy(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE))
candidate.transfer_to(bad_version, force_key_move = TRUE)
var/datum/antagonist/paradox_clone/antag = candidate.add_antag_datum(/datum/antagonist/paradox_clone)
antag.setup_clone(good_version.mind)
playsound(bad_version, 'sound/items/weapons/zapbang.ogg', 30, TRUE)
bad_version.put_in_hands(new /obj/item/storage/toolbox/mechanical()) //so they dont get stuck in maints
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/proc/find_clone()
var/list/possible_targets = list()
for(var/mob/living/carbon/human/player in GLOB.player_list)
if(!player.client || !player.mind || player.stat != CONSCIOUS)
continue
if(!(player.mind.assigned_role.job_flags & JOB_CREW_MEMBER))
continue
possible_targets += player
if(length(possible_targets))
return pick(possible_targets)
return null
/datum/dynamic_ruleset/midround/from_ghosts/voidwalker
name = "Voidwalker"
config_tag = "Voidwalker"
preview_antag_datum = /datum/antagonist/voidwalker
midround_type = LIGHT_MIDROUND
pref_flag = ROLE_VOIDWALKER
ruleset_flags = RULESET_INVADER
weight = 5
min_pop = 40 // Ensures there's a lot of people near windows
max_antag_cap = 1
ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_VOIDWALKER_VOID)
signup_atom_appearance = /obj/item/clothing/head/helmet/skull/cosmic
/datum/dynamic_ruleset/midround/from_ghosts/voidwalker/can_be_selected()
return ..() && !SSmapping.is_planetary() && !isnull(find_space_spawn())
/datum/dynamic_ruleset/midround/from_ghosts/voidwalker/create_ruleset_body()
return new /mob/living/basic/voidwalker
/datum/dynamic_ruleset/midround/from_ghosts/voidwalker/assign_role(datum/mind/candidate)
candidate.add_antag_datum(/datum/antagonist/voidwalker)
candidate.current.forceMove(find_space_spawn())
playsound(candidate.current, 'sound/effects/magic/ethereal_exit.ogg', 50, TRUE, -1)
/datum/dynamic_ruleset/midround/from_ghosts/fugitives
name = "Fugitive"
config_tag = "Fugitives"
preview_antag_datum = /datum/antagonist/fugitive
midround_type = LIGHT_MIDROUND
pref_flag = ROLE_FUGITIVE
ruleset_flags = RULESET_INVADER|RULESET_ADMIN_CONFIGURABLE
weight = 3
min_pop = 20
max_antag_cap = 4
min_antag_cap = 3
repeatable = FALSE
signup_atom_appearance = /obj/item/card/id/advanced/prisoner
/// What backstory is the fugitive(s)?
VAR_FINAL/fugitive_backstory
/// What backstory is the hunter(s)?
VAR_FINAL/hunter_backstory
/datum/dynamic_ruleset/midround/from_ghosts/fugitives/can_be_selected()
return ..() && !SSmapping.is_planetary() && !isnull(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE))
// If less than a certain number of candidates accept the poll, it varies how many antags are spawned
/datum/dynamic_ruleset/midround/from_ghosts/fugitives/collect_candidates()
. = ..()
if(length(.) <= 1 || prob(30 - (length(.) * 2)))
min_antag_cap = 1
max_antag_cap = 1
/datum/dynamic_ruleset/midround/from_ghosts/fugitives/create_execute_args()
return list(
new /datum/team/fugitive(),
find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE),
)
#define RANDOM_BACKSTORY "Random"
/datum/dynamic_ruleset/midround/from_ghosts/fugitives/configure_ruleset(mob/admin)
var/list/fugitive_backstories = list(
FUGITIVE_BACKSTORY_CULTIST,
FUGITIVE_BACKSTORY_INVISIBLE,
FUGITIVE_BACKSTORY_PRISONER,
FUGITIVE_BACKSTORY_SYNTH,
FUGITIVE_BACKSTORY_WALDO,
RANDOM_BACKSTORY,
RULESET_CONFIG_CANCEL,
)
var/list/hunter_backstories = list(
HUNTER_PACK_BOUNTY,
HUNTER_PACK_COPS,
HUNTER_PACK_MI13,
HUNTER_PACK_PSYKER,
HUNTER_PACK_RUSSIAN,
RANDOM_BACKSTORY,
RULESET_CONFIG_CANCEL,
)
var/picked_fugitive_backstory = tgui_input_list(admin, "Select a fugitive backstory", "Fugitive Backstory", fugitive_backstories)
if(!picked_fugitive_backstory || picked_fugitive_backstory == RULESET_CONFIG_CANCEL)
return RULESET_CONFIG_CANCEL
if(picked_fugitive_backstory != RANDOM_BACKSTORY)
fugitive_backstory = picked_fugitive_backstory
var/picked_hunter_backstory = tgui_input_list(admin, "Select a hunter backstory", "Hunter Backstory", hunter_backstories)
if(!picked_hunter_backstory || picked_hunter_backstory == RULESET_CONFIG_CANCEL)
return RULESET_CONFIG_CANCEL
if(picked_hunter_backstory != RANDOM_BACKSTORY)
hunter_backstory = picked_hunter_backstory
return null
#undef RANDOM_BACKSTORY
/datum/dynamic_ruleset/midround/from_ghosts/fugitives/execute()
if(length(selected_minds) == 1)
fugitive_backstory ||= pick(
FUGITIVE_BACKSTORY_INVISIBLE,
FUGITIVE_BACKSTORY_WALDO,
)
else
fugitive_backstory ||= pick(
FUGITIVE_BACKSTORY_CULTIST,
FUGITIVE_BACKSTORY_PRISONER,
FUGITIVE_BACKSTORY_SYNTH,
)
hunter_backstory ||= pick(
HUNTER_PACK_COPS,
HUNTER_PACK_RUSSIAN,
HUNTER_PACK_BOUNTY,
HUNTER_PACK_PSYKER,
HUNTER_PACK_MI13,
)
. = ..()
addtimer(CALLBACK(src, PROC_REF(check_spawn_hunters), 10 MINUTES), 1 MINUTES)
/datum/dynamic_ruleset/midround/from_ghosts/fugitives/assign_role(datum/mind/candidate, datum/team/fugitive/team, turf/team_spawn)
candidate.current.forceMove(team_spawn)
equip_fugitive(candidate.current, team)
if(candidate == selected_minds[1])
equip_fugitive_leader(candidate.current)
playsound(candidate.current, 'sound/items/weapons/emitter.ogg', 50, TRUE)
/datum/dynamic_ruleset/midround/from_ghosts/fugitives/proc/equip_fugitive(mob/living/carbon/human/fugitive, datum/team/fugitive/team)
fugitive.set_species(/datum/species/human)
randomize_human_normie(fugitive)
var/datum/antagonist/fugitive/antag = new()
antag.backstory = fugitive_backstory
fugitive.mind.add_antag_datum(antag, team)
// Should really datumize this at some point
switch(fugitive_backstory)
if(FUGITIVE_BACKSTORY_PRISONER)
fugitive.equipOutfit(/datum/outfit/prisoner)
if(FUGITIVE_BACKSTORY_CULTIST)
fugitive.equipOutfit(/datum/outfit/yalp_cultist)
if(FUGITIVE_BACKSTORY_WALDO)
fugitive.equipOutfit(/datum/outfit/waldo)
if(FUGITIVE_BACKSTORY_SYNTH)
fugitive.equipOutfit(/datum/outfit/synthetic)
if(FUGITIVE_BACKSTORY_INVISIBLE)
fugitive.equipOutfit(/datum/outfit/invisible_man)
/datum/dynamic_ruleset/midround/from_ghosts/fugitives/proc/equip_fugitive_leader(mob/living/carbon/human/fugitive)
var/turf/leader_turf = get_turf(fugitive)
var/obj/item/storage/toolbox/mechanical/toolbox = new(leader_turf)
fugitive.put_in_hands(toolbox)
switch(fugitive_backstory)
if(FUGITIVE_BACKSTORY_SYNTH)
new /obj/item/choice_beacon/augments(leader_turf)
new /obj/item/autosurgeon(leader_turf)
/datum/dynamic_ruleset/midround/from_ghosts/fugitives/proc/check_spawn_hunters(remaining_time)
//if the emergency shuttle has been called, spawn hunters now to give them a chance
if(remaining_time == 0 || !EMERGENCY_IDLE_OR_RECALLED)
spawn_hunters()
return
addtimer(CALLBACK(src, PROC_REF(check_spawn_hunters), remaining_time - 1 MINUTES), 1 MINUTES)
/datum/dynamic_ruleset/midround/from_ghosts/fugitives/proc/spawn_hunters()
var/list/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for a group of [span_notice(hunter_backstory)]?", check_jobban = list(ROLE_FUGITIVE_HUNTER, ROLE_SYNDICATE), alert_pic = /obj/machinery/sleeper, role_name_text = hunter_backstory)
shuffle_inplace(candidates)
var/datum/map_template/shuttle/hunter/ship
switch(hunter_backstory)
if(HUNTER_PACK_COPS)
ship = new /datum/map_template/shuttle/hunter/space_cop
if(HUNTER_PACK_RUSSIAN)
ship = new /datum/map_template/shuttle/hunter/russian
if(HUNTER_PACK_BOUNTY)
ship = new /datum/map_template/shuttle/hunter/bounty
if(HUNTER_PACK_PSYKER)
ship = new /datum/map_template/shuttle/hunter/psyker
if(HUNTER_PACK_MI13)
ship = new/datum/map_template/shuttle/hunter/mi13_foodtruck
var/x = rand(TRANSITIONEDGE, world.maxx - TRANSITIONEDGE - ship.width)
var/y = rand(TRANSITIONEDGE, world.maxy - TRANSITIONEDGE - ship.height)
var/z = SSmapping.empty_space.z_value
var/turf/placement_turf = locate(x, y ,z)
if(!placement_turf)
CRASH("Fugitive Hunters (Created from fugitive event) found no turf to load in")
if(!ship.load(placement_turf))
CRASH("Loading [hunter_backstory] ship failed!")
for(var/turf/shuttle_turf in ship.get_affected_turfs(placement_turf))
for(var/obj/effect/mob_spawn/ghost_role/human/fugitive/spawner in shuttle_turf)
if(length(candidates))
var/mob/our_candidate = candidates[1]
var/mob/spawned_mob = spawner.create_from_ghost(our_candidate)
candidates -= our_candidate
notify_ghosts(
"[spawner.prompt_name] has awoken: [spawned_mob]!",
source = spawned_mob,
header = "Come look!",
)
else
notify_ghosts(
"[spawner.prompt_name] spawner has been created!",
source = spawner,
header = "Spawn Here!",
)
var/list/announcement_text_list = list()
var/announcement_title = ""
switch(hunter_backstory)
if(HUNTER_PACK_COPS)
announcement_text_list += "Attention Crew of [station_name()], this is the Police. A wanted criminal has been reported taking refuge on your station."
announcement_text_list += "We have a warrant from the SSC authorities to take them into custody. Officers have been dispatched to your location."
announcement_text_list += "We demand your cooperation in bringing this criminal to justice."
announcement_title += "Spacepol Command"
if(HUNTER_PACK_RUSSIAN)
announcement_text_list += "Zdraviya zhelaju, [station_name()] crew. We are coming to your station."
announcement_text_list += "There is a criminal aboard. We will arrest them and return them to the gulag. That's good, yes?"
announcement_title += "Russian Freighter"
if(HUNTER_PACK_BOUNTY)
announcement_text_list += "[station_name()]. One of our bounty marks has ended up on your station. We will be arriving to collect shortly."
announcement_text_list += "Let's make this quick. If you don't want trouble, stay the hell out of our way."
announcement_title += "Unregistered Signal"
if(HUNTER_PACK_PSYKER)
announcement_text_list += "HEY, CAN YOU HEAR US? We're coming to your station. There's a bad guy down there, really bad guy. We need to arrest them."
announcement_text_list += "We're also offering fortune telling services out of the front door if you have paying customers."
announcement_title += "Fortune-Telling Entertainment Shuttle"
if(HUNTER_PACK_MI13)
announcement_text_list += "Illegal intrusion detected in the crew monitoring network. Central Command has been informed."
announcement_text_list += "Please report any suspicious individuals or behaviour to your local security team."
announcement_title += "Nanotrasen Intrusion Countermeasures Electronics"
if(!length(announcement_text_list))
announcement_text_list += "Unidentified ship detected near the station."
stack_trace("Fugitive hunter announcement was unable to generate an announcement text based on backstory: [hunter_backstory]")
if(!length(announcement_title))
announcement_title += "Unknown Signal"
stack_trace("Fugitive hunter announcement was unable to generate an announcement title based on backstory: [hunter_backstory]")
priority_announce(jointext(announcement_text_list, " "), announcement_title)
/datum/dynamic_ruleset/midround/from_ghosts/morph
name = "Morph"
config_tag = "Morph"
// preview_antag_datum = /datum/antagonist/morph // Doesn't actually have its own pref
midround_type = LIGHT_MIDROUND
candidate_role = "Morphling"
jobban_flag = ROLE_ALIEN
ruleset_flags = RULESET_INVADER
weight = 0
max_antag_cap = 1
signup_atom_appearance = /mob/living/basic/morph
/datum/dynamic_ruleset/midround/from_ghosts/morph/can_be_selected()
return ..() && !isnull(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE))
/datum/dynamic_ruleset/midround/from_ghosts/morph/create_ruleset_body()
return new /mob/living/basic/morph(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE))
/datum/dynamic_ruleset/midround/from_ghosts/morph/assign_role(datum/mind/candidate)
candidate.set_assigned_role(SSjob.get_job_type(/datum/job/morph))
candidate.add_antag_datum(/datum/antagonist/morph)
/datum/dynamic_ruleset/midround/from_ghosts/slaughter_demon
name = "Slaughter Demon"
config_tag = "Slaughter Demon"
candidate_role = "Slaughter Demon"
// preview_antag_datum = /datum/antagonist/slaughter // Doesn't actually have its own pref
midround_type = HEAVY_MIDROUND
jobban_flag = ROLE_ALIEN
ruleset_flags = RULESET_INVADER
weight = 0
min_pop = 20
max_antag_cap = 1
signup_atom_appearance = /mob/living/basic/demon/slaughter
/datum/dynamic_ruleset/midround/from_ghosts/slaughter_demon/can_be_selected()
return ..() && !isnull(find_space_spawn())
/datum/dynamic_ruleset/midround/from_ghosts/slaughter_demon/create_ruleset_body()
var/turf/spawnloc = find_space_spawn()
. = new /mob/living/basic/demon/slaughter(spawnloc)
new /obj/effect/dummy/phased_mob/blood(spawnloc, .)
/datum/dynamic_ruleset/midround/from_ghosts/slaughter_demon/assign_role(datum/mind/candidate)
return // handled by new() entirely
/datum/dynamic_ruleset/midround/from_living
min_antag_cap = 1
max_antag_cap = 1
repeatable = TRUE
/datum/dynamic_ruleset/midround/from_living/set_config_value(nvar, nval)
if(nvar == NAMEOF(src, min_antag_cap) || nvar == NAMEOF(src, max_antag_cap))
return FALSE
return ..()
/datum/dynamic_ruleset/midround/from_living/vv_edit_var(var_name, var_value)
if(var_name == NAMEOF(src, min_antag_cap) || var_name == NAMEOF(src, max_antag_cap))
return FALSE
return ..()
/datum/dynamic_ruleset/midround/from_living/collect_candidates()
return GLOB.alive_player_list
/datum/dynamic_ruleset/midround/from_living/is_valid_candidate(mob/candidate, client/candidate_client)
if(candidate.stat == DEAD || isnull(candidate.mind))
return FALSE
// only pick members of the crew
if(!job_check(candidate))
return FALSE
if(!antag_check(candidate))
return FALSE
// checks for stuff like bitrunner avatars and ghost mafia
if(HAS_TRAIT(candidate, TRAIT_MIND_TEMPORARILY_GONE) || HAS_TRAIT(candidate, TRAIT_TEMPORARY_BODY))
return FALSE
if(SEND_SIGNAL(candidate, COMSIG_MOB_MIND_BEFORE_MIDROUND_ROLL, src, pref_flag) & CANCEL_ROLL)
return FALSE
return ..()
/// Checks if the candidate is a valid job for this ruleset - by default you probably only want crew members. (Return FALSE to mark the candidate invalid)
/datum/dynamic_ruleset/midround/from_living/proc/job_check(mob/candidate)
if(!(candidate.mind.assigned_role.job_flags & JOB_CREW_MEMBER))
return FALSE
if(candidate.mind.assigned_role.title in get_blacklisted_roles())
return FALSE
return TRUE
/// Checks if the candidate is an antag - most of the time you don't want to double dip. (Return FALSE to mark the candidate invalid)
/datum/dynamic_ruleset/midround/from_living/proc/antag_check(mob/candidate)
return !candidate.is_antag()
/datum/dynamic_ruleset/midround/from_living/traitor
name = "Traitor"
config_tag = "Midround Traitor"
preview_antag_datum = /datum/antagonist/traitor
midround_type = LIGHT_MIDROUND
false_alarm_able = TRUE
pref_flag = ROLE_SLEEPER_AGENT
jobban_flag = ROLE_TRAITOR
ruleset_flags = RULESET_VARIATION
weight = 10
min_pop = 3
blacklisted_roles = list(
JOB_HEAD_OF_PERSONNEL,
)
/datum/dynamic_ruleset/midround/from_living/traitor/assign_role(datum/mind/candidate)
candidate.add_antag_datum(/datum/antagonist/traitor)
/datum/dynamic_ruleset/midround/from_living/traitor/false_alarm()
priority_announce(
"Attention crew, it appears that someone on your station has hijacked your telecommunications and broadcasted an unknown signal.",
"[command_name()] High-Priority Update",
)
/datum/dynamic_ruleset/midround/from_living/traitor/mass
name = "Mass Traitors"
config_tag = "Mass Traitors"
midround_type = HEAVY_MIDROUND
min_pop = 15
min_antag_cap = 2
max_antag_cap = 4
repeatable_weight_decrease = 8
blacklisted_roles = list()
weight = alist(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 3,
DYNAMIC_TIER_MEDIUMHIGH = 8,
DYNAMIC_TIER_HIGH = 10,
)
/datum/dynamic_ruleset/midround/from_living/malf_ai
name = "Malfunctioning AI"
config_tag = "Midround Malfunctioning AI"
preview_antag_datum = /datum/antagonist/malf_ai
midround_type = HEAVY_MIDROUND
pref_flag = ROLE_MALF_MIDROUND
jobban_flag = ROLE_MALF
ruleset_flags = RULESET_HIGH_IMPACT
weight = alist(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 1,
DYNAMIC_TIER_MEDIUMHIGH = 3,
DYNAMIC_TIER_HIGH = 3,
)
min_pop = 30
repeatable = FALSE
/datum/dynamic_ruleset/midround/from_living/malf_ai/get_always_blacklisted_roles()
return list()
/datum/dynamic_ruleset/midround/from_living/malf_ai/job_check(mob/candidate)
return istype(candidate.mind.assigned_role, /datum/job/ai)
/datum/dynamic_ruleset/midround/from_living/malf_ai/assign_role(datum/mind/candidate)
candidate.add_antag_datum(/datum/antagonist/malf_ai)
/datum/dynamic_ruleset/midround/from_living/malf_ai/can_be_selected()
return ..() && !HAS_TRAIT(SSstation, STATION_TRAIT_HUMAN_AI)
/datum/dynamic_ruleset/midround/from_living/blob
name = "Blob Infection"
config_tag = "Blob Infection"
preview_antag_datum = /datum/antagonist/blob/infection
midround_type = HEAVY_MIDROUND
pref_flag = ROLE_BLOB_INFECTION
jobban_flag = ROLE_BLOB
weight = alist(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 1,
DYNAMIC_TIER_MEDIUMHIGH = 3,
DYNAMIC_TIER_HIGH = 3,
)
min_pop = 30
repeatable_weight_decrease = 3
/datum/dynamic_ruleset/midround/from_living/blob/assign_role(datum/mind/candidate)
candidate.add_antag_datum(/datum/antagonist/blob/infection)
notify_ghosts(
"[candidate.current.real_name] has become a blob host!",
source = candidate.current,
header = "So Bulbous...",
)
/datum/dynamic_ruleset/midround/from_living/obsesed
name = "Obsession"
config_tag = "Midround Obsessed"
preview_antag_datum = /datum/antagonist/obsessed
midround_type = LIGHT_MIDROUND
pref_flag = ROLE_OBSESSED
blacklisted_roles = list()
weight = alist(
DYNAMIC_TIER_LOW = 5,
DYNAMIC_TIER_LOWMEDIUM = 5,
DYNAMIC_TIER_MEDIUMHIGH = 3,
DYNAMIC_TIER_HIGH = 1,
)
min_pop = 5
/datum/dynamic_ruleset/midround/from_living/obsesed/is_valid_candidate(mob/candidate, client/candidate_client)
return ..() && !!candidate.get_organ_by_type(/obj/item/organ/brain)
/datum/dynamic_ruleset/midround/from_living/obsesed/antag_check(mob/candidate)
// Obsessed is a special case, it can select other antag players
return !candidate.mind.has_antag_datum(/datum/antagonist/obsessed)
/datum/dynamic_ruleset/midround/from_living/obsesed/assign_role(datum/mind/candidate)
var/obj/item/organ/brain/brain = candidate.current.get_organ_by_type(__IMPLIED_TYPE__)
brain.brain_gain_trauma(/datum/brain_trauma/special/obsessed)
notify_ghosts(
"[candidate.current.real_name] has developed an obsession with someone!",
source = candidate.current,
header = "Love Can Bloom",
)