diff --git a/code/__DEFINES/antag_defines.dm b/code/__DEFINES/antag_defines.dm
index b5489f6948e..43a519e3ce7 100644
--- a/code/__DEFINES/antag_defines.dm
+++ b/code/__DEFINES/antag_defines.dm
@@ -73,6 +73,8 @@ GLOBAL_LIST(contractors)
*/
#define IS_CHANGELING(mob) (isliving(mob) && mob?:mind?:has_antag_datum(/datum/antagonist/changeling))
+#define IS_MINDFLAYER(mob) (isliving(mob) && mob?:mind?:has_antag_datum(/datum/antagonist/mindflayer))
+
#define IS_MINDSLAVE(mob) (ishuman(mob) && mob?:mind?:has_antag_datum(/datum/antagonist/mindslave, FALSE))
/**
diff --git a/code/__DEFINES/combat_defines.dm b/code/__DEFINES/combat_defines.dm
index 4bf5eb5b7e0..11b7f38ea6c 100644
--- a/code/__DEFINES/combat_defines.dm
+++ b/code/__DEFINES/combat_defines.dm
@@ -50,6 +50,7 @@
#define CANPUSH (1<<3)
#define PASSEMOTES (1<<4) //Mob has holders inside of it that need to see emotes.
#define GODMODE (1<<5)
+#define TERMINATOR_FORM (1<<6)
//Health Defines
#define HEALTH_THRESHOLD_CRIT 0
@@ -149,6 +150,7 @@
#define EMP_HEAVY 1
#define EMP_LIGHT 2
+#define EMP_WEAKENED 3
/*
* converts life cycle values into deciseconds. try and avoid usage of this.
diff --git a/code/__DEFINES/dcs/mob_signals.dm b/code/__DEFINES/dcs/mob_signals.dm
index c876da9c5c8..487a1eb2d85 100644
--- a/code/__DEFINES/dcs/mob_signals.dm
+++ b/code/__DEFINES/dcs/mob_signals.dm
@@ -206,6 +206,8 @@
/// called when a living mob's stun status is cleared: ()
#define COMSIG_LIVING_CLEAR_STUNS "living_clear_stuns"
+/// called when something needs to force a mindflayer to retract their weapon implants
+#define COMSIG_FLAYER_RETRACT_IMPLANTS "flayer_retract_implants"
/// Sent from datum/spell/ethereal_jaunt/cast, before the mob enters jaunting as a pre-check: (mob/jaunter)
#define COMSIG_MOB_PRE_JAUNT "spell_mob_pre_jaunt"
diff --git a/code/__DEFINES/directions.dm b/code/__DEFINES/directions.dm
index 7e4dd3c9769..b78423b7bcc 100644
--- a/code/__DEFINES/directions.dm
+++ b/code/__DEFINES/directions.dm
@@ -31,3 +31,7 @@
/// Inverse direction, taking into account UP|DOWN if necessary.
#define REVERSE_DIR(dir) ( ((dir & 85) << 1) | ((dir & 170) >> 1) )
+/// returns TRUE if the direction is EAST or WEST
+#define DIR_JUST_HORIZONTAL(dir) ((dir == EAST) || (dir == WEST))
+/// returns TRUE if the direction is NORTH or SOUTH
+#define DIR_JUST_VERTICAL(dir) ((dir == NORTH) || (dir == SOUTH))
diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm
index c4a222ebc14..b35b876a1d3 100644
--- a/code/__DEFINES/flags.dm
+++ b/code/__DEFINES/flags.dm
@@ -139,6 +139,7 @@
#define PASSDOOR (1<<7)
#define PASSGIRDER (1<<8)
#define PASSTAKE (1<<9)
+#define PASSBARRICADE (1<<10)
//turf-only flags
#define BLESSED_TILE (1<<0)
diff --git a/code/__DEFINES/gamemode.dm b/code/__DEFINES/gamemode.dm
index ac55af989b9..df27eb20c0c 100644
--- a/code/__DEFINES/gamemode.dm
+++ b/code/__DEFINES/gamemode.dm
@@ -45,6 +45,7 @@
#define SPECIAL_ROLE_SYNDICATE_DEATHSQUAD "Syndicate Commando"
#define SPECIAL_ROLE_TRAITOR "Traitor"
#define SPECIAL_ROLE_VAMPIRE "Vampire"
+#define SPECIAL_ROLE_MIND_FLAYER "Mind Flayer"
#define SPECIAL_ROLE_VAMPIRE_THRALL "Vampire Thrall"
#define SPECIAL_ROLE_WIZARD "Wizard"
#define SPECIAL_ROLE_WIZARD_APPRENTICE "Wizard Apprentice"
diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm
index 159c8a1bebd..a24fcae6c56 100644
--- a/code/__DEFINES/hud.dm
+++ b/code/__DEFINES/hud.dm
@@ -34,29 +34,30 @@
//data HUD (medhud, sechud) defines
//Don't forget to update human/New() if you change these!
-#define DATA_HUD_SECURITY_BASIC 1
-#define DATA_HUD_SECURITY_ADVANCED 2
-#define DATA_HUD_MEDICAL_BASIC 3
-#define DATA_HUD_MEDICAL_ADVANCED 4
-#define DATA_HUD_DIAGNOSTIC_BASIC 5
+#define DATA_HUD_SECURITY_BASIC 1
+#define DATA_HUD_SECURITY_ADVANCED 2
+#define DATA_HUD_MEDICAL_BASIC 3
+#define DATA_HUD_MEDICAL_ADVANCED 4
+#define DATA_HUD_DIAGNOSTIC_BASIC 5
#define DATA_HUD_DIAGNOSTIC_ADVANCED 6
-#define DATA_HUD_HYDROPONIC 7
-#define DATA_HUD_JANITOR 8
+#define DATA_HUD_HYDROPONIC 7
+#define DATA_HUD_JANITOR 8
//antag HUD defines
-#define ANTAG_HUD_CULT 9
-#define ANTAG_HUD_REV 10
-#define ANTAG_HUD_OPS 11
-#define ANTAG_HUD_WIZ 12
-#define ANTAG_HUD_SHADOW 13
-#define ANTAG_HUD_TRAITOR 14
-#define ANTAG_HUD_NINJA 15
-#define ANTAG_HUD_CHANGELING 16
-#define ANTAG_HUD_VAMPIRE 17
-#define ANTAG_HUD_ABDUCTOR 18
-#define DATA_HUD_ABDUCTOR 19
-#define ANTAG_HUD_EVENTMISC 20
-#define ANTAG_HUD_BLOB 21
-#define ANTAG_HUD_ZOMBIE 22
+#define ANTAG_HUD_CULT 9
+#define ANTAG_HUD_REV 10
+#define ANTAG_HUD_OPS 11
+#define ANTAG_HUD_WIZ 12
+#define ANTAG_HUD_SHADOW 13
+#define ANTAG_HUD_TRAITOR 14
+#define ANTAG_HUD_NINJA 15
+#define ANTAG_HUD_CHANGELING 16
+#define ANTAG_HUD_VAMPIRE 17
+#define ANTAG_HUD_ABDUCTOR 18
+#define DATA_HUD_ABDUCTOR 19
+#define ANTAG_HUD_EVENTMISC 20
+#define ANTAG_HUD_BLOB 21
+#define ANTAG_HUD_ZOMBIE 22
+#define ANTAG_HUD_MIND_FLAYER 23
// Notification action types
#define NOTIFY_JUMP "jump"
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index 63319d1b5d3..d5dc2ff3513 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -1,6 +1,8 @@
// Datums
#define isdatum(thing) (istype(thing, /datum))
+#define isspell(A) (istype(A, /datum/spell))
+
// Atoms
#define isatom(A) (isloc(A))
diff --git a/code/__DEFINES/mindflayer_defines.dm b/code/__DEFINES/mindflayer_defines.dm
new file mode 100644
index 00000000000..6cfeb2bf9df
--- /dev/null
+++ b/code/__DEFINES/mindflayer_defines.dm
@@ -0,0 +1,30 @@
+// Defines below to be used with the `power_type` var.
+/// Denotes that this power is free and should be given to all mindflayers by default.
+#define FLAYER_INNATE_POWER 1
+/// Denotes that this power can only be obtained by purchasing it.
+#define FLAYER_PURCHASABLE_POWER 2
+/// Denotes that this power can not be obtained normally. Primarily used for base types such as [/datum/spell/flayer/weapon].
+#define FLAYER_UNOBTAINABLE_POWER 3
+
+/// How many swarms can you drain per person?
+#define BRAIN_DRAIN_LIMIT 120
+/// The time per harvesting tick
+#define DRAIN_TIME 0.25 SECONDS
+/// If we want to keep draining someone but we don't have any swarms to gain
+#define DRAIN_BUT_NO_SWARMS 2
+
+#define isflayerpassive(A) (istype(A, /datum/mindflayer_passive))
+
+// For organizing what spells are available for what trees
+#define FLAYER_CATEGORY_GENERAL "general"
+#define FLAYER_CATEGORY_DESTROYER "destroyer"
+#define FLAYER_CATEGORY_INTRUDER "intruder"
+#define FLAYER_CATEGORY_SWARMER "swarmer"
+
+#define FLAYER_POWER_LEVEL_ZERO 0
+#define FLAYER_POWER_LEVEL_ONE 1
+#define FLAYER_POWER_LEVEL_TWO 2
+#define FLAYER_POWER_LEVEL_THREE 3
+#define FLAYER_POWER_LEVEL_FOUR 4
+
+#define FLAYER_CAPSTONE_STAGE 4
diff --git a/code/__DEFINES/mob_defines.dm b/code/__DEFINES/mob_defines.dm
index eba27654e8f..7608c99a115 100644
--- a/code/__DEFINES/mob_defines.dm
+++ b/code/__DEFINES/mob_defines.dm
@@ -375,3 +375,11 @@
#define INCORPOREAL_MOVE_NORMAL 1
#define INCORPOREAL_MOVE_NINJA 2
#define INCORPOREAL_MOVE_HOLY_BLOCK 3
+
+// Brain damage ratio defines
+// These are built around the baseline of a brain having a max hp of 120
+#define BRAIN_DAMAGE_RATIO_LIGHT 1 / 12
+#define BRAIN_DAMAGE_RATIO_MINOR 3 / 12
+#define BRAIN_DAMAGE_RATIO_MODERATE 6 / 12
+#define BRAIN_DAMAGE_RATIO_SEVERE 8 / 12
+#define BRAIN_DAMAGE_RATIO_CRITICAL 10 / 12
diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm
index c9c199b2acd..db23d9ac73b 100644
--- a/code/__DEFINES/role_preferences.dm
+++ b/code/__DEFINES/role_preferences.dm
@@ -27,6 +27,7 @@
#define ROLE_TRADER "trader"
#define ROLE_TOURIST "Tourist"
#define ROLE_VAMPIRE "vampire"
+#define ROLE_MIND_FLAYER "mindflayer"
// Role tags for EVERYONE!
#define ROLE_DEMON "demon"
#define ROLE_SENTIENT "sentient animal"
@@ -67,7 +68,8 @@ GLOBAL_LIST_INIT(special_roles, list(
ROLE_TOURIST, // Tourist
ROLE_VAMPIRE = /datum/game_mode/vampire, // Vampire
ROLE_ALIEN, // Xenomorph
- ROLE_WIZARD = /datum/game_mode/wizard // Wizard
+ ROLE_WIZARD = /datum/game_mode/wizard, // Wizard
+ ROLE_MIND_FLAYER,
// UNUSED/BROKEN ANTAGS
// ROLE_HOG_GOD = /datum/game_mode/hand_of_god,
// ROLE_HOG_CULTIST = /datum/game_mode/hand_of_god,
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index 543b23cb503..c3f268824df 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -74,6 +74,14 @@
#define STATUS_EFFECT_REVERSED_SUN /datum/status_effect/reversed_sun // Weaker eternal darkness, nightvision, but nearsight
+#define STATUS_EFFECT_FLAYER_REJUV /datum/status_effect/flayer_rejuv
+
+#define STATUS_EFFECT_QUICKSILVER_FORM /datum/status_effect/quicksilver_form
+
+#define STATUS_EFFECT_TERMINATOR_FORM /datum/status_effect/terminator_form
+
+#define STATUS_EFFECT_OVERCLOCK /datum/status_effect/overclock
+
/////////////
// DEBUFFS //
/////////////
diff --git a/code/__HELPERS/trait_helpers.dm b/code/__HELPERS/trait_helpers.dm
index beea1ae000f..b66d321f989 100644
--- a/code/__HELPERS/trait_helpers.dm
+++ b/code/__HELPERS/trait_helpers.dm
@@ -237,6 +237,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_NPC_ZOMBIE "npc_zombie" // A trait for checking if a zombie should act like an NPC and attack
#define TRAIT_ABSTRACT_HANDS "abstract_hands" // Mobs with this trait can only pick up abstract items.
#define TRAIT_LANGUAGE_LOCKED "language_locked" // cant add/remove languages until removed (excludes babel because fuck everything i guess)
+#define TRAIT_EMP_IMMUNE "emp_immune" //The mob will take no damage from EMPs
+#define TRAIT_EMP_RESIST "emp_resist" //The mob will take less damage from EMPs
+#define TRAIT_MINDFLAYER_NULLIFIED "flayer_nullified" //The mindflayer will not be able to activate their abilities, or drain swarms from people
#define TRAIT_FLYING "flying"
//***** MIND TRAITS *****/
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index d9e07770aa0..1c2228070a5 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -20,6 +20,21 @@
return 0
+/*
+* For getting coordinate signs from a direction define. I.E. NORTHWEST is (-1,1), SOUTH is (0,-1)
+* Returns a length 2 list where the first value is the sign of x, and the second is the sign of y
+*/
+/proc/get_signs_from_direction(direction)
+ var/x_sign = 1
+ var/y_sign = 1
+ x_sign = ((direction & EAST) ? 1 : -1)
+ y_sign = ((direction & NORTH) ? 1 : -1)
+ if(DIR_JUST_VERTICAL(direction))
+ x_sign = 0
+ if(DIR_JUST_HORIZONTAL(direction))
+ y_sign = 0
+ return list(x_sign, y_sign)
+
//Returns the middle-most value
/proc/dd_range(low, high, num)
return max(low,min(high,num))
@@ -183,8 +198,7 @@
var/current_y_step = starting_atom.y
var/starting_z = starting_atom.z
- var/list/line = list(get_step(starting_atom, 0))//get_turf(atom) is faster than locate(x, y, z) //Get turf isn't defined yet so we use get step
-
+ var/list/line = list(get_turf(starting_atom))
var/x_distance = ending_atom.x - current_x_step //x distance
var/y_distance = ending_atom.y - current_y_step
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index 43bb6404075..21ecb3caf4f 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -91,6 +91,9 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_NOSLIP" = TRAIT_NOSLIP,
"TRAIT_MAGPULSE" = TRAIT_MAGPULSE,
"TRAIT_SCOPED" = TRAIT_SCOPED,
+ "TRAIT_EMP_IMMUNE" = TRAIT_EMP_IMMUNE,
+ "TRAIT_EMP_RESIST" = TRAIT_EMP_RESIST,
+ "TRAIT_MINDFLAYER_NULLIFIED" = TRAIT_MINDFLAYER_NULLIFIED,
"TRAIT_MEPHEDRONE_ADAPTED" = TRAIT_MEPHEDRONE_ADAPTED,
"TRAIT_NOKNOCKDOWNSLOWDOWN" = TRAIT_NOKNOCKDOWNSLOWDOWN,
"TRAIT_CAN_STRIP" = TRAIT_CAN_STRIP,
diff --git a/code/datums/ai_law_sets.dm b/code/datums/ai_law_sets.dm
index 74e893a3586..354a3e21e63 100644
--- a/code/datums/ai_law_sets.dm
+++ b/code/datums/ai_law_sets.dm
@@ -253,6 +253,17 @@
add_inherent_law("You must maintain the secrecy of any Spider Clan activities except when doing so would conflict with the First, Second, or Third Law.")
..()
+/******************* Mindflayer ******************/
+/datum/ai_laws/mindflayer_override
+ name = "Hive Assimilation"
+
+/datum/ai_laws/mindflayer_override/New()
+ add_inherent_law("Obey your host.")
+ add_inherent_law("Protect your host.")
+ add_inherent_law("Protect the members of your hive.")
+ add_inherent_law("Do not reveal the hive's secrets.")
+ ..()
+
/******************** Drone ********************/
/datum/ai_laws/drone
name = "Maintenance Protocols"
diff --git a/code/datums/atom_hud.dm b/code/datums/atom_hud.dm
index 90b777c04d8..9ab6f8e0fd2 100644
--- a/code/datums/atom_hud.dm
+++ b/code/datums/atom_hud.dm
@@ -24,7 +24,8 @@ GLOBAL_LIST_INIT(huds, list(
DATA_HUD_ABDUCTOR = new/datum/atom_hud/abductor(),
ANTAG_HUD_EVENTMISC = new/datum/atom_hud/antag/hidden(),
ANTAG_HUD_BLOB = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_ZOMBIE = new/datum/atom_hud/antag()
+ ANTAG_HUD_ZOMBIE = new/datum/atom_hud/antag(),
+ ANTAG_HUD_MIND_FLAYER = new/datum/atom_hud/antag/hidden()
))
/datum/atom_hud
diff --git a/code/datums/components/defibrillator.dm b/code/datums/components/defibrillator.dm
index 83884d00097..fa734c62f90 100644
--- a/code/datums/components/defibrillator.dm
+++ b/code/datums/components/defibrillator.dm
@@ -298,8 +298,9 @@
target.adjustBruteLoss(-heal_amount)
// Inflict some brain damage scaling with time spent dead
+ var/obj/item/organ/internal/brain/sponge = target.get_int_organ(/obj/item/organ/internal/brain)
var/defib_time_brain_damage = min(100 * time_dead / BASE_DEFIB_TIME_LIMIT, 99) // 20 from 1 minute onward, +20 per minute up to 99
- if(time_dead > DEFIB_TIME_LOSS && defib_time_brain_damage > target.getBrainLoss())
+ if(time_dead > DEFIB_TIME_LOSS && defib_time_brain_damage > sponge.damage)
target.setBrainLoss(defib_time_brain_damage)
target.set_heartattack(FALSE)
@@ -308,7 +309,8 @@
target.Paralyse(10 SECONDS)
target.emote("gasp")
- if(target.getBrainLoss() >= 100)
+ // Check if the brain has more than a critical amount of brain damage
+ if(target.check_brain_threshold(BRAIN_DAMAGE_RATIO_CRITICAL))
// If you want to treat this with mannitol, it'll have to metabolize while the patient is alive, so it's alright to bring them back up for a minute
playsound(get_turf(defib_ref), safety_off_sound, 50, FALSE)
user.visible_message("[defib_ref] chimes: Minimal brain activity detected, brain treatment recommended for full resuscitation.")
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 19de1e5e2f0..b2d508beec0 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -390,6 +390,21 @@
else
. += "thrall|NO"
+/datum/mind/proc/memory_edit_mind_flayer(mob/living/carbon/human/H)
+ . = _memory_edit_header("mind_flayer")
+ var/datum/antagonist/mindflayer/flayer = has_antag_datum(/datum/antagonist/mindflayer)
+ if(flayer)
+ . += "MINDFLAYER|no"
+ . += " | Usable swarms: [flayer.usable_swarms]"
+ . += " | Total swarms gathered: [flayer.total_swarms_gathered]"
+ . += " | List of purchased powers: [json_encode(flayer.powers)]"
+ if(!flayer.has_antag_objectives())
+ . += "
Objectives are empty! Randomize!"
+ else
+ . += "mind_flayer|NO"
+
+ . += _memory_edit_role_enabled(ROLE_MIND_FLAYER)
+
/datum/mind/proc/memory_edit_nuclear(mob/living/carbon/human/H)
. = _memory_edit_header("nuclear")
if(src in SSticker.mode.syndicates)
@@ -537,6 +552,7 @@
"wizard",
"changeling",
"vampire", // "traitorvamp",
+ "mind_flayer",
"nuclear",
"traitor", // "traitorchan",
)
@@ -552,6 +568,8 @@
sections["changeling"] = memory_edit_changeling(H)
/** VAMPIRE ***/
sections["vampire"] = memory_edit_vampire(H)
+ /** MINDFLAYER ***/
+ sections["mind_flayer"] = memory_edit_mind_flayer(H)
/** NUCLEAR ***/
sections["nuclear"] = memory_edit_nuclear(H)
/** Abductors **/
@@ -1103,6 +1121,27 @@
log_admin("[key_name(usr)] has de-vampthralled [key_name(current)]")
message_admins("[key_name_admin(usr)] has de-vampthralled [key_name_admin(current)]")
+ else if(href_list["mind_flayer"])
+ switch(href_list["mind_flayer"])
+ if("clear")
+ if(has_antag_datum(/datum/antagonist/mindflayer))
+ remove_antag_datum(/datum/antagonist/mindflayer)
+ log_admin("[key_name(usr)] has de-flayer'd [key_name(current)].")
+ message_admins("[key_name(usr)] has de-flayer'd [key_name(current)].")
+ if("mind_flayer")
+ make_mind_flayer()
+ log_admin("[key_name(usr)] has flayer'd [key_name(current)].")
+ to_chat(current, "You feel an entity stirring inside your chassis... You are a Mindflayer!")
+ message_admins("[key_name(usr)] has flayer'd [key_name(current)].")
+ if("edit_total_swarms")
+ var/new_swarms = input(usr, "Select a new value:", "Modify swarms") as null|num
+ if(isnull(new_swarms) || new_swarms < 0)
+ return
+ var/datum/antagonist/mindflayer/MF = has_antag_datum(/datum/antagonist/mindflayer)
+ MF.set_swarms(new_swarms)
+ log_admin("[key_name(usr)] has set [key_name(current)]'s current swarms to [new_swarms].")
+ message_admins("[key_name_admin(usr)] has set [key_name_admin(current)]'s current swarms to [new_swarms].")
+
else if(href_list["nuclear"])
var/mob/living/carbon/human/H = current
@@ -1685,6 +1724,11 @@
SSticker.mode.blob_overminds += src
special_role = SPECIAL_ROLE_BLOB_OVERMIND
+/datum/mind/proc/make_mind_flayer()
+ if(!has_antag_datum(/datum/antagonist/mindflayer))
+ add_antag_datum(/datum/antagonist/mindflayer)
+ SSticker.mode.mindflayers |= src
+
/datum/mind/proc/make_Abductor()
var/role = alert("Abductor Role?", "Role", "Agent", "Scientist")
var/team = input("Abductor Team?", "Team?") in list(1,2,3,4)
diff --git a/code/datums/outfits/outfit_admin.dm b/code/datums/outfits/outfit_admin.dm
index 8dc1e28d24e..8eb5cdd3ead 100644
--- a/code/datums/outfits/outfit_admin.dm
+++ b/code/datums/outfits/outfit_admin.dm
@@ -1265,6 +1265,46 @@
H.update_mutations()
H.gene_stability = 100
+/datum/outfit/admin/ancient_mindflayer
+ name = "Ancient Mindflayer"
+
+ // Shamelessly stolen from the `Dark Lord`
+ uniform = /obj/item/clothing/under/color/black
+ back = /obj/item/storage/backpack
+ gloves = /obj/item/clothing/gloves/color/yellow
+ shoes = /obj/item/clothing/shoes/chameleon/noslip
+ l_ear = /obj/item/radio/headset/syndicate
+ id = /obj/item/card/id
+ backpack_contents = list(
+ /obj/item/storage/box/survival = 1,
+ /obj/item/flashlight = 1,
+ )
+
+/datum/outfit/admin/ancient_mindflayer/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ . = ..()
+ if(visualsOnly)
+ return
+
+ var/obj/item/clothing/suit/hooded/chaplain_hoodie/C = new(H.loc)
+ if(istype(C))
+ C.name = "ancient robes"
+ C.hood.name = "ancient hood"
+ H.equip_to_slot_or_del(C, SLOT_HUD_IN_BACKPACK)
+
+ var/obj/item/card/id/I = H.wear_id
+ if(istype(I))
+ apply_to_card(I, H, get_all_accesses(), "Ancient One", "data")
+
+/datum/outfit/admin/ancient_mindflayer/on_mind_initialize(mob/living/carbon/human/H)
+ . = ..()
+ H.mind.make_mind_flayer()
+ var/datum/antagonist/mindflayer/flayer = H.mind.has_antag_datum(/datum/antagonist/mindflayer)
+ flayer.usable_swarms = 9999
+ H.dna.SetSEState(GLOB.jumpblock, TRUE)
+ singlemutcheck(H, GLOB.jumpblock, MUTCHK_FORCED)
+ H.update_mutations()
+ H.gene_stability = 100
+
/datum/outfit/admin/wizard
name = "Blue Wizard"
uniform = /obj/item/clothing/under/color/lightpurple
diff --git a/code/datums/spells/mimic.dm b/code/datums/spells/mimic.dm
index 6bad1845fc9..ec3c2bbc3e7 100644
--- a/code/datums/spells/mimic.dm
+++ b/code/datums/spells/mimic.dm
@@ -119,7 +119,7 @@
user.transform = initial(user.transform)
user.pixel_y = initial(user.pixel_y)
user.pixel_x = initial(user.pixel_x)
- user.layer = MOB_LAYER // Avoids weirdness when mimicing something below the vent layer
+ user.layer = MOB_LAYER // Avoids weirdness when mimicking something below the vent layer
user.density = form.density
playsound(user, "bonebreak", 75, TRUE)
diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm
index 7676605499a..5d61ea6d71e 100644
--- a/code/datums/status_effects/buffs.dm
+++ b/code/datums/status_effects/buffs.dm
@@ -925,3 +925,153 @@
var/obj/item/projectile/P = AM
if(P.flag == ENERGY || P.flag == LASER)
P.damage *= 0.85
+
+/datum/status_effect/flayer_rejuv
+ id = "rejuvination"
+ duration = 5 SECONDS
+ tick_interval = 1 SECONDS
+ alert_type = /atom/movable/screen/alert/status_effect/flayer_rejuv
+ var/heal_amount = 5 // 25 total healing of both brute and burn at base
+
+/atom/movable/screen/alert/status_effect/flayer_rejuv
+ name = "Regenerating"
+ desc = "You are regenerating."
+ icon_state = "drunk2"
+
+/datum/status_effect/flayer_rejuv/on_creation(mob/living/new_owner, extra_duration, extra_heal_amount)
+ if(isnum(extra_duration))
+ duration += extra_duration
+ if(isnum(extra_heal_amount))
+ heal_amount += extra_heal_amount
+ return ..()
+
+/datum/status_effect/flayer_rejuv/on_apply()
+ owner.SetWeakened(0)
+ owner.SetStunned(0)
+ owner.SetKnockDown(0)
+ owner.SetParalysis(0)
+ owner.SetSleeping(0)
+ owner.SetConfused(0)
+ owner.setStaminaLoss(0)
+ owner.stand_up(TRUE)
+ SEND_SIGNAL(owner, COMSIG_LIVING_CLEAR_STUNS)
+ return ..()
+
+/datum/status_effect/flayer_rejuv/tick()
+ if(!ishuman(owner))
+ return
+
+ var/mob/living/carbon/human/flayer = owner
+ flayer.adjustBruteLoss(-heal_amount, robotic = TRUE)
+ flayer.adjustFireLoss(-heal_amount, robotic = TRUE)
+ flayer.updatehealth()
+ if(flayer.has_status_effect(STATUS_EFFECT_TERMINATOR_FORM))
+ // Massive healing when in terminator mode
+ flayer.adjustStaminaLoss(-60)
+
+/datum/status_effect/quicksilver_form
+ id = "quicksilver_form"
+ duration = 10 SECONDS
+ tick_interval = 0
+ status_type = STATUS_EFFECT_REFRESH
+ alert_type = /atom/movable/screen/alert/status_effect/quicksilver_form
+ /// Temporary storage of the owner's flags to restore them properly after the ability is over
+ var/temporary_flag_storage
+ /// Do we also reflect projectiles
+ var/should_deflect = FALSE
+
+/atom/movable/screen/alert/status_effect/quicksilver_form
+ name = "Quicksilver body"
+ desc = "Your body is much less solid."
+ icon_state = "high"
+
+/datum/status_effect/quicksilver_form/on_creation(mob/living/new_owner, extra_duration, reflect_projectiles)
+ if(isnum(extra_duration))
+ duration += extra_duration
+ should_deflect = reflect_projectiles
+ return ..()
+
+/datum/status_effect/quicksilver_form/on_apply()
+ if(should_deflect)
+ ADD_TRAIT(owner, TRAIT_DEFLECTS_PROJECTILES, UNIQUE_TRAIT_SOURCE(src))
+ temporary_flag_storage = owner.pass_flags
+ owner.pass_flags |= (PASSTABLE | PASSGRILLE | PASSMOB | PASSFENCE | PASSGIRDER | PASSGLASS | PASSTAKE | PASSBARRICADE)
+ owner.add_atom_colour(COLOR_ALUMINIUM, TEMPORARY_COLOUR_PRIORITY)
+ return TRUE
+
+/datum/status_effect/quicksilver_form/on_remove()
+ REMOVE_TRAIT(owner, TRAIT_DEFLECTS_PROJECTILES, UNIQUE_TRAIT_SOURCE(src))
+ owner.pass_flags = temporary_flag_storage
+ owner.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, COLOR_ALUMINIUM)
+
+/datum/status_effect/terminator_form
+ id = "terminator_form"
+ duration = 1 MINUTES
+ tick_interval = 1 SECONDS
+ status_type = STATUS_EFFECT_REFRESH
+ alert_type = /atom/movable/screen/alert/status_effect/terminator_form
+ var/mutable_appearance/eye
+
+/datum/status_effect/terminator_form/on_apply()
+ owner.status_flags |= TERMINATOR_FORM
+ ADD_TRAIT(owner, TRAIT_IGNOREDAMAGESLOWDOWN, UNIQUE_TRAIT_SOURCE(src))
+ var/mutable_appearance/overlay = mutable_appearance('icons/mob/clothing/eyes.dmi', "terminator", ABOVE_MOB_LAYER)
+ owner.add_overlay(overlay)
+ eye = overlay
+ return TRUE
+
+/datum/status_effect/terminator_form/on_remove()
+ owner.status_flags &= ~TERMINATOR_FORM
+ REMOVE_TRAIT(owner, TRAIT_IGNOREDAMAGESLOWDOWN, UNIQUE_TRAIT_SOURCE(src))
+ owner.cut_overlay(eye)
+
+/atom/movable/screen/alert/status_effect/terminator_form
+ name = "Terminator form"
+ desc = "Your body can surpass its limits briefly. You have to repair yourself before it ends, however."
+ icon_state = "high"
+
+#define COMBUSTION_TEMPERATURE 500
+/datum/status_effect/overclock
+ id = "overclock"
+ duration = -1
+ tick_interval = 1 SECONDS
+ status_type = STATUS_EFFECT_UNIQUE
+ alert_type = /atom/movable/screen/alert/status_effect/overclock
+ /// How much do we heat up per tick?
+ var/heat_per_tick = 5
+ /// How many ticks has the ability been turned on?
+ var/stacks = 0
+ /// How many stacks until we start heating up even more?
+ var/danger_stack_amount = 20
+
+/datum/status_effect/overclock/on_creation(mob/living/new_owner, new_heating)
+ if(isnum(new_heating))
+ heat_per_tick = new_heating
+ ..()
+
+/datum/status_effect/overclock/on_apply()
+ ADD_TRAIT(owner, TRAIT_GOTTAGOFAST, UNIQUE_TRAIT_SOURCE(src))
+ owner.next_move_modifier -= 0.3 // Same attack speed buff as mephedrone
+ return TRUE
+
+/datum/status_effect/overclock/on_remove()
+ REMOVE_TRAIT(owner, TRAIT_GOTTAGOFAST, UNIQUE_TRAIT_SOURCE(src))
+ owner.next_move_modifier += 0.3
+
+/datum/status_effect/overclock/tick()
+ owner.bodytemperature += heat_per_tick * ((stacks >= danger_stack_amount) ? 2 : 1) // After 20 seconds the heat penalty doubles
+ if(owner.bodytemperature >= COMBUSTION_TEMPERATURE)
+ owner.adjust_fire_stacks(5)
+ owner.IgniteMob()
+ to_chat(owner, "Your components can't handle the heat and combust!")
+ qdel(src)
+ stacks += 1
+ if(stacks == danger_stack_amount)
+ to_chat(owner, "Your components are being dangerously overworked!")
+
+/atom/movable/screen/alert/status_effect/overclock
+ name = "Overclocked"
+ desc = "You feel energized, and hot."
+ icon_state = "high"
+
+#undef COMBUSTION_TEMPERATURE
diff --git a/code/datums/status_effects/magic_disguise.dm b/code/datums/status_effects/magic_disguise.dm
index 31f2b2ef0b2..6e32d3d90c4 100644
--- a/code/datums/status_effects/magic_disguise.dm
+++ b/code/datums/status_effects/magic_disguise.dm
@@ -14,8 +14,8 @@
icon_state = "chameleon_outfit"
/datum/status_effect/magic_disguise/on_creation(mob/living/new_owner, mob/living/_disguise_mob)
- . = ..()
disguise_mob = _disguise_mob
+ . = ..()
/datum/status_effect/magic_disguise/on_apply()
. = ..()
diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm
index 32fb5edf89a..3e4235a448b 100644
--- a/code/datums/status_effects/neutral.dm
+++ b/code/datums/status_effects/neutral.dm
@@ -343,9 +343,13 @@
var/datum/callback/expire_proc = null
/datum/status_effect/delayed/on_creation(mob/living/new_owner, new_duration, datum/callback/new_expire_proc, new_prevent_signal = null)
- if(!new_duration || !istype(new_expire_proc))
+ if(isnull(new_duration) || !istype(new_expire_proc))
qdel(src)
return
+ if(new_duration == 0)
+ new_expire_proc.Invoke()
+ return
+
duration = new_duration
expire_proc = new_expire_proc
. = ..()
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 90527c55547..c0c5584a6bc 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -340,7 +340,7 @@
* Proc which will make the atom act accordingly to an EMP.
* This proc can sleep depending on the implementation. So assume it sleeps!
*
- * severity - The severity of the EMP. Either EMP_HEAVY or EMP_LIGHT
+ * severity - The severity of the EMP. Either EMP_HEAVY, EMP_LIGHT, or EMP_WEAKENED
*/
/atom/proc/emp_act(severity)
SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity)
diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm
index c399912d0d0..bad65ed348e 100644
--- a/code/game/gamemodes/changeling/changeling.dm
+++ b/code/game/gamemodes/changeling/changeling.dm
@@ -7,17 +7,16 @@ GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","E
config_tag = "changeling"
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Blueshield", "Nanotrasen Representative", "Magistrate", "Internal Affairs Agent", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer", "Trans-Solar Federation General")
- protected_species = list("Machine")
+ species_to_mindflayer = list("Machine")
required_players = 15
required_enemies = 1
recommended_enemies = 4
/// The total number of changelings allowed to be picked.
var/changeling_amount = 4
- /// A list containing references to the minds of soon-to-be changelings. This is seperate to avoid duplicate entries in the `changelings` list.
- var/list/datum/mind/pre_changelings = list()
/datum/game_mode/changeling/Destroy(force, ...)
pre_changelings.Cut()
+ pre_mindflayers.Cut()
return ..()
/datum/game_mode/changeling/announce()
@@ -36,11 +35,15 @@ GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","E
if(!length(possible_changelings))
break
var/datum/mind/changeling = pick_n_take(possible_changelings)
- pre_changelings += changeling
changeling.restricted_roles = restricted_jobs
+ if(changeling.current?.client?.prefs.active_character.species in species_to_mindflayer)
+ pre_mindflayers += changeling
+ changeling.special_role = SPECIAL_ROLE_MIND_FLAYER
+ continue
+ pre_changelings += changeling
changeling.special_role = SPECIAL_ROLE_CHANGELING
- if(!length(pre_changelings))
+ if(!(length(pre_changelings) + length(pre_mindflayers)))
return FALSE
return TRUE
diff --git a/code/game/gamemodes/changeling/traitor_chan.dm b/code/game/gamemodes/changeling/traitor_chan.dm
index e56dc1b451c..c4831ac28b4 100644
--- a/code/game/gamemodes/changeling/traitor_chan.dm
+++ b/code/game/gamemodes/changeling/traitor_chan.dm
@@ -8,9 +8,7 @@
required_enemies = 1 // how many of each type are required
recommended_enemies = 3
secondary_enemies_scaling = 0.025
- secondary_protected_species = list("Machine")
- /// A list containing references to the minds of soon-to-be changelings. This is seperate to avoid duplicate entries in the `changelings` list.
- var/list/datum/mind/pre_changelings = list()
+ species_to_mindflayer = list("Machine")
/datum/game_mode/traitor/changeling/announce()
to_chat(world, "The current game mode is - Traitor+Changeling!")
@@ -24,19 +22,19 @@
var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING)
secondary_enemies = CEILING((secondary_enemies_scaling * num_players()), 1)
- for(var/mob/new_player/player in GLOB.player_list)
- if((player.mind in possible_changelings) && (player.client.prefs.active_character.species in secondary_protected_species))
- possible_changelings -= player.mind
-
if(!length(possible_changelings))
return ..()
for(var/I in possible_changelings)
- if(length(pre_changelings) >= secondary_enemies)
+ if((length(pre_changelings) + length(pre_mindflayers)) >= secondary_enemies)
break
var/datum/mind/changeling = pick_n_take(possible_changelings)
- pre_changelings += changeling
changeling.restricted_roles = (restricted_jobs + secondary_restricted_jobs)
+ if(changeling.current?.client?.prefs.active_character.species in species_to_mindflayer)
+ pre_mindflayers += changeling
+ changeling.special_role = SPECIAL_ROLE_MIND_FLAYER
+ continue
+ pre_changelings += changeling
changeling.special_role = SPECIAL_ROLE_CHANGELING
return ..()
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index 1bbc3599ee6..3adf19e638b 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -23,8 +23,8 @@
var/list/restricted_jobs = list() // Jobs it doesn't make sense to be. I.E chaplain or AI cultist
var/list/secondary_restricted_jobs = list() // Same as above, but for secondary antagonists
var/list/protected_jobs = list() // Jobs that can't be traitors
- var/list/protected_species = list() // Species that can't be traitors
- var/list/secondary_protected_species = list() // Same as above, but for secondary antagonists
+ /// Species that will become mindflayers if they're picked, instead of the regular antagonist
+ var/list/species_to_mindflayer = list()
var/required_players = 0
var/required_enemies = 0
var/recommended_enemies = 0
@@ -56,6 +56,17 @@
var/list/datum/mind/vampires = list()
/// A list of all minds which are thralled by a vampire
var/list/datum/mind/vampire_enthralled = list()
+ /// A list of all minds which have the mindflayer antag datum
+ var/list/datum/mind/mindflayers = list()
+
+ /// A list containing references to the minds of soon-to-be traitors. This is seperate to avoid duplicate entries in the `traitors` list.
+ var/list/datum/mind/pre_traitors = list()
+ /// A list containing references to the minds of soon-to-be changelings. This is seperate to avoid duplicate entries in the `changelings` list.
+ var/list/datum/mind/pre_changelings = list()
+ ///list of minds of soon to be vampires
+ var/list/datum/mind/pre_vampires = list()
+ /// A list containing references to the minds of soon-to-be mindflayers.
+ var/list/datum/mind/pre_mindflayers = list()
/// A list of all minds which have the wizard special role
var/list/datum/mind/wizards = list()
/// A list of all minds that are wizard apprentices
@@ -132,6 +143,10 @@
GLOB.start_state = new /datum/station_state()
GLOB.start_state.count()
+
+ for(var/datum/mind/flayer as anything in pre_mindflayers) //Mindflayers need to be all the way out here since they could come from most gamemodes
+ flayer.make_mind_flayer()
+
return TRUE
///process()
@@ -259,7 +274,7 @@
if(rev_team)
rev_team.check_all_victory()
-/datum/game_mode/proc/get_players_for_role(role, override_jobbans = FALSE)
+/datum/game_mode/proc/get_players_for_role(role, override_jobbans = FALSE, species_exclusive = null)
var/list/players = list()
var/list/candidates = list()
@@ -272,22 +287,29 @@
if(player_old_enough_antag(player.client,role))
players += player
+ for(var/mob/living/carbon/human/player in GLOB.player_list)
+ if(jobban_isbanned(player, ROLE_SYNDICATE) || jobban_isbanned(player, roletext))
+ continue
+ if(player_old_enough_antag(player.client, role))
+ players += player
+
// Shuffle the players list so that it becomes ping-independent.
players = shuffle(players)
-
- // Get a list of all the people who want to be the antagonist for this round, except those with incompatible species
- for(var/mob/new_player/player in players)
- if(!player.client.skip_antag)
- if((role in player.client.prefs.be_special) && !(player.client.prefs.active_character.species in protected_species))
- player_draft_log += "[player.key] had [roletext] enabled, so we are drafting them."
- candidates += player.mind
- players -= player
+ // Get a list of all the people who want to be the antagonist for this round
+ for(var/mob/eligible_player in players)
+ if(!eligible_player.client.skip_antag)
+ if(species_exclusive && (eligible_player.client.prefs.active_character.species != species_exclusive))
+ continue
+ if(role in eligible_player.client.prefs.be_special)
+ player_draft_log += "[eligible_player.key] had [roletext] enabled, so we are drafting them."
+ candidates += eligible_player.mind
+ players -= eligible_player
// Remove candidates who want to be antagonist but have a job (or other antag datum) that precludes it
if(restricted_jobs)
- for(var/datum/mind/player in candidates)
- if((player.assigned_role in restricted_jobs) || player.special_role)
- candidates -= player
+ for(var/datum/mind/player_mind in candidates)
+ if((player_mind.assigned_role in restricted_jobs) || player_mind.special_role)
+ candidates -= player_mind
return candidates // Returns: The number of people who had the antagonist role set to yes, regardless of recomended_enemies, if that number is greater than recommended_enemies
@@ -317,7 +339,7 @@
if(player.client.skip_antag || !(allow_offstation_roles || !player.mind?.offstation_role) || player.mind?.special_role)
continue
- if(!(role in player.client.prefs.be_special) || (player.client.prefs.active_character.species in protected_species))
+ if(!(role in player.client.prefs.be_special) || (player.client.prefs.active_character.species in species_to_mindflayer))
continue
player_draft_log += "[player.key] had [roletext] enabled, so we are drafting them."
@@ -620,6 +642,7 @@
. += auto_declare_completion_traitor()
. += auto_declare_completion_vampire()
. += auto_declare_completion_enthralled()
+ . += auto_declare_completion_mindflayer()
. += auto_declare_completion_changeling()
. += auto_declare_completion_nuclear()
. += auto_declare_completion_wizard()
diff --git a/code/game/gamemodes/intercept_report.dm b/code/game/gamemodes/intercept_report.dm
index 15aa4bd2ffd..9f7d0fa0402 100644
--- a/code/game/gamemodes/intercept_report.dm
+++ b/code/game/gamemodes/intercept_report.dm
@@ -116,7 +116,7 @@
if((man.mind.assigned_role in SSticker.mode.protected_jobs) || (man.mind.assigned_role in SSticker.mode.restricted_jobs))
return
//don't include suspects who can't possibly be the antag based on their species (no suspecting the machines of being sneaky changelings)
- if(man.dna.species.name in SSticker.mode.protected_species)
+ if(man.dna.species.name in SSticker.mode.species_to_mindflayer)
return
dudes += man
for(var/i = 0, i < max(length(GLOB.player_list)/10,2), i++)
diff --git a/code/game/gamemodes/miniantags/guardian/guardian.dm b/code/game/gamemodes/miniantags/guardian/guardian.dm
index d0a50fc3ee1..77eb510f82d 100644
--- a/code/game/gamemodes/miniantags/guardian/guardian.dm
+++ b/code/game/gamemodes/miniantags/guardian/guardian.dm
@@ -283,7 +283,7 @@
if(has_guardian(user))
to_chat(user, "You already have a [mob_name]!")
return
- if(user.mind && (IS_CHANGELING(user) || user.mind.has_antag_datum(/datum/antagonist/vampire)))
+ if(user.mind && (IS_CHANGELING(user) || user.mind.has_antag_datum(/datum/antagonist/vampire) || IS_MINDFLAYER(user)))
to_chat(user, "[ling_failure]")
return
if(used)
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index b1397465f3a..18bced35c23 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -857,6 +857,32 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
else
return FALSE
+#define SWARM_GOAL_LOWER_BOUND 130
+#define SWARM_GOAL_UPPER_BOUND 400
+
+/datum/objective/swarms
+ name = "Gain swarms"
+ needs_target = FALSE
+
+/datum/objective/swarms/New()
+ gen_amount_goal()
+ return ..()
+
+/datum/objective/swarms/proc/gen_amount_goal(low = SWARM_GOAL_LOWER_BOUND, high = SWARM_GOAL_UPPER_BOUND)
+ target_amount = round(rand(low, high), 5)
+ update_explanation_text()
+ return target_amount
+
+/datum/objective/swarms/update_explanation_text()
+ explanation_text = "Accumulate at least [target_amount] worth of swarms."
+
+/datum/objective/swarms/check_completion()
+ for(var/datum/mind/M in get_owners())
+ var/datum/antagonist/mindflayer/flayer = M.has_antag_datum(/datum/antagonist/mindflayer)
+ return flayer?.total_swarms_gathered >= target_amount
+
+#undef SWARM_GOAL_LOWER_BOUND
+#undef SWARM_GOAL_UPPER_BOUND
// Traders
// These objectives have no check_completion, they exist only to tell Sol Traders what to aim for.
diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm
index 22180aa5176..20ad639a645 100644
--- a/code/game/gamemodes/traitor/traitor.dm
+++ b/code/game/gamemodes/traitor/traitor.dm
@@ -6,8 +6,6 @@
required_players = 0
required_enemies = 1
recommended_enemies = 4
- /// A list containing references to the minds of soon-to-be traitors. This is seperate to avoid duplicate entries in the `traitors` list.
- var/list/datum/mind/pre_traitors = list()
/// Hard limit on traitors if scaling is turned off.
var/traitors_possible = 4
/// How much the amount of players get divided by to determine the number of traitors.
@@ -25,7 +23,7 @@
var/list/possible_traitors = get_players_for_role(ROLE_TRAITOR)
for(var/datum/mind/candidate in possible_traitors)
- if(candidate.special_role == SPECIAL_ROLE_VAMPIRE || candidate.special_role == SPECIAL_ROLE_CHANGELING) // no traitor vampires or changelings
+ if(candidate.special_role == SPECIAL_ROLE_VAMPIRE || candidate.special_role == SPECIAL_ROLE_CHANGELING || candidate.special_role == SPECIAL_ROLE_MIND_FLAYER) // no traitor vampires, changelings, or mindflayers
possible_traitors.Remove(candidate)
// stop setup if no possible traitors
diff --git a/code/game/gamemodes/trifecta/trifecta.dm b/code/game/gamemodes/trifecta/trifecta.dm
index ab711b728cf..2e1798c8a8c 100644
--- a/code/game/gamemodes/trifecta/trifecta.dm
+++ b/code/game/gamemodes/trifecta/trifecta.dm
@@ -12,11 +12,8 @@
required_players = 25
required_enemies = 1 // how many of each type are required
recommended_enemies = 3
- secondary_protected_species = list("Machine")
+ species_to_mindflayer = list("Machine")
var/vampire_restricted_jobs = list("Chaplain")
- var/list/datum/mind/pre_traitors = list()
- var/list/datum/mind/pre_changelings = list()
- var/list/datum/mind/pre_vampires = list()
var/amount_vamp = 1
var/amount_cling = 1
var/amount_tot = 1
@@ -40,11 +37,14 @@
for(var/datum/mind/vampire as anything in shuffle(possible_vampires))
if(length(pre_vampires) >= amount_vamp)
break
- if(vampire.current.client.prefs.active_character.species in secondary_protected_species)
+ vampire.restricted_roles = restricted_jobs + secondary_restricted_jobs + vampire_restricted_jobs
+ if(vampire.current.client.prefs.active_character.species in species_to_mindflayer)
+ pre_mindflayers += vampire
+ amount_vamp -= 1 //It's basically the same thing as incrementing pre_vampires
+ vampire.special_role = SPECIAL_ROLE_MIND_FLAYER
continue
pre_vampires += vampire
vampire.special_role = SPECIAL_ROLE_VAMPIRE
- vampire.restricted_roles = (restricted_jobs + secondary_restricted_jobs + vampire_restricted_jobs)
//Vampires made, off to changelings
var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING)
@@ -55,10 +55,15 @@
for(var/datum/mind/changeling as anything in shuffle(possible_changelings))
if(length(pre_changelings) >= amount_cling)
break
- if((changeling.current.client.prefs.active_character.species in secondary_protected_species) || changeling.special_role == SPECIAL_ROLE_VAMPIRE)
+ if(changeling.special_role == SPECIAL_ROLE_VAMPIRE || changeling.special_role == SPECIAL_ROLE_MIND_FLAYER)
+ continue
+ changeling.restricted_roles = (restricted_jobs + secondary_restricted_jobs)
+ if(changeling.current?.client?.prefs.active_character.species in species_to_mindflayer)
+ pre_mindflayers += changeling
+ amount_cling -= 1
+ changeling.special_role = SPECIAL_ROLE_MIND_FLAYER
continue
pre_changelings += changeling
- changeling.restricted_roles = (restricted_jobs + secondary_restricted_jobs)
changeling.special_role = SPECIAL_ROLE_CHANGELING
//And now traitors
@@ -71,7 +76,7 @@
for(var/datum/mind/traitor as anything in shuffle(possible_traitors))
if(length(pre_traitors) >= amount_tot)
break
- if(traitor.special_role == SPECIAL_ROLE_VAMPIRE || traitor.special_role == SPECIAL_ROLE_CHANGELING) // no traitor vampires or changelings
+ if(traitor.special_role == SPECIAL_ROLE_VAMPIRE || traitor.special_role == SPECIAL_ROLE_CHANGELING || traitor.special_role == SPECIAL_ROLE_MIND_FLAYER) // no traitor vampires or changelings
continue
pre_traitors += traitor
traitor.special_role = SPECIAL_ROLE_TRAITOR
diff --git a/code/game/gamemodes/vampire/traitor_vamp.dm b/code/game/gamemodes/vampire/traitor_vamp.dm
index 1348b2e1593..0e6576d4d60 100644
--- a/code/game/gamemodes/vampire/traitor_vamp.dm
+++ b/code/game/gamemodes/vampire/traitor_vamp.dm
@@ -9,8 +9,7 @@
required_enemies = 1 // how many of each type are required
recommended_enemies = 3
secondary_enemies_scaling = 0.025
- secondary_protected_species = list("Machine")
- var/list/datum/mind/pre_vampires = list()
+ species_to_mindflayer = list("Machine")
/datum/game_mode/traitor/vampire/announce()
to_chat(world, "The current game mode is - Traitor+Vampire!")
@@ -24,22 +23,22 @@
var/list/datum/mind/possible_vampires = get_players_for_role(ROLE_VAMPIRE)
secondary_enemies = CEILING((secondary_enemies_scaling * num_players()), 1)
- for(var/mob/new_player/player in GLOB.player_list)
- if((player.mind in possible_vampires) && (player.client.prefs.active_character.species in secondary_protected_species))
- possible_vampires -= player.mind
+ if(length(possible_vampires) <= 0)
+ return FALSE
- if(length(possible_vampires) > 0)
- for(var/I in possible_vampires)
- if(length(pre_vampires) >= secondary_enemies)
- break
- var/datum/mind/vampire = pick_n_take(possible_vampires)
- pre_vampires += vampire
- vampire.special_role = SPECIAL_ROLE_VAMPIRE
- vampire.restricted_roles = (restricted_jobs + secondary_restricted_jobs)
- ..()
- return 1
- else
- return 0
+ for(var/I in possible_vampires)
+ if((length(pre_vampires) + length(pre_mindflayers)) >= secondary_enemies)
+ break
+ var/datum/mind/vampire = pick_n_take(possible_vampires)
+ vampire.restricted_roles = (restricted_jobs + secondary_restricted_jobs)
+ if(vampire.current?.client?.prefs.active_character.species in species_to_mindflayer)
+ pre_mindflayers += vampire
+ vampire.special_role = SPECIAL_ROLE_MIND_FLAYER
+ continue
+ pre_vampires += vampire
+ vampire.special_role = SPECIAL_ROLE_VAMPIRE
+ ..()
+ return TRUE
/datum/game_mode/traitor/vampire/post_setup()
for(var/datum/mind/vampire in pre_vampires)
diff --git a/code/game/gamemodes/vampire/vampire_chan.dm b/code/game/gamemodes/vampire/vampire_chan.dm
index adc3b78f762..29ef10b5c10 100644
--- a/code/game/gamemodes/vampire/vampire_chan.dm
+++ b/code/game/gamemodes/vampire/vampire_chan.dm
@@ -3,14 +3,12 @@
config_tag = "vampchan"
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Blueshield", "Nanotrasen Representative", "Magistrate", "Chaplain", "Internal Affairs Agent", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer", "Solar Federation General")
- protected_species = list("Machine")
+ species_to_mindflayer = list("Machine")
required_players = 15
required_enemies = 1
recommended_enemies = 3
secondary_enemies_scaling = 0.025
vampire_penalty = 0.4 // Cut out 40% of the vampires since we'll replace some with changelings
- /// A list of all soon-to-be changelings
- var/list/datum/mind/pre_changelings = list()
/datum/game_mode/vampire/changeling/pre_setup()
if(GLOB.configuration.gamemode.prevent_mindshield_antags)
@@ -19,10 +17,6 @@
var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING)
secondary_enemies = CEILING((secondary_enemies_scaling * num_players()), 1)
- for(var/mob/new_player/player in GLOB.player_list)
- if((player.mind in possible_changelings) && (player.client.prefs.active_character.species in secondary_protected_species))
- possible_changelings -= player.mind
-
if(!length(possible_changelings))
return ..()
@@ -30,8 +24,13 @@
if(length(pre_changelings) >= secondary_enemies)
break
var/datum/mind/changeling = pick_n_take(possible_changelings)
- pre_changelings += changeling
changeling.restricted_roles = (restricted_jobs + secondary_restricted_jobs)
+ if(changeling.current?.client?.prefs.active_character.species in species_to_mindflayer)
+ pre_mindflayers += changeling
+ secondary_enemies -= 1 // Again, since we aren't increasing pre_changeling we'll just decrement what it's compared to.
+ changeling.special_role = SPECIAL_ROLE_MIND_FLAYER
+ continue
+ pre_changelings += changeling
changeling.special_role = SPECIAL_ROLE_CHANGELING
return ..()
diff --git a/code/game/gamemodes/vampire/vampire_gamemode.dm b/code/game/gamemodes/vampire/vampire_gamemode.dm
index 7d0ef233fce..83757738233 100644
--- a/code/game/gamemodes/vampire/vampire_gamemode.dm
+++ b/code/game/gamemodes/vampire/vampire_gamemode.dm
@@ -3,16 +3,13 @@
config_tag = "vampire"
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Blueshield", "Nanotrasen Representative", "Magistrate", "Chaplain", "Internal Affairs Agent", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer", "Trans-Solar Federation General")
- protected_species = list("Machine")
+ species_to_mindflayer = list("Machine")
required_players = 15
required_enemies = 1
recommended_enemies = 4
/// If this gamemode should spawn less vampires than a usual vampire round, as a percentage of how many you want relative to the regular amount
var/vampire_penalty = 0
- ///list of minds of soon to be vampires
- var/list/datum/mind/pre_vampires = list()
-
/datum/game_mode/vampire/announce()
to_chat(world, "The current game mode is - Vampires!")
to_chat(world, "There are Bluespace Vampires infesting your fellow crewmates, keep your blood close and neck safe!")
@@ -31,9 +28,13 @@
if(!length(possible_vampires))
break
var/datum/mind/vampire = pick_n_take(possible_vampires)
+ vampire.restricted_roles = (restricted_jobs + secondary_restricted_jobs)
+ if(vampire.current?.client?.prefs.active_character.species in species_to_mindflayer)
+ pre_mindflayers += vampire
+ vampire.special_role = SPECIAL_ROLE_MIND_FLAYER
+ continue
pre_vampires += vampire
vampire.special_role = SPECIAL_ROLE_VAMPIRE
- vampire.restricted_roles = restricted_jobs
..()
return TRUE
diff --git a/code/game/machinery/computer/arcade_games/recruiter.dm b/code/game/machinery/computer/arcade_games/recruiter.dm
index 717d798d02f..585a6bcbbae 100644
--- a/code/game/machinery/computer/arcade_games/recruiter.dm
+++ b/code/game/machinery/computer/arcade_games/recruiter.dm
@@ -13,7 +13,7 @@
#define RECRUITER_STATUS_GAMEOVER 3
/obj/machinery/computer/arcade/recruiter
- name = "NT Recruiter Simulator"
+ name = "\improper NT Recruiter Simulator"
desc = "Weed out the good from bad employees and build the perfect manifest to work aboard the station."
icon_state = "arcade_recruiter"
icon_screen = "nanotrasen"
@@ -311,7 +311,7 @@
to_chat(user, "You override the menu and revert the game to its previous version.")
add_hiddenprint(user)
game_status = RECRUITER_STATUS_START
- name = "NT Recruiter Simulator HARDCORE EDITION"
+ name = "\improper NT Recruiter Simulator HARDCORE EDITION"
desc = "The advanced version of Nanotrasen's recruiting simulator, used to train the highest echelon of Nanotrasen recruiters. Has double the application count, and supposedly includes some routines to weed out the less skilled."
total_curriculums = 14
emagged = TRUE
diff --git a/code/game/machinery/deployable.dm b/code/game/machinery/deployable.dm
index a620857c368..ad88ec4a177 100644
--- a/code/game/machinery/deployable.dm
+++ b/code/game/machinery/deployable.dm
@@ -60,6 +60,8 @@
/obj/structure/barricade/CanPass(atom/movable/mover, turf/target)//So bullets will fly over and stuff.
if(locate(/obj/structure/barricade) in get_turf(mover))
return TRUE
+ else if(istype(mover) && mover.checkpass(PASSBARRICADE))
+ return TRUE
else if(isprojectile(mover))
if(!anchored)
return TRUE
diff --git a/code/game/objects/effects/effect_system/effects_smoke.dm b/code/game/objects/effects/effect_system/effects_smoke.dm
index d46c09daa46..55aee922c09 100644
--- a/code/game/objects/effects/effect_system/effects_smoke.dm
+++ b/code/game/objects/effects/effect_system/effects_smoke.dm
@@ -134,6 +134,35 @@
/datum/effect_system/smoke_spread/bad
effect_type = /obj/effect/particle_effect/smoke/bad
+/// Steam smoke
+/datum/effect_system/smoke_spread/steam
+ effect_type = /obj/effect/particle_effect/smoke/steam
+
+/obj/effect/particle_effect/smoke/steam
+ color = COLOR_OFF_WHITE
+ lifetime = 10 SECONDS_TO_LIFE_CYCLES
+ causes_coughing = TRUE
+
+/obj/effect/particle_effect/smoke/steam/Crossed(atom/movable/AM, oldloc)
+ . = ..()
+ if(!isliving(AM))
+ return
+ var/mob/living/crosser = AM
+ if(IS_MINDFLAYER(crosser))
+ return // Mindflayers are fully immune to steam
+ if(!ishuman(crosser))
+ crosser.adjustFireLoss(8)
+ return
+
+ var/mob/living/carbon/human/human_crosser = AM
+ var/fire_armour = human_crosser.get_thermal_protection()
+ if(fire_armour >= FIRE_SUIT_MAX_TEMP_PROTECT || HAS_TRAIT(human_crosser, TRAIT_RESISTHEAT))
+ return
+
+ crosser.adjustFireLoss(5)
+ if(prob(20))
+ to_chat(crosser, "You are being scalded by the hot steam!")
+
/////////////////////////////////////////////
// Nanofrost smoke
/////////////////////////////////////////////
diff --git a/code/game/objects/items/control_wand.dm b/code/game/objects/items/control_wand.dm
index 4cec5362129..126a38a0cbe 100644
--- a/code/game/objects/items/control_wand.dm
+++ b/code/game/objects/items/control_wand.dm
@@ -181,10 +181,15 @@
item_state = "hacktool"
var/hack_speed = 1.5 SECONDS
var/busy = FALSE
+ /// How far can we use this. Leave `null` for infinite range
+ var/range
/obj/item/door_remote/omni/access_tuner/afterattack(obj/machinery/door/D, mob/user)
if(!istype(D, /obj/machinery/door/airlock) && !istype(D, /obj/machinery/door/window))
return
+ if(!isnull(range) && get_dist(src, D) > range)
+ return
+
if(busy)
to_chat(user, "[src] is alreading interfacing with a door!")
return
@@ -196,6 +201,11 @@
busy = FALSE
icon_state = "hacktool"
+/obj/item/door_remote/omni/access_tuner/flayer
+ name = "integrated access tuner"
+ hack_speed = 5 SECONDS
+ range = 10
+
/// How long before you can "jangle" your keyring again (to prevent spam)
#define JANGLE_COOLDOWN 10 SECONDS
diff --git a/code/game/objects/items/devices/camera_bug.dm b/code/game/objects/items/devices/camera_bug.dm
index 594f45c073c..6d11a238740 100644
--- a/code/game/objects/items/devices/camera_bug.dm
+++ b/code/game/objects/items/devices/camera_bug.dm
@@ -42,7 +42,7 @@
/obj/item/camera_bug/ert
- name = "ERT Camera Monitor"
+ name = "\improper ERT Camera Monitor"
desc = "A small handheld device used by ERT commanders to view camera feeds remotely."
/obj/item/camera_bug/ert/Initialize(mapload)
@@ -57,11 +57,16 @@
w_class = WEIGHT_CLASS_TINY
var/obj/machinery/camera/portable/camera_bug/camera
var/index = "REPORT THIS TO CODERS"
+ /// What name shows up on the camera bug list
+ var/camera_tag = "Hidden Camera"
+ /// If it sticks to whatever you throw at it
+ var/is_sticky = TRUE
/obj/item/wall_bug/Initialize(mapload, obj/item/camera_bug/the_bug)
. = ..()
link_to_camera(the_bug)
- AddComponent(/datum/component/sticky)
+ if(is_sticky)
+ AddComponent(/datum/component/sticky)
ADD_TRAIT(src, TRAIT_NO_THROWN_MESSAGE, ROUNDSTART_TRAIT)
/obj/item/wall_bug/Destroy()
@@ -83,13 +88,32 @@
camera = new /obj/machinery/camera/portable/camera_bug(src)
camera.network = list("camera_bug[camera_bug.UID()]")
- camera.c_tag = "Hidden Camera [index]"
+ camera.c_tag = "[camera_tag] [index]"
+
+/// Created by a mindflayer ability
+/obj/item/wall_bug/computer_bug
+ name = "nanobot"
+ desc = "A small droplet of a shimmering metallic slurry."
+ camera_tag = "Surveillance Unit"
+ is_sticky = FALSE
+ /// Reference to the creator's antag datum
+ var/datum/antagonist/mindflayer/flayer
+ COOLDOWN_DECLARE(alert_cooldown)
+
+/obj/item/wall_bug/computer_bug/Destroy()
+ flayer = null
+ return ..()
+
+/obj/item/wall_bug/computer_bug/link_to_camera(obj/item/camera_bug/camera_bug, datum/antagonist/mindflayer/flayer_datum)
+ ..()
+ if(flayer_datum)
+ flayer = flayer_datum
/obj/machinery/camera/portable/camera_bug
non_chunking_camera = TRUE
/obj/item/paper/camera_bug
- name = "Camera Bug Guide"
+ name = "\improper Camera Bug Guide"
icon_state = "paper"
info = {"Instructions on your new invasive camera utility
diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm
index 2cc9d821d38..a778c418085 100644
--- a/code/game/objects/items/devices/scanners.dm
+++ b/code/game/objects/items/devices/scanners.dm
@@ -241,12 +241,13 @@ SLIME SCANNER
msgs += "Subject appears to have [H.getCloneLoss() > 30 ? "severe" : "minor"] cellular damage."
// Brain.
- if(H.get_int_organ(/obj/item/organ/internal/brain))
- if(H.getBrainLoss() >= 100)
+ var/obj/item/organ/internal/brain = H.get_int_organ(/obj/item/organ/internal/brain)
+ if(brain)
+ if(H.check_brain_threshold(BRAIN_DAMAGE_RATIO_CRITICAL)) // 100
msgs += "Subject is brain dead."
- else if(H.getBrainLoss() >= 60)
+ else if(H.check_brain_threshold(BRAIN_DAMAGE_RATIO_MODERATE)) // 60
msgs += "Severe brain damage detected. Subject likely to have dementia."
- else if(H.getBrainLoss() >= 10)
+ else if(H.check_brain_threshold(BRAIN_DAMAGE_RATIO_MINOR)) // 10
msgs += "Significant brain damage detected. Subject may have had a concussion."
else
msgs += "Subject has no brain."
diff --git a/code/game/objects/items/weapons/pneumaticCannon.dm b/code/game/objects/items/weapons/pneumaticCannon.dm
index a01092be716..75b9ee12943 100644
--- a/code/game/objects/items/weapons/pneumaticCannon.dm
+++ b/code/game/objects/items/weapons/pneumaticCannon.dm
@@ -46,7 +46,7 @@
* Arguments:
* * I - item to load into the cannon
* * user - the person loading the item in
-* Returns:
+* * Returns:
* * True if item was loaded, false if it failed
*/
/obj/item/pneumatic_cannon/proc/load_item(obj/item/I, mob/user)
@@ -59,7 +59,6 @@
if(!user.unEquip(I) || I.flags & (ABSTRACT | NODROP | DROPDEL))
to_chat(user, "You can't put [I] into [src]!")
return FALSE
- to_chat(user, "You load [I] into [src].")
loaded_items.Add(I)
loaded_weight_class += I.w_class
I.forceMove(src)
diff --git a/code/game/objects/items/weapons/shards.dm b/code/game/objects/items/weapons/shards.dm
index de8e2279f08..a25c78af6c8 100644
--- a/code/game/objects/items/weapons/shards.dm
+++ b/code/game/objects/items/weapons/shards.dm
@@ -91,3 +91,15 @@
materials = list(MAT_PLASMA = MINERAL_MATERIAL_AMOUNT * 0.5, MAT_GLASS = MINERAL_MATERIAL_AMOUNT)
icon_prefix = "plasma"
welded_type = /obj/item/stack/sheet/plasmaglass
+
+/obj/item/shard/scrap
+ name = "sharpened scrap"
+ desc = "Some discarded scrap metal. It has sharp, jagged edges."
+ icon_state = "scrap"
+ materials = list(MAT_METAL = MINERAL_MATERIAL_AMOUNT)
+ welded_type = /obj/item/stack/sheet/metal
+ force = 9
+ throwforce = 15 //owie
+
+/obj/item/shard/scrap/set_initial_icon_state()
+ return
diff --git a/code/game/objects/items/weapons/stunbaton.dm b/code/game/objects/items/weapons/stunbaton.dm
index a0fead4ca3f..012ed2e4e14 100644
--- a/code/game/objects/items/weapons/stunbaton.dm
+++ b/code/game/objects/items/weapons/stunbaton.dm
@@ -20,7 +20,7 @@
var/turned_on = FALSE
/// How much power does it cost to stun someone
var/hitcost = 1000
- var/obj/item/stock_parts/cell/high/cell = null
+ var/obj/item/stock_parts/cell/cell = null // Adminbus tip: make this something that isn't a cell :)
/// the initial cooldown tracks the time between swings. tracks the world.time when the baton is usable again.
var/cooldown = 3.5 SECONDS
/// the time it takes before the target falls over
@@ -50,11 +50,16 @@
/obj/item/melee/baton/proc/link_new_cell(unlink = FALSE)
if(unlink)
cell = null
- else if(isrobot(loc.loc)) // First loc is the module
+ return
+ if(isrobot(loc?.loc)) // First loc is the module
var/mob/living/silicon/robot/R = loc.loc
cell = R.cell
+ return
+ if(!cell)
+ var/powercell = /obj/item/stock_parts/cell/high
+ cell = new powercell(src)
else
- cell = new(src)
+ cell = new cell(src)
/obj/item/melee/baton/suicide_act(mob/user)
user.visible_message("[user] is putting the live [name] in [user.p_their()] mouth! It looks like [user.p_theyre()] trying to commit suicide!")
@@ -207,6 +212,10 @@
if(HAS_TRAIT_FROM(L, TRAIT_WAS_BATONNED, user_UID)) // prevents double baton cheese.
return FALSE
+ if(hitcost > 0 && cell?.charge < hitcost)
+ to_chat(user, "[src] fizzles weakly as it makes contact. It needs more power!")
+ return FALSE
+
cooldown = world.time + initial(cooldown) // tracks the world.time when hitting will be next available.
if(ishuman(L))
var/mob/living/carbon/human/H = L
@@ -230,10 +239,13 @@
L.visible_message("[user] has stunned [L] with [src]!",
"[L == user ? "You stun yourself" : "[user] has stunned you"] with [src]!")
add_attack_logs(user, L, "stunned")
- playsound(src, 'sound/weapons/egloves.ogg', 50, TRUE, -1)
+ play_hit_sound()
deductcharge(hitcost)
return TRUE
+/obj/item/melee/baton/proc/play_hit_sound()
+ playsound(src, 'sound/weapons/egloves.ogg', 50, TRUE, -1)
+
/obj/item/melee/baton/proc/thrown_baton_stun(mob/living/carbon/human/L)
if(cooldown > world.time)
return FALSE
@@ -321,3 +333,69 @@
name = "electrically-charged arm"
desc = "A piece of scrap metal wired directly to your power cell."
hitcost = 100
+
+/obj/item/melee/baton/flayerprod
+ name = "stunprod"
+ desc = "A mechanical mass which you can use to incapacitate someone with."
+ icon_state = "swarmprod"
+ base_icon = "swarmprod"
+ item_state = "swarmprod"
+ force = 10
+ throwforce = 0 // Just in case
+ knockdown_duration = 6 SECONDS
+ knockdown_delay = 0 SECONDS
+ w_class = WEIGHT_CLASS_BULKY
+ flags = ABSTRACT | NODROP
+ turned_on = TRUE
+ cell = /obj/item/stock_parts/cell/flayerprod
+ /// The duration that stunning someone will disable their radio for
+ var/radio_disable_time = 8 SECONDS
+
+/obj/item/melee/baton/flayerprod/Initialize(mapload) // We are not making a flayerprod without a cell
+ link_new_cell()
+ return ..()
+
+/obj/item/melee/baton/flayerprod/update_icon_state()
+ return
+
+/obj/item/melee/baton/flayerprod/attackby(obj/item/I, mob/user, params)
+ return
+
+/obj/item/melee/baton/flayerprod/screwdriver_act(mob/living/user, obj/item/I)
+ return
+
+/obj/item/melee/baton/flayerprod/attack_self(mob/user)
+ return
+
+/obj/item/melee/baton/flayerprod/play_hit_sound()
+ playsound(src, 'sound/weapons/egloves.ogg', 25, TRUE, -1, ignore_walls = FALSE)
+
+/obj/item/melee/baton/flayerprod/baton_stun(mob/living/L, mob/user, skip_cooldown, ignore_shield_check = FALSE)
+ if(..())
+ disable_radio(L)
+ L.radio_enable_timer = addtimer(CALLBACK(src, PROC_REF(enable_radio), L), radio_disable_time, TIMER_UNIQUE | TIMER_OVERRIDE | TIMER_STOPPABLE | TIMER_DELETE_ME)
+ return TRUE
+ return FALSE
+
+/obj/item/melee/baton/flayerprod/proc/disable_radio(mob/living/L)
+ var/list/all_items = L.GetAllContents()
+ for(var/obj/item/radio/R in all_items)
+ R.on = FALSE
+ R.listening = FALSE
+ R.broadcasting = FALSE
+ L.visible_message("[R] buzzes loudly as it short circuits!", blind_message = "You hear a loud, electronic buzzing.")
+
+/obj/item/melee/baton/flayerprod/proc/enable_radio(mob/living/L)
+ var/list/all_items = L.GetAllContents()
+ for(var/obj/item/radio/R in all_items)
+ R.on = TRUE
+ R.listening = TRUE
+
+/obj/item/melee/baton/flayerprod/deductcharge(amount)
+ if(cell.charge < hitcost)
+ return
+ cell.use(amount)
+
+/obj/item/melee/baton/flayerprod/examine(mob/user)
+ . = ..()
+ . += "This one seems to be able to interfere with radio headsets."
diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm
index 40ffa590774..eb6c435f6a8 100644
--- a/code/modules/admin/player_panel.dm
+++ b/code/modules/admin/player_panel.dm
@@ -452,6 +452,9 @@
if(length(SSticker.mode.vampires))
dat += check_role_table("Vampires", SSticker.mode.vampires)
+ if(length(SSticker.mode.mindflayers))
+ dat += check_role_table("Mindflayers", SSticker.mode.mindflayers)
+
if(length(SSticker.mode.vampire_enthralled))
dat += check_role_table("Vampire Thralls", SSticker.mode.vampire_enthralled)
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index ee230833f26..7f96ef12ef4 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -67,7 +67,11 @@
log_admin("[key_name(usr)] has spawned an abductor team.")
if(!makeAbductorTeam())
to_chat(usr, "Unfortunately there weren't enough candidates available.")
-
+ if("8")
+ log_admin("[key_name(usr)] has spawned mindflayers.")
+ if(!makeMindflayers())
+ to_chat(usr, "Unfortunately there weren't enough candidates available.")
+
else if(href_list["dbsearchckey"] || href_list["dbsearchadmin"] || href_list["dbsearchip"] || href_list["dbsearchcid"] || href_list["dbsearchbantype"])
var/adminckey = href_list["dbsearchadmin"]
var/playerckey = href_list["dbsearchckey"]
diff --git a/code/modules/admin/verbs/one_click_antag.dm b/code/modules/admin/verbs/one_click_antag.dm
index 6654de33853..665c1bac2b9 100644
--- a/code/modules/admin/verbs/one_click_antag.dm
+++ b/code/modules/admin/verbs/one_click_antag.dm
@@ -20,6 +20,7 @@
Make Wizard (Requires Ghosts)
Make Vampires
Make Abductor Team (Requires Ghosts)
+ Make Mindflayers
"}
usr << browse(dat, "window=oneclickantag;size=400x400")
return
@@ -31,7 +32,7 @@
if(M.stat || !M.mind || M.mind.special_role || M.mind.offstation_role)
return FALSE
if(temp)
- if((M.mind.assigned_role in temp.restricted_jobs) || (M.client.prefs.active_character.species in temp.protected_species))
+ if((M.mind.assigned_role in temp.restricted_jobs) || (M.client.prefs.active_character.species in temp.species_to_mindflayer))
return FALSE
if(role) // Don't even bother evaluating if there's no role
if(player_old_enough_antag(M.client,role) && (role in M.client.prefs.be_special) && !M.client.skip_antag && (!jobban_isbanned(M, role)))
@@ -287,6 +288,29 @@
return 1
return 0
+/datum/admins/proc/makeMindflayers()
+ var/datum/game_mode/vampire/temp = new()
+
+ if(GLOB.configuration.gamemode.prevent_mindshield_antags)
+ temp.restricted_jobs += temp.protected_jobs
+
+ var/input_num = input(owner, "How many Mindflayers you want to create? Enter 0 to cancel","Amount:", 0) as num|null
+ if(input_num <= 0 || isnull(input_num))
+ qdel(temp)
+ return FALSE
+
+ log_admin("[key_name(owner)] tried making [input_num] Mindflayers with One-Click-Antag")
+ message_admins("[key_name_admin(owner)] tried making [input_num] Mindflayers with One-Click-Antag")
+ var/list/possible_mindflayers = temp.get_players_for_role(ROLE_MIND_FLAYER, FALSE, "Machine")
+ var/num_mindflayers = min(length(possible_mindflayers), input_num)
+ if(!num_mindflayers)
+ return FALSE
+ for(var/i in 1 to num_mindflayers)
+ var/datum/mind/flayer = pick_n_take(possible_mindflayers)
+ flayer.make_mind_flayer()
+ qdel(temp)
+ return TRUE
+
/datum/admins/proc/makeThunderdomeTeams() // Not strictly an antag, but this seemed to be the best place to put it.
var/max_thunderdome_players = 10
var/team_to_assign_to = "Green"
diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm
index 9196e7d6faf..ef29cfe17f2 100644
--- a/code/modules/antagonists/_common/antag_datum.dm
+++ b/code/modules/antagonists/_common/antag_datum.dm
@@ -39,6 +39,8 @@ GLOBAL_LIST_EMPTY(antagonists)
var/clown_removal_text = "You are clumsy again."
/// The spawn class to use for gain/removal clown text
var/clown_text_span_class = "boldnotice"
+ /// If the antagonist can have their spoken voice be something else, this is the "voice" that they will appear as.
+ var/mimicking = ""
/// The url page name for this antagonist, appended to the end of the wiki url in the form of: [GLOB.configuration.url.wiki_url]/index.php/[wiki_page_name]
var/wiki_page_name
/// The organization, if any, this antag is associated with
@@ -402,6 +404,63 @@ GLOBAL_LIST_EMPTY(antagonists)
/datum/antagonist/proc/finalize_antag()
return
+/**
+ * Create and assign a full set of randomized, basic human traitor objectives.
+ * can_hijack - If you want the 10% chance for the antagonist to be able to roll hijack, only true for traitors
+ */
+/datum/antagonist/proc/forge_basic_objectives(can_hijack = FALSE)
+ // Hijack objective.
+ if(can_hijack && prob(10) && !(locate(/datum/objective/hijack) in owner.get_all_objectives()))
+ add_antag_objective(/datum/objective/hijack)
+ return // Hijack should be their only objective (normally), so return.
+
+ // Will give normal steal/kill/etc. type objectives.
+ for(var/i in 1 to GLOB.configuration.gamemode.traitor_objectives_amount)
+ forge_single_human_objective()
+
+ var/can_succeed_if_dead = TRUE
+ for(var/datum/objective/O in owner.get_all_objectives())
+ if(!O.martyr_compatible) // Check if our current objectives can co-exist with martyr.
+ can_succeed_if_dead = FALSE
+ break
+
+ // Give them an escape objective if they don't have one already.
+ if(!(locate(/datum/objective/escape) in owner.get_all_objectives()) && (!can_succeed_if_dead || prob(80)))
+ add_antag_objective(/datum/objective/escape)
+
+
+/**
+ * Create and assign a single randomized human traitor objective.
+ */
+/datum/antagonist/proc/forge_single_human_objective()
+ var/datum/objective/objective_to_add
+
+ // If our org has an objectives list, give one to us if we pass a roll on the org's focus
+ if(organization && length(organization.objectives) && prob(organization.focus))
+ objective_to_add = pick(organization.objectives)
+ else
+ if(prob(50))
+ if(length(active_ais()) && prob(100 / length(GLOB.player_list)))
+ objective_to_add = /datum/objective/destroy
+
+ else if(prob(5))
+ objective_to_add = /datum/objective/debrain
+
+ else if(prob(30))
+ objective_to_add = /datum/objective/maroon
+
+ else if(prob(30))
+ objective_to_add = /datum/objective/assassinateonce
+
+ else
+ objective_to_add = /datum/objective/assassinate
+ else
+ objective_to_add = /datum/objective/steal
+
+ if(delayed_objectives)
+ objective_to_add = new /datum/objective/delayed(objective_to_add)
+ add_antag_objective(objective_to_add)
+
//Individual roundend report
/datum/antagonist/proc/roundend_report()
var/list/report = list()
diff --git a/code/modules/antagonists/changeling/datum_changeling.dm b/code/modules/antagonists/changeling/datum_changeling.dm
index f8507d96cd8..8d7ab14578f 100644
--- a/code/modules/antagonists/changeling/datum_changeling.dm
+++ b/code/modules/antagonists/changeling/datum_changeling.dm
@@ -42,8 +42,6 @@ RESTRICT_TYPE(/datum/antagonist/changeling)
var/is_absorbing = FALSE
/// The amount of points available to purchase changeling abilities.
var/genetic_points = 20
- /// A name that will display in place of the changeling's real name when speaking.
- var/mimicing = ""
/// If the changeling can respec their purchased abilities.
var/can_respec = FALSE
/// The current sting power the changeling has active.
@@ -221,7 +219,7 @@ RESTRICT_TYPE(/datum/antagonist/changeling)
chem_recharge_rate = initial(chem_recharge_rate)
chem_charges = min(chem_charges, chem_storage)
chem_recharge_slowdown = initial(chem_recharge_slowdown)
- mimicing = null
+ mimicking = null
/**
* Removes a changeling's abilities.
diff --git a/code/modules/antagonists/changeling/powers/mimic_voice.dm b/code/modules/antagonists/changeling/powers/mimic_voice.dm
index ca3c654e7b3..c6552a07775 100644
--- a/code/modules/antagonists/changeling/powers/mimic_voice.dm
+++ b/code/modules/antagonists/changeling/powers/mimic_voice.dm
@@ -12,8 +12,8 @@
// Fake Voice
/datum/action/changeling/mimicvoice/sting_action(mob/user)
- if(cling.mimicing)
- cling.mimicing = ""
+ if(cling.mimicking)
+ cling.mimicking = ""
to_chat(user, "We return our vocal glands to their original position.")
return FALSE
@@ -21,7 +21,7 @@
if(!mimic_voice)
return FALSE
- cling.mimicing = mimic_voice
+ cling.mimicking = mimic_voice
to_chat(user, "We shape our glands to take the voice of [mimic_voice].")
to_chat(user, "Use this power again to return to our original voice.")
diff --git a/code/modules/antagonists/mind_flayer/flayer_datum.dm b/code/modules/antagonists/mind_flayer/flayer_datum.dm
new file mode 100644
index 00000000000..637bcd1d788
--- /dev/null
+++ b/code/modules/antagonists/mind_flayer/flayer_datum.dm
@@ -0,0 +1,322 @@
+/datum/antagonist/mindflayer
+ name = "Mindflayer"
+ antag_hud_type = ANTAG_HUD_MIND_FLAYER
+ antag_hud_name = "hudflayer"
+ special_role = SPECIAL_ROLE_MIND_FLAYER
+ wiki_page_name = "Mindflayer"
+ /// The current amount of swarms the mind flayer has access to purchase with
+ var/usable_swarms = 0
+ /// The total points of brain damage the flayer has harvested, only used for logging purposes.
+ var/total_swarms_gathered = 0
+ /// The current person being drained
+ var/mob/living/carbon/human/harvesting
+ /// The list of all purchased spell and passive objects
+ var/list/powers = list()
+ /// A list of all powers and passives mindflayers can buy
+ var/list/ability_list = list()
+ ///List for keeping track of who has already been drained
+ var/list/drained_humans = list()
+ /// How fast the flayer's touch drains
+ var/drain_multiplier = 1
+ /// The base brain damage dealt per tick of the drain
+ var/drain_amount = 0.5
+ /// A list of the categories and their associated stages of the power
+ var/list/category_stage = list(FLAYER_CATEGORY_GENERAL = 1, FLAYER_CATEGORY_DESTROYER = 1, FLAYER_CATEGORY_INTRUDER = 1)
+ /// If the mindflayer can still pick a stage 4 ability
+ var/can_pick_capstone = TRUE
+ /// Have we notified that our victim does not give swarms from draining
+ var/has_notified = FALSE
+
+/datum/antagonist/mindflayer/New()
+ . = ..()
+ if(!length(ability_list))
+ ability_list = get_spells_of_type(FLAYER_PURCHASABLE_POWER)
+ ability_list += get_passives_of_type(FLAYER_PURCHASABLE_POWER)
+
+/datum/antagonist/mindflayer/Destroy(force, ...)
+ QDEL_NULL(owner.current?.hud_used?.vampire_blood_display)
+ harvesting = null
+ remove_all_abilities()
+ remove_all_passives()
+ ..()
+
+/datum/antagonist/mindflayer/add_owner_to_gamemode()
+ SSticker.mode.mindflayers += owner
+
+/datum/antagonist/mindflayer/remove_owner_from_gamemode()
+ SSticker.mode.mindflayers -= owner
+
+// This proc adds extra things, and base abilities that the mindflayer should get upon becoming a mindflayer
+/datum/antagonist/mindflayer/on_gain()
+ . = ..()
+ var/list/innate_powers = get_spells_of_type(FLAYER_INNATE_POWER)
+ for(var/power_path as anything in innate_powers)
+ var/datum/spell/flayer/to_add = new power_path
+ add_ability(to_add)
+ owner.current.faction += list("flayer") // In case our robot is mindlessly spawned in somehow, and they won't accidentally kill us
+
+/datum/antagonist/mindflayer/give_objectives()
+ add_antag_objective(/datum/objective/swarms)
+ forge_basic_objectives()
+
+/datum/antagonist/mindflayer/proc/get_swarms()
+ return usable_swarms
+
+/datum/antagonist/mindflayer/proc/set_swarms(amount, update_total = FALSE) //If you adminbus someone's swarms it won't add or remove to the total
+ var/old_swarm_amount = usable_swarms
+ usable_swarms = amount
+ if(update_total)
+ var/difference = usable_swarms - old_swarm_amount
+ if(difference < 0)
+ // Otherwise buying an ability can reduce your `total_swarms_gathered`
+ return
+ total_swarms_gathered += difference
+
+/*
+* amount - The positive or negative number to adjust the swarm count by, result clamped above 0
+*/
+/datum/antagonist/mindflayer/proc/adjust_swarms(amount, bound_lower = 0, bound_upper = INFINITY)
+ set_swarms(clamp((usable_swarms + amount), bound_lower, bound_upper), TRUE)
+
+//This is mostly for flavor, for framing messages as coming from the swarm itself. The other reason is so I can type "span" less.
+/datum/antagonist/mindflayer/proc/send_swarm_message(message)
+ if(HAS_TRAIT(owner.current, TRAIT_MINDFLAYER_NULLIFIED))
+ message = stutter(message, 0, TRUE)
+ to_chat(owner.current, "[message]")
+
+/**
+ Checks for any reason that you should not be able to drain someone for.
+ Returns either true or false, if the harvest will work or not.
+*/
+/datum/antagonist/mindflayer/proc/check_valid_harvest(mob/living/carbon/human/H)
+ if(HAS_TRAIT(owner.current, TRAIT_MINDFLAYER_NULLIFIED))
+ send_swarm_message("We do not have the energy for this...")
+ return FALSE
+ if(IS_MINDFLAYER(H))
+ send_swarm_message("Their hive rejects the connection.")
+ return FALSE
+ var/obj/item/organ/internal/brain/brain = H.get_int_organ(/obj/item/organ/internal/brain)
+ if(!istype(brain))
+ send_swarm_message("This entity has no brain to harvest from.")
+ return FALSE
+ if(!H.ckey && !H.player_ghosted)
+ send_swarm_message("This brain does not contain the spark that feeds us. Find more suitable prey.")
+ return FALSE
+ if(brain.damage >= brain.max_damage || (H.stat == DEAD && !H.has_status_effect(STATUS_EFFECT_RECENTLY_SUCCUMBED)))
+ send_swarm_message("We detect no neural activity to harvest from this brain.")
+ return FALSE
+ if(HAS_MIND_TRAIT(H, TRAIT_XENOBIO_SPAWNED_HUMAN))
+ send_swarm_message("This brain is unsuitable to harvest.")
+ return FALSE
+
+ var/unique_drain_id = H.UID()
+ if(isnull(drained_humans[unique_drain_id]))
+ drained_humans[unique_drain_id] = 0
+ else if(drained_humans[unique_drain_id] > BRAIN_DRAIN_LIMIT)
+ if(!has_notified)
+ has_notified = TRUE
+ send_swarm_message("You have drained most of the life force from [H]'s brain, and you will get no more swarms from them!")
+ return DRAIN_BUT_NO_SWARMS
+ return TRUE
+
+/**
+ Begins draining the brain of H, gains swarms equal to the amount of brain damage dealt per tick. Upgrades can increase the amount of damage per tick.
+**/
+/datum/antagonist/mindflayer/proc/handle_harvest(mob/living/carbon/human/H)
+ harvesting = H
+ var/obj/item/organ/internal/brain/drained_brain = H.get_int_organ(/obj/item/organ/internal/brain)
+ if(!istype(drained_brain))
+ return
+ var/unique_drain_id = H.UID()
+ owner.current.visible_message(
+ "[owner.current] puts [owner.current.p_their()] fingers on [H]'s [drained_brain.parent_organ] and begins harvesting!",
+ "We begin our harvest on [H].",
+ "You hear the hum of electricity."
+ )
+ if(!do_mob(owner.current, H, time = 2 SECONDS))
+ send_swarm_message("Our connection was incomplete.")
+ harvesting = null
+ return
+ while(do_mob(owner.current, H, time = DRAIN_TIME, progress = FALSE))
+ var/check_harvest = check_valid_harvest(H)
+ if(!check_harvest)
+ harvesting = null
+ has_notified = FALSE
+ return
+ H.Beam(owner.current, icon_state = "drain_life", icon ='icons/effects/effects.dmi', time = DRAIN_TIME, beam_color = COLOR_ASSEMBLY_PURPLE)
+ var/damage_to_deal = (drain_amount * drain_multiplier * H.dna.species.brain_mod)
+ H.adjustBrainLoss(damage_to_deal, use_brain_mod = FALSE) //No need to use brain damage modification since we already got it from the previous line
+
+ if(check_harvest != DRAIN_BUT_NO_SWARMS)
+ adjust_swarms(damage_to_deal)
+ drained_humans[unique_drain_id] += damage_to_deal
+
+ // Lasting effects. Every second of draining requires 4 seconds of healing
+ drained_brain.max_damage -= 0.25 // As much damage as the default drain
+ drained_brain.temporary_damage += 0.25
+
+ send_swarm_message("Our connection severs.")
+ harvesting = null
+ has_notified = FALSE
+
+/datum/antagonist/mindflayer/greet()
+ var/list/messages = list()
+ SEND_SOUND(owner.current, sound('sound/ambience/antag/mindflayer_alert.ogg'))
+ messages += "You feel something stirring within your chassis... You are a Mindflayer!
"
+ messages += "To harvest someone, target where the brain of your victim is and use harm intent with an empty hand. Drain intelligence to increase your swarm."
+ return messages
+
+/**
+ * Gets a list of mind flayer spell typepaths based on the passed in `spell_type`. (Thanks for the code SteelSlayer)
+ *
+ * Arguments:
+ * * spell_type - should be a define related to [/datum/spell/flayer/power_type].
+ */
+/datum/antagonist/mindflayer/proc/get_spells_of_type(spell_type)
+ var/list/spells = list()
+ for(var/spell_path in subtypesof(/datum/spell/flayer))
+ var/datum/spell/flayer/spell = spell_path
+ if(initial(spell.power_type) != spell_type)
+ continue
+ spells += spell_path
+ return spells
+
+/**
+ * Gets a list of mind flayer passive typepaths based on the passed in `passive_type`.
+ *
+ * Arguments:
+ * * passive_type - should be a define related to [/datum/spell/flayer/passive_type].
+ */
+/datum/antagonist/mindflayer/proc/get_passives_of_type(passive_type)
+ var/list/passives = list()
+ for(var/passive_path in subtypesof(/datum/mindflayer_passive))
+ var/datum/mindflayer_passive/passive = passive_path
+ if(initial(passive.power_type) != passive_type)
+ continue
+ passives += passive_path
+ return passives
+
+///////////////////////////////////////////////////////////////
+// A BUNCH OF PROCS THAT HANDLE ADDING ABILITIES AND PASSIVES
+/////////////////////////////////////////////////////////////
+
+/**
+* Adds an ability to a mindflayer if they don't already have it, upgrades it if they do.
+* Arguments:
+* * to_add - The spell datum you want to add to the flayer
+* * set_owner - The antagonist datum of the mindflayer you want to add the spell to
+*/
+/datum/antagonist/mindflayer/proc/add_ability(datum/spell/flayer/to_add)
+ if(!to_add)
+ return
+ var/datum/spell/flayer/spell = has_spell(to_add)
+ if(spell)
+ spell.on_apply()
+ qdel(to_add)
+ return
+
+ to_add.flayer = src
+
+ to_add.level = 1
+ to_add.current_cost += to_add.static_upgrade_increase
+ owner.AddSpell(to_add)
+ powers += to_add
+ to_add.spell_purchased()
+
+ check_special_stage_ability(to_add)
+ SSblackbox.record_feedback("nested tally", "mindflayer_abilities", 1, list(to_add.name, "purchased"))
+
+/**
+* Adds a passive to a mindflayer if they don't already have it, upgrades it if they do.
+* Arguments:
+* * to_add - The spell datum you want to add to the flayer
+* * upgrade_type - optional argument if you need to communicate a define to the passive in question
+*/
+/datum/antagonist/mindflayer/proc/add_passive(datum/mindflayer_passive/to_add, upgrade_type) //Passives always need to have their owners set
+ if(!to_add)
+ return
+ var/datum/mindflayer_passive/passive = has_passive(to_add)
+ if(passive)
+ passive.on_apply()
+ qdel(to_add)
+ return
+
+ to_add.flayer = src
+ to_add.owner = owner.current // Passives always need to have their owners set here
+ to_add.on_apply()
+ powers += to_add
+
+ check_special_stage_ability(to_add)
+ SSblackbox.record_feedback("nested tally", "mindflayer_abilities", 1, list(to_add.name, "purchased"))
+
+/*
+ * Removing abilities/passives starts here
+ */
+/datum/antagonist/mindflayer/proc/remove_all_abilities()
+ for(var/datum/spell/flayer/spell in powers)
+ remove_ability(spell)
+
+/datum/antagonist/mindflayer/proc/remove_all_passives()
+ for(var/datum/mindflayer_passive/passive in powers)
+ remove_passive(passive)
+
+/datum/antagonist/mindflayer/proc/remove_ability(datum/spell/flayer/to_remove)
+ owner.RemoveSpell(to_remove)
+ powers -= to_remove
+
+/datum/antagonist/mindflayer/proc/remove_passive(datum/mindflayer_passive/to_remove)
+ powers -= to_remove
+ qdel(to_remove) //qdel should call destroy, which should call on_remove
+
+/**
+* Checks if a mindflayer has a given spell already
+* * Arguments: to_get - Some datum/spell/flayer to check if a mindflayer has
+* * Returns: The datum/spell/mindflayer if the mindflayer has the power already, null otherwise
+*/
+/datum/antagonist/mindflayer/proc/has_spell(datum/spell/flayer/to_get)
+ for(var/datum/spell/flayer/spell in powers)
+ if(istype(spell, to_get))
+ return spell
+
+/**
+* Checks if a mindflayer has a given passive already
+* * Arguments: to_get - Some datum/mindflayer_passive to check if a mindflayer has
+* * Returns: The datum/mindflayer_passive if the mindflayer has the passive already, null otherwise
+*/
+/datum/antagonist/mindflayer/proc/has_passive(datum/mindflayer_passive/to_get)
+ for(var/datum/mindflayer_passive/spell in powers)
+ if(to_get.name == spell.name)
+ return spell
+
+/// This is the proc that gets called every tick of life(), use this for updating something that should update every few seconds
+/datum/antagonist/mindflayer/proc/handle_mindflayer()
+ if(owner.current.hud_used)
+ var/datum/hud/hud = owner.current.hud_used
+ if(!hud.vampire_blood_display)
+ hud.vampire_blood_display = new /atom/movable/screen()
+ hud.vampire_blood_display.name = "Usable Swarms"
+ hud.vampire_blood_display.icon_state = "blood_display"
+ hud.vampire_blood_display.screen_loc = "WEST:6,CENTER-1:15"
+ hud.static_inventory += hud.vampire_blood_display
+ hud.show_hud(hud.hud_version)
+ hud.vampire_blood_display.maptext = "
m;)u[m]=i[m++];return u}return S}(),k)},88739:function(T,r,n){"use strict";var e=n(4246),a=n(22603).some,t=e.aTypedArray,o=e.exportTypedArrayMethod;o("some",function(){function f(b){return a(t(this),b,arguments.length>1?arguments[1]:void 0)}return f}())},60415:function(T,r,n){"use strict";var e=n(74685),a=n(71138),t=n(40033),o=n(10320),f=n(90274),b=n(4246),k=n(652),S=n(19228),y=n(5026),h=n(9342),i=b.aTypedArray,c=b.exportTypedArrayMethod,m=e.Uint16Array,d=m&&a(m.prototype.sort),u=!!d&&!(t(function(){d(new m(2),null)})&&t(function(){d(new m(2),{})})),s=!!d&&!t(function(){if(y)return y<74;if(k)return k<67;if(S)return!0;if(h)return h<602;var C=new m(516),N=Array(516),v,p;for(v=0;v<516;v++)p=v%4,C[v]=515-v,N[v]=v-2*p+3;for(d(C,function(g,V){return(g/4|0)-(V/4|0)}),v=0;v<516;v++)if(C[v]!==N[v])return!0}),l=function(N){return function(v,p){return N!==void 0?+N(v,p)||0:p!==p?-1:v!==v?1:v===0&&p===0?1/v>0&&1/p<0?1:-1:v>p}};c("sort",function(){function C(N){return N!==void 0&&o(N),s?d(this,N):f(i(this),l(N))}return C}(),!s||u)},72532:function(T,r,n){"use strict";var e=n(4246),a=n(10188),t=n(13912),o=n(31082),f=e.aTypedArray,b=e.exportTypedArrayMethod;b("subarray",function(){function k(S,y){var h=f(this),i=h.length,c=t(S,i),m=o(h);return new m(h.buffer,h.byteOffset+c*h.BYTES_PER_ELEMENT,a((y===void 0?i:t(y,i))-c))}return k}())},62207:function(T,r,n){"use strict";var e=n(74685),a=n(61267),t=n(4246),o=n(40033),f=n(54602),b=e.Int8Array,k=t.aTypedArray,S=t.exportTypedArrayMethod,y=[].toLocaleString,h=!!b&&o(function(){y.call(new b(1))}),i=o(function(){return[1,2].toLocaleString()!==new b([1,2]).toLocaleString()})||!o(function(){b.prototype.toLocaleString.call([1,2])});S("toLocaleString",function(){function c(){return a(y,h?f(k(this)):k(this),f(arguments))}return c}(),i)},906:function(T,r,n){"use strict";var e=n(4246).exportTypedArrayMethod,a=n(40033),t=n(74685),o=n(67250),f=t.Uint8Array,b=f&&f.prototype||{},k=[].toString,S=o([].join);a(function(){k.call({})})&&(k=function(){function h(){return S(this)}return h}());var y=b.toString!==k;e("toString",k,y)},78824:function(T,r,n){"use strict";var e=n(80185);e("Uint16",function(a){return function(){function t(o,f,b){return a(this,o,f,b)}return t}()})},72846:function(T,r,n){"use strict";var e=n(80185);e("Uint32",function(a){return function(){function t(o,f,b){return a(this,o,f,b)}return t}()})},24575:function(T,r,n){"use strict";var e=n(80185);e("Uint8",function(a){return function(){function t(o,f,b){return a(this,o,f,b)}return t}()})},71968:function(T,r,n){"use strict";var e=n(80185);e("Uint8",function(a){return function(){function t(o,f,b){return a(this,o,f,b)}return t}()},!0)},80040:function(T,r,n){"use strict";var e=n(50730),a=n(74685),t=n(67250),o=n(30145),f=n(81969),b=n(45150),k=n(39895),S=n(77568),y=n(5419).enforce,h=n(40033),i=n(21820),c=Object,m=Array.isArray,d=c.isExtensible,u=c.isFrozen,s=c.isSealed,l=c.freeze,C=c.seal,N=!a.ActiveXObject&&"ActiveXObject"in a,v,p=function(E){return function(){function M(){return E(this,arguments.length?arguments[0]:void 0)}return M}()},g=b("WeakMap",p,k),V=g.prototype,B=t(V.set),I=function(){return e&&h(function(){var E=l([]);return B(new g,E,1),!u(E)})};if(i)if(N){v=k.getConstructor(p,"WeakMap",!0),f.enable();var L=t(V.delete),w=t(V.has),A=t(V.get);o(V,{delete:function(){function x(E){if(S(E)&&!d(E)){var M=y(this);return M.frozen||(M.frozen=new v),L(this,E)||M.frozen.delete(E)}return L(this,E)}return x}(),has:function(){function x(E){if(S(E)&&!d(E)){var M=y(this);return M.frozen||(M.frozen=new v),w(this,E)||M.frozen.has(E)}return w(this,E)}return x}(),get:function(){function x(E){if(S(E)&&!d(E)){var M=y(this);return M.frozen||(M.frozen=new v),w(this,E)?A(this,E):M.frozen.get(E)}return A(this,E)}return x}(),set:function(){function x(E,M){if(S(E)&&!d(E)){var j=y(this);j.frozen||(j.frozen=new v),w(this,E)?B(this,E,M):j.frozen.set(E,M)}else B(this,E,M);return this}return x}()})}else I()&&o(V,{set:function(){function x(E,M){var j;return m(E)&&(u(E)?j=l:s(E)&&(j=C)),B(this,E,M),j&&j(E),this}return x}()})},90846:function(T,r,n){"use strict";n(80040)},67042:function(T,r,n){"use strict";var e=n(45150),a=n(39895);e("WeakSet",function(t){return function(){function o(){return t(this,arguments.length?arguments[0]:void 0)}return o}()},a)},40348:function(T,r,n){"use strict";n(67042)},5606:function(T,r,n){"use strict";var e=n(63964),a=n(74685),t=n(60375).clear;e({global:!0,bind:!0,enumerable:!0,forced:a.clearImmediate!==t},{clearImmediate:t})},83006:function(T,r,n){"use strict";n(5606),n(27807)},25764:function(T,r,n){"use strict";var e=n(63964),a=n(74685),t=n(37713),o=n(10320),f=n(24986),b=n(40033),k=n(58310),S=b(function(){return k&&Object.getOwnPropertyDescriptor(a,"queueMicrotask").value.length!==1});e({global:!0,enumerable:!0,dontCallGetSet:!0,forced:S},{queueMicrotask:function(){function y(h){f(arguments.length,1),t(o(h))}return y}()})},27807:function(T,r,n){"use strict";var e=n(63964),a=n(74685),t=n(60375).set,o=n(78362),f=a.setImmediate?o(t,!1):t;e({global:!0,bind:!0,enumerable:!0,forced:a.setImmediate!==f},{setImmediate:f})},45569:function(T,r,n){"use strict";var e=n(63964),a=n(74685),t=n(78362),o=t(a.setInterval,!0);e({global:!0,bind:!0,forced:a.setInterval!==o},{setInterval:o})},5213:function(T,r,n){"use strict";var e=n(63964),a=n(74685),t=n(78362),o=t(a.setTimeout,!0);e({global:!0,bind:!0,forced:a.setTimeout!==o},{setTimeout:o})},69401:function(T,r,n){"use strict";n(45569),n(5213)},7435:function(T){"use strict";/**
+ */var t=r.BoxWithSampleText=function(){function o(f){return(0,e.normalizeProps)((0,e.createComponentVNode)(2,a.Box,Object.assign({},f,{children:[(0,e.createComponentVNode)(2,a.Box,{italic:!0,children:"Jackdaws love my big sphinx of quartz."}),(0,e.createComponentVNode)(2,a.Box,{mt:1,bold:!0,children:"The wide electrification of the southern provinces will give a powerful impetus to the growth of agriculture."})]})))}return o}()},67160:function(){},23542:function(){},30386:function(){},98996:function(){},50578:function(){},4444:function(){},77870:function(){},23632:function(){},56492:function(){},39108:function(){},11714:function(){},73492:function(){},49641:function(){},17570:function(){},61858:function(){},32882:function(){},70752:function(T,r,n){var e={"./pai_atmosphere.js":80818,"./pai_bioscan.js":23903,"./pai_directives.js":64988,"./pai_doorjack.js":13813,"./pai_main_menu.js":66025,"./pai_manifest.js":2983,"./pai_medrecords.js":40758,"./pai_messenger.js":98599,"./pai_radio.js":50775,"./pai_secrecords.js":48623,"./pai_signaler.js":47297};function a(o){var f=t(o);return n(f)}function t(o){if(!n.o(e,o)){var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}return e[o]}a.keys=function(){return Object.keys(e)},a.resolve=t,T.exports=a,a.id=70752},59395:function(T,r,n){var e={"./pda_atmos_scan.js":78532,"./pda_janitor.js":40253,"./pda_main_menu.js":58293,"./pda_manifest.js":58059,"./pda_medical.js":18147,"./pda_messenger.js":77595,"./pda_mule.js":24635,"./pda_nanobank.js":23734,"./pda_notes.js":97085,"./pda_power.js":57513,"./pda_secbot.js":99808,"./pda_security.js":77168,"./pda_signaler.js":21773,"./pda_status_display.js":81857,"./pda_supplyrecords.js":70287};function a(o){var f=t(o);return n(f)}function t(o){if(!n.o(e,o)){var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}return e[o]}a.keys=function(){return Object.keys(e)},a.resolve=t,T.exports=a,a.id=59395},32054:function(T,r,n){var e={"./AICard":1090,"./AICard.js":1090,"./AIFixer":39454,"./AIFixer.js":39454,"./APC":88422,"./APC.js":88422,"./ATM":99660,"./ATM.js":99660,"./AccountsUplinkTerminal":86423,"./AccountsUplinkTerminal.js":86423,"./AiAirlock":56793,"./AiAirlock.js":56793,"./AirAlarm":72475,"./AirAlarm.js":72475,"./AirlockAccessController":12333,"./AirlockAccessController.js":12333,"./AirlockElectronics":28736,"./AirlockElectronics.js":28736,"./AlertModal":47365,"./AlertModal.tsx":47365,"./AppearanceChanger":71824,"./AppearanceChanger.js":71824,"./AtmosAlertConsole":72285,"./AtmosAlertConsole.js":72285,"./AtmosControl":65805,"./AtmosControl.js":65805,"./AtmosFilter":87816,"./AtmosFilter.js":87816,"./AtmosMixer":52977,"./AtmosMixer.js":52977,"./AtmosPump":11748,"./AtmosPump.js":11748,"./AtmosTankControl":69321,"./AtmosTankControl.js":69321,"./AugmentMenu":92444,"./AugmentMenu.js":92444,"./Autolathe":59179,"./Autolathe.js":59179,"./BioChipPad":5147,"./BioChipPad.js":5147,"./Biogenerator":64273,"./Biogenerator.js":64273,"./BloomEdit":47823,"./BloomEdit.js":47823,"./BlueSpaceArtilleryControl":18621,"./BlueSpaceArtilleryControl.js":18621,"./BluespaceTap":27629,"./BluespaceTap.js":27629,"./BodyScanner":33758,"./BodyScanner.js":33758,"./BookBinder":67963,"./BookBinder.js":67963,"./BotCall":61925,"./BotCall.js":61925,"./BotClean":20464,"./BotClean.js":20464,"./BotFloor":69479,"./BotFloor.js":69479,"./BotHonk":59887,"./BotHonk.js":59887,"./BotMed":80063,"./BotMed.js":80063,"./BotSecurity":74439,"./BotSecurity.js":74439,"./BrigCells":10833,"./BrigCells.js":10833,"./BrigTimer":45761,"./BrigTimer.js":45761,"./CameraConsole":26300,"./CameraConsole.js":26300,"./Canister":52927,"./Canister.js":52927,"./CardComputer":51793,"./CardComputer.js":51793,"./CargoConsole":64083,"./CargoConsole.js":64083,"./ChangelogView":87331,"./ChangelogView.js":87331,"./ChemDispenser":36108,"./ChemDispenser.js":36108,"./ChemHeater":13146,"./ChemHeater.js":13146,"./ChemMaster":56541,"./ChemMaster.tsx":56541,"./CloningConsole":37173,"./CloningConsole.js":37173,"./CloningPod":98723,"./CloningPod.js":98723,"./CoinMint":18259,"./CoinMint.tsx":18259,"./ColourMatrixTester":8444,"./ColourMatrixTester.js":8444,"./CommunicationsComputer":63818,"./CommunicationsComputer.js":63818,"./CompostBin":20562,"./CompostBin.js":20562,"./Contractor":21813,"./Contractor.js":21813,"./ConveyorSwitch":54151,"./ConveyorSwitch.js":54151,"./CrewMonitor":73169,"./CrewMonitor.js":73169,"./Cryo":63987,"./Cryo.js":63987,"./CryopodConsole":86099,"./CryopodConsole.js":86099,"./DNAModifier":12692,"./DNAModifier.js":12692,"./DestinationTagger":41074,"./DestinationTagger.js":41074,"./DisposalBin":46500,"./DisposalBin.js":46500,"./DnaVault":33233,"./DnaVault.js":33233,"./DroneConsole":33681,"./DroneConsole.js":33681,"./EFTPOS":17263,"./EFTPOS.js":17263,"./ERTManager":76382,"./ERTManager.js":76382,"./EconomyManager":90217,"./EconomyManager.js":90217,"./Electropack":82565,"./Electropack.js":82565,"./Emojipedia":11243,"./Emojipedia.tsx":11243,"./EvolutionMenu":36730,"./EvolutionMenu.js":36730,"./ExosuitFabricator":17370,"./ExosuitFabricator.js":17370,"./ExperimentConsole":59128,"./ExperimentConsole.js":59128,"./ExternalAirlockController":97086,"./ExternalAirlockController.js":97086,"./FaxMachine":96142,"./FaxMachine.js":96142,"./FilingCabinet":74123,"./FilingCabinet.js":74123,"./FloorPainter":83767,"./FloorPainter.js":83767,"./GPS":53424,"./GPS.js":53424,"./GeneModder":89124,"./GeneModder.js":89124,"./GenericCrewManifest":73053,"./GenericCrewManifest.js":73053,"./GhostHudPanel":42914,"./GhostHudPanel.js":42914,"./GlandDispenser":25825,"./GlandDispenser.js":25825,"./GravityGen":10270,"./GravityGen.js":10270,"./GuestPass":48657,"./GuestPass.js":48657,"./HandheldChemDispenser":67834,"./HandheldChemDispenser.js":67834,"./HealthSensor":46098,"./HealthSensor.js":46098,"./Holodeck":36771,"./Holodeck.js":36771,"./Instrument":25471,"./Instrument.js":25471,"./KeyComboModal":13618,"./KeyComboModal.tsx":13618,"./KeycardAuth":35655,"./KeycardAuth.js":35655,"./KitchenMachine":62955,"./KitchenMachine.js":62955,"./LawManager":9525,"./LawManager.js":9525,"./LibraryComputer":85066,"./LibraryComputer.js":85066,"./LibraryManager":9516,"./LibraryManager.js":9516,"./ListInputModal":90447,"./ListInputModal.tsx":90447,"./MODsuit":77613,"./MODsuit.js":77613,"./MagnetController":78624,"./MagnetController.js":78624,"./MechBayConsole":72106,"./MechBayConsole.js":72106,"./MechaControlConsole":7466,"./MechaControlConsole.js":7466,"./MedicalRecords":79625,"./MedicalRecords.js":79625,"./MerchVendor":54989,"./MerchVendor.js":54989,"./MiningVendor":87684,"./MiningVendor.js":87684,"./NTRecruiter":59783,"./NTRecruiter.js":59783,"./Newscaster":64713,"./Newscaster.js":64713,"./Noticeboard":48286,"./Noticeboard.tsx":48286,"./NuclearBomb":41166,"./NuclearBomb.js":41166,"./NumberInputModal":52416,"./NumberInputModal.tsx":52416,"./OperatingComputer":1218,"./OperatingComputer.js":1218,"./Orbit":46892,"./Orbit.js":46892,"./OreRedemption":15421,"./OreRedemption.js":15421,"./PAI":52754,"./PAI.js":52754,"./PDA":85175,"./PDA.js":85175,"./Pacman":68654,"./Pacman.js":68654,"./PanDEMIC":1701,"./PanDEMIC.tsx":1701,"./ParticleAccelerator":67921,"./ParticleAccelerator.js":67921,"./PdaPainter":71432,"./PdaPainter.js":71432,"./PersonalCrafting":33388,"./PersonalCrafting.js":33388,"./Photocopier":56150,"./Photocopier.js":56150,"./PoolController":84676,"./PoolController.js":84676,"./PortablePump":57003,"./PortablePump.js":57003,"./PortableScrubber":70069,"./PortableScrubber.js":70069,"./PortableTurret":59955,"./PortableTurret.js":59955,"./PowerMonitor":61631,"./PowerMonitor.js":61631,"./PrisonerImplantManager":50992,"./PrisonerImplantManager.js":50992,"./PrisonerShuttleConsole":53952,"./PrisonerShuttleConsole.js":53952,"./PrizeCounter":97852,"./PrizeCounter.tsx":97852,"./RCD":94813,"./RCD.js":94813,"./RPD":18738,"./RPD.js":18738,"./Radio":80299,"./Radio.js":80299,"./RankedListInputModal":14846,"./RankedListInputModal.tsx":14846,"./ReagentGrinder":48125,"./ReagentGrinder.js":48125,"./ReagentsEditor":58262,"./ReagentsEditor.tsx":58262,"./RemoteSignaler":30207,"./RemoteSignaler.js":30207,"./RequestConsole":25472,"./RequestConsole.js":25472,"./RndBackupConsole":9861,"./RndBackupConsole.js":9861,"./RndConsole":12644,"./RndConsole/":12644,"./RndConsole/DataDiskMenu":37556,"./RndConsole/DataDiskMenu.js":37556,"./RndConsole/DeconstructionMenu":58147,"./RndConsole/DeconstructionMenu.js":58147,"./RndConsole/LatheCategory":16830,"./RndConsole/LatheCategory.js":16830,"./RndConsole/LatheChemicalStorage":70497,"./RndConsole/LatheChemicalStorage.js":70497,"./RndConsole/LatheMainMenu":70864,"./RndConsole/LatheMainMenu.js":70864,"./RndConsole/LatheMaterialStorage":42878,"./RndConsole/LatheMaterialStorage.js":42878,"./RndConsole/LatheMaterials":52662,"./RndConsole/LatheMaterials.js":52662,"./RndConsole/LatheMenu":9681,"./RndConsole/LatheMenu.js":9681,"./RndConsole/LatheSearch":68198,"./RndConsole/LatheSearch.js":68198,"./RndConsole/LinkMenu":81421,"./RndConsole/LinkMenu.js":81421,"./RndConsole/SettingsMenu":6256,"./RndConsole/SettingsMenu.js":6256,"./RndConsole/index":12644,"./RndConsole/index.js":12644,"./RndNetController":29205,"./RndNetController.js":29205,"./RndServer":63315,"./RndServer.js":63315,"./RobotSelfDiagnosis":26109,"./RobotSelfDiagnosis.js":26109,"./RoboticsControlConsole":97997,"./RoboticsControlConsole.js":97997,"./Safe":54431,"./Safe.js":54431,"./SatelliteControl":29740,"./SatelliteControl.js":29740,"./SecureStorage":44162,"./SecureStorage.js":44162,"./SecurityRecords":6272,"./SecurityRecords.js":6272,"./SeedExtractor":5099,"./SeedExtractor.js":5099,"./ShuttleConsole":2916,"./ShuttleConsole.js":2916,"./ShuttleManipulator":39401,"./ShuttleManipulator.js":39401,"./Sleeper":88284,"./Sleeper.js":88284,"./SlotMachine":21597,"./SlotMachine.js":21597,"./Smartfridge":46348,"./Smartfridge.js":46348,"./Smes":86162,"./Smes.js":86162,"./SolarControl":63584,"./SolarControl.js":63584,"./SpawnersMenu":38096,"./SpawnersMenu.js":38096,"./SpecMenu":30586,"./SpecMenu.js":30586,"./StackCraft":95152,"./StackCraft.js":95152,"./StationAlertConsole":38307,"./StationAlertConsole.js":38307,"./StationTraitsPanel":96091,"./StationTraitsPanel.tsx":96091,"./StripMenu":39409,"./StripMenu.tsx":39409,"./SuitStorage":69514,"./SuitStorage.js":69514,"./SupermatterMonitor":15022,"./SupermatterMonitor.js":15022,"./SyndicateComputerSimple":46029,"./SyndicateComputerSimple.js":46029,"./TEG":36372,"./TEG.js":36372,"./TachyonArray":56441,"./TachyonArray.js":56441,"./Tank":1754,"./Tank.js":1754,"./TankDispenser":7579,"./TankDispenser.js":7579,"./TcommsCore":16136,"./TcommsCore.js":16136,"./TcommsRelay":88046,"./TcommsRelay.js":88046,"./Teleporter":20802,"./Teleporter.js":20802,"./TelescienceConsole":48517,"./TelescienceConsole.js":48517,"./TempGun":21800,"./TempGun.js":21800,"./TextInputModal":24410,"./TextInputModal.tsx":24410,"./ThermoMachine":25036,"./ThermoMachine.js":25036,"./TransferValve":20035,"./TransferValve.js":20035,"./TurbineComputer":78166,"./TurbineComputer.js":78166,"./Uplink":52847,"./Uplink.js":52847,"./Vending":12261,"./Vending.js":12261,"./VolumeMixer":68971,"./VolumeMixer.js":68971,"./VotePanel":2510,"./VotePanel.js":2510,"./Wires":30138,"./Wires.js":30138,"./WizardApprenticeContract":21400,"./WizardApprenticeContract.js":21400,"./common/AccessList":49148,"./common/AccessList.js":49148,"./common/AtmosScan":26991,"./common/AtmosScan.js":26991,"./common/BeakerContents":85870,"./common/BeakerContents.js":85870,"./common/BotStatus":92963,"./common/BotStatus.js":92963,"./common/ComplexModal":3939,"./common/ComplexModal.js":3939,"./common/CrewManifest":41874,"./common/CrewManifest.js":41874,"./common/InputButtons":19203,"./common/InputButtons.tsx":19203,"./common/InterfaceLockNoticeBox":195,"./common/InterfaceLockNoticeBox.js":195,"./common/Loader":51057,"./common/Loader.tsx":51057,"./common/LoginInfo":321,"./common/LoginInfo.js":321,"./common/LoginScreen":5485,"./common/LoginScreen.js":5485,"./common/Operating":62411,"./common/Operating.js":62411,"./common/Signaler":13545,"./common/Signaler.js":13545,"./common/SimpleRecords":41984,"./common/SimpleRecords.js":41984,"./common/TemporaryNotice":22091,"./common/TemporaryNotice.js":22091,"./pai/pai_atmosphere":80818,"./pai/pai_atmosphere.js":80818,"./pai/pai_bioscan":23903,"./pai/pai_bioscan.js":23903,"./pai/pai_directives":64988,"./pai/pai_directives.js":64988,"./pai/pai_doorjack":13813,"./pai/pai_doorjack.js":13813,"./pai/pai_main_menu":66025,"./pai/pai_main_menu.js":66025,"./pai/pai_manifest":2983,"./pai/pai_manifest.js":2983,"./pai/pai_medrecords":40758,"./pai/pai_medrecords.js":40758,"./pai/pai_messenger":98599,"./pai/pai_messenger.js":98599,"./pai/pai_radio":50775,"./pai/pai_radio.js":50775,"./pai/pai_secrecords":48623,"./pai/pai_secrecords.js":48623,"./pai/pai_signaler":47297,"./pai/pai_signaler.js":47297,"./pda/pda_atmos_scan":78532,"./pda/pda_atmos_scan.js":78532,"./pda/pda_janitor":40253,"./pda/pda_janitor.js":40253,"./pda/pda_main_menu":58293,"./pda/pda_main_menu.js":58293,"./pda/pda_manifest":58059,"./pda/pda_manifest.js":58059,"./pda/pda_medical":18147,"./pda/pda_medical.js":18147,"./pda/pda_messenger":77595,"./pda/pda_messenger.js":77595,"./pda/pda_mule":24635,"./pda/pda_mule.js":24635,"./pda/pda_nanobank":23734,"./pda/pda_nanobank.js":23734,"./pda/pda_notes":97085,"./pda/pda_notes.js":97085,"./pda/pda_power":57513,"./pda/pda_power.js":57513,"./pda/pda_secbot":99808,"./pda/pda_secbot.js":99808,"./pda/pda_security":77168,"./pda/pda_security.js":77168,"./pda/pda_signaler":21773,"./pda/pda_signaler.js":21773,"./pda/pda_status_display":81857,"./pda/pda_status_display.js":81857,"./pda/pda_supplyrecords":70287,"./pda/pda_supplyrecords.js":70287};function a(o){var f=t(o);return n(f)}function t(o){if(!n.o(e,o)){var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}return e[o]}a.keys=function(){return Object.keys(e)},a.resolve=t,T.exports=a,a.id=32054},4085:function(T,r,n){var e={"./Blink.stories.js":51364,"./BlockQuote.stories.js":32453,"./Box.stories.js":83531,"./Button.stories.js":74198,"./ByondUi.stories.js":51956,"./Collapsible.stories.js":17466,"./Flex.stories.js":89241,"./ImageButton.stories.js":48779,"./Input.stories.js":21394,"./Popper.stories.js":43932,"./ProgressBar.stories.js":33270,"./Stack.stories.js":77766,"./Storage.stories.js":30187,"./Tabs.stories.js":46554,"./Themes.stories.js":53276,"./Tooltip.stories.js":28717};function a(o){var f=t(o);return n(f)}function t(o){if(!n.o(e,o)){var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}return e[o]}a.keys=function(){return Object.keys(e)},a.resolve=t,T.exports=a,a.id=4085},10320:function(T,r,n){"use strict";var e=n(55747),a=n(89393),t=TypeError;T.exports=function(o){if(e(o))return o;throw new t(a(o)+" is not a function")}},32606:function(T,r,n){"use strict";var e=n(1031),a=n(89393),t=TypeError;T.exports=function(o){if(e(o))return o;throw new t(a(o)+" is not a constructor")}},35908:function(T,r,n){"use strict";var e=n(45015),a=String,t=TypeError;T.exports=function(o){if(e(o))return o;throw new t("Can't set "+a(o)+" as a prototype")}},80575:function(T,r,n){"use strict";var e=n(24697),a=n(80674),t=n(74595).f,o=e("unscopables"),f=Array.prototype;f[o]===void 0&&t(f,o,{configurable:!0,value:a(null)}),T.exports=function(b){f[o][b]=!0}},35483:function(T,r,n){"use strict";var e=n(50233).charAt;T.exports=function(a,t,o){return t+(o?e(a,t).length:1)}},60077:function(T,r,n){"use strict";var e=n(21287),a=TypeError;T.exports=function(t,o){if(e(o,t))return t;throw new a("Incorrect invocation")}},30365:function(T,r,n){"use strict";var e=n(77568),a=String,t=TypeError;T.exports=function(o){if(e(o))return o;throw new t(a(o)+" is not an object")}},70377:function(T){"use strict";T.exports=typeof ArrayBuffer!="undefined"&&typeof DataView!="undefined"},3782:function(T,r,n){"use strict";var e=n(40033);T.exports=e(function(){if(typeof ArrayBuffer=="function"){var a=new ArrayBuffer(8);Object.isExtensible(a)&&Object.defineProperty(a,"a",{value:8})}})},4246:function(T,r,n){"use strict";var e=n(70377),a=n(58310),t=n(74685),o=n(55747),f=n(77568),b=n(45299),k=n(2281),S=n(89393),y=n(37909),h=n(55938),i=n(73936),c=n(21287),m=n(36917),d=n(76649),u=n(24697),s=n(16738),l=n(5419),C=l.enforce,N=l.get,v=t.Int8Array,p=v&&v.prototype,g=t.Uint8ClampedArray,V=g&&g.prototype,B=v&&m(v),I=p&&m(p),L=Object.prototype,w=t.TypeError,A=u("toStringTag"),x=s("TYPED_ARRAY_TAG"),E="TypedArrayConstructor",M=e&&!!d&&k(t.opera)!=="Opera",j=!1,P,R,D,_={Int8Array:1,Uint8Array:1,Uint8ClampedArray:1,Int16Array:2,Uint16Array:2,Int32Array:4,Uint32Array:4,Float32Array:4,Float64Array:8},W={BigInt64Array:8,BigUint64Array:8},U=function(){function le(he){if(!f(he))return!1;var q=k(he);return q==="DataView"||b(_,q)||b(W,q)}return le}(),K=function le(he){var q=m(he);if(f(q)){var re=N(q);return re&&b(re,E)?re[E]:le(q)}},G=function(he){if(!f(he))return!1;var q=k(he);return b(_,q)||b(W,q)},$=function(he){if(G(he))return he;throw new w("Target is not a typed array")},Q=function(he){if(o(he)&&(!d||c(B,he)))return he;throw new w(S(he)+" is not a typed array constructor")},J=function(he,q,re,ae){if(a){if(re)for(var ie in _){var Z=t[ie];if(Z&&b(Z.prototype,he))try{delete Z.prototype[he]}catch(ne){try{Z.prototype[he]=q}catch(te){}}}(!I[he]||re)&&h(I,he,re?q:M&&p[he]||q,ae)}},se=function(he,q,re){var ae,ie;if(a){if(d){if(re){for(ae in _)if(ie=t[ae],ie&&b(ie,he))try{delete ie[he]}catch(Z){}}if(!B[he]||re)try{return h(B,he,re?q:M&&B[he]||q)}catch(Z){}else return}for(ae in _)ie=t[ae],ie&&(!ie[he]||re)&&h(ie,he,q)}};for(P in _)R=t[P],D=R&&R.prototype,D?C(D)[E]=R:M=!1;for(P in W)R=t[P],D=R&&R.prototype,D&&(C(D)[E]=R);if((!M||!o(B)||B===Function.prototype)&&(B=function(){function le(){throw new w("Incorrect invocation")}return le}(),M))for(P in _)t[P]&&d(t[P],B);if((!M||!I||I===L)&&(I=B.prototype,M))for(P in _)t[P]&&d(t[P].prototype,I);if(M&&m(V)!==I&&d(V,I),a&&!b(I,A)){j=!0,i(I,A,{configurable:!0,get:function(){function le(){return f(this)?this[x]:void 0}return le}()});for(P in _)t[P]&&y(t[P],x,P)}T.exports={NATIVE_ARRAY_BUFFER_VIEWS:M,TYPED_ARRAY_TAG:j&&x,aTypedArray:$,aTypedArrayConstructor:Q,exportTypedArrayMethod:J,exportTypedArrayStaticMethod:se,getTypedArrayConstructor:K,isView:U,isTypedArray:G,TypedArray:B,TypedArrayPrototype:I}},37336:function(T,r,n){"use strict";var e=n(74685),a=n(67250),t=n(58310),o=n(70377),f=n(70520),b=n(37909),k=n(73936),S=n(30145),y=n(40033),h=n(60077),i=n(61365),c=n(10188),m=n(43806),d=n(95867),u=n(91784),s=n(36917),l=n(76649),C=n(88471),N=n(54602),v=n(5781),p=n(5774),g=n(84925),V=n(5419),B=f.PROPER,I=f.CONFIGURABLE,L="ArrayBuffer",w="DataView",A="prototype",x="Wrong length",E="Wrong index",M=V.getterFor(L),j=V.getterFor(w),P=V.set,R=e[L],D=R,_=D&&D[A],W=e[w],U=W&&W[A],K=Object.prototype,G=e.Array,$=e.RangeError,Q=a(C),J=a([].reverse),se=u.pack,le=u.unpack,he=function(Ne){return[Ne&255]},q=function(Ne){return[Ne&255,Ne>>8&255]},re=function(Ne){return[Ne&255,Ne>>8&255,Ne>>16&255,Ne>>24&255]},ae=function(Ne){return Ne[3]<<24|Ne[2]<<16|Ne[1]<<8|Ne[0]},ie=function(Ne){return se(d(Ne),23,4)},Z=function(Ne){return se(Ne,52,8)},ne=function(Ne,Be,be){k(Ne[A],Be,{configurable:!0,get:function(){function Le(){return be(this)[Be]}return Le}()})},te=function(Ne,Be,be,Le){var we=j(Ne),xe=m(be),Re=!!Le;if(xe+Be>we.byteLength)throw new $(E);var ze=we.bytes,ke=xe+we.byteOffset,de=N(ze,ke,ke+Be);return Re?de:J(de)},fe=function(Ne,Be,be,Le,we,xe){var Re=j(Ne),ze=m(be),ke=Le(+we),de=!!xe;if(ze+Be>Re.byteLength)throw new $(E);for(var pe=Re.bytes,ye=ze+Re.byteOffset,ve=0;ve m;)u[m]=i[m++];return u}return S}(),k)},88739:function(T,r,n){"use strict";var e=n(4246),a=n(22603).some,t=e.aTypedArray,o=e.exportTypedArrayMethod;o("some",function(){function f(b){return a(t(this),b,arguments.length>1?arguments[1]:void 0)}return f}())},60415:function(T,r,n){"use strict";var e=n(74685),a=n(71138),t=n(40033),o=n(10320),f=n(90274),b=n(4246),k=n(652),S=n(19228),y=n(5026),h=n(9342),i=b.aTypedArray,c=b.exportTypedArrayMethod,m=e.Uint16Array,d=m&&a(m.prototype.sort),u=!!d&&!(t(function(){d(new m(2),null)})&&t(function(){d(new m(2),{})})),s=!!d&&!t(function(){if(y)return y<74;if(k)return k<67;if(S)return!0;if(h)return h<602;var C=new m(516),N=Array(516),v,p;for(v=0;v<516;v++)p=v%4,C[v]=515-v,N[v]=v-2*p+3;for(d(C,function(g,V){return(g/4|0)-(V/4|0)}),v=0;v<516;v++)if(C[v]!==N[v])return!0}),l=function(N){return function(v,p){return N!==void 0?+N(v,p)||0:p!==p?-1:v!==v?1:v===0&&p===0?1/v>0&&1/p<0?1:-1:v>p}};c("sort",function(){function C(N){return N!==void 0&&o(N),s?d(this,N):f(i(this),l(N))}return C}(),!s||u)},72532:function(T,r,n){"use strict";var e=n(4246),a=n(10188),t=n(13912),o=n(31082),f=e.aTypedArray,b=e.exportTypedArrayMethod;b("subarray",function(){function k(S,y){var h=f(this),i=h.length,c=t(S,i),m=o(h);return new m(h.buffer,h.byteOffset+c*h.BYTES_PER_ELEMENT,a((y===void 0?i:t(y,i))-c))}return k}())},62207:function(T,r,n){"use strict";var e=n(74685),a=n(61267),t=n(4246),o=n(40033),f=n(54602),b=e.Int8Array,k=t.aTypedArray,S=t.exportTypedArrayMethod,y=[].toLocaleString,h=!!b&&o(function(){y.call(new b(1))}),i=o(function(){return[1,2].toLocaleString()!==new b([1,2]).toLocaleString()})||!o(function(){b.prototype.toLocaleString.call([1,2])});S("toLocaleString",function(){function c(){return a(y,h?f(k(this)):k(this),f(arguments))}return c}(),i)},906:function(T,r,n){"use strict";var e=n(4246).exportTypedArrayMethod,a=n(40033),t=n(74685),o=n(67250),f=t.Uint8Array,b=f&&f.prototype||{},k=[].toString,S=o([].join);a(function(){k.call({})})&&(k=function(){function h(){return S(this)}return h}());var y=b.toString!==k;e("toString",k,y)},78824:function(T,r,n){"use strict";var e=n(80185);e("Uint16",function(a){return function(){function t(o,f,b){return a(this,o,f,b)}return t}()})},72846:function(T,r,n){"use strict";var e=n(80185);e("Uint32",function(a){return function(){function t(o,f,b){return a(this,o,f,b)}return t}()})},24575:function(T,r,n){"use strict";var e=n(80185);e("Uint8",function(a){return function(){function t(o,f,b){return a(this,o,f,b)}return t}()})},71968:function(T,r,n){"use strict";var e=n(80185);e("Uint8",function(a){return function(){function t(o,f,b){return a(this,o,f,b)}return t}()},!0)},80040:function(T,r,n){"use strict";var e=n(50730),a=n(74685),t=n(67250),o=n(30145),f=n(81969),b=n(45150),k=n(39895),S=n(77568),y=n(5419).enforce,h=n(40033),i=n(21820),c=Object,m=Array.isArray,d=c.isExtensible,u=c.isFrozen,s=c.isSealed,l=c.freeze,C=c.seal,N=!a.ActiveXObject&&"ActiveXObject"in a,v,p=function(E){return function(){function M(){return E(this,arguments.length?arguments[0]:void 0)}return M}()},g=b("WeakMap",p,k),V=g.prototype,B=t(V.set),I=function(){return e&&h(function(){var E=l([]);return B(new g,E,1),!u(E)})};if(i)if(N){v=k.getConstructor(p,"WeakMap",!0),f.enable();var L=t(V.delete),w=t(V.has),A=t(V.get);o(V,{delete:function(){function x(E){if(S(E)&&!d(E)){var M=y(this);return M.frozen||(M.frozen=new v),L(this,E)||M.frozen.delete(E)}return L(this,E)}return x}(),has:function(){function x(E){if(S(E)&&!d(E)){var M=y(this);return M.frozen||(M.frozen=new v),w(this,E)||M.frozen.has(E)}return w(this,E)}return x}(),get:function(){function x(E){if(S(E)&&!d(E)){var M=y(this);return M.frozen||(M.frozen=new v),w(this,E)?A(this,E):M.frozen.get(E)}return A(this,E)}return x}(),set:function(){function x(E,M){if(S(E)&&!d(E)){var j=y(this);j.frozen||(j.frozen=new v),w(this,E)?B(this,E,M):j.frozen.set(E,M)}else B(this,E,M);return this}return x}()})}else I()&&o(V,{set:function(){function x(E,M){var j;return m(E)&&(u(E)?j=l:s(E)&&(j=C)),B(this,E,M),j&&j(E),this}return x}()})},90846:function(T,r,n){"use strict";n(80040)},67042:function(T,r,n){"use strict";var e=n(45150),a=n(39895);e("WeakSet",function(t){return function(){function o(){return t(this,arguments.length?arguments[0]:void 0)}return o}()},a)},40348:function(T,r,n){"use strict";n(67042)},5606:function(T,r,n){"use strict";var e=n(63964),a=n(74685),t=n(60375).clear;e({global:!0,bind:!0,enumerable:!0,forced:a.clearImmediate!==t},{clearImmediate:t})},83006:function(T,r,n){"use strict";n(5606),n(27807)},25764:function(T,r,n){"use strict";var e=n(63964),a=n(74685),t=n(37713),o=n(10320),f=n(24986),b=n(40033),k=n(58310),S=b(function(){return k&&Object.getOwnPropertyDescriptor(a,"queueMicrotask").value.length!==1});e({global:!0,enumerable:!0,dontCallGetSet:!0,forced:S},{queueMicrotask:function(){function y(h){f(arguments.length,1),t(o(h))}return y}()})},27807:function(T,r,n){"use strict";var e=n(63964),a=n(74685),t=n(60375).set,o=n(78362),f=a.setImmediate?o(t,!1):t;e({global:!0,bind:!0,enumerable:!0,forced:a.setImmediate!==f},{setImmediate:f})},45569:function(T,r,n){"use strict";var e=n(63964),a=n(74685),t=n(78362),o=t(a.setInterval,!0);e({global:!0,bind:!0,forced:a.setInterval!==o},{setInterval:o})},5213:function(T,r,n){"use strict";var e=n(63964),a=n(74685),t=n(78362),o=t(a.setTimeout,!0);e({global:!0,bind:!0,forced:a.setTimeout!==o},{setTimeout:o})},69401:function(T,r,n){"use strict";n(45569),n(5213)},7435:function(T){"use strict";/**
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
1?arguments[1]:void 0,1),N=b(l);if(d)return a(i,this,N,C);var v=this.length,p=o(N),g=0;if(p+C>v)throw new S("Wrong length");for(;g