mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-20 19:49:39 +01:00
## LTS Document Check this document before making any significant future changes to blood worms, please. https://hackmd.io/@RikuTheKiller/H1AHQSKNZx ## About The Pull Request THIS PR SHOULD ABSOLUTELY BE TM'D FIRST Blood worms are a new progression antag. When the event runs, 2 candidates are picked from ghosts and spawned in as blood worm hatchlings, which then have to grow up, do a couple objectives and take over the station. Hatchlings are weak outside of a host, while juveniles can stand their own reasonably well. Adults have high offensive power and can only be dealt with using the right gear or a lot of luck and robustness. They're meant to be a moment of glory for achieving maximum progression and they can bootstrap the next hatchlings by gathering corpses before cocooning. Each growth stage requires 30 seconds in a cocoon, which can only be created after consuming a lot of blood. There's a falloff curve on a per-blood-type basis, meaning you can't drain the same person over and over again to reach adulthood. The medbay freezer is a priority target for the blood worms and can get one of them to the juvenile stage if fully ransacked. It takes 500 blood to mature from hatchling to juvenile, and 1500 blood to mature from juvenile to adult. You can only get up to 1000 blood from synthetic sources like monkeys, and consuming synthetic blood is 30% less efficient. Blood worms can also examine living targets to see how much blood a target has, and how much growth the blood worm would gain for consuming that blood. Blood worms spawn in vents and have night vision for maneuvering in maintenance. Hatchlings can ventcrawl, while juveniles can move around by breaking things. Optionally, you can take over a host with a lot of access like the Captain to go basically anywhere, especially if nobody knows you killed the captain. Behind the scenes, host-taking kicks the host's original mind to a backseat mob. This needs the most testing in practice, but it's confirmed that it returns the host's mind back to their body, at least in testing. All mob, ability and action sprites are made by INFRARED_BARON. Legal rights were transferred to me after I paid for the commission. Note, I've been working on this massive PR for quite a while, so documenting every small change is really hard! Apologies for anything I've missed. There's a lot. Final note, admins can spawn these by either: A. Trigger the midround event via the dynamic-panel verb, under the Rulesets tab. B. Giving someone the Blood Worm antag datum via the Traitor Panel in the Player Panel for the target player. This will transform their mob into a valid Blood Worm, with all of the associated objectives and such. ### Active Abilities 1. Leech Blood (No Host) - Lets the blood worm drain blood from living targets and reagent containers. Uses an aggressive grab to restrain living targets until leeching is over, which takes around a second to initiate. Causes oxyloss during the leeching. NPC monkeys can't escape from this and it floors targets as well. 2. Spit Blood (Both) - Multi-function ability, lets the blood worm fire ranged corrosive blood spit at targets, melt restraints on their hosts by right-clicking, and as an adult, shoot a burst of blood spit at a target by right-clicking. Note of the right-click abilities, shooting bursts can't be done while in a host. (to avoid unfair stealth kills) Shooting a burst has a much longer cooldown than shooting normally. All spit types cost blood to use. 3. Invade Corpse (No Host) - Lets the blood worm take a host for themselves, consuming all of the host's blood and in essence, "becoming" the host. Any bloodloss inflicted on the host is taken as damage to the blood worm, and the blood worm retains its weakness to fire even in this state. Burn damage itself no longer has any extra damage, though. 4. Leave Host (Host) - Title, literally just leaves the host after a delay. Notably works even while the host is moving, dead, incapacitated or otherwise fucked up in any way, shape or form. 5. Inject Blood (Host) - Lets the blood worm heal its host. The potency of this increases as the worm grows up, but so does the cooldown and blood consumption. This works on organ damage, injuries, etc. 6. Mature (No Host) - Makes the blood worm enter a cocoon for 30 seconds, emerging as the next growth stage. Requires an increasing amount of consumed blood / growth as the blood worm uses it. 7. Reproduce (No Host, Adult Only) - Makes the blood worm enter a cocoon for 30 seconds, with 4 hatchlings emerging out of it, including the original blood worm, now reverted back into a hatchling as well. 8. Revive Host (Host) - If the host is in a viable state to be revived, revives them after an animation sequence plays out. ### Passive Abilities 1. Space Immunity - Blood worms are immune to the cold, low pressures and a lack of oxygen. Only the immunity to a lack of oxygen carries on to hosts from this. 2. Organ Insertion - Blood worms can insert organs into their hosts by right-clicking on them with the organ in-hand. This mainly exists to deal with hosts that lack organs, and avoids the gotcha where an adult blood worm ends up gutting their host by hitting them too hard, as they can simply fix it on the spot. 3. Life Support - Blood worm hosts don't need a heart, lungs or a liver to survive. Lungs are useful for speaking, and a liver is necessary to process reagents. 4. Regeneration - Blood worms slowly heal over time. This is nowhere near enough to overcome bleeding or heat damage, since it's 0.3 hp/s for a hatchling, 0.4 hp/s for a juvenile and 0.5 hp/s for an adult. 5. Night Vision - Blood worms can see in the dark. This doesn't extend to hosts. 6. Ventcrawling - Hatchling blood worms can ventcrawl. 7. Doorcrawling - Hatchling and juvenile blood worms can slide under doors. Doing so takes 3 seconds for a hatchling and 5 seconds for a juvenile. 8. HUD - Blood worms can tell how much blood targets have at a glance, via a blood HUD bar exclusive to them. They can also tell apart other blood worm hosts from normal people via an antag HUD. There's also an examine message they can use on living targets for even more info. ### Weaknesses 1. Heat and Fire - Blood worms quickly die to heat, their bodies are flammable and their blood will burn up if their host's core temperature is too high. The main counter to this is getting a host with flame-resistant gear. 2. Bleeding - While in a host, bleeding wounds will directly damage the blood worm itself. How much a host needs to bleed before the worm dies depends on their growth stage. Blood worm hosts keep bleeding even while dead, so just keep hitting them and they'll die. Blood worms automatically leave their hosts when they hit 10% health or lower, and their hosts bleed 50% faster than normal people. 3. Stuns - Blood worms have no way of dealing with a stunned host other than getting out. They can deal with any restraints by melting them, though. 4. Testing - Security can order a blood worm testing crate from cargo, either for a 20 minute cooldown via the security cargo interface console, or for 10000 credits via the supply console. It contains 4 single-use testers that hurt a bit when applied, but are instant to use and 100% accurate. The stopgap is that they're really fucking expensive and only work once per item. ### Screenshot <img width="280" height="132" alt="image" src="https://github.com/user-attachments/assets/00d22361-997e-4347-a0bf-aa240de40727" /> ## Why It's Good For The Game Antagonist variety, mainly. This is basically Cortical Borers 2: Electric Boogaloo. Currently, we lack any antagonists with mind control abilities. That really sucks! I've also gotten a lot of positive feedback about the antagonist while working on it. This antagonist also has great potential for roleplay, as they can take over hosts, surprise attack people by getting out of a dead corpse, talk to each other using Wormspeak, etc. I think we're also itching for variety on "pest" antagonists. Right now we just have spiders and xenos. Everybody knows these two, so why not mix it up a bit? And as for balance? Blood worms are relatively easy to dispatch when you know their weaknesses, which are extremely clear. Bleeding for hosts, fire for either one, lasers for the worms themselves. As long as you get the host in crit and keep hitting, you've pretty much won, and they can't keep spamming Inject Blood forever since they'll quickly run out of blood to use. ## Changelog 🆑 add: Added a new heavy roundstart/midround antagonist, the Blood Worm. Credit to INFRARED_BARON for the sprites! fix: Removing traits based on a source no longer causes issues with trait signals. fix: High-priority effects no longer double-trigger due to subsystem issues. fix: Weighted averaging in reagent merging code has been band-aid fixed. It's not the best, but it works. /🆑
1220 lines
48 KiB
Plaintext
1220 lines
48 KiB
Plaintext
/datum/dynamic_ruleset/midround
|
|
repeatable = TRUE
|
|
repeatable_weight_decrease = 2
|
|
/// LIGHT_MIDROUND or HEAVY_MIDROUND - determines which pool it enters
|
|
var/midround_type
|
|
/// If the false alarm event can pick this ruleset to trigger, well, a false alarm
|
|
var/false_alarm_able = FALSE
|
|
|
|
/**
|
|
* Collect candidates handles getting the broad pool of players we want to pick from
|
|
*
|
|
* You can sleep in this - say, if you wanted to poll players.
|
|
*/
|
|
/datum/dynamic_ruleset/midround/proc/collect_candidates()
|
|
return list()
|
|
|
|
/**
|
|
* Called when the ruleset is selected for false alarm
|
|
*/
|
|
/datum/dynamic_ruleset/midround/proc/false_alarm()
|
|
return
|
|
|
|
/datum/dynamic_ruleset/midround/spiders
|
|
name = "Spiders"
|
|
config_tag = "Spiders"
|
|
midround_type = HEAVY_MIDROUND
|
|
false_alarm_able = TRUE
|
|
ruleset_flags = RULESET_INVADER
|
|
weight = alist(
|
|
DYNAMIC_TIER_LOW = 0,
|
|
DYNAMIC_TIER_LOWMEDIUM = 0,
|
|
DYNAMIC_TIER_MEDIUMHIGH = 1,
|
|
DYNAMIC_TIER_HIGH = 2,
|
|
)
|
|
min_pop = 30
|
|
min_antag_cap = 0
|
|
/// Determines how many eggs to create - can take a formula like antag_cap
|
|
var/egg_count = 2
|
|
|
|
/datum/dynamic_ruleset/midround/spiders/can_be_selected()
|
|
return ..() && (GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT) && !isnull(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE))
|
|
|
|
/datum/dynamic_ruleset/midround/spiders/execute()
|
|
var/num_egg = get_antag_cap(length(GLOB.alive_player_list), egg_count)
|
|
while(num_egg > 0)
|
|
var/turf/spawn_loc = find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE)
|
|
if(isnull(spawn_loc))
|
|
break
|
|
var/obj/effect/mob_spawn/ghost_role/spider/midwife/new_eggs = new(spawn_loc)
|
|
new_eggs.amount_grown = 98
|
|
num_egg--
|
|
|
|
addtimer(CALLBACK(src, PROC_REF(announce_spiders)), rand(375, 600) SECONDS)
|
|
|
|
/datum/dynamic_ruleset/midround/spiders/proc/announce_spiders()
|
|
priority_announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", ANNOUNCER_ALIENS)
|
|
|
|
/datum/dynamic_ruleset/midround/spiders/false_alarm()
|
|
announce_spiders()
|
|
|
|
/datum/dynamic_ruleset/midround/pirates
|
|
name = "Pirates"
|
|
config_tag = "Light Pirates"
|
|
midround_type = LIGHT_MIDROUND
|
|
jobban_flag = ROLE_TRAITOR
|
|
ruleset_flags = RULESET_INVADER|RULESET_ADMIN_CONFIGURABLE
|
|
weight = 3
|
|
min_pop = 15
|
|
min_antag_cap = 0 // ship will spawn if there are no ghosts around
|
|
|
|
/// Pool to pick pirates from
|
|
var/list/datum/pirate_gang/pirate_pool
|
|
|
|
/datum/dynamic_ruleset/midround/pirates/New(list/dynamic_config)
|
|
. = ..()
|
|
pirate_pool = default_pirate_pool()
|
|
|
|
/datum/dynamic_ruleset/midround/pirates/can_be_selected()
|
|
return ..() && !SSmapping.is_planetary() && (GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT) && length(default_pirate_pool()) > 0
|
|
|
|
// An abornmal ruleset that selects no players, but just spawns a pirate ship
|
|
/datum/dynamic_ruleset/midround/pirates/execute()
|
|
send_pirate_threat(pirate_pool)
|
|
|
|
/// Returns what pool of pirates to drawn from
|
|
/// Returned list is mutated by the ruleset
|
|
/datum/dynamic_ruleset/midround/pirates/proc/default_pirate_pool()
|
|
return GLOB.light_pirate_gangs
|
|
|
|
/datum/dynamic_ruleset/midround/pirates/heavy
|
|
name = "Pirates"
|
|
config_tag = "Heavy Pirates"
|
|
midround_type = HEAVY_MIDROUND
|
|
jobban_flag = ROLE_TRAITOR
|
|
ruleset_flags = RULESET_INVADER
|
|
weight = 3
|
|
min_pop = 25
|
|
min_antag_cap = 0 // ship will spawn if there are no ghosts around
|
|
|
|
/datum/dynamic_ruleset/midround/pirates/heavy/default_pirate_pool()
|
|
return GLOB.heavy_pirate_gangs
|
|
|
|
#define RANDOM_PIRATE_POOL "Random"
|
|
|
|
/datum/dynamic_ruleset/midround/pirates/configure_ruleset(mob/admin)
|
|
var/list/admin_pool = list("[RULESET_CONFIG_CANCEL]" = TRUE, "[RANDOM_PIRATE_POOL]" = TRUE)
|
|
for(var/datum/pirate_gang/gang as anything in default_pirate_pool())
|
|
admin_pool[gang.name] = gang
|
|
var/picked = tgui_input_list(admin, "Select a pirate gang", "Pirate Gang Selection", admin_pool)
|
|
if(!picked || picked == RULESET_CONFIG_CANCEL)
|
|
return RULESET_CONFIG_CANCEL
|
|
if(picked == RANDOM_PIRATE_POOL)
|
|
return null
|
|
|
|
pirate_pool = list(admin_pool[picked])
|
|
return null
|
|
|
|
#undef RANDOM_PIRATE_POOL
|
|
|
|
#define NO_ANSWER 0
|
|
#define POSITIVE_ANSWER 1
|
|
#define NEGATIVE_ANSWER 2
|
|
|
|
/datum/dynamic_ruleset/midround/pirates/proc/send_pirate_threat(list/pirate_selection)
|
|
var/datum/pirate_gang/chosen_gang = pick_n_take(pirate_selection)
|
|
///If there was nothing to pull from our requested list, stop here.
|
|
if(!chosen_gang)
|
|
message_admins("Error attempting to run the space pirate event, as the given pirate gangs list was empty.")
|
|
return
|
|
//set payoff
|
|
var/payoff = 0
|
|
var/datum/bank_account/account = SSeconomy.get_dep_account(ACCOUNT_CAR)
|
|
if(account)
|
|
payoff = max(PAYOFF_MIN, FLOOR(account.account_balance * 0.80, 1000))
|
|
var/datum/comm_message/threat = chosen_gang.generate_message(payoff)
|
|
//send message
|
|
priority_announce("Incoming subspace communication. Secure channel opened at all communication consoles.", "Incoming Message", SSstation.announcer.get_rand_report_sound())
|
|
threat.answer_callback = CALLBACK(src, PROC_REF(pirates_answered), threat, chosen_gang, payoff, world.time)
|
|
addtimer(CALLBACK(src, PROC_REF(spawn_pirates), threat, chosen_gang), RESPONSE_MAX_TIME)
|
|
GLOB.communications_controller.send_message(threat, unique = TRUE)
|
|
|
|
/datum/dynamic_ruleset/midround/pirates/proc/pirates_answered(datum/comm_message/threat, datum/pirate_gang/chosen_gang, payoff, initial_send_time)
|
|
if(world.time > initial_send_time + RESPONSE_MAX_TIME)
|
|
priority_announce(chosen_gang.response_too_late, sender_override = chosen_gang.ship_name, color_override = chosen_gang.announcement_color)
|
|
return
|
|
if(!threat?.answered)
|
|
return
|
|
if(threat.answered == NEGATIVE_ANSWER)
|
|
priority_announce(chosen_gang.response_rejected, sender_override = chosen_gang.ship_name, color_override = chosen_gang.announcement_color)
|
|
return
|
|
|
|
var/datum/bank_account/plundered_account = SSeconomy.get_dep_account(ACCOUNT_CAR)
|
|
if(plundered_account)
|
|
if(plundered_account.adjust_money(-payoff))
|
|
chosen_gang.paid_off = TRUE
|
|
priority_announce(chosen_gang.response_received, sender_override = chosen_gang.ship_name, color_override = chosen_gang.announcement_color)
|
|
else
|
|
priority_announce(chosen_gang.response_not_enough, sender_override = chosen_gang.ship_name, color_override = chosen_gang.announcement_color)
|
|
|
|
/datum/dynamic_ruleset/midround/pirates/proc/spawn_pirates(datum/comm_message/threat, datum/pirate_gang/chosen_gang)
|
|
if(chosen_gang.paid_off)
|
|
return
|
|
|
|
var/list/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for a [span_notice("pirate crew of [chosen_gang.name]?")]", check_jobban = ROLE_TRAITOR, alert_pic = /obj/item/claymore/cutlass, role_name_text = "pirate crew")
|
|
shuffle_inplace(candidates)
|
|
|
|
var/template_key = "pirate_[chosen_gang.ship_template_id]"
|
|
var/datum/map_template/shuttle/pirate/ship = SSmapping.shuttle_templates[template_key]
|
|
var/x = rand(TRANSITIONEDGE,world.maxx - TRANSITIONEDGE - ship.width)
|
|
var/y = rand(TRANSITIONEDGE,world.maxy - TRANSITIONEDGE - ship.height)
|
|
var/z = SSmapping.empty_space.z_value
|
|
var/turf/T = locate(x,y,z)
|
|
if(!T)
|
|
CRASH("Pirate event found no turf to load in")
|
|
|
|
if(!ship.load(T))
|
|
CRASH("Loading pirate ship failed!")
|
|
|
|
for(var/turf/area_turf as anything in ship.get_affected_turfs(T))
|
|
for(var/obj/effect/mob_spawn/ghost_role/human/pirate/spawner in area_turf)
|
|
if(candidates.len > 0)
|
|
var/mob/our_candidate = candidates[1]
|
|
var/mob/spawned_mob = spawner.create_from_ghost(our_candidate)
|
|
candidates -= our_candidate
|
|
notify_ghosts(
|
|
"The [chosen_gang.ship_name] has an object of interest: [spawned_mob]!",
|
|
source = spawned_mob,
|
|
header = "Pirates!",
|
|
)
|
|
else
|
|
notify_ghosts(
|
|
"The [chosen_gang.ship_name] has an object of interest: [spawner]!",
|
|
source = spawner,
|
|
header = "Pirate Spawn Here!",
|
|
)
|
|
|
|
priority_announce(chosen_gang.arrival_announcement, sender_override = chosen_gang.ship_name)
|
|
|
|
#undef NO_ANSWER
|
|
#undef POSITIVE_ANSWER
|
|
#undef NEGATIVE_ANSWER
|
|
/**
|
|
* ### Ghost rulesets
|
|
*
|
|
* Rulesets which select an observer/ghost player to play as a new character
|
|
*
|
|
* Implementation notes:
|
|
* - prepare_role will handle making the body for the mob for you. Avoid touching it if not necessary.
|
|
* - create_ruleset_body is what makes the new /mob for the candidate. It handles putting the player in the body for you.
|
|
* You can override it entirely for to spawn a different mob type.
|
|
* You can also override it to spawn nothing, if you're doing special handling in assign_role, but you'll have to handle moving the player yourself.
|
|
* - assign_role is what gives the player their antag datum.
|
|
*/
|
|
/datum/dynamic_ruleset/midround/from_ghosts
|
|
///Path of an item to show up in ghost polls for applicants to sign up.
|
|
var/signup_atom_appearance = /obj/structure/sign/poster/contraband/syndicate_recruitment
|
|
/// Text shown in the candidate poll. Optional, if unset uses pref_flag. (Though required if pref_flag is unset)
|
|
var/candidate_role
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/can_be_selected()
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
return ..() && (GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT)
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/get_candidate_mind(mob/dead/candidate)
|
|
// Ghost roles will always get a fresh mind
|
|
return new /datum/mind(candidate.key)
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/prepare_for_role(datum/mind/candidate)
|
|
var/mob/living/body = create_ruleset_body()
|
|
if(isnull(body))
|
|
return
|
|
candidate.transfer_to(body, force_key_move = TRUE) // yoinks the candidate's client
|
|
if(ishuman(body))
|
|
var/mob/living/carbon/human/human_body = body
|
|
body.client?.prefs.safe_transfer_prefs_to(body)
|
|
human_body.dna.remove_all_mutations()
|
|
human_body.dna.update_dna_identity()
|
|
|
|
/**
|
|
* Handles making the body for the candidate
|
|
*
|
|
* Handling loc is not necessary here - you can do it in assign_role
|
|
*
|
|
* Returning null will skip body creation entirely, though you will be expected to do it yourself in assign_role
|
|
*/
|
|
/datum/dynamic_ruleset/midround/from_ghosts/proc/create_ruleset_body()
|
|
return new /mob/living/carbon/human
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/collect_candidates()
|
|
var/readable_poll_role = candidate_role || pref_flag
|
|
if(isnull(readable_poll_role))
|
|
stack_trace("[config_tag]: No candidate role or pref_flag set, give it a human readable candidate roll at the bare minimum.")
|
|
readable_poll_role = "Some Midround Antagonist Without A Role Set (Yell At Coders)"
|
|
|
|
return SSpolling.poll_candidates(
|
|
group = trim_candidates(GLOB.dead_player_list | GLOB.current_observers_list),
|
|
question = "Looking for volunteers to become [span_notice(readable_poll_role)] for [span_danger(name)]",
|
|
// check_jobban = list(ROLE_SYNDICATE, jobban_flag || pref_flag), // Not necessary, handled in trim_candidates()
|
|
// role = pref_flag, // Not necessary, handled in trim_candidates()
|
|
poll_time = 30 SECONDS,
|
|
alert_pic = signup_atom_appearance,
|
|
role_name_text = readable_poll_role,
|
|
)
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/wizard
|
|
name = "Wizard"
|
|
config_tag = "Midround Wizard"
|
|
preview_antag_datum = /datum/antagonist/wizard
|
|
midround_type = HEAVY_MIDROUND
|
|
candidate_role = "Wizard"
|
|
pref_flag = ROLE_WIZARD_MIDROUND
|
|
jobban_flag = ROLE_WIZARD
|
|
ruleset_flags = RULESET_INVADER|RULESET_HIGH_IMPACT
|
|
weight = alist(
|
|
DYNAMIC_TIER_LOW = 0,
|
|
DYNAMIC_TIER_LOWMEDIUM = 0,
|
|
DYNAMIC_TIER_MEDIUMHIGH = 1,
|
|
DYNAMIC_TIER_HIGH = 2,
|
|
)
|
|
min_pop = 30
|
|
max_antag_cap = 1
|
|
ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_WIZARDDEN)
|
|
signup_atom_appearance = /obj/item/clothing/head/wizard
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/wizard/assign_role(datum/mind/candidate)
|
|
candidate.add_antag_datum(/datum/antagonist/wizard) // moves to lair for us
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/nukies
|
|
name = "Nuclear Operatives"
|
|
config_tag = "Midround Nukeops"
|
|
preview_antag_datum = /datum/antagonist/nukeop
|
|
midround_type = HEAVY_MIDROUND
|
|
candidate_role = "Operative"
|
|
pref_flag = ROLE_OPERATIVE_MIDROUND
|
|
jobban_flag = ROLE_OPERATIVE
|
|
ruleset_flags = RULESET_INVADER|RULESET_HIGH_IMPACT
|
|
weight = alist(
|
|
DYNAMIC_TIER_LOW = 0,
|
|
DYNAMIC_TIER_LOWMEDIUM = 1,
|
|
DYNAMIC_TIER_MEDIUMHIGH = 3,
|
|
DYNAMIC_TIER_HIGH = 3,
|
|
)
|
|
min_pop = 30
|
|
min_antag_cap = list("denominator" = 18, "offset" = 1)
|
|
repeatable = FALSE
|
|
ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_NUKIEBASE)
|
|
signup_atom_appearance = /obj/machinery/nuclearbomb/syndicate
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/nukies/create_execute_args()
|
|
return list(
|
|
new /datum/team/nuclear(),
|
|
get_most_experienced(selected_minds, pref_flag),
|
|
)
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/nukies/assign_role(datum/mind/candidate, datum/team/nuclear/nuke_team, datum/mind/most_experienced)
|
|
if(most_experienced == candidate)
|
|
candidate.add_antag_datum(/datum/antagonist/nukeop/leader, nuke_team) // moves to nuke base for us
|
|
else
|
|
candidate.add_antag_datum(/datum/antagonist/nukeop, nuke_team) // moves to nuke base for us
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/nukies/round_result()
|
|
var/datum/antagonist/nukeop/nukie = selected_minds[1].has_antag_datum(/datum/antagonist/nukeop)
|
|
var/datum/team/nuclear/nuke_team = nukie.get_team()
|
|
var/result = nuke_team.get_result()
|
|
switch(result)
|
|
if(NUKE_RESULT_FLUKE)
|
|
SSticker.mode_result = "loss - syndicate nuked - disk secured"
|
|
SSticker.news_report = NUKE_SYNDICATE_BASE
|
|
if(NUKE_RESULT_NUKE_WIN)
|
|
SSticker.mode_result = "win - syndicate nuke"
|
|
SSticker.news_report = STATION_DESTROYED_NUKE
|
|
if(NUKE_RESULT_NOSURVIVORS)
|
|
SSticker.mode_result = "halfwin - syndicate nuke - did not evacuate in time"
|
|
SSticker.news_report = STATION_DESTROYED_NUKE
|
|
if(NUKE_RESULT_WRONG_STATION)
|
|
SSticker.mode_result = "halfwin - blew wrong station"
|
|
SSticker.news_report = NUKE_MISS
|
|
if(NUKE_RESULT_WRONG_STATION_DEAD)
|
|
SSticker.mode_result = "halfwin - blew wrong station - did not evacuate in time"
|
|
SSticker.news_report = NUKE_MISS
|
|
if(NUKE_RESULT_CREW_WIN_SYNDIES_DEAD)
|
|
SSticker.mode_result = "loss - evacuation - disk secured - syndi team dead"
|
|
SSticker.news_report = OPERATIVES_KILLED
|
|
if(NUKE_RESULT_CREW_WIN)
|
|
SSticker.mode_result = "loss - evacuation - disk secured"
|
|
SSticker.news_report = OPERATIVES_KILLED
|
|
if(NUKE_RESULT_DISK_LOST)
|
|
SSticker.mode_result = "halfwin - evacuation - disk not secured"
|
|
SSticker.news_report = OPERATIVE_SKIRMISH
|
|
if(NUKE_RESULT_DISK_STOLEN)
|
|
SSticker.mode_result = "halfwin - detonation averted"
|
|
SSticker.news_report = OPERATIVE_SKIRMISH
|
|
else
|
|
SSticker.mode_result = "halfwin - interrupted"
|
|
SSticker.news_report = OPERATIVE_SKIRMISH
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/nukies/clown
|
|
name = "Clown Operatives"
|
|
config_tag = "Midround Clownops"
|
|
preview_antag_datum = /datum/antagonist/nukeop/clownop
|
|
candidate_role = "Operative"
|
|
pref_flag = ROLE_CLOWN_OPERATIVE_MIDROUND
|
|
jobban_flag = ROLE_CLOWN_OPERATIVE
|
|
weight = 0
|
|
signup_atom_appearance = /obj/machinery/nuclearbomb/syndicate/bananium
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/nukies/clown/assign_role(datum/mind/candidate, datum/team/nuclear/nuke_team, datum/mind/most_experienced)
|
|
if(most_experienced == candidate)
|
|
candidate.add_antag_datum(/datum/antagonist/nukeop/leader/clownop, nuke_team) // moves to nuke base for us
|
|
else
|
|
candidate.add_antag_datum(/datum/antagonist/nukeop/clownop, nuke_team) // moves to nuke base for us
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/blob
|
|
name = "Blob"
|
|
config_tag = "Blob"
|
|
preview_antag_datum = /datum/antagonist/blob
|
|
midround_type = HEAVY_MIDROUND
|
|
false_alarm_able = TRUE
|
|
pref_flag = ROLE_BLOB
|
|
ruleset_flags = RULESET_INVADER
|
|
weight = alist(
|
|
DYNAMIC_TIER_LOW = 0,
|
|
DYNAMIC_TIER_LOWMEDIUM = 1,
|
|
DYNAMIC_TIER_MEDIUMHIGH = 3,
|
|
DYNAMIC_TIER_HIGH = 3,
|
|
)
|
|
min_pop = 30
|
|
max_antag_cap = 1
|
|
repeatable_weight_decrease = 3
|
|
signup_atom_appearance = /obj/structure/blob/normal
|
|
/// How many points does the blob spawn with
|
|
var/starting_points = OVERMIND_STARTING_POINTS
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/blob/create_ruleset_body()
|
|
return new /mob/eye/blob(get_blobspawn(), starting_points)
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/blob/assign_role(datum/mind/candidate)
|
|
return // everything is handled by blob new()
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/blob/proc/get_blobspawn()
|
|
if(!length(GLOB.blobstart))
|
|
var/obj/effect/landmark/observer_start/default = locate() in GLOB.landmarks_list
|
|
return get_turf(default)
|
|
|
|
return pick(GLOB.blobstart)
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/blob/false_alarm()
|
|
priority_announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", ANNOUNCER_OUTBREAK5)
|
|
|
|
// Set status displays to biohazard alert even for false alarm
|
|
send_status_display_biohazard_alert()
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph
|
|
name = "Alien Infestation"
|
|
config_tag = "Xenomorph"
|
|
preview_antag_datum = /datum/antagonist/xeno
|
|
midround_type = HEAVY_MIDROUND
|
|
false_alarm_able = TRUE
|
|
pref_flag = ROLE_ALIEN
|
|
ruleset_flags = RULESET_INVADER
|
|
weight = alist(
|
|
DYNAMIC_TIER_LOW = 0,
|
|
DYNAMIC_TIER_LOWMEDIUM = 1,
|
|
DYNAMIC_TIER_MEDIUMHIGH = 5,
|
|
DYNAMIC_TIER_HIGH = 5,
|
|
)
|
|
min_pop = 30
|
|
max_antag_cap = 1
|
|
min_antag_cap = 1
|
|
repeatable_weight_decrease = 3
|
|
signup_atom_appearance = /mob/living/basic/alien
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/New(list/dynamic_config)
|
|
. = ..()
|
|
max_antag_cap += prob(50) // 50% chance to get a second xeno, free!
|
|
|
|
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/can_be_selected()
|
|
return ..() && length(find_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
|
|
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",
|
|
)
|