/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_vent_spawns()) > 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_vent_spawns()) /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/blood_worms name = "Blood Worm Infestation" config_tag = "Midround Blood Worm" preview_antag_datum = /datum/antagonist/blood_worm/infestation // Please set this to HEAVY_MIDROUND once dynamic has fine-grained handling for spawn times and doesn't restrict all heavy midrounds to spawning after 40 minutes. // Blood worms are intended to spawn 10-30 minutes into a round. This is a band-aid fix, and the better of two evils. (wrong threat tier vs wrong round timing) midround_type = LIGHT_MIDROUND false_alarm_able = TRUE pref_flag = ROLE_BLOOD_WORM_INFESTATION jobban_flag = ROLE_BLOOD_WORM candidate_role = "Blood Worm" ruleset_flags = RULESET_INVADER weight = 2 // For reference, Nightmare has a weight of 5. min_pop = 20 // Blood worms are limited by resources, so low pop means they have a harder time getting their tail in the door. min_antag_cap = 1 max_antag_cap = 2 signup_atom_appearance = /mob/living/basic/blood_worm/juvenile /datum/dynamic_ruleset/midround/from_ghosts/blood_worms/can_be_selected() return ..() && length(find_vent_spawns()) > 0 /datum/dynamic_ruleset/midround/from_ghosts/blood_worms/execute() . = ..() addtimer(CALLBACK(src, PROC_REF(announce_worms)), rand(450, 750) SECONDS) /datum/dynamic_ruleset/midround/from_ghosts/blood_worms/create_ruleset_body() return new /mob/living/basic/blood_worm/hatchling /datum/dynamic_ruleset/midround/from_ghosts/blood_worms/create_execute_args() return list(find_vent_spawns()) /datum/dynamic_ruleset/midround/from_ghosts/blood_worms/assign_role(datum/mind/candidate, list/vent_list) candidate.add_antag_datum(/datum/antagonist/blood_worm) 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/blood_worms/proc/announce_worms() 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/blood_worms/false_alarm() announce_worms() /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", )