Files
Bubberstation/code/controllers/subsystem/dynamic/dynamic_ruleset_midround.dm
Rhials 822a1d3d55 Fugitive Hunters spawn again (#92761)
## About The Pull Request

Fugitive hunters no longer runtime on their timer check. 

The `check_spawn_hunters` proc was getting passed the fugitive hunters'
backstory (not needed in the proc) instead of the time until their
expected spawn.
## Why It's Good For The Game

The fugitive hunters are a pretty important part of the fugitive antag.
It's like playing hide and seek without any seekers. It sucks.

Closes the unresolved part of #92618.
2025-08-29 15:15:08 -05:00

1155 lines
45 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 = list(
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 = list(
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 = list(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 1,
DYNAMIC_TIER_MEDIUMHIGH = 3,
DYNAMIC_TIER_HIGH = 3,
)
min_pop = 30
min_antag_cap = list("denominator" = 18, "offset" = 1)
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 = list(
DYNAMIC_TIER_LOW = 0,
DYNAMIC_TIER_LOWMEDIUM = 1,
DYNAMIC_TIER_MEDIUMHIGH = 3,
DYNAMIC_TIER_HIGH = 3,
)
min_pop = 30
max_antag_cap = 1
repeatable_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)
/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 = list(
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 = list(
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 = list(
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
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/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
/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_ruleset_body()
return // handled by assign_role() entirely
/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/assign_role(datum/mind/candidate)
var/mob/living/carbon/human/good_version = find_clone()
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.original_ref = WEAKREF(good_version.mind)
antag.setup_clone()
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
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/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 = list(
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 = list(
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 = list(
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",
)