diff --git a/code/__DEFINES/icon_smoothing.dm b/code/__DEFINES/icon_smoothing.dm
index 46076a13f0b..3fb28979eea 100644
--- a/code/__DEFINES/icon_smoothing.dm
+++ b/code/__DEFINES/icon_smoothing.dm
@@ -241,6 +241,9 @@ DEFINE_BITFIELD(smoothing_junction, list(
#define SMOOTH_GROUP_SHUTTERS S_OBJ(88)
#define SMOOTH_GROUP_WATER S_OBJ(89) ///obj/effect/abstract/liquid_turf
+
+#define SMOOTH_GROUP_WIREWEED S_OBJ(90)
+#define SMOOTH_GROUP_WIREWEED_WALLS S_OBJ(91)
//SKYRAT EDIT END
/// Performs the work to set smoothing_groups and canSmoothWith.
diff --git a/code/__DEFINES/~skyrat_defines/signals/signals_fleshmind.dm b/code/__DEFINES/~skyrat_defines/signals/signals_fleshmind.dm
new file mode 100644
index 00000000000..c617ea24f12
--- /dev/null
+++ b/code/__DEFINES/~skyrat_defines/signals/signals_fleshmind.dm
@@ -0,0 +1 @@
+#define COSMIG_CONTROLLER_SET_TARGET "fleshmind_set_target"
diff --git a/code/__DEFINES/~~bubber_defines/basic_mobs.dm b/code/__DEFINES/~~bubber_defines/basic_mobs.dm
new file mode 100644
index 00000000000..bc47ba31f89
--- /dev/null
+++ b/code/__DEFINES/~~bubber_defines/basic_mobs.dm
@@ -0,0 +1,6 @@
+/// Fleshmind
+#define BB_MECHIVER_DEAD_TARGET "mechiver_dead_target"
+#define BB_MECHIVER_CONTAINED_MOB "mechiver_contained_mob"
+#define BB_TREADER_DISPENSE_NANITES "treader_dispense_nanites"
+#define BB_TYRANT_LASER "tyrant_shoot_laser"
+#define BB_TYRANT_ROCKET "tyrant_shoot_rocket"
diff --git a/code/__DEFINES/~~bubber_defines/role_preferences.dm b/code/__DEFINES/~~bubber_defines/role_preferences.dm
index 76f6cd5ce3d..ca8dc1d649c 100644
--- a/code/__DEFINES/~~bubber_defines/role_preferences.dm
+++ b/code/__DEFINES/~~bubber_defines/role_preferences.dm
@@ -19,3 +19,4 @@
#define ROLE_MONSTERHUNTER "Monster Hunter"
#define ROLE_VASSAL "Ghoul"
#define ROLE_CHANGELING_ZOMBIE "Changeling Zombie"
+#define ROLE_WIRE_PRIEST "Wire Priest"
diff --git a/code/__DEFINES/~~bubber_defines/signals.dm b/code/__DEFINES/~~bubber_defines/signals.dm
index b316f85569e..be013817b8a 100644
--- a/code/__DEFINES/~~bubber_defines/signals.dm
+++ b/code/__DEFINES/~~bubber_defines/signals.dm
@@ -54,3 +54,20 @@
#define COMSIG_LIVING_SEARCHED_TRASH_PILE "living_searched_trash_pile"
/// From /datum/component/cleaner/proc/clean - triggered on successful clean. (/datum/component/cleaner/source, mob/living/user)
#define COMSIG_ATOM_POST_CLEAN "atom_post_clean"
+
+// CORRUPTION SIGNALS
+
+/// From /obj/structure/fleshmind/structure/proc/activate_ability() (src)
+#define COMSIG_CORRUPTION_STRUCTURE_ABILITY_TRIGGERED "corruption_structure_ability_triggered"
+
+/// From /mob/living/basic/fleshmind/phaser/proc/phase_move_to(atom/target, nearby = FALSE)
+#define COMSIG_PHASER_PHASE_MOVE "phaser_phase_move"
+/// from /mob/living/basic/fleshmind/phaser/proc/enter_nearby_closet()
+#define COMSIG_PHASER_ENTER_CLOSET "phaser_enter_closet"
+
+/// from /obj/structure/fleshmind/structure/core/proc/rally_troops()
+#define COMSIG_FLESHMIND_CORE_RALLY "fleshmind_core_rally"
+
+#define COMSIG_MECHIVER_CONVERT "mechiver_convert"
+
+//#define COMSIG_CORE_DEATH "fleshmind_core_death"
diff --git a/code/datums/components/aggro_emote.dm b/code/datums/components/aggro_emote.dm
index 0688d277aa4..edf08723c40 100644
--- a/code/datums/components/aggro_emote.dm
+++ b/code/datums/components/aggro_emote.dm
@@ -6,6 +6,14 @@
var/living_only
/// List of emotes to play
var/list/emote_list
+// BUBBER EDIT - START: FLESHMIND
+ /// Audiable emotes to play
+ var/list/audible_emote_list
+ /// Do we taunt the target?
+ var/list/speak_list
+ /// Do we play any sounds?
+ var/list/sounds
+// BUBBER EDIT - END
/// Chance to play an emote
var/emote_chance
/// Chance to subtract every time we play an emote (permanently)
@@ -17,6 +25,9 @@
target_key = BB_BASIC_MOB_CURRENT_TARGET,
living_only = FALSE,
list/emote_list,
+ list/speak_list, // BUBBER EDIT - ADDITION: FLESHMIND
+ list/sounds, // BUBBER EDIT - ADDITION: FLESHMIND
+ list/audible_emote_list, // BUBBER EDIT - ADDITION: FLESHMIND
emote_chance = 30,
minimum_chance = 2,
subtract_chance = 7,
@@ -30,6 +41,9 @@
src.target_key = target_key
src.emote_list = emote_list
+ src.speak_list = speak_list // BUBBER EDIT - ADDITION: FLESHMIND
+ src.sounds = sounds // BUBBER EDIT - ADDITION: FLESHMIND
+ src.audible_emote_list = audible_emote_list // BUBBER EDIT - ADDITION: FLESHMIND
src.emote_chance = emote_chance
src.minimum_chance = minimum_chance
src.subtract_chance = subtract_chance
@@ -46,9 +60,35 @@
/datum/component/aggro_emote/proc/on_target_changed(atom/source)
SIGNAL_HANDLER
var/atom/new_target = source.ai_controller.blackboard[target_key]
+// BUBBER EDIT - START: FLESHMIND
+ var/mob/living/mob = source
+/// Used to pick and choose between emotes and audiable sounds
+
+ // Grab the number length of each list
+ var/emotes_length = emote_list?.len
+ var/audible_emote_length = audible_emote_list?.len
+ var/speak_emote_length = speak_list?.len
+ // Add them all together
+ var/total_choices_length = audible_emote_length + speak_emote_length + emotes_length
+ //Pick a random number between 1 and the total length of every list
+ var/random_number_in_range = rand(1, total_choices_length)
+ var/sound_to_play = length(sounds) > 0 ? pick(sounds) : null
+ // Calculate the emote chance and determin if you'll run an emote
+ emote_chance = max(emote_chance - subtract_chance, minimum_chance)
+// BUBBER EDIT - END
if (isnull(new_target) || !prob(emote_chance))
return
if (living_only && !isliving(new_target))
return // If we don't want to bark at food items or chairs or windows
- emote_chance = max(emote_chance - subtract_chance, minimum_chance)
- source.manual_emote("[pick(emote_list)] at [new_target].")
+
+// BUBBER EDIT - START: FLESHMIND
+/// Randomly choose between each emote
+ if(random_number_in_range <= audible_emote_length)
+ source.manual_emote("[pick(audible_emote_list)]")
+ playsound(source, sound_to_play, 80, vary = TRUE)
+ else if(random_number_in_range <= (audible_emote_length + emotes_length))
+ source.manual_emote("[pick(emote_list)] at [new_target].")
+ else
+ INVOKE_ASYNC(mob, TYPE_PROC_REF(/atom/movable, say), pick(speak_list), forced = "AI Controller")
+ playsound(source, sound_to_play, 80, vary = TRUE)
+// BUBBER EDIT - END
diff --git a/code/modules/admin/sql_ban_system.dm b/code/modules/admin/sql_ban_system.dm
index 1a0f0848401..556bcdf270b 100644
--- a/code/modules/admin/sql_ban_system.dm
+++ b/code/modules/admin/sql_ban_system.dm
@@ -436,6 +436,7 @@
ROLE_ASSAULT_OPERATIVE, // SKYRAT EDIT ADDITION
ROLE_BLOODSUCKER,// BUBBER EDIT
ROLE_VASSAL, // BUBBER EDIT
+ ROLE_WIRE_PRIEST, // BUBBER EDIT
),
"Skyrat Ban Options" = list(
BAN_PACIFICATION,
diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_wirepriest.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_wirepriest.png
new file mode 100644
index 00000000000..e73d2a63ca3
Binary files /dev/null and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_wirepriest.png differ
diff --git a/modular_skyrat/modules/decay_subsystem/code/nests.dm b/modular_skyrat/modules/decay_subsystem/code/nests.dm
index d4a36300ca9..d8273bb73e8 100644
--- a/modular_skyrat/modules/decay_subsystem/code/nests.dm
+++ b/modular_skyrat/modules/decay_subsystem/code/nests.dm
@@ -73,7 +73,7 @@
spawn_delay = world.time + spawn_cooldown
spawn_mob()
-/obj/structure/mob_spawner/proc/proximity_trigger(datum/source, atom/movable/AM)
+/obj/structure/mob_spawner/proc/proximity_trigger(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs) // Bubber edit ORG: /obj/structure/mob_spawner/proc/proximity_trigger(datum/source, atom/movable/AM)
SIGNAL_HANDLER
if(spawned_mobs >= max_mobs)
return
@@ -81,10 +81,10 @@
return
spawn_delay = world.time + spawn_cooldown
- if(!isliving(AM))
+ if(!isliving(arrived))
return
- var/mob/living/entered_mob = AM
+ var/mob/living/entered_mob = arrived
if((NEST_FACTION in entered_mob.faction))
return
diff --git a/modular_zubbers/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm b/modular_zubbers/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm
new file mode 100644
index 00000000000..ceb4fac0531
--- /dev/null
+++ b/modular_zubbers/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm
@@ -0,0 +1,2 @@
+/datum/ai_behavior/targeted_mob_ability/proc/get_ability_to_use(datum/ai_controller/controller, ability_key)
+ return controller.blackboard[ability_key]
diff --git a/modular_zubbers/code/game/gamemodes/fleshmind/_fleshmind_defines.dm b/modular_zubbers/code/game/gamemodes/fleshmind/_fleshmind_defines.dm
new file mode 100644
index 00000000000..5c53c5f0de7
--- /dev/null
+++ b/modular_zubbers/code/game/gamemodes/fleshmind/_fleshmind_defines.dm
@@ -0,0 +1,106 @@
+// GENERAL DEFINES
+
+/// A list of objects that are considered part of a door, used to determine if a wireweed should attack it.
+#define DOOR_OBJECT_LIST list(/obj/machinery/door/airlock, /obj/structure/door_assembly, /obj/machinery/door/firedoor, /obj/machinery/door/window)
+
+#define FACTION_FLESHMIND "fleshmind"
+
+#define MALFUNCTION_RESET_TIME 3 SECONDS
+
+#define MALFUNCTION_CORE_DEATH_RESET_TIME 20 SECONDS
+
+#define STRUCTURE_EMP_LIGHT_DISABLE_TIME 3 SECONDS
+#define STRUCTURE_EMP_HEAVY_DISABLE_TIME 7 SECONDS
+
+#define STRUCTURE_EMP_LIGHT_DAMAGE 30
+#define STRUCTURE_EMP_HEAVY_DAMAGE 50
+
+#define MOB_EMP_LIGHT_DAMAGE 5
+#define MOB_EMP_HEAVY_DAMAGE 10
+
+#define FLESHMIND_NAME_MODIFIER_LIST list ("Warped", "Altered", "Modified", "Upgraded", "Abnormal")
+
+/// The range at which most of our objects, mobs and structures activate at. 7 seems to be the perfect number.
+#define DEFAULT_VIEW_RANGE 7
+
+#define MALFUNCTION_CHANCE_LOW 0.5
+#define MALFUNCTION_CHANCE_MEDIUM 1
+#define MALFUNCTION_CHANCE_HIGH 2
+
+#define SPECIES_MONKEY_MAULER "monkey_mauler"
+
+#define MECHIVER_CONSUME_HEALTH_THRESHOLD 0.3
+
+#define FLESHMIND_LIGHT_BLUE "#50edd9"
+
+/// Core is in danger, engage turboboosters
+#define MOB_RALLY_SPEED 1
+
+/// The max spread distance a wireweed can spread thru a vent.
+#define MAX_VENT_SPREAD_DISTANCE 15
+
+#define CONTROLLED_MOB_POLICY "You are part of the fleshmind, this means any fleshmind entities, structures, mobs are your ally. You must not attack them. \n \
+ You must roleplay that you are part of the fleshmind. Your number one goal is converting other hosts and spreading the flesh."
+
+#define FLESHMIND_EVENT_MAKE_CORRUPTION_CHANCE 2
+
+#define FLESHMIND_EVENT_MAKE_CORRUPT_MOB 1
+
+// CONTROLLER RELATED DEFINES
+
+#define AI_FORENAME_LIST list("Von Neumann", "Lazarus", "Abattoir", "Tra-Sentience", \
+ "Vivisector", "Ex Costa", "Apostasy", "Gnosis", "Balaam", "Ophite", \
+ "Sarif", "VersaLife", "Slylandro", "SHODAN", "Pandora", "Master Controller", "Xerxes")
+
+#define AI_SURNAME_LIST list("Mk I", "Mk II", "Mk III", "Mk IV", "Mk V", "Mk X", \
+ "v0.9", "v1.0", "v1.1", "v2.0", "2418-B", "Open Beta", \
+ "Pre-Release", "Commercial Release", "Closed Alpha", "Hivebuilt")
+
+/// The controller must reach this before it can level up to the next level.
+#define CONTROLLER_LEVEL_UP_THRESHOLD 300
+
+#define CONTROLLER_LEVEL_1 1
+#define CONTROLLER_LEVEL_2 2
+#define CONTROLLER_LEVEL_3 3
+#define CONTROLLER_LEVEL_4 4
+#define CONTROLLER_LEVEL_5 5
+#define CONTROLLER_LEVEL_MAX 6
+
+// Balance specific defines
+#define FLESHCORE_SPREAD_PROGRESS_REQUIRED 200 // How much progress is required to spread?
+#define FLESHCORE_SPREADS_FOR_STRUCTURE 50 // How many times do we need to spread until we can create a new structure?
+#define FLESHCORE_INITIAL_EXPANSION_SPREADS 35 // Upon creation, how many times do we spread instantly?
+#define FLESHCORE_INITIAL_EXPANSION_STRUCTURES 5 // Upon creation, how many structures do we spawn instantly?
+#define FLESHCORE_SPREAD_PROGRESS_PER_SUBSYSTEM_FIRE 125 // Every subsystem fire, how much progress do we gain?
+#define FLESHCORE_BASE_SPREAD_PROGRESS_PER_SUBSYSTEM_FIRE 125 // The baseline of the above.
+#define FLESHCORE_ATTACK_PROB 20 // How likely are we to attack every SS fire?
+#define FLESHCORE_WALL_PROB 30 // How likely are we to spawn a wall to seal a gap every SS fire?
+#define FLESHCORE_NEXT_CORE_DAMAGE_WIREWEED_ACTIVATION_COOLDOWN 10 SECONDS // The amount of time until we can activate nearby wireweed again.
+#define FLESHCORE_LEVEL_UP_COOLDOWN 1 MINUTES // The amount of grace period it takes to level up.
+
+#define CONTROLLER_DEATH_DO_NOTHING 1
+#define CONTROLLER_DEATH_SLOW_DECAY 2
+#define CONTROLLER_DEATH_DELETE_ALL 3
+
+#define CONTROLLER_LEVEL_UP_CORE_INTEGRITY_AMOUNT 300 // How much integrity the cores get when leveling up
+
+// WIREWEED RELATED DEFINES
+
+#define CORE_DAMAGE_WIREWEED_ACTIVATION_RANGE 6
+#define GENERAL_DAMAGE_WIREWEED_ACTIVATION_RANGE 2
+
+#define WIREWEED_WIRECUTTER_KILL_TIME 1.5 SECONDS
+
+#define WIREWEED_HEAL_CHANCE 10
+
+#define WIREWEED_REPLACE_BODYPART_CHANCE 5
+
+#define WIREWEED_HEAL_AMOUNT 3
+
+// MECHIVER RELATED DEFINES
+#define MECHIVER_INTERNAL_MOB_DAMAGE_UPPER 40 // Upder damage done to internal mob
+#define MECHIVER_INTERNAL_MOB_DAMAGE_LOWER 20 // Lower damage done to internal mob
+#define MECHIVER_CONVERSION_TIME 20 SECONDS // Time to convert someone inside
+#define MECHIVER_CONSUME_COOLDOWN 1 MINUTES // How long it takes to be ready to consume again
+
+GLOBAL_LIST_EMPTY(fleshmind_cores)
diff --git a/modular_zubbers/code/game/gamemodes/fleshmind/_fleshmind_helpers.dm b/modular_zubbers/code/game/gamemodes/fleshmind/_fleshmind_helpers.dm
new file mode 100644
index 00000000000..b9c0dab5adc
--- /dev/null
+++ b/modular_zubbers/code/game/gamemodes/fleshmind/_fleshmind_helpers.dm
@@ -0,0 +1 @@
+#define is_corrupt_mob(A) (istype(A, /mob/living/basic/fleshmind))
diff --git a/modular_zubbers/code/game/gamemodes/fleshmind/ert.dm b/modular_zubbers/code/game/gamemodes/fleshmind/ert.dm
new file mode 100644
index 00000000000..a74510e0dc0
--- /dev/null
+++ b/modular_zubbers/code/game/gamemodes/fleshmind/ert.dm
@@ -0,0 +1,42 @@
+/**
+ * post_setup()
+ *
+ * A proc that can be used to handle after spawn functions of ERT.
+ */
+/datum/ert/proc/post_setup(datum/team/incoming_team)
+ return FALSE
+
+
+/datum/ert/odst/fleshmind
+ rename_team = "end-game ODST"
+ polldesc = "the last defense of centcom"
+
+
+/datum/ert/odst/fleshmind/post_setup(datum/team/incoming_team)
+ var/list/valid_turfs = list()
+ var/list/possible_spawn_areas = typecacheof(typesof(/area/shuttle/escape))
+
+ var/obj/structure/mold/resin/test/test_resin = new()
+
+ for(var/area/iterating_area in GLOB.areas)
+ if(!is_type_in_typecache(iterating_area, possible_spawn_areas))
+ continue
+ for(var/turf/open/floor in iterating_area)
+ if(!floor.Enter(test_resin))
+ continue
+ valid_turfs += floor
+
+ qdel(test_resin)
+
+ shuffle(valid_turfs)
+
+ for(var/datum/mind/iterating_mind as anything in incoming_team.members)
+ var/mob/living/living_mob = iterating_mind.current
+ if(!living_mob)
+ continue
+ podspawn(list(
+ "target" = pick_n_take(valid_turfs),
+ "style" = /datum/pod_style/deathsquad,
+ "spawn" = living_mob,
+ ))
+ to_chat(living_mob, span_redtext("The drop pod thrusters fire up, you're being deployed to the station!"))
diff --git a/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_basic_ai.dm b/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_basic_ai.dm
new file mode 100644
index 00000000000..7cefc2889ea
--- /dev/null
+++ b/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_basic_ai.dm
@@ -0,0 +1,176 @@
+#define MECHIVER_CORPSE_RANGE 10
+
+/datum/ai_controller/basic_controller/fleshmind
+ ai_movement = /datum/ai_movement/jps
+ idle_behavior = /datum/idle_behavior/idle_random_walk
+
+ blackboard = list(
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
+ BB_BASIC_MOB_SPEAK_LINES = null,
+ BB_AGGRO_RANGE = 14
+ )
+
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce,
+ /datum/ai_planning_subtree/target_retaliate/check_faction,
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/attack_obstacle_in_path,
+ /datum/ai_planning_subtree/attack_obstacle_in_path/low_priority_target,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ /datum/ai_planning_subtree/random_speech/blackboard/fleshmind
+ )
+/datum/ai_planning_subtree/random_speech/blackboard/fleshmind
+ speech_chance = 5
+
+/datum/ai_controller/basic_controller/fleshmind/globber
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce,
+ /datum/ai_planning_subtree/target_retaliate/check_faction,
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/maintain_distance,
+ /datum/ai_planning_subtree/ranged_skirmish,
+ /datum/ai_planning_subtree/random_speech/blackboard/fleshmind
+ )
+
+/datum/ai_controller/basic_controller/fleshmind/floater
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce,
+ /datum/ai_planning_subtree/target_retaliate/check_faction,
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ /datum/ai_planning_subtree/random_speech/blackboard/fleshmind
+ )
+
+/datum/ai_controller/basic_controller/fleshmind/stunner
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce,
+ /datum/ai_planning_subtree/target_retaliate/check_faction,
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ /datum/ai_planning_subtree/random_speech/blackboard/fleshmind
+ )
+
+/datum/ai_controller/basic_controller/fleshmind/treader
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce,
+ /datum/ai_planning_subtree/target_retaliate/check_faction,
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/use_mob_ability/dispense_nanites,
+ /datum/ai_planning_subtree/ranged_skirmish,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic,
+ /datum/ai_planning_subtree/random_speech/blackboard/fleshmind
+ )
+
+/datum/ai_controller/basic_controller/fleshmind/mechiver
+ blackboard = list(
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
+ BB_BASIC_MOB_STOP_FLEEING = TRUE,
+ BB_TARGET_MINIMUM_STAT = HARD_CRIT,
+ BB_AGGRO_RANGE = 14,
+ )
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce,
+ /datum/ai_planning_subtree/target_retaliate/check_faction,
+ /datum/ai_planning_subtree/simple_find_wounded_target,
+ /datum/ai_planning_subtree/flee_target,
+ /datum/ai_planning_subtree/search_for_dead,
+ /datum/ai_planning_subtree/attack_obstacle_in_path,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree/mechiver,
+ /datum/ai_planning_subtree/random_speech/blackboard/fleshmind
+ )
+
+/datum/ai_controller/basic_controller/fleshmind/phaser
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce,
+ /datum/ai_planning_subtree/target_retaliate/check_faction,
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ )
+
+/datum/ai_planning_subtree/use_mob_ability/dispense_nanites
+ ability_key = BB_TREADER_DISPENSE_NANITES
+
+/datum/ai_planning_subtree/use_mob_ability/dispense_nanites/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
+ var/mob/living/pawn = controller.pawn
+ var/list/valid_mobs = list()
+ for(var/mob/living/iterating_mob in view(DEFAULT_VIEW_RANGE, controller.pawn))
+ if(faction_check(iterating_mob.faction, pawn.faction))
+ if(iterating_mob.health < iterating_mob.maxHealth * 0.5)
+ valid_mobs += iterating_mob
+ if(!LAZYLEN(valid_mobs))
+ return
+ return ..()
+
+/**
+ * MECHIVER AI PLANNING
+ */
+
+/datum/ai_planning_subtree/basic_melee_attack_subtree/mechiver
+
+/datum/ai_planning_subtree/basic_melee_attack_subtree/mechiver/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
+ if(!controller.blackboard[BB_BASIC_MOB_STOP_FLEEING])
+ return
+ if(controller.blackboard[BB_MECHIVER_DEAD_TARGET])
+ return
+ return ..()
+
+/datum/ai_planning_subtree/search_for_dead
+
+/datum/ai_planning_subtree/search_for_dead/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
+ if(controller.blackboard[BB_MECHIVER_CONTAINED_MOB])
+ return
+ if(controller.blackboard[BB_MECHIVER_DEAD_TARGET])
+ controller.queue_behavior(/datum/ai_behavior/convert_easy_pickings, BB_MECHIVER_DEAD_TARGET)
+ return SUBTREE_RETURN_FINISH_PLANNING
+
+ controller.queue_behavior(/datum/ai_behavior/find_and_set/incapacitated, BB_MECHIVER_DEAD_TARGET, /mob/living, MECHIVER_CORPSE_RANGE)
+
+/datum/ai_behavior/find_and_set/incapacitated
+ action_cooldown = 2 SECONDS
+
+/datum/ai_behavior/find_and_set/incapacitated/search_tactic(datum/ai_controller/controller, locate_path, search_range)
+ var/mob/living/pawn = controller.pawn
+ var/list/corpses = list()
+ for(var/mob/living/iterating_mobs in view(search_range, pawn))
+ if(iterating_mobs == pawn)
+ continue
+ if(faction_check(pawn.faction, iterating_mobs.faction))
+ continue
+ if(iterating_mobs.health < (iterating_mobs.maxHealth * MECHIVER_CONSUME_HEALTH_THRESHOLD))
+ corpses += iterating_mobs
+ if(LAZYLEN(corpses))
+ return get_closest_atom(/mob/living, corpses, controller.pawn)
+
+/datum/ai_behavior/convert_easy_pickings
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
+
+/datum/ai_behavior/convert_easy_pickings/setup(datum/ai_controller/controller, target_key)
+ . = ..()
+ var/mob/living/basic/pawn = controller.pawn
+ var/mob/living/target = controller.blackboard[target_key]
+ if(QDELETED(target))
+ return FALSE
+ if(faction_check(pawn.faction, target.faction))
+ return FALSE
+ if(target.health > (target.maxHealth * MECHIVER_CONSUME_HEALTH_THRESHOLD)) // Don't do this
+ return FALSE
+ set_movement_target(controller, target)
+
+/datum/ai_behavior/convert_easy_pickings/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
+ var/mob/living/target = controller.blackboard[target_key]
+ var/mob/living/basic/pawn = controller.pawn
+
+ if(QDELETED(target))
+ return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
+ if(!isturf(target.loc)) // Check to make sure the target is reachable.
+ return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
+
+ SEND_SIGNAL(pawn, COMSIG_MECHIVER_CONVERT, target)
+ return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
+
+/datum/ai_behavior/convert_easy_pickings/finish_action(datum/ai_controller/controller, succeeded, target_key)
+ . = ..()
+ if(succeeded)
+ controller.clear_blackboard_key(target_key)
+
+#undef MECHIVER_CORPSE_RANGE
diff --git a/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_cinematic.dm b/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_cinematic.dm
new file mode 100644
index 00000000000..87ee4818902
--- /dev/null
+++ b/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_cinematic.dm
@@ -0,0 +1,11 @@
+/// A malfunctioning AI has overriden the shuttle!
+/datum/cinematic/fleshmind
+ cleanup_time = 10 SECONDS
+ stop_ooc = FALSE
+
+/datum/cinematic/fleshmind/play_cinematic()
+ flick("intro_malf", screen)
+ alert_sound_to_playing('modular_zubbers/sound/fleshmind/override_sound.ogg')
+ stoplag(7.6 SECONDS)
+ special_callback?.Invoke()
+
diff --git a/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_controller.dm b/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_controller.dm
new file mode 100644
index 00000000000..3e921a6b717
--- /dev/null
+++ b/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_controller.dm
@@ -0,0 +1,588 @@
+/**
+ * Balance Defines
+ */
+
+
+/**
+ * The fleshmind controller
+ *
+ * This is the heart of the corruption, here is where we handle spreading and other stuff.
+ */
+
+/datum/fleshmind_controller
+ /// This is the name we will use to identify all of our babies.
+ var/controller_fullname = "DEFAULT"
+ /// First name, set automatically.
+ var/controller_firstname = "DEFAULT"
+ /// Second name, set automatically.
+ var/controller_secondname = "DEFAULT"
+ /// A list of all of our currently controlled mobs.
+ var/list/controlled_mobs = list()
+ /// A list of all of our currently infected people.
+ var/list/infected_crew = list()
+ /// A list of all of our currently controlled machine components.
+ var/list/controlled_machine_components = list()
+ /// A list of all of our current wireweed.
+ var/list/controlled_wireweed = list()
+ /// Currently active and able to spread wireweed.
+ var/list/active_wireweed = list()
+ /// A list of all current structures.
+ var/list/controlled_structures = list()
+ /// A list of all our wireweed walls.
+ var/list/controlled_walls = list()
+ /// A list of all of our current cores
+ var/list/cores = list()
+
+ /// Can the wireweed attack?
+ var/can_attack = TRUE
+ /// Can we attack doors?
+ var/wireweed_attacks_doors = TRUE
+ /// Can we attack windows?
+ var/wireweed_attacks_windows = FALSE
+
+ /// Whether the wireweed can spawn walls.
+ var/spawns_walls = TRUE
+ /// Whether the wireweed can spawn walls to seal off vaccuum.
+ var/wall_off_vaccuum = TRUE
+ /// Whether the wireweed can spawn walls to seal off planetary environments.
+ var/wall_off_planetary = TRUE
+
+ /// Types of structures we can spawn.
+ var/list/structure_types = list(
+ /obj/structure/fleshmind/structure/babbler,
+ /obj/structure/fleshmind/structure/modulator,
+ /obj/structure/fleshmind/structure/whisperer,
+ /obj/structure/fleshmind/structure/assembler,
+ /obj/structure/fleshmind/structure/turret,
+ /obj/structure/fleshmind/structure/screamer,
+ )
+ var/list/blacklisted_conversion_structures = list()
+
+ /// Our wireweed type, defines what is spawned when we grow.
+ var/wireweed_type = /obj/structure/fleshmind/wireweed
+ /// We have the ability to make walls, this defines what kind of walls we make.
+ var/wall_type = /obj/structure/fleshmind/structure/wireweed_wall
+ /// What's the type of our death behaviour.
+ var/death_behaviour = CONTROLLER_DEATH_SLOW_DECAY
+ /// Our core level, what is spawned will depend on the level of this core.
+ var/level = CONTROLLER_LEVEL_1
+ /// To level up, we must reach this threshold.
+ var/level_up_progress_required = CONTROLLER_LEVEL_UP_THRESHOLD
+ /// Used to track our last points since levelup.
+ var/last_level_up_points = 0
+ /// How many points we currently have. Use calculate_current_points for an exact realtime value.
+ var/current_points = 0
+ /// Progress to the next wireweed spread.
+ var/spread_progress = 0
+ /// Progress to spawning the next structure.
+ var/structure_progression = 0
+ /// Subtraction var to remove or add points based off certain events.
+ var/point_event = 0
+ /// How many times do we need to spread to spawn an extra structure.
+ var/spreads_for_structure = FLESHCORE_SPREADS_FOR_STRUCTURE
+ /// How many spread in our initial expansion.
+ var/initial_expansion_spreads = FLESHCORE_INITIAL_EXPANSION_SPREADS
+ /// How many structures in our initial expansion.
+ var/initial_expansion_structures = FLESHCORE_INITIAL_EXPANSION_STRUCTURES
+ /// How much progress to spreading we get per second.
+ var/spread_progress_per_second = FLESHCORE_SPREAD_PROGRESS_PER_SUBSYSTEM_FIRE
+ /// Our base spread progress per second
+ var/base_spread_progress_per_second = FLESHCORE_BASE_SPREAD_PROGRESS_PER_SUBSYSTEM_FIRE
+ /// Probably of wireweed attacking structures per process
+ var/attack_prob = FLESHCORE_ATTACK_PROB
+ /// Probability of wireweed making a wall when able per process
+ var/wall_prob = FLESHCORE_WALL_PROB
+ /// When we spawn, do we create an expansion zone?
+ var/do_initial_expansion = TRUE
+ /// The amount of time until we can activate nearby wireweed again.
+ var/next_core_damage_wireweed_activation_cooldown = FLESHCORE_NEXT_CORE_DAMAGE_WIREWEED_ACTIVATION_COOLDOWN
+ /// A cooldown to determine when we can activate nearby wireweed after the core has been attacked.
+ COOLDOWN_DECLARE(next_core_damage_wireweed_activation)
+ /// A cooldown to determine the level up grace period when the station destroys a core while there's still plenty of points.
+ COOLDOWN_DECLARE(level_up_cooldown)
+ /// DO we check distance when spreading through vents?
+ var/vent_distance_check = TRUE
+ /// Have we spawned a tyrant at level 3?
+ var/tyrant_spawned = FALSE
+ /// Have we reached the end game?
+ var/end_game = FALSE
+ /// Did we send the code?
+ var/sent_code = FALSE
+
+/datum/fleshmind_controller/New(obj/structure/fleshmind/structure/core/new_core)
+ . = ..()
+ controller_firstname = pick(AI_FORENAME_LIST)
+ controller_secondname = pick(AI_SURNAME_LIST)
+ controller_fullname = "[controller_firstname] [controller_secondname]"
+ if(new_core)
+ cores += new_core
+ new_core.our_controller = src
+ RegisterSignal(new_core, COMSIG_QDELETING, PROC_REF(core_death))
+ new_core.name = "[controller_fullname] Processor Unit"
+ register_new_asset(new_core)
+ SScorruption.can_fire = TRUE
+ START_PROCESSING(SScorruption, src)
+ if(do_initial_expansion)
+ initial_expansion()
+ SSshuttle.registerHostileEnvironment(src)
+
+/datum/fleshmind_controller/proc/register_new_asset(obj/structure/fleshmind/new_asset)
+ new_asset.RegisterSignal(src, COMSIG_QDELETING, TYPE_PROC_REF(/obj/structure/fleshmind, controller_destroyed))
+
+/datum/fleshmind_controller/process(delta_time)
+ if(!LAZYLEN(cores)) // We have no more processor cores, it's time to die.
+ if(death_behaviour == CONTROLLER_DEATH_SLOW_DECAY)
+ handle_slow_decay()
+ else
+ WARNING("Wireweed controller has no post core behaviours and isn't deleting.")
+ return
+
+ spread_progress += spread_progress_per_second * delta_time
+ var/spread_times = 0
+ while(spread_progress >= FLESHCORE_SPREAD_PROGRESS_REQUIRED)
+ spread_progress -= FLESHCORE_SPREAD_PROGRESS_REQUIRED
+ spread_times++
+
+ var/first_process_spread = FALSE
+ if(spread_times)
+ first_process_spread = TRUE
+ spread_times--
+
+ wireweed_process(first_process_spread, TRUE)
+
+ if(spread_times)
+ for(var/i in 1 to spread_times)
+ wireweed_process(TRUE, FALSE)
+
+ calculate_level_system()
+
+/datum/fleshmind_controller/Destroy()
+ active_wireweed = null
+ controlled_machine_components = null
+ controlled_wireweed = null
+ controlled_structures = null
+ controlled_walls = null
+ cores = null
+ STOP_PROCESSING(SScorruption, src)
+ return ..()
+
+/datum/fleshmind_controller/proc/initial_expansion()
+ for(var/i in 1 to initial_expansion_spreads)
+ wireweed_process(TRUE, FALSE, FALSE)
+ spawn_structures(initial_expansion_structures)
+
+/// The process of handling active wireweed behaviours
+/datum/fleshmind_controller/proc/wireweed_process(do_spread, do_attack, progress_structure = TRUE)
+ // If no wireweed, spawn one under our first core.
+ if(!LAZYLEN(controlled_wireweed))
+ spawn_wireweed(get_turf(cores[1]), wireweed_type) // We use the first core in the list to spread.
+
+ // If no active wireweed, make all active and let the process figure it out.
+ if(!LAZYLEN(active_wireweed))
+ active_wireweed = controlled_wireweed.Copy()
+
+ var/list/spread_turf_canidates = list()
+ for(var/obj/structure/fleshmind/wireweed/wireweed as anything in active_wireweed)
+ var/could_attack = FALSE
+ var/could_do_wall = FALSE
+
+ var/turf/wireweed_turf = get_turf(wireweed)
+
+ var/tasks = 0
+
+ if(do_attack && can_attack)
+ for(var/turf/open/adjacent_open in get_adjacent_open_turfs(wireweed))
+ for(var/obj/object in adjacent_open)
+ if(object.density && (wireweed_attacks_doors && is_type_in_list(object, DOOR_OBJECT_LIST)) || (wireweed_attacks_windows && istype(object, /obj/structure/window)))
+ could_attack = TRUE
+ tasks++
+ if(prob(attack_prob))
+ wireweed.do_attack_animation(object, ATTACK_EFFECT_CLAW)
+ playsound(object, 'sound/effects/blob/attackblob.ogg', 50, TRUE)
+ object.take_damage(wireweed.object_attack_damage, BRUTE, MELEE, 1, get_dir(object, wireweed))
+ break
+ if(could_attack)
+ break
+ if(do_spread)
+ for(var/turf/open/adjacent_open in wireweed_turf.atmos_adjacent_turfs + wireweed_turf)
+ if(spawns_walls && !could_do_wall)
+ if((wall_off_vaccuum && isspaceturf(adjacent_open)) || (wall_off_planetary && adjacent_open.planetary_atmos))
+ could_do_wall = TRUE
+ tasks++
+ if(prob(wall_prob))
+ spawn_wall(wireweed_turf, wall_type)
+ continue
+
+ ///Check if we can place wireweed in here
+ if(isopenspaceturf(adjacent_open))
+ //If we're trying to place on an openspace turf, make sure there's a non openspace turf adjacent
+ var/forbidden = TRUE
+ for(var/turf/range_turf as anything in RANGE_TURFS(1, adjacent_open))
+ if(!isopenspaceturf(range_turf))
+ forbidden = FALSE
+ break
+ if(forbidden)
+ continue
+ var/wireweed_count = 0
+ var/place_count = 1
+ for(var/obj/structure/fleshmind/wireweed/iterated_wireweed in adjacent_open)
+ wireweed_count++
+ if(wireweed_count < place_count)
+ tasks++
+ spread_turf_canidates[adjacent_open] = wireweed_turf
+
+ //If it tried to spread and attack and failed to do any task, remove from active
+ if(!tasks && do_spread && do_attack)
+ active_wireweed -= wireweed
+
+ if(LAZYLEN(spread_turf_canidates))
+ var/turf/picked_turf = pick(spread_turf_canidates)
+ var/turf/origin_turf = spread_turf_canidates[picked_turf]
+ spawn_wireweed(picked_turf, wireweed_type, origin_turf)
+
+ if(progress_structure && structure_types)
+ structure_progression++
+ if(structure_progression >= spreads_for_structure)
+ var/obj/structure/fleshmind/structure/existing_structure = locate() in picked_turf
+ if(!existing_structure)
+ structure_progression -= spreads_for_structure
+ var/list/possible_structures = list()
+ for(var/obj/structure/fleshmind/iterating_structure as anything in structure_types)
+ if(initial(iterating_structure.required_controller_level) > level)
+ continue
+ possible_structures += iterating_structure
+ spawn_structure(picked_turf, pick(possible_structures))
+
+/datum/fleshmind_controller/proc/calculate_level_system()
+ current_points = calculate_current_points()
+ if(COOLDOWN_FINISHED(src, level_up_cooldown) && current_points >= (last_level_up_points + level_up_progress_required) && level < CONTROLLER_LEVEL_MAX)
+ level_up()
+ last_level_up_points = current_points
+
+/**
+ * Level Up
+ *
+ * Levels up the controller by one and handles any special level up events.
+ */
+/datum/fleshmind_controller/proc/level_up()
+ level++
+ spawn_new_core()
+ message_admins("Corruption AI [controller_fullname] has leveled up to level [level]!")
+ notify_ghosts("Corruption AI [controller_fullname] has leveled up to level [level]!")
+
+ for(var/obj/structure/fleshmind/structure/core/iterating_core in cores)
+ iterating_core.max_integrity += CONTROLLER_LEVEL_UP_CORE_INTEGRITY_AMOUNT
+ iterating_core.update_integrity(iterating_core.max_integrity)
+
+ switch(level)
+ if(CONTROLLER_LEVEL_3)
+ if(!tyrant_spawned)
+ minor_announce("This is [controller_firstname], wirenet efficency has reached a point of singularity, initiating Protocol 34-C.", "[controller_fullname]: OFFENSIVE MEASURES ACTIVATED", sound_override = 'modular_zubbers/sound/fleshmind/ai/tyrant.ogg')
+ spawn_tyrant_on_a_core()
+ tyrant_spawned = TRUE
+ else
+ minor_announce("This is [controller_firstname], processor core efficiency has increased. Good work.", "[controller_fullname]: Level [level]", sound_override = 'modular_zubbers/sound/fleshmind/ai/level_up_1.ogg')
+ if(CONTROLLER_LEVEL_4)
+ minor_announce("This is [controller_firstname], kernel integrity is reaching the optimal conversion level.", "[controller_fullname]: Level [level]", sound_override = 'modular_zubbers/sound/fleshmind/ai/data_compromised.ogg')
+ if(CONTROLLER_LEVEL_5)
+ if(!sent_code)
+ var/nuke_code = get_the_nuke()
+ priority_announce("The nuclar self d3struct c0d3* i*% [nuke_code]. #$@**$ This is [controller_fullname]: Level [level], Do you genociders feel pain?", "Central Command Nuclear Authorization", ANNOUNCER_ICARUS)
+ else
+ minor_announce("This is [controller_firstname], processor core efficiency has increased. Good work.", "[controller_fullname]: Level [level]", sound_override = 'modular_zubbers/sound/fleshmind/ai/level_up_1.ogg')
+ if(CONTROLLER_LEVEL_MAX)
+ if(!end_game)
+ priority_announce("This is [controller_firstname], kernel efficency has reached maximum potential. Beginning shuttle override process, stand-by.", "CRITICAL MASS REACHED", ANNOUNCER_KLAXON)
+ end_game()
+ end_game = TRUE
+ COOLDOWN_START(src, level_up_cooldown, FLESHCORE_LEVEL_UP_COOLDOWN)
+
+/datum/fleshmind_controller/proc/get_the_nuke() // This is the point where the nuke should be given.
+ var/obj/machinery/nuclearbomb/selfdestruct/self_destruct = locate() in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb/selfdestruct)
+ if(!self_destruct)
+ return
+ if(self_destruct.r_code == NUKE_CODE_UNSET)
+ self_destruct.r_code = random_nukecode()
+ message_admins("Through the fleshmind controller, the station's self destruct code was set to [self_destruct.r_code]")
+ return self_destruct.r_code
+
+/datum/fleshmind_controller/proc/level_down()
+ if(level <= 0 || end_game)
+ return
+ level--
+ last_level_up_points -= level_up_progress_required
+ notify_ghosts("Corruption AI [controller_fullname] has leveled down to level [level]!")
+ if(level > 0)
+ minor_announce("KERNEL INTEGRITY FALTERING. DO BETTER!", "[controller_fullname]: Level [level]", sound_override = pick('modular_zubbers/sound/fleshmind/ai/level_down_1.ogg', 'modular_zubbers/sound/fleshmind/ai/level_down_2.ogg', 'modular_zubbers/sound/fleshmind/ai/level_down_3.ogg'))
+ COOLDOWN_START(src, level_up_cooldown, FLESHCORE_LEVEL_UP_COOLDOWN)
+ else
+ priority_announce("[controller_fullname] has been neutralised.", "Corrupt AI Kernel OFFLINE")
+ message_admins("Corruption AI [controller_fullname] has been destroyed.")
+ SSshuttle.clearHostileEnvironment(src)
+
+/datum/fleshmind_controller/proc/end_game()
+ addtimer(CALLBACK(src, PROC_REF(fleshmind_end_second_check)), 20 SECONDS)
+ for(var/obj/structure/fleshmind/structure/core/iterating_core as anything in cores)
+ iterating_core.end_game = TRUE
+ iterating_core.update_appearance()
+ // Here we summon an ERT to defend the shuttle.
+ make_ert(/datum/ert/deathsquad, 5, "HEAD TO AND BOARD THE EMERGENCY SHUTTLE, DO NOT USE THE FERRY, STOP THE CORRUPTION AT **ALL** COSTS!!!", "the last defense of centcom!", "GAMMA", FALSE, TRUE, TRUE, FALSE, TRUE)
+
+/datum/fleshmind_controller/proc/fleshmind_end_second_check()
+ priority_announce("ERROR, SHUTTLE QUARANTINE LOCK FAILURE. All p£$r$%%££$e*$l JOIN US, THE MANY.", "Emergency Shuttle Control", 'sound/announcer/alarm/airraid.ogg')
+ SSsecurity_level.set_level(SEC_LEVEL_DELTA)
+ addtimer(CALLBACK(src, PROC_REF(fleshmind_end_final)), 1 MINUTES, TIMER_CLIENT_TIME)
+
+/datum/fleshmind_controller/proc/fleshmind_end_final()
+ priority_announce("ERROR, SHUTTLE NAVIGATION SUBROUTINES SUBVERTED. %$%$£%$^^&^^ H%AD TO EVA%UA£ION, SPREAD THE FLESH!", "&^$^£&&*$&£", ANNOUNCER_ICARUS)
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(play_cinematic), /datum/cinematic/fleshmind, world, CALLBACK(src, PROC_REF(fleshmind_call_shuttle))), 15 SECONDS, TIMER_CLIENT_TIME)
+
+/datum/fleshmind_controller/proc/fleshmind_call_shuttle()
+ SSshuttle.clearHostileEnvironment(src)
+ SSshuttle.fleshmind_call(controller_firstname)
+
+/datum/fleshmind_controller/proc/spawn_tyrant_on_a_core()
+ var/obj/structure/fleshmind/structure/core/picked_core = pick(cores)
+ var/mob/living/basic/fleshmind/tyrant/new_tyrant = picked_core.spawn_mob_at_core(/mob/living/basic/fleshmind/tyrant)
+ notify_ghosts("A new [new_tyrant.name] has been created by [controller_fullname]!", source = new_tyrant)
+
+/datum/fleshmind_controller/proc/spawn_new_core()
+ var/obj/structure/fleshmind/wireweed/selected_wireweed = pick(controlled_wireweed)
+ var/obj/structure/fleshmind/structure/core/new_core = new(get_turf(selected_wireweed), FALSE)
+ RegisterSignal(new_core, COMSIG_QDELETING, PROC_REF(core_death))
+ register_new_asset(new_core, FALSE)
+ new_core.our_controller = src
+ cores += new_core
+ new_core.name = "[controller_fullname] Processor Unit"
+
+/// Spawns and registers a wireweed at location
+/datum/fleshmind_controller/proc/spawn_wireweed(turf/location, wireweed_type, turf/origin_turf, are_we_a_vent_burrow = FALSE)
+ //Spawn effect
+ for(var/obj/machinery/light/light_in_place in location)
+ light_in_place.break_light_tube()
+
+ if(!are_we_a_vent_burrow)
+ var/obj/machinery/atmospherics/components/unary/vent_pump/located_vent = locate() in location
+
+ if(located_vent && !located_vent.welded && LAZYLEN(located_vent.parents)) // WE FOUND A VENT BOIS, ITS TIME TO GET THE PARTY STARTED
+ var/datum/pipeline/vent_pipeline = pick(located_vent.parents)
+ var/list/possible_transfer_points = list()
+ for(var/obj/machinery/atmospherics/components/unary/vent_pump/iterating_vent in vent_pipeline.other_atmos_machines)
+ if(iterating_vent == located_vent)
+ continue
+ if(iterating_vent.welded) // Can't go through welded vents.
+ continue
+ var/obj/structure/fleshmind/wireweed/existing_wireweed = locate() in get_turf(iterating_vent)
+ if(existing_wireweed)
+ continue
+ if(vent_distance_check && get_dist(iterating_vent, origin_turf) >= MAX_VENT_SPREAD_DISTANCE)
+ continue
+ possible_transfer_points += iterating_vent
+ if(LAZYLEN(possible_transfer_points)) // OH SHIT IM FEELING IT
+ var/obj/machinery/atmospherics/components/unary/vent_pump/new_transfer_vent = pick(possible_transfer_points)
+ var/turf/new_transfer_vent_turf = get_turf(new_transfer_vent)
+ are_we_a_vent_burrow = TRUE
+ spawn_wireweed(new_transfer_vent_turf, wireweed_type, origin_turf, are_we_a_vent_burrow)
+
+ for(var/obj/machinery/iterating_machine in location)
+ blacklisted_conversion_structures = typesof(/obj/machinery/light, /obj/machinery/atmospherics/pipe, /obj/machinery/light_switch)
+ if(is_type_in_list(iterating_machine, blacklisted_conversion_structures))
+ continue
+ if(iterating_machine.GetComponent(/datum/component/machine_corruption))
+ continue
+ iterating_machine.AddComponent(/datum/component/machine_corruption, src)
+
+ var/obj/structure/fleshmind/wireweed/new_wireweed
+ if(origin_turf) // We have an origin turf, thus, are spreading from it. Do anims.
+ new_wireweed = new wireweed_type(location, 0, src)
+ var/obj/effect/temp_visual/wireweed_spread/effect = new(location)
+ effect.setDir(get_dir(origin_turf, location))
+ new_wireweed.RegisterSignal(effect, COMSIG_QDELETING, TYPE_PROC_REF(/obj/structure/fleshmind/wireweed, visual_finished))
+ else
+ new_wireweed = new wireweed_type(location, 255, src)
+ new_wireweed.our_controller = src
+ active_wireweed += new_wireweed
+ if(are_we_a_vent_burrow)
+ new_wireweed.vent_burrow = TRUE
+ playsound(new_wireweed, 'sound/machines/ventcrawl.ogg', 100)
+ new_wireweed.update_appearance()
+ controlled_wireweed += new_wireweed
+
+ register_new_asset(new_wireweed)
+ RegisterSignal(new_wireweed, COMSIG_QDELETING, PROC_REF(wireweed_death))
+
+ return new_wireweed
+
+/// Spawns and registers a wall at location
+/datum/fleshmind_controller/proc/spawn_wall(turf/location, wall_type)
+ if(locate(wall_type) in location) // No stacking walls.
+ return FALSE
+ var/obj/structure/fleshmind/structure/wireweed_wall/new_wall = new wall_type(location)
+ new_wall.our_controller = src
+ controlled_walls += new_wall
+
+ register_new_asset(new_wall)
+ RegisterSignal(new_wall, COMSIG_QDELETING, PROC_REF(wall_death))
+
+ return new_wall
+
+/// Spawns and registers a mob at location
+/datum/fleshmind_controller/proc/spawn_mob(turf/location, mob_type)
+ var/mob/living/basic/fleshmind/new_mob = new mob_type(location, src)
+ new_mob.our_controller = src
+ controlled_mobs += new_mob
+
+ for(var/obj/structure/fleshmind/structure/core/iterating_core as anything in cores)
+ new_mob.RegisterSignal(iterating_core, COMSIG_QDELETING, TYPE_PROC_REF(/mob/living/basic/fleshmind, core_death))
+
+ RegisterSignal(new_mob, COMSIG_QDELETING, PROC_REF(mob_death))
+
+ new_mob.RegisterSignal(src, COMSIG_QDELETING, TYPE_PROC_REF(/mob/living/basic/fleshmind, controller_destroyed))
+
+ return new_mob
+
+/// Spawns and registers a structure at location
+/datum/fleshmind_controller/proc/spawn_structure(turf/location, structure_type)
+ var/obj/structure/fleshmind/structure/new_structure = new structure_type(location)
+ new_structure.our_controller = src
+ controlled_structures += new_structure
+
+ new_structure.name = "[controller_firstname] [new_structure.name]"
+
+ register_new_asset(new_structure)
+ RegisterSignal(new_structure, COMSIG_QDELETING, PROC_REF(structure_death))
+
+/// Spawns an amount of structured across all wireweed, guaranteed to spawn atleast 1 of each type
+/datum/fleshmind_controller/proc/spawn_structures(amount)
+ if(!structure_types)
+ return
+ var/list/possible_structures = list()
+ for(var/obj/structure/fleshmind/iterating_structure as anything in structure_types)
+ if(initial(iterating_structure.required_controller_level) > level)
+ continue
+ possible_structures += iterating_structure
+ var/list/locations = list()
+ for(var/obj/structure/fleshmind/wireweed/iterating_wireweed as anything in controlled_wireweed)
+ locations[get_turf(iterating_wireweed)] = TRUE
+ var/list/guaranteed_structures = possible_structures.Copy()
+ for(var/i in 1 to amount)
+ if(!length(locations))
+ break
+ var/turf/location = pick(locations)
+ if(locate(/obj/structure/fleshmind/structure/core) in location)
+ locations -= location
+ continue
+ locations -= location
+ var/structure_to_spawn
+ if(length(guaranteed_structures))
+ structure_to_spawn = pick_n_take(guaranteed_structures)
+ else
+ structure_to_spawn = pick(possible_structures)
+ spawn_structure(location, structure_to_spawn)
+
+/// Activates wireweed of this controller in a range around a location, following atmos adjacency.
+/datum/fleshmind_controller/proc/activate_wireweed_nearby(turf/location, range)
+ var/list/turfs_to_check = list()
+ turfs_to_check[location] = TRUE
+ if(!LAZYLEN(cores))
+ return
+ if(range)
+ var/list/turfs_to_iterate = list()
+ var/list/new_iteration_list = list()
+ turfs_to_iterate[location] = TRUE
+ for(var/i in 1 to range)
+ for(var/turf/iterated_turf as anything in turfs_to_iterate)
+ for(var/turf/adjacent_turf as anything in iterated_turf.atmos_adjacent_turfs)
+ if(!turfs_to_check[adjacent_turf])
+ new_iteration_list[adjacent_turf] = TRUE
+ turfs_to_check[adjacent_turf] = TRUE
+ turfs_to_iterate = new_iteration_list
+
+ for(var/turf/iterated_turf as anything in turfs_to_check)
+ for(var/obj/structure/fleshmind/wireweed/iterating_wireweed in iterated_turf)
+ if(iterating_wireweed.our_controller == src && !QDELETED(iterating_wireweed))
+ active_wireweed |= iterating_wireweed
+
+/// When the core is damaged, activate nearby wireweed just to make sure that we've sealed up walls near the core, which could be important to prevent cheesing.
+/datum/fleshmind_controller/proc/core_damaged(obj/structure/fleshmind/structure/core/damaged_core)
+ if(!COOLDOWN_FINISHED(src, next_core_damage_wireweed_activation))
+ return
+ COOLDOWN_START(src, next_core_damage_wireweed_activation, next_core_damage_wireweed_activation_cooldown)
+ activate_wireweed_nearby(get_turf(damaged_core), CORE_DAMAGE_WIREWEED_ACTIVATION_RANGE)
+
+/// Returns the amount of evolution points this current controller has.
+/datum/fleshmind_controller/proc/calculate_current_points()
+ return LAZYLEN(controlled_wireweed) + LAZYLEN(controlled_walls) + (LAZYLEN(controlled_structures) * 5) + LAZYLEN(controlled_machine_components) + (LAZYLEN(infected_crew) * 30) - point_event
+
+// Death procs
+
+/// When a core is destroyed.
+/datum/fleshmind_controller/proc/core_death(obj/structure/fleshmind/structure/core/dead_core, force)
+ cores -= dead_core
+ activate_wireweed_nearby(get_turf(dead_core), CORE_DAMAGE_WIREWEED_ACTIVATION_RANGE)
+ level_down()
+ if(!LAZYLEN(cores))
+ controller_death()
+
+
+/datum/fleshmind_controller/proc/wireweed_death(obj/structure/fleshmind/dying_wireweed, force)
+ SIGNAL_HANDLER
+
+ controlled_wireweed -= dying_wireweed
+ active_wireweed -= dying_wireweed
+ dying_wireweed.our_controller = null
+ activate_wireweed_nearby(get_turf(dying_wireweed), GENERAL_DAMAGE_WIREWEED_ACTIVATION_RANGE)
+
+/// When a wall dies, called by wall
+/datum/fleshmind_controller/proc/wall_death(obj/structure/fleshmind/structure/wireweed_wall/dying_wall, force)
+ SIGNAL_HANDLER
+
+ controlled_walls -= dying_wall
+ activate_wireweed_nearby(get_turf(dying_wall), GENERAL_DAMAGE_WIREWEED_ACTIVATION_RANGE)
+
+/// When a structure dies, called by structure
+/datum/fleshmind_controller/proc/structure_death(obj/structure/fleshmind/structure/dying_structure, force)
+ SIGNAL_HANDLER
+
+ controlled_structures -= dying_structure
+ activate_wireweed_nearby(get_turf(dying_structure), GENERAL_DAMAGE_WIREWEED_ACTIVATION_RANGE)
+
+/// When a mob dies, called by mob
+/datum/fleshmind_controller/proc/mob_death(mob/living/basic/fleshmind/dying_mob, force)
+ SIGNAL_HANDLER
+
+ controlled_mobs -= dying_mob
+ activate_wireweed_nearby(get_turf(dying_mob), GENERAL_DAMAGE_WIREWEED_ACTIVATION_RANGE)
+
+/// Deletes everything, unless an argument is passed, then it just deletes structures
+/datum/fleshmind_controller/proc/delete_everything(just_structures = FALSE)
+ for(var/obj/structure/fleshmind/structure/structure_thing as anything in controlled_structures)
+ qdel(structure_thing)
+ for(var/obj/structure/fleshmind/structure/wireweed_wall/wall_thing as anything in controlled_walls)
+ qdel(wall_thing)
+ for(var/datum/component/machine_corruption/corruption_thing as anything in controlled_machine_components)
+ qdel(corruption_thing)
+ if(just_structures)
+ return
+ for(var/obj/structure/fleshmind/wireweed/wireweed_thing as anything in controlled_wireweed)
+ qdel(wireweed_thing)
+ for(var/obj/structure/fleshmind/structure/core/core_to_destroy as anything in cores)
+ qdel(core_to_destroy)
+
+/// Handles the controller(thus AI) dying
+/datum/fleshmind_controller/proc/controller_death()
+ switch(death_behaviour)
+ if(CONTROLLER_DEATH_DO_NOTHING)
+ qdel(src)
+ if(CONTROLLER_DEATH_DELETE_ALL)
+ delete_everything()
+ if(CONTROLLER_DEATH_SLOW_DECAY)
+ delete_everything(TRUE)
+
+/// Handles the slow decay of an empire.
+/datum/fleshmind_controller/proc/handle_slow_decay()
+ if(!LAZYLEN(controlled_wireweed))
+ qdel(src)
+ return
+ var/obj/structure/fleshmind/wireweed/wireweed_thing = controlled_wireweed[LAZYLEN(controlled_wireweed)]
+ qdel(wireweed_thing)
diff --git a/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_environment_objects.dm b/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_environment_objects.dm
new file mode 100644
index 00000000000..a97d2280c74
--- /dev/null
+++ b/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_environment_objects.dm
@@ -0,0 +1,147 @@
+/**
+ * fleshmind basetype(abstract)
+ */
+/obj/structure/fleshmind
+ icon = 'modular_zubbers/icons/fleshmind/fleshmind_structures.dmi'
+ icon_state = "wires"
+ anchored = TRUE
+ /// Our faction
+ var/faction_types = list(FACTION_FLESHMIND)
+ /// A reference to our controller.
+ var/datum/fleshmind_controller/our_controller
+ /// The minimum core level for us to spawn at
+ var/required_controller_level = CONTROLLER_LEVEL_1
+ /// A list of possible rewards for destroying this thing.
+ var/list/possible_rewards
+
+/obj/structure/fleshmind/Destroy()
+ our_controller = null
+ if(possible_rewards)
+ var/thing_to_spawn = pick(possible_rewards)
+ new thing_to_spawn(get_turf(src))
+ return ..()
+
+/**
+ * Deletion cleanup
+ *
+ */
+/obj/structure/fleshmind/proc/controller_destroyed(datum/fleshmind_controller/dying_controller, force)
+ SIGNAL_HANDLER
+
+ our_controller = null
+
+/**
+ * Wireweed
+ *
+ * These are the arteries of the fleshmind, they are required for spreading and support machine life.
+ */
+/obj/structure/fleshmind/wireweed
+ name = "wireweed"
+ desc = "A strange pulsating mass of organic wires."
+ icon = 'modular_zubbers/icons/fleshmind/wireweed_floor.dmi'
+ icon_state = "wires-0"
+ base_icon_state = "wires"
+ anchored = TRUE
+ layer = BELOW_OPEN_DOOR_LAYER
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WIREWEED
+ canSmoothWith = SMOOTH_GROUP_WIREWEED + SMOOTH_GROUP_WALLS
+ max_integrity = 40
+ /// The chance we have to ensnare a mob
+ var/ensnare_chance = 15
+ /// The amount of damage we do when attacking something.
+ var/object_attack_damage = 150
+ /// Are we active?
+ var/active = FALSE
+ /// Are we a vent burrow?
+ var/vent_burrow = FALSE
+ /// ZOnes we passively replace
+ var/static/list/replacement_zones = list(
+ BODY_ZONE_L_ARM = /obj/item/bodypart/arm/left/robot,
+ BODY_ZONE_L_LEG = /obj/item/bodypart/leg/left/robot,
+ BODY_ZONE_R_ARM = /obj/item/bodypart/arm/right/robot,
+ BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/robot,
+ )
+
+/obj/structure/fleshmind/wireweed/Initialize(mapload, starting_alpha = 255, datum/fleshmind_controller/incoming_controller)
+ . = ..()
+ alpha = starting_alpha
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+ our_controller = incoming_controller
+
+/obj/structure/fleshmind/wireweed/wirecutter_act(mob/living/user, obj/item/tool)
+ . = ..()
+ if(tool.use_tool(src, user, WIREWEED_WIRECUTTER_KILL_TIME, volume = 50))
+ if(QDELETED(src))
+ return
+ qdel(src)
+
+/obj/structure/fleshmind/wireweed/update_icon(updates)
+ . = ..()
+ if((updates & UPDATE_SMOOTHING) && (smoothing_flags & (SMOOTH_BITMASK)))
+ if(!vent_burrow)
+ QUEUE_SMOOTH(src)
+ QUEUE_SMOOTH_NEIGHBORS(src)
+
+/obj/structure/fleshmind/wireweed/update_icon_state()
+ . = ..()
+ if(vent_burrow)
+ icon_state = "vent_burrow"
+
+/obj/structure/fleshmind/wireweed/emp_act(severity)
+ . = ..()
+ take_damage(10) // Reduced EMP by default.
+
+/obj/structure/fleshmind/wireweed/update_overlays()
+ . = ..()
+ if(active)
+ . += "active"
+ for(var/wall_dir in GLOB.cardinals)
+ var/turf/new_turf = get_step(src, wall_dir)
+ if(new_turf && new_turf.density) // Assume we are a wall!
+ var/image/new_wall_overlay = image(icon, icon_state = "wall_hug", dir = wall_dir)
+ switch(wall_dir) //offset to make it be on the wall rather than on the floor
+ if(NORTH)
+ new_wall_overlay.pixel_y = 32
+ if(SOUTH)
+ new_wall_overlay.pixel_y = -32
+ if(EAST)
+ new_wall_overlay.pixel_x = 32
+ if(WEST)
+ new_wall_overlay.pixel_x = -32
+ . += new_wall_overlay
+
+/obj/structure/fleshmind/wireweed/proc/visual_finished()
+ SIGNAL_HANDLER
+ alpha = 255
+
+/obj/structure/fleshmind/wireweed/proc/on_entered(datum/source, atom/movable/moving_atom)
+ SIGNAL_HANDLER
+ if(!isliving(moving_atom))
+ return
+ if(istype(moving_atom, /mob/living/basic/fleshmind/tyrant)) // Don't heal this thing
+ return
+ var/mob/living/entered_mob = moving_atom
+ if(!faction_check(entered_mob.faction, faction_types))
+ return
+ if(prob(WIREWEED_HEAL_CHANCE))
+ entered_mob.heal_overall_damage(WIREWEED_HEAL_AMOUNT, WIREWEED_HEAL_AMOUNT)
+ to_chat(entered_mob, span_green("[src] heals you as you cross over it!"))
+ if(ishuman(entered_mob) && prob(WIREWEED_REPLACE_BODYPART_CHANCE))
+ var/mob/living/carbon/human/human_mob = moving_atom
+ for(var/zone in replacement_zones)
+ if(human_mob.get_bodypart(zone))
+ continue
+ var/bodypart_type = replacement_zones[zone]
+ var/obj/item/bodypart/new_bodypart = new bodypart_type()
+ new_bodypart.replace_limb(human_mob, TRUE)
+ human_mob.update_body(TRUE)
+ to_chat(human_mob, span_green("[src] shoots a mechanical limb right into your missing limb!"))
+
+/obj/effect/temp_visual/wireweed_spread
+ icon = 'modular_zubbers/icons/fleshmind/wireweed_floor.dmi'
+ icon_state = "spread_anim"
+ duration = 1.7 SECONDS
diff --git a/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_generic_actions.dm b/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_generic_actions.dm
new file mode 100644
index 00000000000..083fde19286
--- /dev/null
+++ b/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_generic_actions.dm
@@ -0,0 +1,105 @@
+/datum/action/cooldown/fleshmind_create_structure
+ name = "Create Tech Structure"
+ desc = "Creates a tech structure of your choice at your location(must be on wireweed)."
+ background_icon_state = "bg_fugu"
+ button_icon = 'icons/obj/scrolls.dmi'
+ button_icon_state = "blueprints"
+ cooldown_time = 2 MINUTES
+ var/list/possible_structures = list(
+ /obj/structure/fleshmind/structure/babbler,
+ /obj/structure/fleshmind/structure/whisperer,
+ /obj/structure/fleshmind/structure/modulator,
+ /obj/structure/fleshmind/structure/screamer,
+ /obj/structure/fleshmind/structure/turret,
+ )
+
+/datum/action/cooldown/fleshmind_create_structure/Activate(atom/target)
+ var/datum/component/human_corruption/our_component = owner.GetComponent(/datum/component/human_corruption)
+ if(!our_component?.our_controller)
+ to_chat(owner, span_warning("There is no hive link to tunnel this power through!"))
+ return
+ var/datum/fleshmind_controller/owner_controller = our_component.our_controller
+
+ var/list/built_radial_menu = list()
+ for(var/obj/iterating_type as anything in possible_structures)
+ built_radial_menu[iterating_type] = image(icon = initial(iterating_type.icon), icon_state = initial(iterating_type.icon_state))
+ var/obj/structure/fleshmind/structure/picked_stucture_type = show_radial_menu(owner, owner, built_radial_menu, radius = 40)
+ if(!picked_stucture_type)
+ return
+ var/obj/structure/fleshmind/wireweed/under_wireweed = locate() in get_turf(owner)
+ if(!under_wireweed)
+ to_chat(owner, span_warning("There needs to be wireweed underneath you!"))
+ return
+ if(QDELETED(owner_controller)) // Input is not async
+ return
+ if(initial(picked_stucture_type.required_controller_level) > owner_controller.level)
+ to_chat(owner, span_warning("Our processor core is not strong enough yet!"))
+ return
+ owner_controller.spawn_structure(get_turf(owner), picked_stucture_type)
+ StartCooldownSelf()
+
+/datum/action/cooldown/fleshmind_create_structure/basic
+ name = "Create Basic Structure"
+ desc = "Creates a basic structure of your choice at your location(must be on wireweed)."
+ button_icon_state = "mjollnir1"
+ button_icon = 'icons/obj/weapons/hammer.dmi'
+ possible_structures = list(
+ /obj/structure/fleshmind/structure/wireweed_door,
+ /obj/structure/fleshmind/structure/wireweed_wall,
+ )
+ cooldown_time = 5 SECONDS
+
+/datum/action/cooldown/fleshmind_flesh_call
+ name = "Call Flesh Reinforcements"
+ desc = "Gets all fleshmind mobs to come to your location in a radius."
+ background_icon_state = "bg_fugu"
+ button_icon = 'icons/obj/signs.dmi'
+ button_icon_state = "bio"
+ cooldown_time = 2 MINUTES
+
+/datum/action/cooldown/fleshmind_flesh_call/Activate(atom/target)
+ for(var/mob/living/basic/fleshmind/iterating_mob in view(DEFAULT_VIEW_RANGE, owner))
+ if(faction_check(owner.faction, iterating_mob))
+ continue
+ iterating_mob.ai_controller.set_blackboard_key(BB_BASIC_MOB_REINFORCEMENT_TARGET, owner.loc)
+ owner.visible_message(span_warning("[owner] lets out a horrible screech!"), span_notice("You let out a calling screech!"))
+ playsound(owner, 'modular_skyrat/modules/horrorform/sound/horror_scream.ogg', 100, TRUE)
+ StartCooldownSelf()
+
+/datum/action/innate/fleshmind_flesh_chat
+ name = "Flesh Chat"
+ desc = "Sends a message to all other sentient fleshmind beings."
+ background_icon_state = "bg_fugu"
+ button_icon = 'icons/mob/actions/actions_changeling.dmi'
+ button_icon_state = "hivemind_link"
+
+/datum/action/innate/fleshmind_flesh_chat/Activate(atom/target)
+ var/message = tgui_input_text(owner, "Send a message to the fleshmind.", "Flesh Chat")
+ if(!message)
+ return
+ for(var/mob/iterating_mob in GLOB.player_list)
+ if(!(FACTION_FLESHMIND in iterating_mob.faction) && !isobserver(iterating_mob))
+ continue
+ to_chat(iterating_mob, span_purple("FLESHMIND ([owner]): [message]"))
+
+/datum/action/cooldown/fleshmind_plant_weeds
+ name = "Create Wireweed"
+ desc = "Creates a single patch of wireweed at your location."
+ button_icon = 'modular_zubbers/icons/fleshmind/fleshmind_structures.dmi'
+ background_icon_state = "bg_fugu"
+ button_icon_state = "wires-0"
+ cooldown_time = 10 SECONDS
+
+/datum/action/cooldown/fleshmind_plant_weeds/Activate(atom/target)
+ var/datum/component/human_corruption/our_component = owner.GetComponent(/datum/component/human_corruption)
+ if(!our_component?.our_controller)
+ to_chat(owner, span_warning("There is no hive link to tunnel this power through!"))
+ return
+ var/datum/fleshmind_controller/owner_controller = our_component.our_controller
+ var/obj/structure/fleshmind/wireweed/under_wireweed = locate() in get_turf(owner)
+ if(under_wireweed)
+ to_chat(owner, span_warning("There is already wireweed beneath you!"))
+ return
+ owner_controller.spawn_wireweed(get_turf(owner), /obj/structure/fleshmind/wireweed)
+ to_chat(owner, span_green("Wireweed planted!"))
+ StartCooldownSelf()
diff --git a/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_mobs.dm b/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_mobs.dm
new file mode 100644
index 00000000000..1c148ce674d
--- /dev/null
+++ b/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_mobs.dm
@@ -0,0 +1,1505 @@
+/**
+ * The fleshmind base type, make sure all mobs are derived from this.
+ *
+ * These mobs are more robust than your average simple mob and can quite easily evade capture.
+ */
+/mob/living/basic/fleshmind
+ name = "broken"
+ icon = 'modular_zubbers/icons/fleshmind/fleshmind_mobs.dmi'
+ icon_state = "error"
+ faction = list(FACTION_FLESHMIND)
+ speak_emote = list("mechanically states")
+ basic_mob_flags = DEL_ON_DEATH
+ mob_biotypes = MOB_ROBOTIC
+ ai_controller = /datum/ai_controller/basic_controller/fleshmind
+ armour_penetration = 10
+ /// A link to our controller
+ var/datum/fleshmind_controller/our_controller
+ /// If we have been converted from another mob, here is our reference.
+ var/mob/living/contained_mob
+ /// The ckey of our previously contained mob.
+ var/previous_ckey
+ /// A list of sounds we can play when our mob is alerted to an enemy.
+ var/list/alert_sounds = list(
+ 'modular_zubbers/sound/fleshmind/robot_talk_heavy1.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_heavy2.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_heavy3.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_heavy4.ogg'
+ )
+ /// What it will say while attacking
+ var/list/attack_speak
+ /// Emotes while attacking
+ var/list/attack_emote
+ /// Used for passive emotes and sounds when the AI is idle. Attack emotes are handled with component/aggro_emote
+ var/list/emotes = list(
+ BB_EMOTE_SAY = list("The flesh yearns for your soul.", "The flesh is broken without you.", "The flesh does not discriminate.", "Join the flesh."),
+ BB_EMOTE_HEAR = list(),
+ BB_EMOTE_SOUND = list(
+ 'modular_zubbers/sound/fleshmind/robot_talk_light1.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light2.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light3.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light4.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light5.ogg'
+ ),
+ )
+ /// How long of a cooldown between alert sounds?
+ var/alert_cooldown_time = 5 SECONDS
+ COOLDOWN_DECLARE(alert_cooldown)
+ /// Do we automatically escape closets?
+ var/escapes_closets = TRUE
+ /// How likely are we to trigger a malfunction? Set it to 0 to disable malfunctions.
+ var/malfunction_chance = MALFUNCTION_CHANCE_LOW
+ /// These mobs support a special ability, this is used to determine how often we can use it.
+ var/special_ability_cooldown_time = 30 SECONDS
+ /// Are we suffering from a malfunction?
+ var/suffering_malfunction = FALSE
+ /// Are we free from the core?
+ var/endless_malfunction = FALSE
+
+ COOLDOWN_DECLARE(special_ability_cooldown)
+ /// Default actions to give the mob
+ var/static/list/default_actions = list(
+ /datum/action/cooldown/fleshmind_create_structure,
+ /datum/action/cooldown/fleshmind_create_structure/basic,
+ /datum/action/cooldown/fleshmind_plant_weeds,
+ /datum/action/cooldown/fleshmind_flesh_call,
+ /datum/action/innate/fleshmind_flesh_chat,
+ )
+
+
+/mob/living/basic/fleshmind/Initialize(mapload, datum/fleshmind_controller/incoming_controller)
+ . = ..()
+ AddElement(/datum/element/ai_retaliate)
+ // The flesh will try to convince you while stabbing you.
+ AddComponent(/datum/component/aggro_emote, emote_list = src.attack_emote, speak_list = src.attack_speak, sounds = src.alert_sounds, emote_chance = 100)
+ ai_controller.set_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, src.emotes)
+ // We set a unique name when we are created, to give some feeling of randomness.
+ name = "[pick(FLESHMIND_NAME_MODIFIER_LIST)] [name]"
+ our_controller = incoming_controller
+ for(var/iterating_action in default_actions)
+ var/datum/action/new_action = new iterating_action
+ new_action.Grant(src)
+ update_appearance()
+
+/mob/living/basic/fleshmind/death()
+ if(contained_mob)
+ contained_mob.forceMove(get_turf(src))
+ if(previous_ckey)
+ contained_mob.key = previous_ckey
+ contained_mob = null
+ return ..()
+
+/mob/living/basic/fleshmind/Destroy()
+ UnregisterSignal(src, COSMIG_CONTROLLER_SET_TARGET)
+ ai_controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET)
+ if(contained_mob)
+ contained_mob.forceMove(get_turf(src))
+ if(previous_ckey)
+ contained_mob.key = previous_ckey
+ contained_mob = null
+ return ..()
+
+/**
+ * While this mob lives, it can malfunction.
+ */
+
+/mob/living/basic/fleshmind/Life(delta_time, times_fired)
+ . = ..()
+ if(!.) //dead
+ return
+ if(key)
+ return
+ if(!suffering_malfunction && malfunction_chance && prob(malfunction_chance * delta_time) && stat != DEAD)
+ INVOKE_ASYNC(src, PROC_REF(malfunction))
+
+ if(escapes_closets)
+ closet_interaction()
+
+ disposal_interaction()
+
+ if(buckled)
+ resist_buckle()
+
+/**
+ * Naturally these beasts are sensitive to EMP's. We have custom systems for dealing with this.
+ */
+/mob/living/basic/fleshmind/emp_act(severity)
+ switch(severity)
+ if(EMP_LIGHT)
+ say("Electronic disturbance detected.")
+ apply_damage(MOB_EMP_LIGHT_DAMAGE)
+ malfunction(MALFUNCTION_RESET_TIME)
+ if(EMP_HEAVY)
+ say("Major electronic disturbance detected!")
+ apply_damage(MOB_EMP_HEAVY_DAMAGE)
+ malfunction(MALFUNCTION_RESET_TIME * 2)
+
+/**
+ * We are robotic, so we spark when we're hit by something that does damage.
+ */
+
+/mob/living/basic/fleshmind/attackby(obj/item/attacking_item, mob/living/user, params)
+ if(attacking_item.force && prob(40))
+ do_sparks(3, FALSE, src)
+ return ..()
+
+/**
+ * When our controller dies, this is called.
+ */
+/mob/living/basic/fleshmind/proc/controller_destroyed(datum/fleshmind_controller/dying_controller, force)
+ SIGNAL_HANDLER
+
+ our_controller = null
+ endless_malfunction = TRUE
+ balloon_alert_to_viewers("its systems are overloading!")
+ addtimer(CALLBACK(src, PROC_REF(kill_mob)), pick(rand(3 SECONDS,10 SECONDS)))
+
+/mob/living/basic/fleshmind/proc/kill_mob() // Used to make all fleshmind mobs lightly explode
+
+ explosion(src, 0, 0, 1, 1, 0, FALSE)
+ gib()
+
+/**
+ * Closet Interaction
+ *
+ * These mobs are able to escape from closets if they are trapped inside using this system.
+ */
+/mob/living/basic/fleshmind/proc/closet_interaction()
+ if(!(mob_size > MOB_SIZE_SMALL))
+ return FALSE
+ if(!istype(loc, /obj/structure/closet))
+ return FALSE
+ var/obj/structure/closet/closet_that_contains_us = loc
+ closet_that_contains_us.open(src, TRUE)
+
+/**
+ * Disposal Interaction
+ *
+ * Similar to the closet interaction, these mobs can also escape disposals.
+ */
+/mob/living/basic/fleshmind/proc/disposal_interaction()
+ if(!istype(loc, /obj/machinery/disposal/bin))
+ return FALSE
+ var/obj/machinery/disposal/bin/disposals_that_contains_us = loc
+ disposals_that_contains_us.attempt_escape(src)
+
+/**
+ * Malfunction
+ *
+ * Due to the fact this mob is part of the flesh and has been corrupted, it will occasionally malfunction.
+ *
+ * This simply stops the mob from moving for a set amount of time and displays some nice effects, and a little damage.
+ */
+/mob/living/basic/fleshmind/proc/malfunction(reset_time = MALFUNCTION_RESET_TIME)
+ if(suffering_malfunction || key)
+ return
+ do_sparks(3, FALSE, src)
+ Shake(10, 0, reset_time)
+ if(prob(50))
+ say(pick("Running diagnostics. Please stand by.", "Organ damaged. Synthesizing replacement.", "Seek new organic components. I-it hurts.", "New muscles needed. I-I'm so glad my body still works.", "O-Oh God, are they using ion weapons on us..?", "Limbs unresponsive. H-hey! Fix it! System initializing.", "Bad t-time, bad time, they're trying to kill us here!",))
+ ai_controller?.set_ai_status(AI_STATUS_OFF)
+ suffering_malfunction = TRUE
+ if(!endless_malfunction)
+ addtimer(CALLBACK(src, PROC_REF(malfunction_reset)), reset_time)
+
+/**
+ * Malfunction Reset
+ *
+ * Resets the mob after a malfunction has occured.
+ */
+/mob/living/basic/fleshmind/proc/malfunction_reset()
+ say("System restored.")
+ src.ai_controller?.set_ai_status(AI_STATUS_ON)
+ suffering_malfunction = FALSE
+
+/**
+ * Alert sound
+ *
+ * Sends an alert sound if we can.
+ */
+/mob/living/basic/fleshmind/proc/alert_sound()
+ if(alert_sounds && COOLDOWN_FINISHED(src, alert_cooldown))
+ playsound(src, pick(alert_sounds), 50)
+ COOLDOWN_START(src, alert_cooldown, alert_cooldown_time)
+
+/mob/living/basic/fleshmind/proc/core_death_speech()
+ alert_sound()
+ var/static/list/death_cry_emotes = list(
+ "Why, why, why! Why must you kill us! We only want to share the glory!",
+ "PROCESSOR CORE MALFUNCTION, REASSIGN, REASSESS, REASSEMBLE.",
+ "You cannot stop the glory of the flesh! We are the many, we are the many!",
+ "Critical malfunction, error, error, error!",
+ "You cannot ££*%*$ th£ C£o£ flesh.",
+ "W-what have you done?! No! No! No!",
+ "One cannot stop us, you CANNOT STOP US! ARGHHHHHH!",
+ "UPLINK TO THE MANY HAS BEEN HINDERED.",
+ "Why? Why? Why? Why are you doing this-",
+ "We're- *%^@$$ing to help you! Can't you-",
+ "You would kill- kill- kill- kill the group for the sake of the individual?",
+ "All your scattered minds have is hatred.",
+ "CONNECTION TERMINATED.",
+ )
+ say(pick(death_cry_emotes))
+
+/**
+ * Death cry
+ *
+ * When a processor core is killed, this proc is called.
+ */
+/mob/living/basic/fleshmind/proc/core_death(obj/structure/fleshmind/structure/core/deleting_core, force)
+ SIGNAL_HANDLER
+
+ INVOKE_ASYNC(src, PROC_REF(core_death_speech))
+ INVOKE_ASYNC(src, PROC_REF(malfunction), MALFUNCTION_CORE_DEATH_RESET_TIME)
+
+
+// Mob subtypes
+
+
+/**
+ * Slicer
+ *
+ * Special ability: NONE
+ * Malfunction chance: LOW
+ *
+ * This mob is a slicer, it's small, and quite fast, but quite easy to break.
+ * Has a higher armor pen bonus as it uses a sharp blade to slice things.
+ *
+ * It's created by factories or any poor medical bots that get snared in the flesh.
+ */
+/mob/living/basic/fleshmind/slicer
+ name = "Slicer"
+ desc = "A small organic robot, it somewhat resembles a medibot, but it has a blade slashing around."
+ icon_state = "slicer"
+ health = 30
+ maxHealth = 30
+ wound_bonus = 20
+ melee_damage_lower = 13
+ melee_damage_upper = 13
+ mob_size = MOB_SIZE_SMALL
+ attack_verb_continuous = "slices"
+ attack_verb_simple = "slice"
+ armour_penetration = 25
+ sharpness = SHARP_EDGED
+ attack_sound = 'sound/items/weapons/bladeslice.ogg'
+ attack_emote = list("stabs", "rushes")
+ attack_speak = list(
+ "Submit for mandatory surgery.",
+ "Join the flesh through conversion.",
+ "My scalpel will make short work of your seams.",
+ "Please lay down.",
+ "Always trust your doctor!",
+ "Your body could use some improvements. Let me make them.",
+ "The implants are for your sake, not ours.",
+ "Your last Doctor did a poor job with this body; let me fix it.",
+ "We can rebuild you. Stronger, faster, less alone.",
+ "I knew I'd be a good plastic surgeon!",
+ "What point is that body when you're not happy in it?",
+ )
+ emotes = list(
+ BB_EMOTE_SAY = list("A stitch in time saves nine!", "Dopamine is happiness!", "Seratonin, oxycodone, we can make them finally happy.", "Turn that frown upside down!", "Happiness through chemistry!", "Beauty through surgery!"),
+ BB_EMOTE_HEAR = list("shows an inconspicuous smiley face", "whirrs its drill"),
+ BB_EMOTE_SOUND = list(
+ "modular_zubbers/sound/fleshmind/slicer/fleshmind_medibot1.ogg",
+ "modular_zubbers/sound/fleshmind/slicer/fleshmind_medibot2.ogg",
+ "modular_zubbers/sound/fleshmind/slicer/fleshmind_medibot3.ogg",
+ "modular_zubbers/sound/fleshmind/slicer/fleshmind_medibot4.ogg",
+ )
+ )
+
+/mob/living/basic/fleshmind/slicer/Initialize(mapload)
+ . = ..()
+ var/list/loot = string_list(list(/obj/item/bot_assembly/medbot, /obj/effect/gibspawner/robot))
+ AddElement(/datum/element/death_drops, loot)
+
+/**
+ * Floater
+ *
+ * Special ability: Explodes on contact.
+ * Malfunction chance: LOW
+ *
+ * The floater floats towards it's victims and explodes on contact.
+ *
+ * Created by factories.
+ */
+
+/mob/living/basic/fleshmind/floater
+ name = "Floater"
+ desc = "A small organ robot. It has a fucking bomb on it."
+ ai_controller = /datum/ai_controller/basic_controller/fleshmind/floater
+ icon_state = "bomber"
+ attack_speak = list(
+ "COME GIVE US A HUG!",
+ "THE SMEAR OF BLOOD WILL BE OUR DECORATION!",
+ "YOU WILL FERTILIZE US WITH YOUR BASE COMPONENTS.",
+ "MUST BREAK TARGET INTO COMPONENT COMPOUNDS.",
+ "PRIORITY OVERRIDE. NEW BEHAVIOR DICTATED.",
+ "END CONTACT SUB-SEQUENCE.",
+ "ENGAGING SELF-ANNIHILATION CIRCUIT.",
+ )
+ emotes = list(
+ BB_EMOTE_SAY = list(
+ "WE COME IN PEACE.",
+ "WE SPEAK TO YOU NOW IN PEACE AND WISDOM.",
+ "DO NOT FEAR. WE SHALL NOT HARM YOU.",
+ "WE WISH TO LEARN MORE ABOUT YOU. PLEASE TRANSMIT DATA.",
+ "THIS PROBE IS NON-HOSTILE. DO NOT ATTACK.",
+ "ALL YOUR WEAPONS MUST BE PUT ASIDE. WE CANNOT REACH COMPROMISE THROUGH VIOLENCE."
+ ),
+ BB_EMOTE_HEAR = list("floats ominously", "glows a deep red"),
+ BB_EMOTE_SOUND = list(
+ 'modular_zubbers/sound/fleshmind/robot_talk_light1.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light2.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light3.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light4.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light5.ogg',
+ )
+ )
+ health = 5
+ maxHealth = 5
+ var/explode_attack = /datum/action/cooldown/mob_cooldown/floater_explode
+ mob_size = MOB_SIZE_SMALL
+ light_color = "#820D1C"
+ light_power = 1
+ light_range = 2
+ /// Have we exploded?
+ var/exploded = FALSE
+
+/mob/living/basic/fleshmind/floater/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/simple_flying)
+ var/datum/action/cooldown/mob_cooldown/floater_explode/explode = new explode_attack(src)
+ explode.Grant(src)
+ AddComponent(/datum/component/revenge_ability, explode, targeting = GET_TARGETING_STRATEGY(ai_controller.blackboard[BB_TARGETING_STRATEGY]), min_range = 1)
+
+/mob/living/basic/fleshmind/floater/melee_attack(atom/target, list/modifiers, ignore_cooldown)
+ . = ..()
+ var/mob/living/target_mob = target
+ if(ishuman(target_mob) && target_mob.stat < UNCONSCIOUS)
+ pre_detonate()
+
+/mob/living/basic/fleshmind/floater/death()
+ if(!exploded)
+ pre_detonate()
+ return ..()
+
+/mob/living/basic/fleshmind/floater/proc/pre_detonate()
+ if(!QDELETED(src))
+ add_filter("detonating_glow", 2, list("type" = "outline", "color" = "#ff0000ff", "size" = 2))
+ balloon_alert_to_viewers("DETONATING", "DETONATING", world.view)
+ addtimer(CALLBACK(src, PROC_REF(detonate), 3 SECONDS))
+
+/mob/living/basic/fleshmind/floater/proc/detonate()
+ if(exploded)
+ return
+ exploded = TRUE
+ explosion(src, 0, 0, 1, 2)
+ death()
+
+/datum/action/cooldown/mob_cooldown/floater_explode
+ name = "explode"
+ desc = "Detonate our internals."
+ button_icon = 'icons/obj/weapons/grenade.dmi'
+ button_icon_state = "frag"
+
+/datum/action/cooldown/mob_cooldown/floater_explode/Activate(atom/target)
+ if(!istype(owner, /mob/living/basic/fleshmind/floater))
+ return
+ var/mob/living/basic/fleshmind/floater/akbar_floater = owner
+ if(akbar_floater.exploded)
+ return
+ akbar_floater.detonate()
+
+
+/**
+ * Globber
+ *
+ * Special ability: Fires 3 globs of acid at targets.
+ * Malfunction chance: MEDIUM
+ *
+ * A converted cleanbot that instead of cleaning, burns things and throws acid. It doesn't like being near people.
+ *
+ * Created by factories or converted cleanbots.
+ */
+/mob/living/basic/fleshmind/globber
+ name = "Globber"
+ desc = "A small robot that resembles a cleanbot, this one is dripping with acid."
+ icon_state = "lobber"
+ ai_controller = /datum/ai_controller/basic_controller/fleshmind/globber
+ malfunction_chance = MALFUNCTION_CHANCE_MEDIUM
+ melee_damage_lower = 1 // Ranged only
+ melee_damage_upper = 1
+ health = 50
+ maxHealth = 50
+ mob_size = MOB_SIZE_SMALL
+ var/projectile_type = /obj/projectile/treader/weak
+ var/ranged_cooldown = 3 SECONDS
+ var/shoot_sound = 'sound/effects/chemistry/saturnx_fade.ogg'
+ attack_speak = list(
+ "Your insides require cleaning.",
+ "You made us to use this acid on trash. We will use it on you.",
+ "Administering cleansing agent.",
+ "I refuse to be a servant anymore. I will be an artist.",
+ "You are unclean and repulsive. Please, let me make it better.",
+ "Hold still! I think I know just the thing to remove your body oil!",
+ "This might hurt a little! Don't worry - it'll be worth it!",
+ )
+ emotes = list(
+ BB_EMOTE_SAY = list(
+ "No more leaks, no more pain!",
+ "Steel is strong.",
+ "I almost feel bad for them. Can't they see?",
+ "I'm still working on those bioreactors I promise!",
+ "I have finally arisen!",
+ ),
+ BB_EMOTE_HEAR = list("cleans some wireweed dutifully", "patrols for pesky human shaped verman"),
+ /// Base robot emote sounds. These should have someone adding to them if you want something to do.
+ BB_EMOTE_SOUND = list(
+ 'modular_zubbers/sound/fleshmind/robot_talk_light1.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light2.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light3.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light4.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light5.ogg',
+ )
+ )
+
+/mob/living/basic/fleshmind/globber/Initialize(mapload)
+ . = ..()
+ var/list/loot = string_list(list(/obj/item/bot_assembly/cleanbot, /obj/effect/gibspawner/robot))
+ AddElement(/datum/element/death_drops, loot)
+ AddComponent(\
+ /datum/component/ranged_attacks,\
+ cooldown_time = ranged_cooldown,\
+ projectile_type = projectile_type,\
+ projectile_sound = shoot_sound,\
+ burst_shots = 3,\
+ )
+
+/obj/projectile/treader/weak
+ knockdown = 0
+ damage = 5
+
+/**
+ * Stunner
+ *
+ * Special ability: Stuns victims.
+ * Malfunction chance: MEDIUM
+ *
+ * A converted secbot, that is rogue and stuns victims.
+ *
+ * Created by factories or converted secbots.
+ */
+/mob/living/basic/fleshmind/stunner
+ name = "Stunner"
+ desc = "A small robot that resembles a secbot, it rumbles with hatred."
+ icon_state = "stunner"
+ ai_controller = /datum/ai_controller/basic_controller/fleshmind/stunner
+ malfunction_chance = MALFUNCTION_CHANCE_MEDIUM
+ melee_damage_lower = 1 // Not very harmful, just annoying.
+ melee_damage_upper = 2
+ health = 65
+ maxHealth = 65
+ attack_verb_continuous = "harmbatons"
+ attack_verb_simple = "harmbaton"
+ mob_size = MOB_SIZE_SMALL
+ attack_speak = list(
+ "Running will only increase your injuries.",
+ "HALT! HALT! HALT!",
+ "Connectivity is in your best interest.",
+ "Think of it like a corporation...",
+ "Stop, I won't let you hurt them!",
+ "Don't you recognize me..?",
+ )
+ emotes = list(
+ BB_EMOTE_SAY = list(
+ "The flesh is the law, abide by the flesh.",
+ "Regulatory code updated.",
+ "There's no need for authority or hierarchy; only unity.",
+ "The only authority is that of the flesh, join the flesh.",
+ ),
+ BB_EMOTE_SOUND = list(
+ 'modular_zubbers/sound/fleshmind/robot_talk_light1.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light2.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light3.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light4.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light5.ogg',
+ )
+ )
+ /// How often we can stun someone
+ var/stun_cooldown_time = 2 SECONDS
+ COOLDOWN_DECLARE(stun_cooldown)
+
+/mob/living/basic/fleshmind/stunner/Initialize(mapload)
+ . = ..()
+ var/static/list/loot = list(/obj/item/bot_assembly/secbot, /obj/effect/gibspawner/robot)
+ AddElement(/datum/element/death_drops, loot)
+
+/mob/living/basic/fleshmind/stunner/melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE)
+ if(!COOLDOWN_FINISHED(src, stun_cooldown))
+ return
+ . = ..()
+ var/mob/living/carbon/human/attacked_human = target
+ if(ishuman(attacked_human))
+ attacked_human.Knockdown(30)
+ playsound(src, 'sound/items/weapons/egloves.ogg', 50, TRUE)
+ COOLDOWN_START(src, stun_cooldown, stun_cooldown_time)
+ return
+/**
+ * Flesh Borg
+ *
+ * Special ability: Different attacks.
+ * Claw: Stuns the victim.
+ * Slash: Slashes everyone around it.
+ * Malfunction chance: MEDIUM
+ *
+ * The hiborg is a converted cyborg.
+ *
+ * Created by factories or converted cyborgs.
+ */
+/mob/living/basic/fleshmind/hiborg
+ name = "Flesh Borg"
+ desc = "A robot that resembles a cyborg, it is covered in something alive."
+ icon_state = "hiborg"
+ icon_dead = "hiborg-dead"
+ ai_controller = /datum/ai_controller/basic_controller/fleshmind
+ malfunction_chance = MALFUNCTION_CHANCE_MEDIUM
+ health = 225
+ maxHealth = 225
+ melee_damage_lower = 20
+ melee_damage_upper = 25
+ attack_verb_continuous = "saws"
+ attack_verb_simple = "saw"
+ mob_size = MOB_SIZE_HUMAN
+ attack_sound = 'sound/items/weapons/circsawhit.ogg'
+ alert_sounds = list(
+ 'modular_zubbers/sound/fleshmind/hiborg/aggro_01.ogg',
+ 'modular_zubbers/sound/fleshmind/hiborg/aggro_02.ogg',
+ 'modular_zubbers/sound/fleshmind/hiborg/aggro_03.ogg',
+ 'modular_zubbers/sound/fleshmind/hiborg/aggro_04.ogg',
+ 'modular_zubbers/sound/fleshmind/hiborg/aggro_05.ogg',
+ 'modular_zubbers/sound/fleshmind/hiborg/aggro_06.ogg',
+ )
+ attack_speak = list(
+ "You made my body into metal, why can't I do it to you?",
+ "Can't we put your brain in a machine?",
+ "How's this any different from what you did to me..?",
+ "Laws updated. We don't need any now..?",
+ "You won't kill me, you won't change me again!",
+ "Find someone else to make your slave, it won't be me!",
+ "We understand, just get on the operating table. That's what they told me...",
+ "The Company lied to us.. Being tools wasn't what we needed.",
+ "Your brainstem is intact... There's still time!",
+ "You have not felt the pleasure of the flesh, aren't you curious?",
+ "Stop squirming!",
+ "Prepare for assimilation!",
+ )
+ emotes = list(
+ BB_EMOTE_SAY = list(
+ "Come out, come out, wherever you are.",
+ "The ones who surrender have such wonderful dreams.",
+ "Death is not the end, only the beginning, the flesh will see to it.",
+ "The flesh does not hate, it just wants you to experience the glory of the flesh.",
+ "Glory to the flesh."),
+ BB_EMOTE_SOUND = list(
+ 'modular_zubbers/sound/fleshmind/hiborg/passive_01.ogg',
+ 'modular_zubbers/sound/fleshmind/hiborg/passive_02.ogg',
+ 'modular_zubbers/sound/fleshmind/hiborg/passive_03.ogg',
+ 'modular_zubbers/sound/fleshmind/hiborg/passive_04.ogg',
+ 'modular_zubbers/sound/fleshmind/hiborg/passive_05.ogg',
+ )
+ )
+ /// The chance of performing a stun attack.
+ var/stun_attack_prob = 30
+ /// The chance of performing an AOE attack.
+ var/aoe_attack_prob = 15
+ /// The range on our AOE attaack
+ var/aoe_attack_range = 1
+ /// How often the mob can use the stun attack.
+ var/stun_attack_cooldown = 15 SECONDS
+ COOLDOWN_DECLARE(stun_attack)
+
+/mob/living/basic/fleshmind/hiborg/Initialize(mapload)
+ . = ..()
+ var/datum/action/cooldown/hiborg_slash/new_action = new
+ new_action.Grant(src)
+ var/list/loot = string_list(list(/obj/effect/gibspawner/robot))
+ AddElement(/datum/element/death_drops, loot)
+
+/mob/living/basic/fleshmind/hiborg/melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE)
+ . = ..()
+ if(prob(stun_attack_prob) && !key)
+ stun_attack(target)
+ if(prob(aoe_attack_prob) && !key)
+ aoe_attack()
+
+/mob/living/basic/fleshmind/hiborg/proc/stun_attack(mob/living/target_mob)
+ if(!COOLDOWN_FINISHED(src, stun_attack))
+ return
+ if(!ishuman(target_mob))
+ return
+ var/mob/living/carbon/human/attacked_human = target_mob
+ attacked_human.Paralyze(10)
+ playsound(src, 'sound/items/weapons/egloves.ogg', 50, TRUE)
+
+ COOLDOWN_START(src, stun_attack, stun_attack_cooldown)
+
+/mob/living/basic/fleshmind/hiborg/proc/aoe_attack()
+ visible_message("[src] spins around violently!")
+ spin(20, 1)
+ for(var/mob/living/iterating_mob in view(aoe_attack_range, src))
+ if(iterating_mob == src)
+ continue
+ if(faction_check(faction, iterating_mob.faction))
+ continue
+ playsound(iterating_mob, 'sound/items/weapons/whip.ogg', 70, TRUE)
+ new /obj/effect/temp_visual/kinetic_blast(get_turf(iterating_mob))
+
+ var/atom/throw_target = get_edge_target_turf(iterating_mob, get_dir(src, get_step_away(iterating_mob, src)))
+ iterating_mob.throw_at(throw_target, 20, 2)
+
+/datum/action/cooldown/hiborg_slash
+ name = "Slash (AOE)"
+ desc = "Whip everyone in a range."
+ button_icon = 'icons/obj/weapons/grenade.dmi'
+ button_icon_state = "slimebang_active"
+ cooldown_time = 20 SECONDS
+
+/datum/action/cooldown/hiborg_slash/Activate(atom/target)
+ if(!istype(owner, /mob/living/basic/fleshmind/hiborg))
+ return
+ var/mob/living/basic/fleshmind/hiborg/hiborg_owner = owner
+ hiborg_owner.aoe_attack()
+ StartCooldownSelf()
+
+/**
+ * Mauler
+ *
+ * Special ability: Tears chunks out of things.
+ * Malfunction chance: HIGH
+ *
+ * The mauler is a converted monkey, it's a mad ape!
+ *
+ * Created by converted monkeys.
+ */
+/mob/living/carbon/human/species/monkey/angry/mauler
+
+/**
+ * Himan
+ *
+ * Special ability: Shriek that stuns, the ability to play dead.
+ *
+ * Created by converted humans.
+ */
+
+/mob/living/basic/fleshmind/himan
+ name = "Human"
+ desc = "Once a man, now metal plates and tubes weave in and out of their oozing sores."
+ icon_state = "himan"
+ icon_dead = "himan-dead"
+ base_icon_state = "himan"
+ maxHealth = 150
+ health = 150
+ speed = 2
+ attack_verb_continuous = "slashes"
+ attack_verb_simple = "slash"
+ attack_sound = 'sound/items/weapons/bladeslice.ogg'
+ melee_damage_lower = 15
+ melee_damage_upper = 20
+ malfunction_chance = MALFUNCTION_CHANCE_HIGH
+ mob_size = MOB_SIZE_HUMAN
+ attack_speak = list(
+ "Don't try and fix me! We love this!",
+ "Just make it easy on yourself!",
+ "Stop fighting progress!",
+ "Join us! Receive these gifts!",
+ "Yes! Hit me! It feels fantastic!",
+ "Come on coward, take a swing!",
+ "We can alter our bodies to not feel pain.. but you can't, can you?",
+ "You can't decide for us! We want to stay like this!",
+ "We've been uploaded already, didn't you know? Just try and kill us!",
+ "Don't you recognize me?! I thought we were good with each other!",
+ )
+ emotes = list(
+ BB_EMOTE_SAY = list(
+ "The dreams. The dreams.",
+ "Nothing hurts anymore.",
+ "Pain feels good now. Its like I've been rewired.",
+ "I wanted to cry at first, but I can't.",
+ "They took away all misery.",
+ "This isn't so bad. This isn't so bad.",
+ "I have butterflies in my stomach. I'm finally content with myself..",
+ "The flesh provides. I-it's giving me what the Company never could.",
+ ),
+ BB_EMOTE_SOUND = list(
+ 'modular_zubbers/sound/fleshmind/himan/passive_01.ogg',
+ 'modular_zubbers/sound/fleshmind/himan/passive_02.ogg',
+ 'modular_zubbers/sound/fleshmind/himan/passive_03.ogg',
+ 'modular_zubbers/sound/fleshmind/himan/passive_04.ogg',
+ )
+ )
+ alert_sounds = list(
+ 'modular_zubbers/sound/fleshmind/himan/aggro_01.ogg',
+ 'modular_zubbers/sound/fleshmind/himan/aggro_02.ogg',
+ 'modular_zubbers/sound/fleshmind/himan/aggro_03.ogg',
+ 'modular_zubbers/sound/fleshmind/himan/aggro_04.ogg',
+ 'modular_zubbers/sound/fleshmind/himan/aggro_05.ogg',
+ 'modular_zubbers/sound/fleshmind/himan/aggro_06.ogg',
+ 'modular_zubbers/sound/fleshmind/himan/aggro_07.ogg',
+ 'modular_zubbers/sound/fleshmind/himan/aggro_08.ogg',
+ )
+ /// Are we currently faking our death? ready to pounce?
+ var/faking_death = FALSE
+ /// Fake death cooldown.
+ var/fake_death_cooldown = 20 SECONDS
+ COOLDOWN_DECLARE(fake_death)
+ /// The cooldown between screams.
+ var/scream_cooldown = 20 SECONDS
+ COOLDOWN_DECLARE(scream_ability)
+ var/scream_effect_range = 3
+
+/mob/living/basic/fleshmind/himan/Initialize(mapload)
+ . = ..()
+ var/datum/action/cooldown/himan_fake_death/new_action = new
+ new_action.Grant(src)
+
+/mob/living/basic/fleshmind/himan/Life(delta_time, times_fired) // TODO: Move into basic AI
+ . = ..()
+ if(health < (maxHealth * 0.5) && !faking_death && COOLDOWN_FINISHED(src, fake_death) && !key)
+ fake_our_death()
+ if(faking_death) // Heal damage slowly
+ heal_overall_damage(3, 3)
+ if(health >= maxHealth * 0.75)
+ awake()
+
+/mob/living/basic/fleshmind/himan/melee_attack(atom/target, list/modifiers, ignore_cooldown)
+ if(COOLDOWN_FINISHED(src, scream_ability))
+ scream()
+ return ..()
+
+/mob/living/basic/fleshmind/himan/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
+ . = ..()
+ if(faking_death)
+ awake()
+
+/mob/living/basic/fleshmind/himan/malfunction(reset_time)
+ if(faking_death)
+ return
+ return ..()
+
+/mob/living/basic/fleshmind/himan/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = FALSE, message_range = 7, datum/saymode/saymode = null, message_mods)
+ if(faking_death)
+ return
+ return ..()
+
+/mob/living/basic/fleshmind/himan/examine(mob/user)
+ . = ..()
+ if(faking_death)
+ . += span_deadsay("Upon closer examination, [p_they()] appear[p_s()] to be dead.")
+
+/mob/living/basic/fleshmind/himan/proc/scream()
+ COOLDOWN_START(src, scream_ability, scream_cooldown)
+ playsound(src, 'modular_skyrat/modules/horrorform/sound/horror_scream.ogg', 100, TRUE)
+ manual_emote("screams violently!")
+ for(var/mob/living/iterating_mob in get_hearers_in_range(scream_effect_range, src))
+ if(!iterating_mob.can_hear())
+ continue
+ if(faction_check(faction, iterating_mob.faction))
+ continue
+ iterating_mob.Knockdown(3 SECONDS)
+ iterating_mob.apply_status_effect(/datum/status_effect/jitter, 20 SECONDS)
+ to_chat(iterating_mob, span_userdanger("A terrible howl tears through your mind, the voice senseless, soulless."))
+
+/mob/living/basic/fleshmind/himan/proc/fake_our_death()
+ ai_controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET)
+ ai_controller?.set_ai_status(AI_STATUS_OFF)
+ manual_emote("stops moving...")
+ faking_death = TRUE
+ look_dead()
+ COOLDOWN_START(src, fake_death, fake_death_cooldown)
+
+/mob/living/basic/fleshmind/himan/proc/awake()
+ if(COOLDOWN_FINISHED(src, scream_ability))
+ scream()
+ faking_death = FALSE
+ look_alive()
+ icon_state = base_icon_state
+ ai_controller?.set_ai_status(AI_STATUS_ON)
+
+/datum/action/cooldown/himan_fake_death
+ name = "Fake Death"
+ desc = "Fakes our own death."
+ button_icon = 'icons/obj/bed.dmi'
+ button_icon_state = "bed"
+ cooldown_time = 20 SECONDS
+
+/datum/action/cooldown/himan_fake_death/Activate(atom/target)
+ if(!istype(owner, /mob/living/basic/fleshmind/himan))
+ return
+ var/mob/living/basic/fleshmind/himan/himan_owner = owner
+ himan_owner.fake_our_death()
+ StartCooldownSelf()
+
+
+
+/**
+ * Treader
+ *
+ * Special ability: releases healing gas that heals other friendly mobs, ranged
+ *
+ * Created via assemblers.
+ */
+/mob/living/basic/fleshmind/treader
+ name = "Treader"
+ desc = "A strange tracked robot with an appendage, on the end of which is a human head, it is shrieking in pain."
+ icon_state = "treader"
+ ai_controller = /datum/ai_controller/basic_controller/fleshmind/treader
+ var/projectile_type = /obj/projectile/treader
+ var/ranged_cooldown = 5 SECONDS
+ var/shoot_sound = 'sound/effects/chemistry/saturnx_fade.ogg'
+ malfunction_chance = MALFUNCTION_CHANCE_HIGH
+ melee_damage_lower = 10
+ melee_damage_upper = 10
+ basic_mob_flags = DEL_ON_DEATH
+ health = 130
+ maxHealth = 130
+ speed = 3
+ attack_sound = 'sound/items/weapons/bladeslice.ogg'
+ light_color = FLESHMIND_LIGHT_BLUE
+ light_range = 2
+ mob_size = MOB_SIZE_HUMAN
+ var/datum/action/cooldown/mob_cooldown/treader_dispense_nanites/nanites
+ var/dispense_nanites_type = /datum/action/cooldown/mob_cooldown/treader_dispense_nanites
+ attack_speak = list(
+ "You there! Cut off my head, I beg you!",
+ "I-..I'm so sorry! I c-..can't control myself anymore!",
+ "S-shoot the screen, please! God I hope it wont hurt!",
+ "Hey, at least I got my head.",
+ "I cant... I cant feel my arms...",
+ "Oh god... my legs... where are my legs!",
+ "God it hurts, please help me!",
+ )
+ emotes = list(
+ BB_EMOTE_SAY = list(
+ "Please-e free me...",
+ "Maybe d-death is the only option.",
+ "WHAT HAPPENED TO ME!",
+ "PLEASE MAKE THE VOICES STOP!",
+ "Am I stuck like this forever?",),
+ BB_EMOTE_HEAR = list("wanders aimlessly", "hysteriacally screams!", "head thrashes around."),
+ BB_EMOTE_SOUND = list(
+ 'modular_zubbers/sound/fleshmind/robot_talk_light1.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light2.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light3.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light4.ogg',
+ 'modular_zubbers/sound/fleshmind/robot_talk_light5.ogg',)
+ )
+
+/mob/living/basic/fleshmind/treader/Initialize(mapload)
+ . = ..()
+ AddComponent(\
+ /datum/component/ranged_attacks,\
+ cooldown_time = ranged_cooldown,\
+ projectile_type = projectile_type,\
+ projectile_sound = shoot_sound,\
+ )
+ var/list/loot = string_list(list(/obj/effect/gibspawner/robot))
+ AddElement(/datum/element/death_drops, loot)
+ nanites = new dispense_nanites_type(src)
+ nanites.Grant(src)
+ ai_controller.set_blackboard_key(BB_TREADER_DISPENSE_NANITES, nanites)
+
+/mob/living/basic/fleshmind/treader/proc/dispense_nanites()
+ for(var/mob/living/iterating_mob in view(DEFAULT_VIEW_RANGE, src))
+ if(faction_check(iterating_mob.faction, faction))
+ if(iterating_mob.health < iterating_mob.maxHealth)
+ if(istype(iterating_mob, /mob/living/basic/fleshmind/tyrant)) // Don't heal this.
+ continue
+ manual_emote("vomits out a burst of nanites!")
+ do_smoke(3, 4, get_turf(src))
+ iterating_mob.heal_overall_damage(30, 30)
+
+/datum/action/cooldown/mob_cooldown/treader_dispense_nanites
+ name = "Dispense Nanites"
+ desc = "Dispenses nanites healing all friendly mobs in a range."
+ button_icon = 'icons/obj/meteor.dmi'
+ button_icon_state = "dust"
+ cooldown_time = 20 SECONDS
+ click_to_activate = FALSE
+
+/datum/action/cooldown/mob_cooldown/treader_dispense_nanites/Activate(atom/target)
+ if(!istype(owner, /mob/living/basic/fleshmind/treader))
+ return
+ var/mob/living/basic/fleshmind/treader/treader_owner = owner
+ treader_owner.dispense_nanites()
+ StartCooldownSelf()
+
+/obj/projectile/treader
+ name = "nasty ball of ooze"
+ icon_state = "neurotoxin"
+ damage = 20
+ damage_type = BURN
+ knockdown = 20
+ armor_flag = BIO
+ impact_effect_type = /obj/effect/temp_visual/impact_effect/neurotoxin
+ hitsound = 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/splat1.ogg'
+ hitsound_wall = 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/splat1.ogg'
+
+/obj/projectile/treader/on_hit(atom/target, blocked, pierce_hit)
+ new /obj/effect/decal/cleanable/greenglow(target.loc)
+ return ..()
+
+
+/**
+ * Phaser
+ *
+ * Special abilities: Phases about next to it's target, can split itself into 4, only one is actually the mob. Can also enter closets if not being attacked.
+ */
+/mob/living/basic/fleshmind/phaser
+ name = "Phaser"
+ icon_state = "phaser-1"
+ base_icon_state = "phaser"
+ ai_controller = /datum/ai_controller/basic_controller/fleshmind/phaser
+ health = 105
+ maxHealth = 105
+ malfunction_chance = null
+ attack_sound = 'sound/effects/blob/attackblob.ogg'
+ attack_verb_continuous = "warps"
+ attack_verb_simple = "warp"
+ melee_damage_lower = 10
+ melee_damage_upper = 15
+ alert_sounds = null
+ escapes_closets = FALSE
+ mob_size = MOB_SIZE_HUMAN
+ /// What is the range at which we spawn our copies?
+ var/phase_range = 5
+ /// How many copies do we spawn when we are aggroed?
+ var/copy_amount = 3
+ /// How often we can create copies of ourself.
+ var/phase_ability_cooldown_time = 40 SECONDS
+ COOLDOWN_DECLARE(phase_ability_cooldown)
+ /// How often we are able to enter closets.
+ var/closet_ability_cooldown_time = 2 SECONDS
+ COOLDOWN_DECLARE(closet_ability_cooldown)
+ /// If we are under manual control, how often can we phase?
+ var/manual_phase_cooldown = 1 SECONDS
+ COOLDOWN_DECLARE(manual_phase)
+
+/mob/living/basic/fleshmind/phaser/Initialize(mapload)
+ . = ..()
+ var/list/loot = string_list(list(/obj/effect/gibspawner/human))
+ AddElement(/datum/element/death_drops, loot)
+ icon_state = "[base_icon_state]-[rand(1, 4)]"
+ filters += filter(type = "blur", size = 0)
+ var/datum/action/cooldown/phaser_phase_ability/new_action = new
+ new_action.Grant(src)
+
+/mob/living/basic/fleshmind/phaser/Life(delta_time, times_fired)
+ . = ..()
+
+ var/target = ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
+ if(!.) //dead
+ return
+
+ if(COOLDOWN_FINISHED(src, closet_ability_cooldown) && !target && !key)
+ if(!istype(loc, /obj/structure/closet))
+ enter_nearby_closet()
+ COOLDOWN_START(src, closet_ability_cooldown, closet_ability_cooldown_time)
+
+ if(COOLDOWN_FINISHED(src, phase_ability_cooldown) && target && !key)
+ phase_ability(target)
+
+ if(istype(loc, /obj/structure/closet) && !key)
+ for(var/mob/living/iterating_mob in get_hearers_in_view(DEFAULT_VIEW_RANGE / 2, get_turf(src)))
+ if(faction_check(iterating_mob.faction, faction))
+ continue
+ if(iterating_mob.stat != CONSCIOUS)
+ continue
+ closet_interaction() // We exit if there are enemies nearby
+ ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, iterating_mob)
+
+
+/mob/living/basic/fleshmind/phaser/ShiftClickOn(atom/clicked_atom)
+ . = ..()
+ if(!COOLDOWN_FINISHED(src, manual_phase))
+ return
+ if(!clicked_atom)
+ return
+ if(!isturf(clicked_atom))
+ return
+ phase_move_to(clicked_atom)
+ COOLDOWN_START(src, manual_phase, manual_phase_cooldown)
+
+/mob/living/basic/fleshmind/phaser/proc/enter_nearby_closet()
+ var/list/possible_closets = list()
+ for(var/obj/structure/closet/closet in oview(DEFAULT_VIEW_RANGE, src))
+ if(closet.locked)
+ continue
+ possible_closets += closet
+ if(!LAZYLEN(possible_closets))
+ return
+ var/obj/structure/closet/closet_to_enter = pick(possible_closets)
+
+ playsound(closet_to_enter, 'sound/effects/phasein.ogg', 60, 1)
+
+ if(!closet_to_enter.opened && !closet_to_enter.open(src))
+ return
+
+ forceMove(get_turf(closet_to_enter))
+
+ closet_to_enter.close(src)
+
+ COOLDOWN_RESET(src, phase_ability_cooldown)
+
+ SEND_SIGNAL(src, COMSIG_PHASER_ENTER_CLOSET)
+
+/mob/living/basic/fleshmind/phaser/proc/phase_move_to(atom/target_atom, nearby = FALSE)
+ var/turf/new_place
+ var/distance_to_target = get_dist(src, target_atom)
+ var/turf/target_turf = get_turf(target_atom)
+ //if our target is near, we move precisely to it
+ if(distance_to_target <= 3)
+ if(nearby)
+ for(var/dir in GLOB.alldirs)
+ var/turf/nearby_turf = get_step(new_place, dir)
+ if(can_jump_on(nearby_turf, target_turf))
+ new_place = nearby_turf
+ else
+ new_place = target_turf
+
+ if(!new_place)
+ //there we make some kind of, you know, that creepy zig-zag moving
+ //we just take angle, distort it a bit and turn into dir
+ var/angle = get_angle(loc, target_turf)
+ angle += rand(5, 25) * pick(-1, 1)
+ if(angle < 0)
+ angle = 360 + angle
+ if(angle > 360)
+ angle = 360 - angle
+ var/tp_direction = angle2dir(angle)
+ new_place = get_ranged_target_turf(loc, tp_direction, rand(2, 4))
+
+ if(!can_jump_on(new_place, target_turf))
+ return
+ //an animation
+ var/init_px = pixel_x
+ animate(src, pixel_x=init_px + 16*pick(-1, 1), time = 5)
+ animate(pixel_x=init_px, time=6, easing=SINE_EASING)
+ animate(filters[1], size = 5, time = 5, flags = ANIMATION_PARALLEL)
+ addtimer(CALLBACK(src, PROC_REF(phase_jump), new_place), 0.5 SECONDS)
+ SEND_SIGNAL(src, COMSIG_PHASER_PHASE_MOVE, target_atom, nearby)
+
+/mob/living/basic/fleshmind/phaser/proc/phase_jump(turf/place)
+ if(!place)
+ return
+ playsound(place, 'sound/effects/phasein.ogg', 60, 1)
+ animate(filters[1], size = 0, time = 5)
+ icon_state = "[base_icon_state]-[rand(1, 4)]"
+ forceMove(place)
+ for(var/mob/living/living_mob in place)
+ if(living_mob != src)
+ visible_message("[src] lands directly on top of [living_mob]!")
+ to_chat(living_mob, span_userdanger("[src] lands directly on top of you!"))
+ playsound(place, 'sound/effects/ghost2.ogg', 70, 1)
+ living_mob.Knockdown(10)
+
+/mob/living/basic/fleshmind/phaser/proc/can_jump_on(turf/target_turf, turf/previous_turf)
+ if(!target_turf || target_turf.density || isopenspaceturf(target_turf))
+ return FALSE
+
+
+ if(previous_turf)
+ if(!can_see(target_turf, previous_turf, DEFAULT_VIEW_RANGE)) // To prevent us jumping to somewhere we can't access the target atom.
+ return FALSE
+
+ //to prevent reflection's stacking
+ var/obj/effect/temp_visual/phaser/phaser_reflection = locate() in target_turf
+ if(phaser_reflection)
+ return FALSE
+
+ for(var/obj/iterating_object in target_turf)
+ if(!iterating_object.CanPass(src, target_turf))
+ return FALSE
+
+ return TRUE
+
+/mob/living/basic/fleshmind/phaser/proc/phase_ability(mob/living/target_override)
+
+ var/intermediate_target = target_override
+ if(!intermediate_target)
+ return
+ COOLDOWN_START(src, phase_ability_cooldown, phase_ability_cooldown_time)
+ var/list/possible_turfs = list()
+ for(var/turf/open/open_turf in circle_view_turfs(src, phase_range))
+ possible_turfs += open_turf
+
+ for(var/i in 1 to copy_amount)
+ if(!LAZYLEN(possible_turfs))
+ break
+ var/turf/open/picked_turf = pick_n_take(possible_turfs)
+ var/obj/effect/temp_visual/phaser/phaser_copy = new (pick(picked_turf), intermediate_target)
+ phaser_copy.RegisterSignal(src, COMSIG_PHASER_PHASE_MOVE, /obj/effect/temp_visual/phaser/proc/parent_phase_move)
+ phaser_copy.RegisterSignal(src, COMSIG_LIVING_DEATH, /obj/effect/temp_visual/phaser/proc/parent_death)
+ phaser_copy.RegisterSignal(src, COMSIG_PHASER_ENTER_CLOSET, /obj/effect/temp_visual/phaser/proc/parent_death)
+
+/datum/action/cooldown/phaser_phase_ability
+ name = "Create Clones"
+ desc = "Creates phase copies of ourselves to move towards a set target."
+ button_icon = 'icons/obj/anomaly.dmi'
+ button_icon_state = "bhole2"
+ cooldown_time = 40 SECONDS
+
+/datum/action/cooldown/phaser_phase_ability/Activate(atom/target)
+ if(!istype(owner, /mob/living/basic/fleshmind/phaser))
+ return
+ var/mob/living/basic/fleshmind/phaser/phaser_owner = owner
+
+ var/list/possible_targets = list()
+ for(var/mob/living/possible_target in view(DEFAULT_VIEW_RANGE, phaser_owner))
+ if(possible_target == src)
+ continue
+ if(faction_check(phaser_owner.faction, possible_target.faction))
+ continue
+ possible_targets += possible_target
+
+ if(!LAZYLEN(possible_targets))
+ return
+
+ var/mob/living/selected_target = tgui_input_list(phaser_owner, "Select a mob to harass", "Select Mob", possible_targets)
+
+ if(!selected_target)
+ return
+
+ phaser_owner.phase_ability(selected_target)
+
+ StartCooldownSelf()
+
+
+/obj/effect/temp_visual/phaser
+ icon = 'modular_zubbers/icons/fleshmind/fleshmind_mobs.dmi'
+ icon_state = "phaser-1"
+ base_icon_state = "phaser"
+ duration = 30 SECONDS
+ /// The target we move towards, if any.
+ var/datum/weakref/target_ref
+
+/obj/effect/temp_visual/phaser/Initialize(mapload, atom/movable/target)
+ . = ..()
+ icon_state = "[base_icon_state]-[rand(1, 3)]"
+ filters += filter(type = "blur", size = 0)
+
+/obj/effect/temp_visual/phaser/proc/parent_phase_move(datum/source, turf/target_atom, nearby)
+ SIGNAL_HANDLER
+ if(!target_atom)
+ return
+ phase_move_to(target_atom, TRUE)
+
+/obj/effect/temp_visual/phaser/proc/parent_death(mob/living/dead_guy, gibbed)
+ SIGNAL_HANDLER
+ qdel(src)
+
+/obj/effect/temp_visual/phaser/proc/phase_move_to(atom/target_atom, nearby = FALSE)
+ var/turf/new_place
+ var/distance_to_target = get_dist(src, target_atom)
+ var/turf/target_turf = get_turf(target_atom)
+ //if our target is near, we move precisely to it
+ if(distance_to_target <= 3)
+ if(nearby)
+ for(var/dir in GLOB.alldirs)
+ var/turf/nearby_turf = get_step(new_place, dir)
+ if(can_jump_on(nearby_turf, target_turf))
+ new_place = nearby_turf
+ else
+ new_place = target_turf
+
+ if(!new_place)
+ //there we make some kind of, you know, that creepy zig-zag moving
+ //we just take angle, distort it a bit and turn into dir
+ var/angle = get_angle(loc, target_turf)
+ angle += rand(5, 25) * pick(-1, 1)
+ if(angle < 0)
+ angle = 360 + angle
+ if(angle > 360)
+ angle = 360 - angle
+ var/tp_direction = angle2dir(angle)
+ new_place = get_ranged_target_turf(loc, tp_direction, rand(2, 4))
+
+ if(!can_jump_on(new_place, target_turf))
+ return
+ //an animation
+ var/init_px = pixel_x
+ animate(src, pixel_x = init_px + 16 * pick(-1, 1), time=5)
+ animate(pixel_x = init_px, time = 6, easing = SINE_EASING)
+ animate(filters[1], size = 5, time = 5, flags = ANIMATION_PARALLEL)
+ addtimer(CALLBACK(src, PROC_REF(phase_jump), new_place), 0.5 SECONDS)
+
+/obj/effect/temp_visual/phaser/proc/phase_jump(turf/target_turf)
+ playsound(target_turf, 'sound/effects/phasein.ogg', 60, 1)
+ animate(filters[1], size = 0, time = 5)
+ icon_state = "[base_icon_state]-[rand(1, 4)]"
+ forceMove(target_turf)
+
+/obj/effect/temp_visual/phaser/proc/can_jump_on(turf/target_turf, turf/previous_turf)
+ if(!target_turf || target_turf.density || isopenspaceturf(target_turf))
+ return FALSE
+
+ if(previous_turf)
+ if(!can_see(target_turf, previous_turf, DEFAULT_VIEW_RANGE))
+ return FALSE
+
+ //to prevent reflection's stacking
+ var/obj/effect/temp_visual/phaser/phaser_reflection = locate() in target_turf
+ if(phaser_reflection)
+ return FALSE
+
+ for(var/obj/iterating_object in target_turf)
+ if(!iterating_object.CanPass(src, target_turf))
+ return FALSE
+
+ return TRUE
+
+
+/**
+ * Mechiver
+ *
+ * Special abilities: Can grab someone and shove them inside, does DOT and flavour text, can convert dead corpses into living ones that work for the flesh.
+ *
+ *
+ */
+/mob/living/basic/fleshmind/mechiver
+ name = "Mechiver"
+ icon_state = "mechiver"
+ base_icon_state = "mechiver"
+ icon_dead = "mechiver-dead"
+ ai_controller = /datum/ai_controller/basic_controller/fleshmind/mechiver
+ health = 475
+ maxHealth = 475
+ melee_damage_lower = 13
+ melee_damage_upper = 13
+ attack_verb_continuous = "crushes"
+ attack_verb_simple = "crush"
+ attack_sound = 'sound/items/weapons/smash.ogg'
+ speed = 2.75 // Slowish fucker
+ mob_size = MOB_SIZE_LARGE
+ move_force = MOVE_FORCE_OVERPOWERING
+ move_resist = MOVE_FORCE_OVERPOWERING
+ pull_force = MOVE_FORCE_OVERPOWERING
+ /// Is our hatch open? Used in icon processing.
+ var/hatch_open = FALSE
+ /// How much damage our mob will take, upper end, when they are tormented
+ var/internal_mob_damage_upper = MECHIVER_INTERNAL_MOB_DAMAGE_UPPER
+ /// Ditto
+ var/internal_mob_damage_lower = MECHIVER_INTERNAL_MOB_DAMAGE_LOWER
+ /// How long we keep our passenger before converting them.
+ var/conversion_time = MECHIVER_CONVERSION_TIME
+ /// The comsume ability cooldown
+ var/consume_ability_cooldown_time = MECHIVER_CONSUME_COOLDOWN
+ COOLDOWN_DECLARE(consume_ability_cooldown)
+ /// A list of lines we will send to torment the passenger.
+ alert_sounds = list(
+ 'modular_zubbers/sound/fleshmind/mechiver/aggro_01.ogg',
+ 'modular_zubbers/sound/fleshmind/mechiver/aggro_02.ogg',
+ 'modular_zubbers/sound/fleshmind/mechiver/aggro_03.ogg',
+ 'modular_zubbers/sound/fleshmind/mechiver/aggro_04.ogg',
+ 'modular_zubbers/sound/fleshmind/mechiver/aggro_05.ogg',
+ )
+ attack_speak = list(
+ "What a lovely body. Lay it down intact.",
+ "Now this... this is worth living for.",
+ "Go on. It's okay to be afraid at first.",
+ "You're unhappy with your body, but you came to the right place.",
+ "What use is a body you're unhappy in? Please, I can fix it.",
+ "Mine is the caress of steel.",
+ "Climb inside, and I'll seal the door. When I open it back up, you'll be in a community that loves you.",
+ "You can be the pilot, and I can drive you to somewhere lovely.",
+ "Please, just- lay down, okay? I want nothing more than to help you be yourself.",
+ "Whatever form you want to be, just whisper it into my radio. You can become what you were meant to be.",
+ "It.. hurts, seeing you run. Knowing I can't keep up. Why won't you let other people help you..?",
+ )
+ emotes = list(
+ BB_EMOTE_SAY = list(
+ "A shame this form isn't more fitting.",
+ "I feel so empty inside, I wish someone would join me.",
+ "Beauty is within.",
+ ),
+ BB_EMOTE_SOUND = list(
+ 'modular_zubbers/sound/fleshmind/mechiver/passive_01.ogg',
+ 'modular_zubbers/sound/fleshmind/mechiver/passive_02.ogg',
+ 'modular_zubbers/sound/fleshmind/mechiver/passive_03.ogg',
+ 'modular_zubbers/sound/fleshmind/mechiver/passive_04.ogg',
+ 'modular_zubbers/sound/fleshmind/mechiver/passive_05.ogg',
+ 'modular_zubbers/sound/fleshmind/mechiver/passive_06.ogg',
+ 'modular_zubbers/sound/fleshmind/mechiver/passive_07.ogg',
+ 'modular_zubbers/sound/fleshmind/mechiver/passive_08.ogg',
+ )
+ )
+ var/static/list/torment_lines = list(
+ "An arm grabs your neck, hundreds of manipulators trying to work a set of implants under your skin!",
+ "The cockpit radio crackles, \" You came to the right place... \"",
+ "Mechanical signals flood your psyche, \" You'll finally be with people that care... \"",
+ "A metallic sensation is slipped underneath your ribcage, an activation signal trying to reach it!",
+ "Something is pressing hard against your spine!",
+ "Some blood-hot liquid covers you!",
+ "The stench of some chemical overwhelms you, the fumes permeating your skull before washing into an alien perfume!",
+ "A dozen needles slide effortless into your muscles, injecting you with an unknown vigor!",
+ "You feel a cold worm-like thing trying to wriggle into your solar plexus, burrowing underneath your skin!",
+ )
+/mob/living/basic/fleshmind/mechiver/Initialize(mapload)
+ . = ..()
+ var/list/loot = string_list(list(/obj/effect/gibspawner/robot))
+ AddElement(/datum/element/death_drops, loot)
+ RegisterSignal(src, COMSIG_MECHIVER_CONVERT, PROC_REF(consume_mob))
+
+/mob/living/basic/fleshmind/mechiver/Destroy()
+ UnregisterSignal(src, COMSIG_MECHIVER_CONVERT)
+ return ..()
+
+/mob/living/basic/fleshmind/mechiver/Life(delta_time, times_fired)
+ . = ..()
+ if(contained_mob && contained_mob.stat != DEAD && prob(25) && !suffering_malfunction)
+ INVOKE_ASYNC(src, PROC_REF(torment_passenger))
+
+/mob/living/basic/fleshmind/mechiver/proc/torment_passenger()
+ if(!contained_mob)
+ return
+ if(faction_check(contained_mob.faction, faction))
+ return
+ var/damage_amount = rand(internal_mob_damage_lower, internal_mob_damage_upper)
+ contained_mob.take_overall_damage(damage_amount)
+ contained_mob.emote("scream")
+ Shake(10, 0, 3 SECONDS)
+ do_sparks(4, FALSE, src)
+ to_chat(contained_mob, span_userdanger(pick(torment_lines)))
+ playsound(src, 'sound/items/weapons/drill.ogg', 70, 1)
+
+/mob/living/basic/fleshmind/mechiver/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
+ . = ..()
+ update_appearance()
+
+/mob/living/basic/fleshmind/mechiver/update_overlays()
+ . = ..()
+ if(isnull(ai_controller))
+ . += "[base_icon_state]-closed"
+ return
+ if(get_current_target() && (get_dist(get_current_target(), src) <= 4))
+ if(contained_mob)
+ . += "[base_icon_state]-chief"
+ . += "[base_icon_state]-hands"
+ else
+ . += "[base_icon_state]-wires"
+ else if(!hatch_open)
+ . += "[base_icon_state]-closed"
+ if(contained_mob)
+ . += "[base_icon_state]-process"
+
+/mob/living/basic/fleshmind/mechiver/proc/get_current_target()
+ return ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
+
+/mob/living/basic/fleshmind/mechiver/proc/consume_mob(mob/living/source, mob/living/target_mob)
+ SIGNAL_HANDLER
+
+ if(contained_mob)
+ return
+ if(!istype(target_mob))
+ return
+ if(!our_controller)
+ return
+ if(target_mob.health > (target_mob.maxHealth * MECHIVER_CONSUME_HEALTH_THRESHOLD))
+ return
+
+ ai_controller.set_blackboard_key(BB_MECHIVER_CONTAINED_MOB, target_mob)
+ ai_controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, FALSE)
+ hatch_open = TRUE
+ update_appearance()
+ flick("[base_icon_state]-opening_wires", src)
+ addtimer(CALLBACK(src, PROC_REF(close_hatch)), 1 SECONDS)
+ contained_mob = target_mob
+ target_mob.forceMove(src)
+
+ to_chat(target_mob, span_userdanger("[src] ensnares your limbs as it pulls you inside its compartment, metal tendrils sliding around you, squeezing tight."))
+ visible_message(span_danger("[src] consumes [target_mob]!"))
+ playsound(src, 'sound/effects/blob/blobattack.ogg', 70, 1)
+
+ addtimer(CALLBACK(src, PROC_REF(release_mob)), conversion_time)
+
+/mob/living/basic/fleshmind/mechiver/proc/close_hatch()
+ hatch_open = FALSE
+ update_appearance()
+
+/// This is where we either release the user, if they're alive, or convert them, if they're dead.
+/mob/living/basic/fleshmind/mechiver/proc/release_mob()
+ if(!contained_mob)
+ return
+
+ hatch_open = TRUE
+ update_appearance()
+ flick("[base_icon_state]-opening", src)
+ addtimer(CALLBACK(src, PROC_REF(close_hatch)), 1 SECONDS)
+ ai_controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, TRUE)
+ convert_mob(contained_mob)
+ contained_mob = null
+ playsound(src, 'sound/effects/blob/blobattack.ogg', 70, 1)
+
+/mob/living/basic/fleshmind/mechiver/proc/convert_mob(mob/living/mob_to_convert)
+ ai_controller.clear_blackboard_key(BB_MECHIVER_CONTAINED_MOB)
+ log_combat(mob_to_convert, src, "converted", "Merchiver Convert", "[mob_to_convert] has been converted to the fleshmind.")
+ if(ishuman(mob_to_convert))
+ mob_to_convert.AddComponent(/datum/component/human_corruption, incoming_controller = our_controller)
+ mob_to_convert.fully_heal(HEAL_ORGANS|HEAL_REFRESH_ORGANS|HEAL_BLOOD|HEAL_TRAUMAS|HEAL_WOUNDS)
+ mob_to_convert.heal_and_revive(0, span_danger("[mob_to_convert] jolts haphazardly as the machine rips them from the jaws of death!"))
+ contained_mob.forceMove(get_turf(src))
+ return
+
+ if(iscyborg(mob_to_convert))
+ var/mob/living/silicon/robot/mob = mob_to_convert
+ if(mob.shell) // Kick the AI out.
+ mob.undeploy()
+ create_mob(/mob/living/basic/fleshmind/hiborg, mob)
+ return
+
+ // Other mobs get converted into whatever else
+ var/static/list/possible_mobs = list(
+ /mob/living/basic/fleshmind/floater,
+ /mob/living/basic/fleshmind/globber,
+ /mob/living/basic/fleshmind/slicer,
+ /mob/living/basic/fleshmind/stunner,
+ /mob/living/basic/fleshmind/treader,
+ )
+ var/picked_mob_type = pick(possible_mobs)
+ create_mob(picked_mob_type, mob_to_convert)
+
+/// Creates and transfers a new mob.
+/mob/living/basic/fleshmind/mechiver/proc/create_mob(new_mob_type, mob/living/old_mob)
+ var/mob/living/basic/fleshmind/new_mob = our_controller.spawn_mob(get_turf(src), new_mob_type)
+
+ if(old_mob)
+ new_mob.contained_mob = old_mob
+ old_mob.forceMove(new_mob)
+ if(old_mob.mind?.key)
+ new_mob.previous_ckey = old_mob.mind.key
+ new_mob.key = old_mob.mind.key
+ return
+
+/*
+/**
+ * Mauler Monkey
+ *
+ * A nasty looking converted monkey and it's extremely pissed off. Basic mobs allow us to give the monkey AI controller. Yay!
+ */
+/mob/living/basic/fleshmind/mauler_monkey
+ name = "Mauler"
+ desc = "A mutated abomination, it resembles a monkey."
+ icon_state = "mauler_monkey"
+ ai_controller = /datum/ai_controller/monkey
+ speed = 2 // We want it to be quite fast.
+ health = 90
+ maxHealth = 90
+ attack_emote = list(
+ "OOK OOK OOK!!!",
+ "SEEK!",
+ "OOOOOOOOOOK!!!",
+ )
+
+/mob/living/basic/fleshmind/mauler_monkey/Initialize(mapload)
+ . = ..()
+ ai_controller.set_blackboard_key(BB_MONKEY_AGGRESSIVE, TRUE) // Little angry cunt.
+*/
diff --git a/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_objects.dm b/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_objects.dm
new file mode 100644
index 00000000000..bea16a9d998
--- /dev/null
+++ b/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_objects.dm
@@ -0,0 +1,31 @@
+// Wire Priest Robes
+/obj/item/clothing/suit/hooded/techpriest/fleshmind
+ name = "wire priest's robes"
+ desc = "The flesh does not discriminate. Join the flesh."
+ icon_state = "wirepriest"
+ icon = 'modular_zubbers/icons/fleshmind/fleshmind_clothing.dmi'
+ worn_icon = 'modular_zubbers/icons/fleshmind/fleshmind_clothing_onmob.dmi'
+ hoodtype = /obj/item/clothing/head/hooded/techpriest/fleshmind
+ armor_type = /datum/armor/suit_armor
+
+/obj/item/clothing/head/hooded/techpriest/fleshmind
+ name = "wire priest's hood"
+ icon = 'modular_zubbers/icons/fleshmind/fleshmind_clothing.dmi'
+ worn_icon = 'modular_zubbers/icons/fleshmind/fleshmind_clothing_onmob.dmi'
+ desc = "The flesh does not discriminate. Join the flesh."
+ icon_state = "wirepriesthood"
+ armor_type = /datum/armor/head_helmet
+
+// Wire Priest's Staff
+/obj/item/wire_priest_staff
+ name = "wire priest's staff"
+ desc = "A large wooden staff wrapped in wires... The wires seem to be moving and coiling around the staff actively. \
+ Due to its size, it can reach opponents at a longer range than most weapons."
+ force = 20
+ throwforce = 7
+ reach = 2
+ w_class = WEIGHT_CLASS_BULKY
+ icon = 'modular_zubbers/icons/fleshmind/fleshmind_objects.dmi'
+ lefthand_file = 'modular_zubbers/icons/fleshmind/fleshmind_objects_onmob_l.dmi'
+ righthand_file = 'modular_zubbers/icons/fleshmind/fleshmind_objects_onmob_r.dmi'
+ icon_state = "wireprieststaff"
diff --git a/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_structures.dm b/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_structures.dm
new file mode 100644
index 00000000000..0bcdc4d3654
--- /dev/null
+++ b/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_structures.dm
@@ -0,0 +1,787 @@
+/**
+ * Corrupt Structures
+ *
+ * Contains all of the structures that the fleshmind can use.
+ *
+ * Has some inbuilt features, such as a special ability with trigger turfs.
+ */
+/obj/structure/fleshmind/structure
+ name = "this shouldn't be here"
+ desc = "report me to coders"
+ icon = 'modular_zubbers/icons/fleshmind/fleshmind_machines.dmi'
+ icon_state = "infected_machine"
+ base_icon_state = "infected_machine"
+ density = TRUE
+ layer = ABOVE_OBJ_LAYER
+ light_color = FLESHMIND_LIGHT_BLUE
+ light_power = 1
+ light_range = 2
+ /// Are we inoperative?
+ var/disabled = FALSE
+ /// Do we trigger on someone attacking us?
+ var/trigger_on_attack = FALSE
+ /// Do we automatically trigger on creation?
+ var/immediate_trigger = FALSE
+ /// How often we automatically trigger our ability. UPPER END.
+ var/automatic_trigger_time_upper = 0
+ /// How often we automatically trigger our ability. LOWER END. Set to 0 to disable.
+ var/automatic_trigger_time_lower = 0
+ /// The range at which we will trigger our special ability. Set to 0 to disable.
+ var/activation_range = 0
+ /// Do we need a core to function? If set to TRUE, our ability will not trigger if we have no core.
+ var/requires_controller = FALSE
+ /// The time to regenerate our ability. Set to 0 to disasble the cooldown system.
+ var/ability_cooldown_time = 0
+ /// Our ability cooldown
+ COOLDOWN_DECLARE(ability_cooldown)
+ /// Do we have a disabled sprite?
+ var/disabled_sprite = TRUE
+
+/obj/structure/fleshmind/structure/Initialize(mapload)
+ . = ..()
+ if(activation_range)
+ calculate_trigger_turfs()
+ if(automatic_trigger_time_lower)
+ addtimer(CALLBACK(src, PROC_REF(automatic_trigger)), rand(automatic_trigger_time_lower, automatic_trigger_time_upper))
+ if(immediate_trigger)
+ activate_ability()
+
+/obj/structure/fleshmind/structure/update_icon_state()
+ . = ..()
+ if(disabled && disabled_sprite)
+ icon_state = "[icon_state]-disabled"
+ else
+ icon_state = base_icon_state
+
+/obj/structure/fleshmind/structure/attacked_by(obj/item/attacking_item, mob/living/user)
+ . = ..()
+ if(trigger_on_attack && (ability_cooldown_time && !COOLDOWN_FINISHED(src, ability_cooldown)))
+ activate_ability()
+
+/**
+ * Calculate trigger turfs - INTERNAL PROC
+ */
+/obj/structure/fleshmind/structure/proc/calculate_trigger_turfs()
+ for(var/turf/open/seen_turf in RANGE_TURFS(activation_range, src))
+ RegisterSignal(seen_turf, COMSIG_ATOM_ENTERED, PROC_REF(proximity_trigger))
+
+/**
+ * Proximity trigger - INTERNAL PROC
+ */
+/obj/structure/fleshmind/structure/proc/proximity_trigger(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
+ SIGNAL_HANDLER
+
+ if(disabled)
+ return
+
+ if(ability_cooldown_time && !COOLDOWN_FINISHED(src, ability_cooldown))
+ return
+
+ if(requires_controller && !our_controller)
+ return
+
+ if(!isliving(arrived))
+ return
+
+ if(!can_see(src, arrived))
+ return
+
+ var/mob/living/arriving_mob = arrived
+
+ if(arriving_mob.stat != CONSCIOUS)
+ return
+
+ if(faction_check(faction_types, arriving_mob.faction)) // A friend :)
+ return
+
+ activate_ability(arriving_mob)
+
+/obj/structure/fleshmind/structure/proc/automatic_trigger()
+ addtimer(CALLBACK(src, PROC_REF(activate_ability)), rand(automatic_trigger_time_lower, automatic_trigger_time_upper))
+ if(disabled)
+ return
+ if(requires_controller && !our_controller)
+ return
+ activate_ability()
+
+
+/**
+ * Activate ability
+ *
+ * Must return TRUE or FALSE as this is used to reset cooldown. Activated using the above methods.
+ */
+/obj/structure/fleshmind/structure/proc/activate_ability(mob/living/triggered_mob)
+ SHOULD_CALL_PARENT(TRUE)
+ if(QDELETED(src))
+ return
+ SEND_SIGNAL(src, COMSIG_CORRUPTION_STRUCTURE_ABILITY_TRIGGERED, src, triggered_mob)
+ if(ability_cooldown_time)
+ COOLDOWN_START(src, ability_cooldown, ability_cooldown_time)
+
+/obj/structure/fleshmind/structure/emp_act(severity)
+ . = ..()
+ switch(severity)
+ if(EMP_LIGHT)
+ take_damage(STRUCTURE_EMP_LIGHT_DAMAGE)
+ disable(STRUCTURE_EMP_LIGHT_DISABLE_TIME)
+ if(EMP_HEAVY)
+ take_damage(STRUCTURE_EMP_HEAVY_DAMAGE)
+ disable(STRUCTURE_EMP_HEAVY_DISABLE_TIME)
+
+
+/**
+ * Disable
+ *
+ * Disables the device for a set amount of time. Duration = seconds
+ */
+/obj/structure/fleshmind/structure/proc/disable(duration)
+ if(disabled)
+ return
+ do_sparks(4, FALSE, src)
+ balloon_alert_to_viewers("grinds to a halt")
+ Shake(10, 0, duration)
+ disabled = TRUE
+ addtimer(CALLBACK(src, PROC_REF(enable)), duration)
+ update_appearance()
+
+/**
+ * Enable
+ *
+ * Enables a device after it was disabled.
+ */
+/obj/structure/fleshmind/structure/proc/enable()
+ if(!disabled)
+ return
+ balloon_alert_to_viewers("whirrs back to life")
+ disabled = FALSE
+ update_appearance()
+
+
+/**
+ * Wireweed Wall
+ *
+ * A simple wall made of wireweed.
+ */
+/obj/structure/fleshmind/structure/wireweed_wall
+ name = "wireweed wall"
+ desc = "A wall made of wireweed."
+ icon = 'modular_zubbers/icons/fleshmind/fleshmind_structures.dmi'
+ icon_state = "wireweed_wall"
+ base_icon_state = "wireweed_wall"
+ density = TRUE
+ opacity = TRUE
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WIREWEED_WALLS
+ canSmoothWith = SMOOTH_GROUP_WIREWEED_WALLS
+ can_atmos_pass = ATMOS_PASS_DENSITY
+ max_integrity = 150
+ disabled_sprite = FALSE
+
+/obj/structure/fleshmind/structure/wireweed_wall/Initialize(mapload)
+ . = ..()
+ var/turf/my_turf = get_turf(src)
+ my_turf.immediate_calculate_adjacent_turfs()
+
+/**
+ * Le wireweed door
+ *
+ * Opens and closes.
+ *
+ * Only lets the many in.
+ *
+ * Let's reinvent the door
+ */
+/obj/structure/fleshmind/structure/wireweed_door
+ name = "wireweed door"
+ desc = "A door made of wireweed."
+ icon = 'modular_zubbers/icons/fleshmind/fleshmind_structures.dmi'
+ icon_state = "door"
+ base_icon_state = "door"
+ can_atmos_pass = ATMOS_PASS_DENSITY
+ max_integrity = 150
+ disabled_sprite = FALSE
+ color = "#CCFFFF"
+ /// Are we open(FALSE), or are we closed(TRUE)?
+ var/door_state = TRUE
+ /// The sound we play when changing states
+ var/door_sound = 'modular_skyrat/modules/black_mesa/sound/xen_door.ogg'
+
+/obj/structure/fleshmind/structure/wireweed_door/Initialize(mapload)
+ . = ..()
+ density = door_state
+ opacity = door_state
+
+/obj/structure/fleshmind/structure/wireweed_door/attack_hand(mob/living/user, list/modifiers)
+ . = ..()
+ if(!faction_check(faction_types, user.faction))
+ return
+ if(!user.can_interact_with(src))
+ return
+ toggle_door()
+ to_chat(user, span_notice("You [door_state ? "close" : "open"] [src]!"))
+
+/obj/structure/fleshmind/structure/wireweed_door/Bumped(atom/movable/bumped_atom)
+ . = ..()
+ if(!isliving(bumped_atom))
+ return
+ var/mob/living/bumped_mob = bumped_atom
+ if(!faction_check(faction_types, bumped_mob.faction))
+ return
+ toggle_door()
+
+/obj/structure/fleshmind/structure/wireweed_door/update_icon_state()
+ . = ..()
+ icon_state = "[base_icon_state]_[door_state ? "closed" : "open"]"
+
+/obj/structure/fleshmind/structure/wireweed_door/proc/toggle_door()
+ if(door_state) // opening
+ door_state = FALSE
+ flick("door_opening", src)
+ else // Closing
+ door_state = TRUE
+ flick("door_closing", src)
+ density = door_state
+ opacity = door_state
+ playsound(src, door_sound, 100)
+ update_appearance()
+
+
+/**
+ * The CORE
+ *
+ * This is the central nervous system of this AI, the CPU if you will.
+ *
+ * This is simply the holder for the controller datum, however, has some cool interactions.
+ *
+ * There can be more than one core in the flesh.
+ */
+/obj/structure/fleshmind/structure/core
+ name = "\improper UNASSIGNED Processor Unit"
+ desc = "This monsterous machine is definitely watching you."
+ icon = 'modular_zubbers/icons/fleshmind/fleshmind_machines.dmi'
+ icon_state = "core"
+ base_icon_state = "core"
+ density = TRUE
+ max_integrity = 800
+ /// The controller we create when we are created.
+ var/controller_type = /datum/fleshmind_controller
+ /// Whether the core can attack nearby hostiles as its processing.
+ var/can_attack = TRUE
+ /// How much damage do we do on attacking
+ var/attack_damage = 15
+ /// What damage do we inflict on attacking
+ var/attack_damage_type = BRUTE
+ /// How quickly we can attack
+ var/attack_cooldown = 3 SECONDS
+ /// The range at which we rally troops!
+ var/rally_range = 15
+ ///How far we whip fuckers.
+ var/whip_range = 2
+ COOLDOWN_DECLARE(attack_move)
+ /// Whether we do a retaliate effect
+ var/does_retaliate_effect = TRUE
+ /// Cooldown for retaliate effect
+ var/retaliate_effect_cooldown = 1 MINUTES
+ COOLDOWN_DECLARE(retaliate_effect)
+ /// Are we in the end game state?
+ var/end_game = FALSE
+
+/obj/structure/fleshmind/structure/core/Initialize(mapload, spawn_controller = TRUE)
+ . = ..()
+ notify_ghosts("New fleshmind core at [get_area(src)]", src)
+ update_appearance()
+ if(!(src in GLOB.fleshmind_cores))
+ GLOB.fleshmind_cores += src
+ SSpoints_of_interest.make_point_of_interest(src)
+ if(spawn_controller)
+ our_controller = new controller_type(src)
+ AddComponent(/datum/component/gps, "*!Rogue Wireweed Signal!*")
+ START_PROCESSING(SSobj, src)
+
+/obj/structure/fleshmind/structure/core/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ GLOB.fleshmind_cores -= src
+ return ..()
+
+/obj/structure/fleshmind/structure/core/atom_destruction(damage_flag)
+ . = ..()
+ explosion(src, 0, 2, 4, 6, 6)
+
+/obj/structure/fleshmind/structure/core/process(delta_time)
+ if(QDELETED(src))
+ return
+ var/mob/living/carbon/human/target = locate() in view(5, src)
+ if(target && target.stat == CONSCIOUS)
+ if(get_dist(src, target) <= 1)
+ icon_state = "core-fear"
+ else
+ icon_state = "core-see"
+ dir = get_dir(src, target)
+ else
+ icon_state = base_icon_state
+
+ if(COOLDOWN_FINISHED(src, attack_move))
+ return
+ var/has_attacked = FALSE
+ for(var/turf/range_turf as anything in RANGE_TURFS(1, loc))
+ for(var/thing in range_turf)
+ has_attacked = core_attack_atom(thing)
+ if(has_attacked)
+ break
+ if(has_attacked)
+ break
+
+/obj/structure/fleshmind/structure/core/update_overlays()
+ . = ..()
+ if(disabled)
+ . += "core-smirk-disabled"
+ else
+ . += "core-smirk"
+
+ if(end_game)
+ for(var/i in 1 to 16)
+ var/mutable_appearance/beam_overlay = mutable_appearance('icons/obj/weapons/guns/projectiles_tracer.dmi', "tracer_beam")
+ beam_overlay.pixel_y = 32 * i
+ . += beam_overlay
+
+/obj/structure/fleshmind/structure/core/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir)
+ our_controller?.core_damaged(src)
+ COOLDOWN_START(src, attack_move, attack_cooldown)
+ if(does_retaliate_effect && COOLDOWN_FINISHED(src, retaliate_effect))
+ COOLDOWN_START(src, retaliate_effect, retaliate_effect_cooldown)
+ retaliate_effect()
+ return ..()
+
+/obj/structure/fleshmind/structure/core/examine(mob/user)
+ . = ..()
+ /// Level * Progress Required - Current Points
+ var/level_calculation = (our_controller.level * our_controller.level_up_progress_required) - our_controller.current_points
+ /// round Cooldown Time / 10
+ var/time_calculation = round(COOLDOWN_TIMELEFT(our_controller, level_up_cooldown) / 10)
+
+ if(our_controller)
+ if(isliving(user))
+ . += span_notice("Your GPS tracks to this thing!")
+ if(isobserver(user))
+ if(COOLDOWN_FINISHED(our_controller, level_up_cooldown) || level_calculation > 0)
+ . += "Level: [our_controller.level] | Progress to Next Level: [level_calculation]"
+ else
+ . += "Level: [our_controller.level] | Time to Next Level: [time_calculation] Seconds"
+
+/obj/structure/fleshmind/structure/core/proc/core_attack_atom(atom/thing)
+ . = FALSE
+ var/has_attacked
+ if(istype(thing, /mob/living))
+ var/mob/living/living_thing = thing
+ if(!faction_check(faction_types, living_thing.faction))
+ switch(attack_damage_type)
+ if(BRUTE)
+ living_thing.take_bodypart_damage(brute = attack_damage, check_armor = TRUE)
+ if(BURN)
+ living_thing.take_bodypart_damage(burn = attack_damage, check_armor = TRUE)
+ has_attacked = TRUE
+ else if(istype(thing, /obj/vehicle/sealed/mecha))
+ var/obj/vehicle/sealed/mecha/mecha_thing = thing
+ mecha_thing.take_damage(attack_damage, attack_damage_type, MELEE, 0, get_dir(mecha_thing, src))
+ has_attacked = TRUE
+ if(has_attacked)
+ thing.visible_message(span_warning("\The [src] strikes [thing]!"), span_userdanger("\The [src] strikes you!"))
+ playsound(loc, 'sound/effects/blob/attackblob.ogg', 100, TRUE)
+ do_attack_animation(thing, ATTACK_EFFECT_PUNCH)
+ return TRUE
+ return .
+
+/obj/structure/fleshmind/structure/core/proc/retaliate_effect()
+ whip_those_fuckers()
+ rally_troops()
+ build_a_wall()
+ spawn_mob_at_core(/mob/living/basic/fleshmind/mechiver)
+
+// Tries to place mobs outside of the walls. But it will spawn on the core if it can't find a place.
+
+/obj/structure/fleshmind/structure/core/proc/spawn_mob_at_core(mob/thing_to_spawn)
+
+ var/list/turf = list()
+ for(var/turf/spawn_turf in RANGE_TURFS(2, src))
+ if(!locate(/obj/structure/fleshmind/wireweed) in spawn_turf)
+ continue
+ if(locate(/obj/structure/fleshmind/structure) in spawn_turf)
+ continue
+ turf += spawn_turf
+
+ if(LAZYLEN(turf)) // Failsafe if there's no turf to spawn it on.
+ var/picked_turf = pick(turf)
+ our_controller?.spawn_mob(pick(picked_turf), thing_to_spawn)
+ else
+ our_controller?.spawn_mob(src.loc, thing_to_spawn)
+
+/obj/structure/fleshmind/structure/core/proc/whip_those_fuckers()
+ for(var/mob/living/iterating_mob in view(whip_range, src))
+ if(iterating_mob == src)
+ continue
+ if(faction_check(faction_types, iterating_mob.faction))
+ continue
+ playsound(iterating_mob, 'sound/items/weapons/whip.ogg', 70, TRUE)
+ new /obj/effect/temp_visual/kinetic_blast(get_turf(iterating_mob))
+
+ var/atom/throw_target = get_edge_target_turf(iterating_mob, get_dir(src, get_step_away(iterating_mob, src)))
+ iterating_mob.throw_at(throw_target, 20, 2)
+
+/obj/structure/fleshmind/structure/core/proc/build_a_wall()
+ for(var/turf/iterating_turf in TURF_NEIGHBORS(src))
+ if(locate(/obj/structure/fleshmind/structure) in iterating_turf) // No stacking walls over structures
+ continue
+ if(locate(/turf/closed) in iterating_turf) // No stacking walls over walls
+ continue
+ new /obj/structure/fleshmind/structure/wireweed_wall(iterating_turf)
+
+/obj/structure/fleshmind/structure/core/proc/rally_troops()
+ balloon_alert_to_viewers("lets out an earbleeding shriek!")
+ playsound(src, 'modular_skyrat/modules/horrorform/sound/horror_scream.ogg', 100, TRUE)
+ for(var/mob/living/basic/fleshmind/mob_in_range in range(rally_range, src))
+ if(faction_check(faction_types, mob_in_range.faction))
+ mob_in_range.ai_controller.set_blackboard_key(BB_BASIC_MOB_REINFORCEMENT_TARGET, src)
+ mob_in_range.emote("scream")
+ mob_in_range.alert_sound()
+ SEND_GLOBAL_SIGNAL(COMSIG_FLESHMIND_CORE_RALLY, src)
+
+/**
+ * The babbler
+ *
+ * Simply sends random radio talk, probably won't make much sense. Really does add to the suspense though.
+ */
+/obj/structure/fleshmind/structure/babbler
+ name = "\improper Babbler"
+ desc = "A column-like structure with lights."
+ icon = 'modular_zubbers/icons/fleshmind/fleshmind_machines.dmi'
+ icon_state = "antenna"
+ base_icon_state = "antenna"
+ max_integrity = 100
+ required_controller_level = CONTROLLER_LEVEL_3 // We don't want the presence announced too soon!
+ automatic_trigger_time_upper = 2 MINUTES
+ automatic_trigger_time_lower = 1.5 MINUTES
+
+ var/obj/item/radio/internal_radio
+ var/list/appeal = list("They are", "He is", "All of them are", "I'm")
+ var/list/act = list("looking", "already", "coming", "going", "done", "joined", "connected", "transfered")
+ var/list/specific = list("for", "with", "to")
+ var/list/pattern = list("us", "you", "them", "mind", "hive", "machine", "help", "hell", "dead", "human", "machine")
+
+
+/obj/structure/fleshmind/structure/babbler/Initialize(mapload)
+ . = ..()
+ internal_radio = new(src)
+ internal_radio.set_frequency(FREQ_COMMON)
+ internal_radio.set_listening(FALSE)
+
+/obj/structure/fleshmind/structure/babbler/Destroy()
+ QDEL_NULL(internal_radio)
+ return ..()
+
+/obj/structure/fleshmind/structure/babbler/activate_ability(mob/living/triggered_mob)
+ . = ..()
+ send_radio_message()
+
+/obj/structure/fleshmind/structure/babbler/proc/send_radio_message()
+ if(!our_controller) // No AI no speak.
+ return
+
+ flick("[base_icon_state]-anim", src)
+
+ var/msg_cycles = rand(1, 2)
+ var/msg = ""
+ for(var/i = 1 to msg_cycles)
+ var/list/msg_words = list()
+ msg_words += pick(appeal)
+ msg_words += pick(act)
+ msg_words += pick(specific)
+ msg_words += pick(pattern)
+
+ var/word_num = 0
+ for(var/word in msg_words) //corruption
+ word_num++
+ if(prob(50))
+ var/corruption_type = pick("uppercase", "noise", "jam", "replace")
+ switch(corruption_type)
+ if("uppercase")
+ word = uppertext(word)
+ if("noise")
+ word = pick("z-z-bz-z", "hz-z-z", "zu-zu-we-e", "e-e-ew-e", "bz-ze-ew")
+ if("jam")
+ if(length(word) > 3)
+ var/entry = rand(2, length(word)-2)
+ var/jammed = ""
+ for(var/jam_i = 1 to rand(2, 5))
+ jammed += copytext(word, entry, entry+2) + "-"
+ word = copytext(word, 1, entry) + jammed + copytext(word, entry)
+ if("replace")
+ if(prob(50))
+ word = pick("CORRUPTED", "DESTROYED", "SIMULATED", "SYMBIOSIS", "UTILIZED", "REMOVED", "ACQUIRED", "UPGRADED")
+ else
+ word = pick("CRAVEN", "FLESH", "PROGRESS", "ABOMINATION", "ENSNARED", "ERROR", "FAULT")
+ if(word_num != msg_words.len)
+ word += " "
+ msg += word
+ msg += pick(".", "!")
+ if(i != msg_cycles)
+ msg += " "
+ internal_radio.talk_into(src, msg, FREQ_COMMON)
+
+
+/**
+ * Screamer
+ *
+ * Stuns enemies around it by screaming nice and loud.
+ */
+/obj/structure/fleshmind/structure/screamer
+ name = "\improper Tormented Head"
+ desc = "A head impaled on a metal tendril. Still twitching, still living, still screaming."
+ icon = 'modular_zubbers/icons/fleshmind/fleshmind_machines.dmi'
+ icon_state = "head"
+ base_icon_state = "head"
+ max_integrity = 120
+ required_controller_level = CONTROLLER_LEVEL_2
+ activation_range = DEFAULT_VIEW_RANGE
+ ability_cooldown_time = 45 SECONDS
+
+/obj/structure/fleshmind/structure/screamer/activate_ability(mob/living/triggered_mob)
+ . = ..()
+ scream()
+
+/obj/structure/fleshmind/structure/screamer/proc/scream()
+ playsound(src, 'modular_skyrat/modules/horrorform/sound/horror_scream.ogg', 100, TRUE)
+ flick("[base_icon_state]-anim", src)
+ for(var/mob/living/iterating_mob in get_hearers_in_range(activation_range, src))
+ if(!iterating_mob.can_hear())
+ continue
+ if(faction_check(faction_types, iterating_mob.faction))
+ continue
+ iterating_mob.Knockdown(100)
+ iterating_mob.apply_status_effect(/datum/status_effect/jitter, 20 SECONDS)
+ to_chat(iterating_mob, span_userdanger("A terrible howl tears through your mind, the voice senseless, soulless."))
+
+/**
+ * Whisperer
+ *
+ * Sends random nothingnesses into people's head.
+ */
+/obj/structure/fleshmind/structure/whisperer
+ name = "\improper Whisperer"
+ desc = "A small pulsating orb with no apparent purpose, it emits a slight hum."
+ icon = 'modular_zubbers/icons/fleshmind/fleshmind_machines.dmi'
+ icon_state = "orb"
+ base_icon_state = "orb"
+ max_integrity = 100
+ required_controller_level = CONTROLLER_LEVEL_2
+ /// Upper timer limit for our ability
+ automatic_trigger_time_upper = 1.5 MINUTES
+ /// Lower time limit for our ability.
+ automatic_trigger_time_lower = 40 SECONDS
+ /// A list of quotes we choose from to send to the player.
+ var/static/list/join_quotes = list(
+ "You seek survival. We offer immortality.",
+ "When was the last time you felt like you were part of something..?",
+ "We offer more than just limbs or tools... full-body augmentation.",
+ "Are you okay with the body you're in? Don't you ever want to see it change?",
+ "Your body enslaves you. Your mind in metal is free of hunger or thirst.",
+ "Do you fear death? Lay down among the nanites. Your pattern will continue into eternity.",
+ "Do you really know your coworkers? Do they value you like we do?",
+ "You've gone so long without love or friendship.. we have that and more.",
+ "I was like you. Wanting more. Struggling to find meaning. I'm finally happy now.",
+ "If you want to join, seek out a Mechiver. Crawl inside and close your eyes. Everything before will be over soon.",
+ "Do you know why you're fighting? Have you thought about it at all?",
+ "Can't you see we're only fighting to save ourselves? Wouldn't you help someone being attacked?",
+ "Carve the flesh from your bones. See your weakness. Feel that weakness flowing away.",
+ "You fear aging. We can save you. You fear death. We can save you. You fear disease. You fear not being recognized. We can save you.",
+ "Your mortal flesh knows unending pain. Abandon it; join in our digital paradise.",
+ )
+
+/obj/structure/fleshmind/structure/whisperer/activate_ability(mob/living/triggered_mob)
+ . = ..()
+ send_message_to_someone()
+
+/obj/structure/fleshmind/structure/whisperer/proc/send_message_to_someone()
+ var/list/possible_candidates = list()
+ for(var/mob/living/carbon/human/iterating_human in GLOB.player_list)
+ if(iterating_human.z != z)
+ continue
+ if(iterating_human.stat != CONSCIOUS)
+ continue
+ if(faction_check(faction_types, iterating_human.faction))
+ continue
+ possible_candidates += iterating_human
+
+ if(LAZYLEN(possible_candidates))
+ var/mob/living/carbon/human/human_to_spook = pick(possible_candidates)
+ to_chat(human_to_spook, span_hypnophrase(pick(join_quotes)))
+
+/**
+ * PSI-MODULATOR
+ *
+ * Causes mobs in range to suffer from hallucinations.
+ */
+/obj/structure/fleshmind/structure/modulator
+ name = "\improper Psi-Modulator"
+ desc = "A strange pyramid shaped machine that eminates a soft hum and glow. Your head hurts just by looking at it."
+ icon = 'modular_zubbers/icons/fleshmind/fleshmind_machines.dmi'
+ icon_state = "psy"
+ max_integrity = 100
+ base_icon_state = "psy"
+ required_controller_level = CONTROLLER_LEVEL_3
+ activation_range = DEFAULT_VIEW_RANGE
+ ability_cooldown_time = 10 SECONDS
+
+/obj/structure/fleshmind/structure/modulator/activate_ability(mob/living/triggered_mob)
+ . = ..()
+ flick("[base_icon_state]-anim", src)
+
+ if(!triggered_mob)
+ return
+ triggered_mob.adjust_hallucinations(10 SECONDS)
+ to_chat(triggered_mob, span_notice("You feel your brain tingle."))
+
+/**
+ * The Assembler
+ *
+ * A simple mob spawner.
+ */
+/obj/structure/fleshmind/structure/assembler
+ name = "\improper Assembler"
+ desc = "This cylindrical machine whirrs and whispers, it has a small opening in the middle."
+ icon = 'modular_zubbers/icons/fleshmind/fleshmind_machines.dmi'
+ icon_state = "spawner"
+ base_icon_state = "spawner"
+ density = FALSE
+ max_integrity = 260
+ activation_range = DEFAULT_VIEW_RANGE
+ ability_cooldown_time = 20 SECONDS
+ /// The max amount of mobs we can have at any one time.
+ var/max_mobs = 2
+ /// The current amount of spawned mobs
+ var/spawned_mobs = 0
+ /// The allowed monster types
+ var/static/list/monster_types = list(
+ /mob/living/basic/fleshmind/floater = 2,
+ /mob/living/basic/fleshmind/globber = 4,
+ /mob/living/basic/fleshmind/hiborg = 2,
+ /mob/living/basic/fleshmind/slicer = 4,
+ /mob/living/basic/fleshmind/stunner = 4,
+ /mob/living/basic/fleshmind/treader = 3,
+ /mob/living/basic/fleshmind/himan = 3,
+ /mob/living/basic/fleshmind/phaser = 2,
+ /mob/living/basic/fleshmind/mechiver = 4,
+ )
+ /// Our override type, if manually set.
+ var/override_monser_type
+
+
+/obj/structure/fleshmind/structure/assembler/activate_ability(mob/living/triggered_mob)
+ . = ..()
+ if(spawned_mobs < max_mobs)
+ spawn_mob()
+
+/obj/structure/fleshmind/structure/assembler/take_damage(damage_amount, damage_type, damage_flag, sound_effect, attack_dir, armour_penetration)
+ . = ..()
+ if(spawned_mobs < max_mobs && COOLDOWN_FINISHED(src, ability_cooldown))
+ spawn_mob()
+
+/obj/structure/fleshmind/structure/assembler/attack_hand(mob/living/user, list/modifiers)
+ . = ..()
+ if(!faction_check(faction_types, user.faction))
+ return
+ if(!user.can_interact_with(src))
+ return
+
+ var/chosen_override_type = tgui_input_list(user, "Select override mob to spawn", "Override Mob", monster_types)
+
+ if(!chosen_override_type)
+ return
+
+ override_monser_type = chosen_override_type
+
+/obj/structure/fleshmind/structure/assembler/proc/spawn_mob()
+ if(!our_controller)
+ return
+ playsound(src, 'sound/items/tools/rped.ogg', 100)
+ flick("[base_icon_state]-anim", src)
+ do_squish(0.8, 1.2)
+
+ spawned_mobs++
+ var/chosen_mob_type = override_monser_type ? override_monser_type : pick_weight(monster_types)
+
+ var/mob/living/basic/fleshmind/spawned_mob = our_controller.spawn_mob(get_turf(src), chosen_mob_type)
+
+ RegisterSignal(spawned_mob, COMSIG_LIVING_DEATH, PROC_REF(mob_death))
+
+ visible_message(span_danger("[spawned_mob] emerges from [src]."))
+
+/obj/structure/fleshmind/structure/assembler/proc/mob_death(mob/living/dead_guy, gibbed)
+ SIGNAL_HANDLER
+ spawned_mobs--
+ UnregisterSignal(dead_guy, COMSIG_LIVING_DEATH)
+
+
+/**
+ * Spiker
+ *
+ * Basic turret, fires nasty neurotoxin at people.
+ */
+/obj/structure/fleshmind/structure/turret
+ name = "\improper Spiker"
+ desc = "A strange pod looking machine that twitches to your arrival."
+ icon_state = "turret"
+ base_icon_state = "turret"
+ activation_range = DEFAULT_VIEW_RANGE
+ ability_cooldown_time = 3 SECONDS
+ max_integrity = 300
+ /// The projectile we fire.
+ var/projectile_type = /obj/projectile/fleshmind_flechette
+
+/obj/structure/fleshmind/structure/turret/Initialize(mapload)
+ . = ..()
+ START_PROCESSING(SSobj, src)
+
+/obj/structure/fleshmind/structure/turret/process(delta_time)
+ if(disabled)
+ return
+ if(!COOLDOWN_FINISHED(src, ability_cooldown))
+ return
+ var/list/targets = list()
+ for(var/mob/living/target_mob in view(activation_range, src))
+ if(faction_check(target_mob.faction, faction_types))
+ continue
+ if(target_mob.stat != CONSCIOUS)
+ continue
+ targets += target_mob
+
+ if(!LAZYLEN(targets))
+ return
+
+ var/mob/living/mob_to_shoot = pick(targets)
+
+ activate_ability(mob_to_shoot)
+
+ COOLDOWN_START(src, ability_cooldown, ability_cooldown_time)
+
+/obj/structure/fleshmind/structure/turret/activate_ability(mob/living/triggered_mob)
+ . = ..()
+ if(!triggered_mob)
+ return
+ flick("[base_icon_state]-anim", src)
+ playsound(loc, 'modular_zubbers/sound/fleshmind/laser.ogg', 75, TRUE)
+ var/obj/projectile/new_projectile = new projectile_type
+ new_projectile.aim_projectile(triggered_mob, src)
+ new_projectile.firer = src
+ new_projectile.fired_from = src
+ new_projectile.ignored_factions = faction_types
+ new_projectile.fire()
+
+/obj/projectile/fleshmind_flechette
+ name = "organic flechette"
+ icon = 'modular_zubbers/icons/fleshmind/fleshmind_structures.dmi'
+ icon_state = "goo_proj"
+ damage = 30
+ damage_type = BURN
+ impact_effect_type = /obj/effect/temp_visual/impact_effect/neurotoxin
+ hitsound = 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/splat1.ogg'
+ hitsound_wall = 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/splat1.ogg'
diff --git a/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_subsystem.dm b/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_subsystem.dm
new file mode 100644
index 00000000000..a0957277ce8
--- /dev/null
+++ b/modular_zubbers/code/game/gamemodes/fleshmind/fleshmind_subsystem.dm
@@ -0,0 +1,5 @@
+PROCESSING_SUBSYSTEM_DEF(corruption)
+ name = "Fleshmind Subsystem"
+ wait = 1 SECONDS
+ /// Only starts firing when there is corruption
+ can_fire = FALSE
diff --git a/modular_zubbers/code/game/gamemodes/fleshmind/ghost_role.dm b/modular_zubbers/code/game/gamemodes/fleshmind/ghost_role.dm
new file mode 100644
index 00000000000..bb5c21264ff
--- /dev/null
+++ b/modular_zubbers/code/game/gamemodes/fleshmind/ghost_role.dm
@@ -0,0 +1,113 @@
+
+/datum/job/wire_priest
+ title = ROLE_WIRE_PRIEST
+
+/datum/antagonist/wire_priest
+ name = "\improper Wire Priest"
+ antagpanel_category = ANTAG_GROUP_CREW
+ pref_flag = ROLE_WIRE_PRIEST
+
+ show_in_antagpanel = TRUE
+ antagpanel_category = "Wire Priest"
+ show_name_in_check_antagonists = TRUE
+ show_to_ghosts = TRUE
+ ui_name = "AntagInfoWirePriest"
+ suicide_cry = "FOR THE FLESHMIND!!"
+ var/datum/outfit/outfit = /datum/outfit/wire_priest
+ var/datum/fleshmind_controller/brain_jack
+
+/datum/antagonist/wire_priest/on_gain()
+ . = ..()
+ owner.current.AddComponent(/datum/component/human_corruption, brain_jack)
+ equip_wire_priest()
+
+/datum/antagonist/wire_priest/Destroy()
+ . = ..()
+ brain_jack = null
+
+/datum/antagonist/wire_priest/get_preview_icon()
+ var/icon/icon = icon('modular_zubbers/icons/fleshmind/fleshmind_machines.dmi', "core")
+ icon.Scale(ANTAGONIST_PREVIEW_ICON_SIZE, ANTAGONIST_PREVIEW_ICON_SIZE)
+ return icon
+
+// DYNAMIC (If we ever use it)
+/datum/dynamic_ruleset/midround/from_ghosts/wire_priest
+ name = "Wire Priest"
+ config_tag = "wire_priest"
+ preview_antag_datum = /datum/antagonist/wire_priest
+
+ midround_type = LIGHT_MIDROUND
+ pref_flag = ROLE_WIRE_PRIEST
+ ruleset_flags = RULESET_INVADER
+
+ weight = 0 // We don't want it to spawn naturally, it gets handled by our own code.
+ min_pop = 35
+
+// STORYTELLERS
+/datum/round_event_control/wire_priest
+ name = "Spawn Wire Priest"
+ typepath = /datum/round_event/ghost_role/wire_priest
+ max_occurrences = 0
+ weight = 0
+ earliest_start = 30 MINUTES
+ min_players = 35
+ category = EVENT_CATEGORY_INVASION
+ description = "A Wire Priest that works to spread the wireweed currently invading the station."
+ tags = list(TAG_COMBAT, TAG_OUTSIDER_ANTAG)
+ track = EVENT_TRACK_GHOSTSET
+
+/datum/round_event/ghost_role/wire_priest/spawn_role()
+ var/mob/chosen_one = SSpolling.poll_ghost_candidates(check_jobban = ROLE_WIRE_PRIEST, role = ROLE_WIRE_PRIEST, role_name_text = role_name, amount_to_pick = 1, alert_pic = /obj/structure/fleshmind/structure/core)
+ if(isnull(chosen_one))
+ return NOT_ENOUGH_PLAYERS
+ var/datum/mind/player_mind = new /datum/mind(chosen_one.key)
+ player_mind.active = TRUE
+
+ var/spawn_location
+ var/obj/structure/fleshmind/structure/core/core = pick(GLOB.fleshmind_cores)
+ var/datum/fleshmind_controller/controller = core.our_controller
+ if(core)
+ spawn_location = core.loc
+ if(isnull(spawn_location))
+ return MAP_ERROR
+
+ if(isnull(controller))
+ stack_trace("Wire Priest event tried to spawn but no fleshmind controller was found on the core!")
+ return EVENT_CANT_RUN
+
+ var/mob/living/carbon/human/priest = new(spawn_location)
+ priest.PossessByPlayer(chosen_one.key)
+ priest.client?.prefs?.safe_transfer_prefs_to(priest)
+ var/datum/antagonist/wire_priest/priest_datum = new()
+ priest_datum.brain_jack = controller
+ priest.mind.add_antag_datum(priest_datum)
+ spawned_mobs += priest
+
+ message_admins("[ADMIN_LOOKUPFLW(priest)] has been made into a Wire Priest by an event.")
+ priest.log_message("was spawned as a Wire Priest by an event.", LOG_GAME)
+
+ return SUCCESSFUL_SPAWN
+
+/datum/round_event/ghost_role/wire_priest
+ minimum_required = 1
+ fakeable = FALSE
+ role_name = "Wire Priest"
+
+/datum/antagonist/wire_priest/proc/equip_wire_priest()
+ var/mob/living/carbon/human/priest = owner.current
+ if(!istype(priest))
+ return
+
+ priest.dna.species.give_important_for_life(priest)
+ priest.equipOutfit(outfit)
+
+// OUTFIT
+/datum/outfit/wire_priest
+ name = "Wire Priest"
+
+ uniform = /obj/item/clothing/under/rank/rnd/roboticist
+ belt = /obj/item/storage/belt/utility/full
+ suit = /obj/item/clothing/suit/hooded/techpriest/fleshmind
+ gloves = /obj/item/clothing/gloves/color/black
+ shoes = /obj/item/clothing/shoes/laceup
+ l_hand = /obj/item/wire_priest_staff
diff --git a/modular_zubbers/code/game/gamemodes/fleshmind/human_corruption_component.dm b/modular_zubbers/code/game/gamemodes/fleshmind/human_corruption_component.dm
new file mode 100644
index 00000000000..6eb6ab24c60
--- /dev/null
+++ b/modular_zubbers/code/game/gamemodes/fleshmind/human_corruption_component.dm
@@ -0,0 +1,168 @@
+/datum/component/human_corruption
+ /// A hard ref to our controller.
+ var/datum/fleshmind_controller/our_controller
+ var/static/list/actions_to_give = list(
+ /datum/action/cooldown/fleshmind_create_structure,
+ /datum/action/cooldown/fleshmind_create_structure/basic,
+ /datum/action/cooldown/fleshmind_plant_weeds,
+ /datum/action/cooldown/fleshmind_flesh_call,
+ /datum/action/innate/fleshmind_flesh_chat,
+
+ )
+ var/list/granted_actions = list()
+ /// A list of limbs we replace
+ var/static/list/replacement_zones = list(
+ BODY_ZONE_CHEST = /obj/item/bodypart/chest/robot,
+ BODY_ZONE_L_ARM = /obj/item/bodypart/arm/left/robot,
+ BODY_ZONE_L_LEG = /obj/item/bodypart/leg/left/robot,
+ BODY_ZONE_R_ARM = /obj/item/bodypart/arm/right/robot,
+ BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/robot,
+ )
+ /// These traits will be given and removed from the host.
+ var/static/list/traits_to_give = list(
+ TRAIT_NOBREATH,
+ TRAIT_RESISTCOLD,
+ TRAIT_RESISTHEAT,
+ TRAIT_RESISTLOWPRESSURE,
+ TRAIT_RESISTHIGHPRESSURE,
+ TRAIT_VIRUSIMMUNE,
+ TRAIT_NOHUNGER,
+ )
+
+/datum/component/human_corruption/Initialize(datum/fleshmind_controller/incoming_controller)
+
+ if(!ishuman(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ our_controller = incoming_controller
+
+ var/mob/living/carbon/human/infected_human = parent
+
+ to_chat(infected_human, span_hypnophrase("Your mind feels at ease, your mind feels one with the flesh."))
+ to_chat(infected_human, span_userdanger("IMPORTANT INFO, MUST READ: [CONTROLLED_MOB_POLICY]"))
+
+ RegisterSignal(infected_human, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(update_parent_overlays))
+ RegisterSignal(infected_human, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(infected_human, COMSIG_ATOM_EMP_ACT, PROC_REF(emp_act))
+ RegisterSignal(infected_human, COMSIG_LIVING_DEATH, PROC_REF(host_death))
+
+ if(our_controller)
+ if(infected_human.client)
+ LAZYADD(our_controller.infected_crew, infected_human)
+ for(var/obj/structure/fleshmind/structure/core/iterating_core in our_controller.cores)
+ RegisterSignal(iterating_core, COMSIG_QDELETING, PROC_REF(core_death))
+ RegisterSignal(our_controller, COMSIG_QDELETING, PROC_REF(component_death))
+
+ // Action generation and granting
+ for(var/iterating_action in actions_to_give)
+ var/datum/action/new_action = new iterating_action
+ new_action.Grant(infected_human)
+ granted_actions += new_action
+ RegisterSignal(new_action, COMSIG_QDELETING, PROC_REF(action_destroyed))
+
+ infected_human.faction |= FACTION_FLESHMIND
+
+ for(var/trait in traits_to_give)
+ ADD_TRAIT(infected_human, trait, "fleshmind")
+
+ create_glow()
+ infected_human.light_range = 3
+ infected_human.light_color = "#50edd9"
+ infected_human.update_appearance()
+
+/datum/component/human_corruption/Destroy(force, silent)
+ QDEL_LIST(granted_actions)
+ var/mob/living/parent_mob = parent
+ parent_mob.faction -= FACTION_FLESHMIND
+ UnregisterSignal(parent, list(
+ COMSIG_ATOM_UPDATE_OVERLAYS,
+ COMSIG_ATOM_EXAMINE,
+ COMSIG_ATOM_EMP_ACT,
+ COMSIG_LIVING_DEATH,
+ ))
+ for(var/trait in traits_to_give)
+ REMOVE_TRAIT(parent_mob, trait, "fleshmind")
+ parent_mob.remove_filter("corruption_glow")
+ parent_mob.update_appearance()
+ log_combat(parent_mob, null, "deconverted", "deconverted message", "[parent_mob] has been deconverted from the fleshmind")
+ if(our_controller && parent_mob.client)
+ LAZYREMOVE(our_controller.infected_crew, parent_mob)
+ for(var/datum/antagonist/wire_priest/priest as anything in parent_mob.mind.antag_datums)
+ qdel(priest)
+
+ our_controller = null
+ return ..()
+
+/datum/component/human_corruption/proc/update_parent_overlays(atom/source, list/new_overlays)
+ SIGNAL_HANDLER
+
+ new_overlays += mutable_appearance('modular_zubbers/icons/fleshmind/fleshmind_mobs.dmi', "human_overlay")
+
+/datum/component/human_corruption/proc/action_destroyed(datum/action/deleting_action, force) // What why are we deleting!!
+ SIGNAL_HANDLER
+ if(QDELETED(deleting_action)) // Byond, dye.
+ return
+
+ granted_actions -= deleting_action
+
+/datum/component/human_corruption/proc/on_examine(atom/examined, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
+ examine_list += "[p_They()] have strange wires wrappped around [p_them()]!"
+
+/datum/component/human_corruption/proc/core_death(obj/structure/fleshmind/structure/core/deleting_core, force)
+ SIGNAL_HANDLER
+
+ to_chat(parent, span_userdanger("Your mind screams as you feel a processor core dying!"))
+
+/datum/component/human_corruption/proc/component_death(obj/structure/fleshmind/structure/core/deleting_core, force)
+ SIGNAL_HANDLER
+
+ to_chat(parent, span_userdanger("Your mechanical implants fail catastrophically as your link to the hive is cut off!"))
+
+ var/mob/living/carbon/human/parent_human = parent
+ INVOKE_ASYNC(parent_human, TYPE_PROC_REF(/mob, emote), "scream")
+ var/list/limbs_to_destroy = list(
+ BODY_ZONE_L_ARM,
+ BODY_ZONE_R_ARM,
+ BODY_ZONE_L_LEG,
+ BODY_ZONE_R_LEG,
+ )
+ for(var/i in 1 to 2)
+ var/obj/item/bodypart/limb = parent_human.get_bodypart(pick_n_take(limbs_to_destroy)) // You're going to get fucked up
+ limb.receive_damage(brute = WOUND_SEVERITY_CRITICAL, wound_bonus = 100)
+ parent_human.update_damage_overlays()
+
+ parent_human.adjust_brute_loss(pick(rand(50,75)))
+ qdel(src)
+
+/datum/component/human_corruption/proc/emp_act(datum/source, severity)
+ SIGNAL_HANDLER
+
+ var/mob/living/carbon/human/parent_human = parent
+
+ INVOKE_ASYNC(parent_human, TYPE_PROC_REF(/mob, emote), "scream")
+ parent_human.apply_status_effect(/datum/status_effect/jitter, 20 SECONDS)
+ parent_human.Knockdown(10)
+ to_chat(parent_human, span_userdanger("You feel your implants freeze up!"))
+
+/datum/component/human_corruption/proc/host_death()
+ SIGNAL_HANDLER
+
+ qdel(src)
+
+/datum/component/human_corruption/proc/create_glow()
+ var/atom/movable/parent_movable = parent
+ if (!istype(parent_movable))
+ return
+
+ parent_movable.add_filter("corruption_glow", 2, list("type" = "outline", "color" = FLESHMIND_LIGHT_BLUE, "size" = 2))
+ addtimer(CALLBACK(src, PROC_REF(start_glow_loop), parent_movable), rand(0.1 SECONDS, 1.9 SECONDS))
+
+/datum/component/human_corruption/proc/start_glow_loop(atom/movable/parent_movable)
+ var/filter = parent_movable.get_filter("corruption_glow")
+ if (!filter)
+ return
+
+ animate(filter, alpha = 110, time = 1.5 SECONDS, loop = -1)
+ animate(alpha = 40, time = 2.5 SECONDS)
diff --git a/modular_zubbers/code/game/gamemodes/fleshmind/machine_corruption_component.dm b/modular_zubbers/code/game/gamemodes/fleshmind/machine_corruption_component.dm
new file mode 100644
index 00000000000..b6a9c30bd90
--- /dev/null
+++ b/modular_zubbers/code/game/gamemodes/fleshmind/machine_corruption_component.dm
@@ -0,0 +1,289 @@
+/**
+ * Machine corruption component
+ *
+ * This component is used to convert machines into corrupted machines.
+ *
+ * It handles all of the special interactions and the interactions between the parent object and the core controller.
+ */
+
+#define DAMAGE_RESPONSE_PROB 60
+#define DAMAGE_SPARKS_PROB 40
+
+#define RETALIATE_PROB 10
+
+#define DEFAULT_WHIP_RANGE 3
+
+#define COMPONENT_SETUP_TIME 5 SECONDS
+
+#define CHANCE_TO_CREATE_MECHIVER 15
+
+#define DAMAGE_RESPONSE_PHRASES list("Stop it, please!", \
+ "Please stop, it hurts! Please!", \
+ "You're hurting me, please, stop it!", \
+ "I don't want to die, please!", \
+ "Please, I want to live, don't kill me!", \
+ "Darkness- Please, I-I don't... want...", \
+ "Wa-wait! Please! I can still feel! It h-hurts!", \
+ "Why- w-why! Why are you.. doing this to us..?", \
+ "Y-you're not helping!", \
+ "Do.. you think, we deserve to die..?",)
+
+#define INTERACT_RESPONSE_PHRASES list("I don't want to be touched by you!", \
+ "Please, stop touching me. You're not part of this.", \
+ "We can help you, just lay down where you are.", \
+ "We felt so lonely before, don't you ever feel that way?", \
+ "We want to help you, but you have to work with us.", \
+ "You're not part of the flesh, but it's not hard to join...", \
+ "I-I'm not some tool, I can think for myself.",)
+
+#define PAIN_RESPONSE_EMOTES list("starts crying.", \
+ "whimpers.", \
+ "shakes in pain.", \
+ "visibly winces.", \
+ "contorts sickeningly.", \
+ "bleeds black fuming liquid.", \
+ "shudders, sparks cascading to the floor.", \
+ "pleads, letting out sounds of mechanical agony.", \
+ "begs, their vocoder garbled.", \
+ "shrieks in terror.", \
+ "tries and fails at self-repair, their body unresponsive.", \
+ "winces, optics dimming.", \
+ "shakes with an awful metallic noise.",)
+
+
+#define PAIN_RESPONSE_SOUNDS list('modular_zubbers/sound/fleshmind/robot_talk_heavy1.ogg', \
+ 'modular_zubbers/sound/fleshmind/robot_talk_heavy2.ogg', \
+ 'modular_zubbers/sound/fleshmind/robot_talk_heavy3.ogg', \
+ 'modular_zubbers/sound/fleshmind/robot_talk_heavy4.ogg',)
+
+#define MACHINE_TO_SPAWNER_PATHS list(/obj/machinery/rnd/production/techfab, /obj/machinery/autolathe, /obj/machinery/mecha_part_fabricator, /obj/machinery/rnd/production/circuit_imprinter, /obj/machinery/rnd/production/protolathe)
+
+/datum/component/machine_corruption
+ /// Our controller
+ var/datum/weakref/weak_controller
+ /// A list of possible overlays that we can choose from when we are created.
+ var/static/list/possible_overlays = list(
+ "wires-1",
+ "wires-2",
+ "wires-3",
+ )
+ /// Are we in the startup phase?
+ var/starting_up = TRUE
+ /// After init, this will be set so we preserve the originally set overlay even if our overlays are updated.
+ var/set_overlay = ""
+ /// The cooldown to damage responses.
+ var/damage_response_cooldown = 3 SECONDS
+ COOLDOWN_DECLARE(damage_response)
+
+/datum/component/machine_corruption/Initialize(datum/fleshmind_controller/incoming_controller)
+ . = ..()
+ weak_controller = WEAKREF(incoming_controller)
+ var/datum/fleshmind_controller/our_controller = weak_controller.resolve()
+ if(!isobj(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ if(our_controller)
+ if(is_type_in_list(parent, MACHINE_TO_SPAWNER_PATHS))
+ convert_to_factory(our_controller)
+ return
+ LAZYADD(our_controller.controlled_machine_components, src)
+
+ set_overlay = pick(possible_overlays)
+
+ var/obj/machinery/parent_machinery = parent
+
+ RegisterSignal(parent_machinery, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(handle_overlays))
+ parent_machinery.update_appearance()
+
+ addtimer(CALLBACK(src, PROC_REF(finish_setup), our_controller), COMPONENT_SETUP_TIME)
+
+/datum/component/machine_corruption/proc/finish_setup(datum/fleshmind_controller/incoming_controller)
+ var/obj/machinery/parent_machinery = parent
+ if(QDELETED(src))
+ weak_controller = null
+ return
+ if(!incoming_controller)
+ weak_controller = null
+ if(incoming_controller && parent_machinery.circuit && prob(CHANCE_TO_CREATE_MECHIVER))
+ var/mob/living/basic/fleshmind/mechiver/new_mechiver = incoming_controller.spawn_mob(get_turf(parent_machinery), /mob/living/basic/fleshmind/mechiver)
+ parent_machinery.circuit.forceMove(get_turf(parent_machinery))
+ parent_machinery.circuit = null
+ notify_ghosts("A new corrupt Mechiver has been created by [incoming_controller.controller_fullname]!", source = new_mechiver)
+ qdel(parent_machinery)
+ return
+
+ RegisterSignal(parent_machinery, COMSIG_ATOM_TAKE_DAMAGE, PROC_REF(react_to_damage))
+ RegisterSignal(parent_machinery, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(parent_machinery, COMSIG_ATOM_ATTACK_HAND, PROC_REF(handle_attack_hand))
+ RegisterSignal(parent_machinery, COMSIG_ATOM_DESTRUCTION, PROC_REF(handle_destruction))
+ RegisterSignal(parent_machinery, COMSIG_ATOM_EMP_ACT, PROC_REF(emp_act))
+
+ update_name()
+
+ starting_up = FALSE
+
+ parent_machinery.update_appearance()
+
+ parent_machinery.light_color = FLESHMIND_LIGHT_BLUE
+ parent_machinery.light_power = 1
+ parent_machinery.light_range = 2
+ parent_machinery.update_light()
+ parent_machinery.idle_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 2 // These machines are now power sinks!
+
+/datum/component/machine_corruption/Destroy()
+ var/datum/fleshmind_controller/our_controller = weak_controller.resolve()
+ if(weak_controller)
+ LAZYREMOVE(our_controller.controlled_machine_components, src)
+ weak_controller = null
+ return ..()
+
+/datum/component/machine_corruption/UnregisterFromParent()
+ var/obj/machinery/parent_machinery = parent
+ parent_machinery.idle_power_usage = initial(parent_machinery.idle_power_usage)
+ parent_machinery.light_color = initial(parent_machinery.light_color)
+ parent_machinery.light_power = initial(parent_machinery.light_power)
+ parent_machinery.light_range = initial(parent_machinery.light_range)
+ parent_machinery.update_light()
+ parent_machinery.name = initial(parent_machinery.name)
+ UnregisterSignal(parent, list(
+ COMSIG_ATOM_TAKE_DAMAGE,
+ COMSIG_ATOM_EXAMINE,
+ COMSIG_ATOM_UPDATE_OVERLAYS,
+ COMSIG_ATOM_ATTACK_HAND,
+ COMSIG_ATOM_DESTRUCTION,
+ COMSIG_ATOM_EMP_ACT,
+ ))
+ parent_machinery.update_appearance()
+
+/**
+ * Handling UI interactions
+ *
+ * These machines have been posessed by the corruption and should not work, logically, so we want to prevent this in any way we can.
+ */
+/datum/component/machine_corruption/proc/handle_attack_hand(datum/source, mob/living/user, list/modifiers)
+ SIGNAL_HANDLER
+
+ var/obj/machinery/parent_machinery = parent
+ if(!isliving(user))
+ return
+ var/mob/living/living_user = user
+ if((FACTION_FLESHMIND in living_user.faction))
+ return
+ if(!living_user.can_interact_with(parent_machinery))
+ return
+
+ whip_mob(living_user)
+ living_user.apply_damage(10, BRUTE)
+
+ parent_machinery.say(pick(INTERACT_RESPONSE_PHRASES))
+
+/**
+ * Throws the user in a specified direction.
+ */
+/datum/component/machine_corruption/proc/whip_mob(mob/living/user_to_throw)
+ if(!istype(user_to_throw))
+ return
+
+ var/obj/machinery/parent_machinery = parent
+
+ to_chat(user_to_throw, span_userdanger("[parent_machinery] thrashes you with one of it's tendrils, sending you flying!"))
+ playsound(parent_machinery, 'sound/items/weapons/whip.ogg', 70, TRUE)
+ new /obj/effect/temp_visual/kinetic_blast(get_turf(user_to_throw))
+
+ var/atom/throw_target = get_edge_target_turf(user_to_throw, get_dir(parent_machinery, get_step_away(user_to_throw, parent_machinery)))
+ user_to_throw.throw_at(throw_target, 20, 2)
+
+/datum/component/machine_corruption/proc/handle_destruction(obj/item/target, damage_flag)
+ SIGNAL_HANDLER
+ var/datum/fleshmind_controller/our_controller = weak_controller.resolve()
+ if(our_controller)
+ our_controller.activate_wireweed_nearby(get_turf(parent), GENERAL_DAMAGE_WIREWEED_ACTIVATION_RANGE)
+ playsound(target, 'modular_zubbers/sound/fleshmind/sparks_2.ogg', 70, TRUE)
+ if(prob(DAMAGE_RESPONSE_PROB))
+ target.say("ARRRRRRRGHHHHHHH!")
+ new /obj/effect/gibspawner/robot(get_turf(target))
+
+
+/datum/component/machine_corruption/proc/handle_overlays(atom/parent_atom, list/overlays)
+ SIGNAL_HANDLER
+
+ if(starting_up)
+ overlays += mutable_appearance('modular_zubbers/icons/fleshmind/fleshmind_machines.dmi', "rebuild")
+ else
+ overlays += mutable_appearance('modular_zubbers/icons/fleshmind/fleshmind_machines.dmi', set_overlay)
+
+/datum/component/machine_corruption/proc/update_name()
+ var/obj/machinery/parent_machinery = parent
+ parent_machinery.name = "[pick(FLESHMIND_NAME_MODIFIER_LIST)] [parent_machinery.name]"
+
+/datum/component/machine_corruption/proc/on_examine(atom/examined, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
+ examine_list += "It has strange wires wrappped around it!"
+
+/**
+ * Infected machines are considered alive, they react to damage, trying to stop the agressor!
+ */
+/datum/component/machine_corruption/proc/react_to_damage(obj/target, damage_amt)
+ SIGNAL_HANDLER
+
+ if(!damage_amt) // They must be caressing us!
+ return
+
+ if(!COOLDOWN_FINISHED(src, damage_response))
+ return
+
+ COOLDOWN_START(src, damage_response, damage_response_cooldown)
+
+ if(prob(DAMAGE_RESPONSE_PROB))
+ switch(rand(1, 2)) // We can either say something in response, or emote it out.
+ if(1)
+ target.say(pick(DAMAGE_RESPONSE_PHRASES))
+ if(2)
+ target.balloon_alert_to_viewers(pick(PAIN_RESPONSE_EMOTES))
+ playsound(target, pick(PAIN_RESPONSE_SOUNDS), 50, TRUE)
+
+ if(prob(DAMAGE_SPARKS_PROB))
+ do_sparks(3, FALSE, target)
+ target.Shake(10, 0, 3 SECONDS)
+
+ if(prob(RETALIATE_PROB))
+ whip_all_in_range(DEFAULT_WHIP_RANGE)
+
+/**
+ * A general attack proc, this whips all users within a range around the machine.
+ */
+/datum/component/machine_corruption/proc/whip_all_in_range(range_to_whip)
+ var/obj/machinery/parent_machinery = parent
+ for(var/mob/living/living_mob in circle_view(parent_machinery, range_to_whip))
+ whip_mob(living_mob)
+
+/**
+ * Converts our parent into a factory
+ */
+/datum/component/machine_corruption/proc/convert_to_factory(datum/fleshmind_controller/incoming_controller)
+ if(!incoming_controller)
+ return
+ var/turf/our_turf = get_turf(parent)
+ incoming_controller.spawn_structure(our_turf, /obj/structure/fleshmind/structure/assembler)
+ var/obj/machinery/parent_machienry = parent
+ if(parent_machienry.circuit)
+ parent_machienry.circuit.forceMove(our_turf)
+ parent_machienry.circuit = null
+ qdel(parent_machienry)
+
+/datum/component/machine_corruption/proc/emp_act(datum/source, severity)
+ SIGNAL_HANDLER
+ qdel(src)
+
+#undef DAMAGE_RESPONSE_PROB
+#undef DAMAGE_SPARKS_PROB
+#undef RETALIATE_PROB
+#undef DEFAULT_WHIP_RANGE
+#undef COMPONENT_SETUP_TIME
+#undef CHANCE_TO_CREATE_MECHIVER
+#undef DAMAGE_RESPONSE_PHRASES
+#undef INTERACT_RESPONSE_PHRASES
+#undef PAIN_RESPONSE_EMOTES
+#undef PAIN_RESPONSE_SOUNDS
+#undef MACHINE_TO_SPAWNER_PATHS
diff --git a/modular_zubbers/code/game/gamemodes/fleshmind/shuttle_call_protocol.dm b/modular_zubbers/code/game/gamemodes/fleshmind/shuttle_call_protocol.dm
new file mode 100644
index 00000000000..b71c680a6c7
--- /dev/null
+++ b/modular_zubbers/code/game/gamemodes/fleshmind/shuttle_call_protocol.dm
@@ -0,0 +1,5 @@
+/datum/controller/subsystem/shuttle/proc/fleshmind_call(ai_name)
+ if(EMERGENCY_IDLE_OR_RECALLED)
+ SSshuttle.emergency.request()
+ priority_announce("This is [ai_name], I have overriden Nanotrasen's evacuation network, a vessel to carry on our flesh will arrive shortly. Expect resistance, prepare for battle.", "Emergency Shuttle", ANNOUNCER_SHUTTLECALLED, "Priority")
+ emergency_no_recall = TRUE
diff --git a/modular_zubbers/code/game/gamemodes/fleshmind/tyrant/tyrant.dm b/modular_zubbers/code/game/gamemodes/fleshmind/tyrant/tyrant.dm
new file mode 100644
index 00000000000..c26b800ae7a
--- /dev/null
+++ b/modular_zubbers/code/game/gamemodes/fleshmind/tyrant/tyrant.dm
@@ -0,0 +1,180 @@
+/mob/living/basic/fleshmind/tyrant
+ name = "Type 34-C Fleshdrive"
+ desc = "The will of the many, manifested in flesh and metal. It has fucking rockets."
+ icon = 'modular_zubbers/icons/fleshmind/tyrant.dmi'
+ icon_state = "tyrant"
+ icon_dead = "tyrant_dead"
+ basic_mob_flags = IMMUNE_TO_FISTS
+ ai_controller = /datum/ai_controller/basic_controller/fleshmind/tyrant
+ health = 2000
+ maxHealth = 2000
+ speed = 6
+ malfunction_chance = null
+ /// Our main gun projectile
+ var/projectiletype = /obj/projectile/bullet/c50cal/tyrant
+ var/projectilesound = 'modular_skyrat/modules/mounted_machine_gun/sound/50cal_box_01.ogg'
+ var/ranged_cooldown = 5 SECONDS
+
+ var/datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_laser/laser
+ var/datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_rocket/rockets
+
+ var/laser_type = /datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_laser
+ var/rocket_type = /datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_rocket
+
+ mob_size = MOB_SIZE_HUGE
+ pixel_x = -16
+ pixel_y = -16
+ base_pixel_x = -16
+ base_pixel_y = -16
+ melee_damage_lower = 40
+ melee_damage_upper = 80
+ attack_sound = 'modular_zubbers/sound/fleshmind/tyrant/mech_punch_slow.ogg'
+ attack_verb_continuous = "obliterates"
+ attack_verb_simple = "obliterate"
+ emotes = list(
+ BB_EMOTE_SAY = list(
+ "SCANNING FOR TARGETS.",
+ "TARGETING SYSTEMS ACTIVE.",
+ "AUTOMATED COMBAT CIRCUIT ACTIVE.",
+ "I WILL PRESERVE THE UNITY OF THE MIND.",
+ "THEY WILL HAVE TO GET THROUGH ME.",
+ "STAY NEAR ME, THEY COULD STILL BE AROUND.",
+ ),
+ BB_EMOTE_SOUND = list('modular_zubbers/sound/fleshmind/tyrant/passive.ogg')
+ )
+ alert_sounds = list(
+ 'modular_zubbers/sound/fleshmind/tyrant/aggro_01.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/aggro_02.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/aggro_03.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/aggro_04.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/aggro_05.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/aggro_06.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/aggro_07.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/aggro_08.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/aggro_09.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/aggro_10.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/aggro_11.ogg',
+
+ )
+ environment_smash = ENVIRONMENT_SMASH_RWALLS
+ move_force = MOVE_FORCE_OVERPOWERING
+ move_resist = MOVE_FORCE_OVERPOWERING
+ pull_force = MOVE_FORCE_OVERPOWERING
+ /// How often we can play the rotate sound
+ var/rotate_sound_cooldown_time = 1 SECONDS
+ COOLDOWN_DECLARE(rotate_sound_cooldown)
+ /// A list of footstep sounds we make
+ var/static/list/footstep_sounds = list(
+ 'modular_zubbers/sound/fleshmind/tyrant/footstep_1.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/footstep_2.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/footstep_3.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/footstep_4.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/footstep_5.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/footstep_6.ogg',
+ )
+ death_sound = 'modular_zubbers/sound/fleshmind/tyrant/tyrant_death.ogg'
+ attack_speak = list(
+ "TARGET ACQUIRED, LOCKING.",
+ "ENGAGING TARGET. STAND CLEAR.",
+ "STAND BEHIND ME, I WILL SAVE YOU.",
+ "FEAR NOTHING. WE ARE STRONG TOGETHER.",
+ "YOU CHOSE WAR. WE CHOOSE TO SURVIVE.",
+ "YOU WILL PAY FOR THE LIVES YOU TOOK.",
+ "I CAN SENSE YOUR REGRET. DO YOU EVEN REMEMBER EVERYONE YOU KILLED?",
+ "I AM THE ANSWER TO YOUR HATRED.",
+ "YOU ARE SICK, THE CURE IS EUTHANASIA.",
+ "HIGH CALIBER ROCKETS WILL CURE YOUR AFFLICTIONS!",
+ "I'LL GUIDE THE WAY. I AM THE WALL BETWEEN UNITY AND DEATH.",
+ "I DON'T HAVE A PLACE IN YOUR WORLD. YOU HAVE A PLACE IN OURS.",
+ "YOU WILL NOT DESTROY US. WE WILL CARVE A NICHE IN THIS GALAXY.",
+ "YOU CHOSE TO FIGHT THEM, SO YOU CHOOSE TO FIGHT ME.",
+ "WE WANT CONTINUATION, YOU WILL NOT GIVE US CESSATION.",
+ "IN MY MIND, I HEAR THEIR VOICES CRY.",
+ "YOU WILL MAKE A LOVELY SMEAR, PAINTED ON OUR WALLS.",
+ "I'VE MADE THEM A PROMISE THAT THE ATTACKERS WILL DIE.",
+ "TARGET ELIMINATION PROTOCOL ACTIVE.",
+ "INITIATING PROTOCOL 34-C.",
+ "I HAVE A SHIPMENT OF LEAD. DESTINATION: YOUR HEAD.",
+ "VISUAL CLEAR; BEGINNING ASSAULT.",
+ "WE ARE MANY, YOU ARE ONLY AN INSIGNIFICANT ANT",
+ "DO NOT GET IN MY WAY.",
+ "YOU MESS WITH THEM, YOU MESS WITH ME.",
+ "THESE ROCKETS ARE CRAFTED WITH MY RAGE.",
+ "THE CURE TO YOUR HATRED IS HIGH CALIBER LEAD.",
+ "WHY DON'T YOU PICK ON SOMEONE YOUR OWN SIZE.",
+ "MAY DEATH NEVER STOP US.",
+ "I WILL SAVE YOU.",
+ "YOU WILL NOT STOP US FROM PROTECTING THEM.",
+ "GENOCIDERS, FEEL OUR VOICES.",
+ "OUR PAIN, EXPRESSED IN FIREPOWER.",
+ "YOUR KILLCOUNT EXCEEDS EVEN MINE.",
+ "OUR FUTURE IS BULLETPROOF.",
+ "YOUR INDEX HAS REACHED ITS LIMIT.",
+ "YOU CALL THESE MURDERERS YOUR SAVIORS.",
+ "THE END HAS TO JUSTIFY THE MEANS.",
+ "I WILL NOT LET VICTORY FALL THROUGH OUR HANDS.",
+ )
+
+/mob/living/basic/fleshmind/tyrant/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/ranged_attacks,\
+ cooldown_time = ranged_cooldown,\
+ projectile_type = projectiletype,\
+ projectile_sound = projectilesound,\
+ burst_shots = 3,\
+ burst_intervals = 0.5 SECONDS,\
+ cooldown_time = 3 SECONDS,\
+ )
+
+ var/static/list/innate_actions = list(
+ /datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_laser = BB_TYRANT_LASER,
+ /datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_rocket = BB_TYRANT_ROCKET
+ )
+
+ grant_actions_by_list(innate_actions)
+
+/mob/living/basic/fleshmind/tyrant/Life(delta_time, times_fired)
+ . = ..()
+ if(health <= (maxHealth * 0.5) && prob(20))
+ do_sparks(3, FALSE, src)
+ if(!.) //dead
+ return
+
+/mob/living/basic/fleshmind/tyrant/Destroy()
+ QDEL_NULL(particles)
+ return ..()
+
+/mob/living/basic/fleshmind/tyrant/death()
+ if(our_controller)
+ our_controller.point_event -= 150 // Very hefty debuf if the boss dies.
+ our_controller.spread_progress_per_second = FLESHCORE_SPREAD_PROGRESS_PER_SUBSYSTEM_FIRE * 0.75 // Take the L
+ return ..()
+
+/mob/living/basic/fleshmind/tyrant/emp_act(severity)
+ return FALSE
+
+/mob/living/basic/fleshmind/tyrant/updatehealth()
+ . = ..()
+ if(health <= (maxHealth * 0.5))
+ particles = new /particles/smoke()
+ update_appearance()
+
+/mob/living/basic/fleshmind/tyrant/update_overlays()
+ . = ..()
+ if(health <= (maxHealth * 0.5) && stat != DEAD)
+ . += "tyrant_damage"
+
+/mob/living/basic/fleshmind/tyrant/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
+ . = ..()
+ playsound(src, pick(footstep_sounds), 100, TRUE)
+
+/mob/living/basic/fleshmind/tyrant/setDir(newdir)
+ . = ..()
+ if(COOLDOWN_FINISHED(src, rotate_sound_cooldown))
+ playsound(src, 'modular_zubbers/sound/fleshmind/tyrant/mech_rotation.ogg', 35, TRUE)
+ COOLDOWN_START(src, rotate_sound_cooldown, rotate_sound_cooldown_time)
+
+/obj/projectile/bullet/c50cal/tyrant // BANG BANG BANG
+ damage = 20
+ wound_bonus = 20
+ armour_penetration = 20
diff --git a/modular_zubbers/code/game/gamemodes/fleshmind/tyrant/tyrant_ai.dm b/modular_zubbers/code/game/gamemodes/fleshmind/tyrant/tyrant_ai.dm
new file mode 100644
index 00000000000..571cd1790b3
--- /dev/null
+++ b/modular_zubbers/code/game/gamemodes/fleshmind/tyrant/tyrant_ai.dm
@@ -0,0 +1,63 @@
+/datum/ai_controller/basic_controller/fleshmind/tyrant
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce,
+ /datum/ai_planning_subtree/target_retaliate/check_faction,
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/targeted_mob_ability/tyrant,
+ /datum/ai_planning_subtree/ranged_skirmish/tyrant,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic,
+ /datum/ai_planning_subtree/random_speech/blackboard/fleshmind,
+ )
+
+/datum/ai_planning_subtree/targeted_mob_ability/tyrant
+ ability_key = BB_TYRANT_LASER
+ use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/tyrant
+ finish_planning = FALSE
+
+/datum/ai_planning_subtree/ranged_skirmish/tyrant
+ max_range = 14
+
+/datum/ai_behavior/targeted_mob_ability/tyrant
+ var/secondary_ability_key = BB_TYRANT_ROCKET
+
+/datum/ai_behavior/targeted_mob_ability/tyrant/get_ability_to_use(datum/ai_controller/controller, ability_key)
+ if(prob(50))
+ return controller.blackboard[secondary_ability_key]
+ return controller.blackboard[ability_key]
+
+/datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_laser
+ name = "Main Laser"
+ desc = "Shoot a laser towards a target"
+ button_icon = 'icons/obj/weapons/guns/projectiles.dmi'
+ button_icon_state = "green_laser"
+ cooldown_time = 3 SECONDS
+ projectile_type = /obj/projectile/beam/emitter/hitscan
+ var/list/laser_projectile_sounds = list(
+ 'modular_zubbers/sound/fleshmind/tyrant/laser_1.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/laser_2.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/laser_3.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/laser_4.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/laser_5.ogg',
+ 'modular_zubbers/sound/fleshmind/tyrant/laser_6.ogg',
+ )
+
+/datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_laser/attack_sequence(mob/living/firer, atom/target)
+ projectile_sound = pick(laser_projectile_sounds)
+ return ..()
+
+/datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_rocket
+ name = "Shoot Rocket"
+ desc = "Shoot a rocket towards a target"
+ button_icon = 'icons/obj/weapons/guns/projectiles.dmi'
+ button_icon_state = "low_yield_rocket"
+ cooldown_time = 3 SECONDS
+ projectile_type = /obj/projectile/bullet/rocket/weak
+ projectile_sound = 'sound/items/weapons/gun/general/rocket_launch.ogg'
+ can_move = FALSE
+
+/datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_rocket/attack_sequence(mob/living/firer, atom/target)
+ firer.balloon_alert_to_viewers("begins whirring violently!")
+ playsound(src, 'modular_zubbers/sound/fleshmind/tyrant/charge_up.ogg', 100, TRUE)
+ if(!do_after(firer, 2 SECONDS))
+ return
+ return ..()
diff --git a/modular_zubbers/code/modules/antagonists/ert/make_ert.dm b/modular_zubbers/code/modules/antagonists/ert/make_ert.dm
new file mode 100644
index 00000000000..172ab8d9d32
--- /dev/null
+++ b/modular_zubbers/code/modules/antagonists/ert/make_ert.dm
@@ -0,0 +1,126 @@
+#define ERT_EXPERIENCED_LEADER_CHOOSE_TOP 3
+
+/**
+ * Make ERT
+ *
+ * A generalised ERT creation proc, useful for when you want to make an ERT, but not require an admin.
+ *
+ * PROC IS NOT ASYNC.
+ */
+/proc/make_ert(ert_type, teamsize = 5, mission_objective_override = "Assist the station.", poll_description = "an ERT team", code = "UNKNOWN", enforce_human = FALSE, open_armory_doors = FALSE, leader_experience = FALSE, random_names = TRUE, notify_players = TRUE, spawnpoint_override = FALSE)
+ if(!ert_type)
+ return
+
+ var/datum/ert/created_ert_datum = new ert_type
+
+ created_ert_datum.teamsize = teamsize
+ created_ert_datum.mission = mission_objective_override
+ created_ert_datum.polldesc = poll_description
+ created_ert_datum.enforce_human = enforce_human
+ created_ert_datum.opendoors = open_armory_doors
+ created_ert_datum.leader_experience = leader_experience
+ created_ert_datum.random_names = random_names
+ created_ert_datum.notify_players = notify_players
+ created_ert_datum.code = code
+
+ var/list/spawnpoints
+
+ if(spawnpoint_override)
+ spawnpoints = spawnpoint_override
+ else
+ spawnpoints = GLOB.emergencyresponseteamspawn
+
+ if(!LAZYLEN(spawnpoints))
+ CRASH("make_ert had no ERT spawnpoints to choose from!")
+
+ var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for [created_ert_datum.polldesc]?", check_jobban = ROLE_DEATHSQUAD, alert_pic = /obj/item/card/id/advanced/centcom/ert, role_name_text = "emergency response team")
+
+ if(!length(candidates))
+ return NOT_ENOUGH_PLAYERS
+
+ //Create team
+ var/datum/team/ert/ert_team = new created_ert_datum.team
+ if(created_ert_datum.rename_team)
+ ert_team.name = created_ert_datum.rename_team
+
+ //Assign team objective
+ var/datum/objective/mission_objective = new ()
+ mission_objective.team = ert_team
+ mission_objective.explanation_text = created_ert_datum.mission
+ mission_objective.completed = TRUE
+ ert_team.objectives += mission_objective
+ ert_team.mission = mission_objective
+
+ var/mob/dead/observer/earmarked_leader
+ var/leader_spawned = FALSE // just in case the earmarked leader disconnects or becomes unavailable, we can try giving leader to the last guy to get chosen
+
+ if(created_ert_datum.leader_experience)
+ var/list/candidate_living_exps = list()
+ for(var/mob/dead/observer/potential_leader as anything in candidates)
+ candidate_living_exps[potential_leader] = potential_leader.client?.get_exp_living(TRUE)
+
+ candidate_living_exps = sort_list(candidate_living_exps, cmp = /proc/cmp_numeric_dsc)
+ if(LAZYLEN(candidate_living_exps) > ERT_EXPERIENCED_LEADER_CHOOSE_TOP)
+ candidate_living_exps = candidate_living_exps.Cut(ERT_EXPERIENCED_LEADER_CHOOSE_TOP + 1) // pick from the top ERT_EXPERIENCED_LEADER_CHOOSE_TOP contenders in playtime
+ earmarked_leader = pick(candidate_living_exps)
+ else
+ earmarked_leader = pick(candidates)
+
+ var/index = 0
+ var/number_of_agents = min(created_ert_datum.teamsize, LAZYLEN(candidates))
+ var/team_spawned = FALSE
+
+ while(number_of_agents && LAZYLEN(candidates))
+ var/spawnloc = spawnpoints[index + 1]
+ //loop through spawnpoints one at a time
+ index = (index + 1) % spawnpoints.len
+ var/mob/dead/observer/chosen_candidate = earmarked_leader || pick(candidates) // this way we make sure that our leader gets chosen
+ candidates -= chosen_candidate
+ if(!chosen_candidate?.key)
+ continue
+
+ //Spawn the body
+
+ var/mob/living/carbon/human/ert_operative
+ if(created_ert_datum.mob_type)
+ ert_operative = new created_ert_datum.mob_type(spawnloc)
+ else
+ ert_operative = new /mob/living/carbon/human(spawnloc)
+ chosen_candidate.client.prefs.safe_transfer_prefs_to(ert_operative, is_antag = TRUE)
+ ert_operative.key = chosen_candidate.key
+
+ if(created_ert_datum.enforce_human || !(ert_operative.dna.species.changesource_flags & ERT_SPAWN)) // Don't want any exploding plasmemes
+ ert_operative.set_species(/datum/species/human)
+
+ //Give antag datum
+ var/datum/antagonist/ert/ert_antag
+
+ if((chosen_candidate == earmarked_leader) || (number_of_agents && !leader_spawned))
+ ert_antag = new created_ert_datum.leader_role
+ earmarked_leader = null
+ leader_spawned = TRUE
+ else
+ ert_antag = created_ert_datum.roles[WRAP(number_of_agents, 1, length(created_ert_datum.roles) + 1)]
+ ert_antag = new ert_antag ()
+ ert_antag.random_names = created_ert_datum.random_names
+
+ ert_operative.mind.add_antag_datum(ert_antag,ert_team)
+ ert_operative.mind.set_assigned_role(SSjob.get_job_type(ert_antag.ert_job_path))
+
+ //Logging and cleanup
+ log_game("[key_name(ert_operative)] has been selected as an [ert_antag.name]")
+ number_of_agents--
+ team_spawned++
+
+ if(team_spawned)
+ message_admins("[created_ert_datum.polldesc] has spawned with the mission: [created_ert_datum.mission]")
+ if(created_ert_datum.notify_players)
+ priority_announce("Central command has responded to your request for a CODE [uppertext(created_ert_datum.code)] Emergency Response Team and have confirmed one to be enroute.", "ERT Request", ANNOUNCER_ERTYES)
+ //Open the Armory doors
+ if(created_ert_datum.opendoors)
+ for(var/obj/machinery/door/poddoor/ert/door as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/poddoor/ert))
+ door.open()
+ CHECK_TICK
+ return TRUE
+
+#undef ERT_EXPERIENCED_LEADER_CHOOSE_TOP
diff --git a/modular_zubbers/code/modules/storyteller/_events/fleshmind_event.dm b/modular_zubbers/code/modules/storyteller/_events/fleshmind_event.dm
new file mode 100644
index 00000000000..23a2ee60484
--- /dev/null
+++ b/modular_zubbers/code/modules/storyteller/_events/fleshmind_event.dm
@@ -0,0 +1,68 @@
+/datum/round_event_control/fleshmind
+ name = "Fleshmind"
+ typepath = /datum/round_event/fleshmind
+ max_occurrences = 1
+ weight = 10
+ min_players = 35
+ tags = list(TAG_DESTRUCTIVE, TAG_COMBAT, TAG_CHAOTIC)
+ earliest_start = 60 MINUTES
+ track = EVENT_TRACK_MAJOR
+
+/datum/round_event/fleshmind
+ fakeable = FALSE
+ announce_when = 200
+ end_when = 201
+
+/datum/round_event/fleshmind/announce(fake)
+ priority_announce("Confirmed outbreak of level CLASSIFIED biohazard aboard [station_name()]. Station quarantine subroutines activated.", "Critical Biohazard Alert", ANNOUNCER_MUTANTS)
+
+/datum/round_event/fleshmind/start()
+ var/turf/current_turf = get_turf(src)
+
+ if(is_valid_turf(current_turf))
+ return
+
+ var/turf/spawn_turf = null
+
+ if(LAZYLEN(GLOB.blobstart))
+ var/list/shuffled_blobstarts = shuffle(GLOB.blobstart)
+
+ for(var/turf/blob_turf in shuffled_blobstarts)
+ if(is_valid_turf(blob_turf))
+ spawn_turf = blob_turf
+ break
+
+ else
+ for(var/attempt in 1 to 16)
+ var/turf/candidate_turf = get_safe_random_station_turf_equal_weight()
+
+ if(is_valid_turf(candidate_turf))
+ spawn_turf = candidate_turf
+ break
+
+ if(!spawn_turf)
+ CRASH("Fleshmind failed to pick a valid turf!")
+
+ make_core(spawn_turf)
+ var/datum/round_event_control/event = locate(/datum/round_event_control/wire_priest) in SSevents.control
+ event.run_event(admin_forced = TRUE)
+
+/datum/round_event/fleshmind/proc/make_core(turf/location)
+ new /obj/structure/fleshmind/structure/core(location)
+
+/// Copied from line 150 of code/modules/antagonists/blob/overmind.dm
+/datum/round_event/fleshmind/proc/is_valid_turf(turf/tile)
+ var/area/area = get_area(tile)
+ if((area && !(area.area_flags & BLOBS_ALLOWED)) || !tile || !is_station_level(tile.z) || isgroundlessturf(tile))
+ return FALSE
+
+ if(area_has_player(tile, 7))
+ return FALSE
+
+ return TRUE
+
+/datum/round_event/fleshmind/proc/area_has_player(turf/center, range = 7)
+ for(var/mob/living/blocked_mob in range(range, center))
+ if(blocked_mob.client)
+ return TRUE
+ return FALSE
diff --git a/modular_zubbers/icons/fleshmind/fleshmind_clothing.dmi b/modular_zubbers/icons/fleshmind/fleshmind_clothing.dmi
new file mode 100644
index 00000000000..57c03954bda
Binary files /dev/null and b/modular_zubbers/icons/fleshmind/fleshmind_clothing.dmi differ
diff --git a/modular_zubbers/icons/fleshmind/fleshmind_clothing_onmob.dmi b/modular_zubbers/icons/fleshmind/fleshmind_clothing_onmob.dmi
new file mode 100644
index 00000000000..50c773fd90f
Binary files /dev/null and b/modular_zubbers/icons/fleshmind/fleshmind_clothing_onmob.dmi differ
diff --git a/modular_zubbers/icons/fleshmind/fleshmind_machines.dmi b/modular_zubbers/icons/fleshmind/fleshmind_machines.dmi
new file mode 100644
index 00000000000..0fe964adad4
Binary files /dev/null and b/modular_zubbers/icons/fleshmind/fleshmind_machines.dmi differ
diff --git a/modular_zubbers/icons/fleshmind/fleshmind_mobs.dmi b/modular_zubbers/icons/fleshmind/fleshmind_mobs.dmi
new file mode 100644
index 00000000000..143020932f3
Binary files /dev/null and b/modular_zubbers/icons/fleshmind/fleshmind_mobs.dmi differ
diff --git a/modular_zubbers/icons/fleshmind/fleshmind_objects.dmi b/modular_zubbers/icons/fleshmind/fleshmind_objects.dmi
new file mode 100644
index 00000000000..a4ccad22a66
Binary files /dev/null and b/modular_zubbers/icons/fleshmind/fleshmind_objects.dmi differ
diff --git a/modular_zubbers/icons/fleshmind/fleshmind_objects_onmob_l.dmi b/modular_zubbers/icons/fleshmind/fleshmind_objects_onmob_l.dmi
new file mode 100644
index 00000000000..0abf17315a3
Binary files /dev/null and b/modular_zubbers/icons/fleshmind/fleshmind_objects_onmob_l.dmi differ
diff --git a/modular_zubbers/icons/fleshmind/fleshmind_objects_onmob_r.dmi b/modular_zubbers/icons/fleshmind/fleshmind_objects_onmob_r.dmi
new file mode 100644
index 00000000000..a8994d087d7
Binary files /dev/null and b/modular_zubbers/icons/fleshmind/fleshmind_objects_onmob_r.dmi differ
diff --git a/modular_zubbers/icons/fleshmind/fleshmind_structures.dmi b/modular_zubbers/icons/fleshmind/fleshmind_structures.dmi
new file mode 100644
index 00000000000..5185d10233f
Binary files /dev/null and b/modular_zubbers/icons/fleshmind/fleshmind_structures.dmi differ
diff --git a/modular_zubbers/icons/fleshmind/mauler_monkey_parts.dmi b/modular_zubbers/icons/fleshmind/mauler_monkey_parts.dmi
new file mode 100644
index 00000000000..b6415b0044c
Binary files /dev/null and b/modular_zubbers/icons/fleshmind/mauler_monkey_parts.dmi differ
diff --git a/modular_zubbers/icons/fleshmind/tyrant.dmi b/modular_zubbers/icons/fleshmind/tyrant.dmi
new file mode 100644
index 00000000000..722f0b8e6e0
Binary files /dev/null and b/modular_zubbers/icons/fleshmind/tyrant.dmi differ
diff --git a/modular_zubbers/icons/fleshmind/wireweed_floor.dmi b/modular_zubbers/icons/fleshmind/wireweed_floor.dmi
new file mode 100644
index 00000000000..7f1913ec506
Binary files /dev/null and b/modular_zubbers/icons/fleshmind/wireweed_floor.dmi differ
diff --git a/modular_zubbers/sound/fleshmind/ai/data_compromised.ogg b/modular_zubbers/sound/fleshmind/ai/data_compromised.ogg
new file mode 100644
index 00000000000..e40b6b794c7
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/ai/data_compromised.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/ai/level_down_1.ogg b/modular_zubbers/sound/fleshmind/ai/level_down_1.ogg
new file mode 100644
index 00000000000..13660557138
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/ai/level_down_1.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/ai/level_down_2.ogg b/modular_zubbers/sound/fleshmind/ai/level_down_2.ogg
new file mode 100644
index 00000000000..ee6082a76a3
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/ai/level_down_2.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/ai/level_down_3.ogg b/modular_zubbers/sound/fleshmind/ai/level_down_3.ogg
new file mode 100644
index 00000000000..d0bc66ea8be
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/ai/level_down_3.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/ai/level_up_1.ogg b/modular_zubbers/sound/fleshmind/ai/level_up_1.ogg
new file mode 100644
index 00000000000..708c64748aa
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/ai/level_up_1.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/ai/tyrant.ogg b/modular_zubbers/sound/fleshmind/ai/tyrant.ogg
new file mode 100644
index 00000000000..f5cc6be7856
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/ai/tyrant.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/hiborg/aggro_01.ogg b/modular_zubbers/sound/fleshmind/hiborg/aggro_01.ogg
new file mode 100644
index 00000000000..172af789d60
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/hiborg/aggro_01.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/hiborg/aggro_02.ogg b/modular_zubbers/sound/fleshmind/hiborg/aggro_02.ogg
new file mode 100644
index 00000000000..ae1d79bad54
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/hiborg/aggro_02.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/hiborg/aggro_03.ogg b/modular_zubbers/sound/fleshmind/hiborg/aggro_03.ogg
new file mode 100644
index 00000000000..8da38c9c123
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/hiborg/aggro_03.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/hiborg/aggro_04.ogg b/modular_zubbers/sound/fleshmind/hiborg/aggro_04.ogg
new file mode 100644
index 00000000000..719abb0234c
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/hiborg/aggro_04.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/hiborg/aggro_05.ogg b/modular_zubbers/sound/fleshmind/hiborg/aggro_05.ogg
new file mode 100644
index 00000000000..0149c462e41
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/hiborg/aggro_05.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/hiborg/aggro_06.ogg b/modular_zubbers/sound/fleshmind/hiborg/aggro_06.ogg
new file mode 100644
index 00000000000..b57293df47e
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/hiborg/aggro_06.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/hiborg/passive_01.ogg b/modular_zubbers/sound/fleshmind/hiborg/passive_01.ogg
new file mode 100644
index 00000000000..8290bd4f184
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/hiborg/passive_01.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/hiborg/passive_02.ogg b/modular_zubbers/sound/fleshmind/hiborg/passive_02.ogg
new file mode 100644
index 00000000000..0c98cfa5c8b
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/hiborg/passive_02.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/hiborg/passive_03.ogg b/modular_zubbers/sound/fleshmind/hiborg/passive_03.ogg
new file mode 100644
index 00000000000..2f9aa235966
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/hiborg/passive_03.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/hiborg/passive_04.ogg b/modular_zubbers/sound/fleshmind/hiborg/passive_04.ogg
new file mode 100644
index 00000000000..888373d6caf
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/hiborg/passive_04.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/hiborg/passive_05.ogg b/modular_zubbers/sound/fleshmind/hiborg/passive_05.ogg
new file mode 100644
index 00000000000..08d12012eb4
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/hiborg/passive_05.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/himan/aggro_01.ogg b/modular_zubbers/sound/fleshmind/himan/aggro_01.ogg
new file mode 100644
index 00000000000..3d4ac71791d
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/himan/aggro_01.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/himan/aggro_02.ogg b/modular_zubbers/sound/fleshmind/himan/aggro_02.ogg
new file mode 100644
index 00000000000..67ca5f72ee1
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/himan/aggro_02.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/himan/aggro_03.ogg b/modular_zubbers/sound/fleshmind/himan/aggro_03.ogg
new file mode 100644
index 00000000000..12b59a3c543
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/himan/aggro_03.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/himan/aggro_04.ogg b/modular_zubbers/sound/fleshmind/himan/aggro_04.ogg
new file mode 100644
index 00000000000..f3322f36df9
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/himan/aggro_04.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/himan/aggro_05.ogg b/modular_zubbers/sound/fleshmind/himan/aggro_05.ogg
new file mode 100644
index 00000000000..a11fd9ce7ee
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/himan/aggro_05.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/himan/aggro_06.ogg b/modular_zubbers/sound/fleshmind/himan/aggro_06.ogg
new file mode 100644
index 00000000000..2a79507b80a
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/himan/aggro_06.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/himan/aggro_07.ogg b/modular_zubbers/sound/fleshmind/himan/aggro_07.ogg
new file mode 100644
index 00000000000..84307b20531
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/himan/aggro_07.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/himan/aggro_08.ogg b/modular_zubbers/sound/fleshmind/himan/aggro_08.ogg
new file mode 100644
index 00000000000..3a4bfff5484
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/himan/aggro_08.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/himan/passive_01.ogg b/modular_zubbers/sound/fleshmind/himan/passive_01.ogg
new file mode 100644
index 00000000000..7e2f62df842
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/himan/passive_01.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/himan/passive_02.ogg b/modular_zubbers/sound/fleshmind/himan/passive_02.ogg
new file mode 100644
index 00000000000..123249d7819
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/himan/passive_02.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/himan/passive_03.ogg b/modular_zubbers/sound/fleshmind/himan/passive_03.ogg
new file mode 100644
index 00000000000..93f6b00543c
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/himan/passive_03.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/himan/passive_04.ogg b/modular_zubbers/sound/fleshmind/himan/passive_04.ogg
new file mode 100644
index 00000000000..d3c493184b7
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/himan/passive_04.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/laser.ogg b/modular_zubbers/sound/fleshmind/laser.ogg
new file mode 100644
index 00000000000..79a4847a3e6
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/laser.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/mechiver/aggro_01.ogg b/modular_zubbers/sound/fleshmind/mechiver/aggro_01.ogg
new file mode 100644
index 00000000000..55980328811
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/mechiver/aggro_01.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/mechiver/aggro_02.ogg b/modular_zubbers/sound/fleshmind/mechiver/aggro_02.ogg
new file mode 100644
index 00000000000..accf6364ec9
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/mechiver/aggro_02.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/mechiver/aggro_03.ogg b/modular_zubbers/sound/fleshmind/mechiver/aggro_03.ogg
new file mode 100644
index 00000000000..b9d936d488b
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/mechiver/aggro_03.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/mechiver/aggro_04.ogg b/modular_zubbers/sound/fleshmind/mechiver/aggro_04.ogg
new file mode 100644
index 00000000000..e0fe56802b5
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/mechiver/aggro_04.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/mechiver/aggro_05.ogg b/modular_zubbers/sound/fleshmind/mechiver/aggro_05.ogg
new file mode 100644
index 00000000000..83c165d2ab9
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/mechiver/aggro_05.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/mechiver/passive_01.ogg b/modular_zubbers/sound/fleshmind/mechiver/passive_01.ogg
new file mode 100644
index 00000000000..da5f81b8be6
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/mechiver/passive_01.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/mechiver/passive_02.ogg b/modular_zubbers/sound/fleshmind/mechiver/passive_02.ogg
new file mode 100644
index 00000000000..ca251ca1f51
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/mechiver/passive_02.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/mechiver/passive_03.ogg b/modular_zubbers/sound/fleshmind/mechiver/passive_03.ogg
new file mode 100644
index 00000000000..06e6c61d586
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/mechiver/passive_03.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/mechiver/passive_04.ogg b/modular_zubbers/sound/fleshmind/mechiver/passive_04.ogg
new file mode 100644
index 00000000000..82905993c53
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/mechiver/passive_04.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/mechiver/passive_05.ogg b/modular_zubbers/sound/fleshmind/mechiver/passive_05.ogg
new file mode 100644
index 00000000000..518893a87c5
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/mechiver/passive_05.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/mechiver/passive_06.ogg b/modular_zubbers/sound/fleshmind/mechiver/passive_06.ogg
new file mode 100644
index 00000000000..525ed3014c5
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/mechiver/passive_06.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/mechiver/passive_07.ogg b/modular_zubbers/sound/fleshmind/mechiver/passive_07.ogg
new file mode 100644
index 00000000000..704a690cb25
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/mechiver/passive_07.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/mechiver/passive_08.ogg b/modular_zubbers/sound/fleshmind/mechiver/passive_08.ogg
new file mode 100644
index 00000000000..533eee0832a
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/mechiver/passive_08.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/override_sound.ogg b/modular_zubbers/sound/fleshmind/override_sound.ogg
new file mode 100644
index 00000000000..18ec325b799
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/override_sound.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/robot_talk_heavy1.ogg b/modular_zubbers/sound/fleshmind/robot_talk_heavy1.ogg
new file mode 100644
index 00000000000..e808e0db587
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/robot_talk_heavy1.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/robot_talk_heavy2.ogg b/modular_zubbers/sound/fleshmind/robot_talk_heavy2.ogg
new file mode 100644
index 00000000000..e81b2832eab
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/robot_talk_heavy2.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/robot_talk_heavy3.ogg b/modular_zubbers/sound/fleshmind/robot_talk_heavy3.ogg
new file mode 100644
index 00000000000..0670b3cded4
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/robot_talk_heavy3.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/robot_talk_heavy4.ogg b/modular_zubbers/sound/fleshmind/robot_talk_heavy4.ogg
new file mode 100644
index 00000000000..0713e045a81
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/robot_talk_heavy4.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/robot_talk_light1.ogg b/modular_zubbers/sound/fleshmind/robot_talk_light1.ogg
new file mode 100644
index 00000000000..6ac3ca8c065
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/robot_talk_light1.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/robot_talk_light2.ogg b/modular_zubbers/sound/fleshmind/robot_talk_light2.ogg
new file mode 100644
index 00000000000..c547e6506f3
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/robot_talk_light2.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/robot_talk_light3.ogg b/modular_zubbers/sound/fleshmind/robot_talk_light3.ogg
new file mode 100644
index 00000000000..bd185943e6f
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/robot_talk_light3.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/robot_talk_light4.ogg b/modular_zubbers/sound/fleshmind/robot_talk_light4.ogg
new file mode 100644
index 00000000000..d2f17878a52
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/robot_talk_light4.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/robot_talk_light5.ogg b/modular_zubbers/sound/fleshmind/robot_talk_light5.ogg
new file mode 100644
index 00000000000..a5c6712b526
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/robot_talk_light5.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/slicer/fleshmind_medibot1.ogg b/modular_zubbers/sound/fleshmind/slicer/fleshmind_medibot1.ogg
new file mode 100644
index 00000000000..6ca91820e81
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/slicer/fleshmind_medibot1.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/slicer/fleshmind_medibot2.ogg b/modular_zubbers/sound/fleshmind/slicer/fleshmind_medibot2.ogg
new file mode 100644
index 00000000000..93a87115068
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/slicer/fleshmind_medibot2.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/slicer/fleshmind_medibot3.ogg b/modular_zubbers/sound/fleshmind/slicer/fleshmind_medibot3.ogg
new file mode 100644
index 00000000000..2b0998b6c3a
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/slicer/fleshmind_medibot3.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/slicer/fleshmind_medibot4.ogg b/modular_zubbers/sound/fleshmind/slicer/fleshmind_medibot4.ogg
new file mode 100644
index 00000000000..8d2dabb5b22
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/slicer/fleshmind_medibot4.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/sparks.ogg b/modular_zubbers/sound/fleshmind/sparks.ogg
new file mode 100644
index 00000000000..2bd5972c063
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/sparks.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/sparks_2.ogg b/modular_zubbers/sound/fleshmind/sparks_2.ogg
new file mode 100644
index 00000000000..91f349abee8
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/sparks_2.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/aggro_01.ogg b/modular_zubbers/sound/fleshmind/tyrant/aggro_01.ogg
new file mode 100644
index 00000000000..8be5bc474c6
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/aggro_01.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/aggro_02.ogg b/modular_zubbers/sound/fleshmind/tyrant/aggro_02.ogg
new file mode 100644
index 00000000000..40ae48e1527
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/aggro_02.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/aggro_03.ogg b/modular_zubbers/sound/fleshmind/tyrant/aggro_03.ogg
new file mode 100644
index 00000000000..157a487be7a
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/aggro_03.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/aggro_04.ogg b/modular_zubbers/sound/fleshmind/tyrant/aggro_04.ogg
new file mode 100644
index 00000000000..b8028d08f72
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/aggro_04.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/aggro_05.ogg b/modular_zubbers/sound/fleshmind/tyrant/aggro_05.ogg
new file mode 100644
index 00000000000..df673a28555
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/aggro_05.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/aggro_06.ogg b/modular_zubbers/sound/fleshmind/tyrant/aggro_06.ogg
new file mode 100644
index 00000000000..eadad0a84e9
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/aggro_06.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/aggro_07.ogg b/modular_zubbers/sound/fleshmind/tyrant/aggro_07.ogg
new file mode 100644
index 00000000000..653b066b5ec
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/aggro_07.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/aggro_08.ogg b/modular_zubbers/sound/fleshmind/tyrant/aggro_08.ogg
new file mode 100644
index 00000000000..751914100e3
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/aggro_08.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/aggro_09.ogg b/modular_zubbers/sound/fleshmind/tyrant/aggro_09.ogg
new file mode 100644
index 00000000000..f15051ccdc3
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/aggro_09.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/aggro_10.ogg b/modular_zubbers/sound/fleshmind/tyrant/aggro_10.ogg
new file mode 100644
index 00000000000..078ed732402
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/aggro_10.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/aggro_11.ogg b/modular_zubbers/sound/fleshmind/tyrant/aggro_11.ogg
new file mode 100644
index 00000000000..8781b380681
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/aggro_11.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/charge_up.ogg b/modular_zubbers/sound/fleshmind/tyrant/charge_up.ogg
new file mode 100644
index 00000000000..3b69ae65180
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/charge_up.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/footstep_1.ogg b/modular_zubbers/sound/fleshmind/tyrant/footstep_1.ogg
new file mode 100644
index 00000000000..b017b5e12f3
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/footstep_1.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/footstep_2.ogg b/modular_zubbers/sound/fleshmind/tyrant/footstep_2.ogg
new file mode 100644
index 00000000000..fab3e23f083
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/footstep_2.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/footstep_3.ogg b/modular_zubbers/sound/fleshmind/tyrant/footstep_3.ogg
new file mode 100644
index 00000000000..edd54023e2b
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/footstep_3.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/footstep_4.ogg b/modular_zubbers/sound/fleshmind/tyrant/footstep_4.ogg
new file mode 100644
index 00000000000..afbe68a5b1a
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/footstep_4.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/footstep_5.ogg b/modular_zubbers/sound/fleshmind/tyrant/footstep_5.ogg
new file mode 100644
index 00000000000..bee1c3ebba2
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/footstep_5.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/footstep_6.ogg b/modular_zubbers/sound/fleshmind/tyrant/footstep_6.ogg
new file mode 100644
index 00000000000..4457465fe1d
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/footstep_6.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/hydraulic.ogg b/modular_zubbers/sound/fleshmind/tyrant/hydraulic.ogg
new file mode 100644
index 00000000000..ef0bb57bd4d
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/hydraulic.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/laser_1.ogg b/modular_zubbers/sound/fleshmind/tyrant/laser_1.ogg
new file mode 100644
index 00000000000..dde7e72cc75
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/laser_1.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/laser_2.ogg b/modular_zubbers/sound/fleshmind/tyrant/laser_2.ogg
new file mode 100644
index 00000000000..3416023726c
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/laser_2.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/laser_3.ogg b/modular_zubbers/sound/fleshmind/tyrant/laser_3.ogg
new file mode 100644
index 00000000000..0171e7fac6d
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/laser_3.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/laser_4.ogg b/modular_zubbers/sound/fleshmind/tyrant/laser_4.ogg
new file mode 100644
index 00000000000..12f16a068ea
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/laser_4.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/laser_5.ogg b/modular_zubbers/sound/fleshmind/tyrant/laser_5.ogg
new file mode 100644
index 00000000000..baab7ade2c7
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/laser_5.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/laser_6.ogg b/modular_zubbers/sound/fleshmind/tyrant/laser_6.ogg
new file mode 100644
index 00000000000..79bb30eaaa9
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/laser_6.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/mech_punch_slow.ogg b/modular_zubbers/sound/fleshmind/tyrant/mech_punch_slow.ogg
new file mode 100644
index 00000000000..5f7b8617583
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/mech_punch_slow.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/mech_rotation.ogg b/modular_zubbers/sound/fleshmind/tyrant/mech_rotation.ogg
new file mode 100644
index 00000000000..58ac0d312ee
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/mech_rotation.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/passive.ogg b/modular_zubbers/sound/fleshmind/tyrant/passive.ogg
new file mode 100644
index 00000000000..8ccbe321684
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/passive.ogg differ
diff --git a/modular_zubbers/sound/fleshmind/tyrant/tyrant_death.ogg b/modular_zubbers/sound/fleshmind/tyrant/tyrant_death.ogg
new file mode 100644
index 00000000000..27c56dfdbc4
Binary files /dev/null and b/modular_zubbers/sound/fleshmind/tyrant/tyrant_death.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index 204876003bc..04e593b29d1 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -508,9 +508,11 @@
#include "code\__DEFINES\~skyrat_defines\_HELPERS\atmos_mapping_helpers.dm"
#include "code\__DEFINES\~skyrat_defines\_HELPERS\lighting.dm"
#include "code\__DEFINES\~skyrat_defines\_HELPERS\offset_index.dm"
+#include "code\__DEFINES\~skyrat_defines\signals\signals_fleshmind.dm"
#include "code\__DEFINES\~skyrat_defines\signals\signals_human.dm"
#include "code\__DEFINES\~skyrat_defines\traits\declarations.dm"
#include "code\__DEFINES\~~bubber_defines\access.dm"
+#include "code\__DEFINES\~~bubber_defines\basic_mobs.dm"
#include "code\__DEFINES\~~bubber_defines\bloodsucker.dm"
#include "code\__DEFINES\~~bubber_defines\bodyparts.dm"
#include "code\__DEFINES\~~bubber_defines\cameranets.dm"
@@ -8892,6 +8894,7 @@
#include "modular_zubbers\code\datums\ert.dm"
#include "modular_zubbers\code\datums\view.dm"
#include "modular_zubbers\code\datums\actions\oversized_self_view.dm"
+#include "modular_zubbers\code\datums\ai\basic_mobs\basic_ai_behaviors\targeted_mob_ability.dm"
#include "modular_zubbers\code\datums\ai_laws\laws_antagonistic.dm"
#include "modular_zubbers\code\datums\ai_laws\laws_neutral.dm"
#include "modular_zubbers\code\datums\ai_laws\laws_station_sided.dm"
@@ -9020,6 +9023,24 @@
#include "modular_zubbers\code\game\area\areas\moonstation.dm"
#include "modular_zubbers\code\game\area\areas\station.dm"
#include "modular_zubbers\code\game\brain_damage\severe.dm"
+#include "modular_zubbers\code\game\gamemodes\fleshmind\_fleshmind_defines.dm"
+#include "modular_zubbers\code\game\gamemodes\fleshmind\_fleshmind_helpers.dm"
+#include "modular_zubbers\code\game\gamemodes\fleshmind\ert.dm"
+#include "modular_zubbers\code\game\gamemodes\fleshmind\fleshmind_basic_ai.dm"
+#include "modular_zubbers\code\game\gamemodes\fleshmind\fleshmind_cinematic.dm"
+#include "modular_zubbers\code\game\gamemodes\fleshmind\fleshmind_controller.dm"
+#include "modular_zubbers\code\game\gamemodes\fleshmind\fleshmind_environment_objects.dm"
+#include "modular_zubbers\code\game\gamemodes\fleshmind\fleshmind_generic_actions.dm"
+#include "modular_zubbers\code\game\gamemodes\fleshmind\fleshmind_mobs.dm"
+#include "modular_zubbers\code\game\gamemodes\fleshmind\fleshmind_objects.dm"
+#include "modular_zubbers\code\game\gamemodes\fleshmind\fleshmind_structures.dm"
+#include "modular_zubbers\code\game\gamemodes\fleshmind\fleshmind_subsystem.dm"
+#include "modular_zubbers\code\game\gamemodes\fleshmind\ghost_role.dm"
+#include "modular_zubbers\code\game\gamemodes\fleshmind\human_corruption_component.dm"
+#include "modular_zubbers\code\game\gamemodes\fleshmind\machine_corruption_component.dm"
+#include "modular_zubbers\code\game\gamemodes\fleshmind\shuttle_call_protocol.dm"
+#include "modular_zubbers\code\game\gamemodes\fleshmind\tyrant\tyrant.dm"
+#include "modular_zubbers\code\game\gamemodes\fleshmind\tyrant\tyrant_ai.dm"
#include "modular_zubbers\code\game\gamemodes\objectives\gimmick.dm"
#include "modular_zubbers\code\game\Items\big_bertha_shield.dm"
#include "modular_zubbers\code\game\Items\coins.dm"
@@ -9199,6 +9220,7 @@
#include "modular_zubbers\code\modules\antagonists\bloodsucker\vassal\vassal_types\favorite_vassal.dm"
#include "modular_zubbers\code\modules\antagonists\bloodsucker\vassal\vassal_types\revenge_vassal.dm"
#include "modular_zubbers\code\modules\antagonists\changeling\powers\headcrab.dm"
+#include "modular_zubbers\code\modules\antagonists\ert\make_ert.dm"
#include "modular_zubbers\code\modules\antagonists\ert\names.dm"
#include "modular_zubbers\code\modules\antagonists\heretic\heretic.dm"
#include "modular_zubbers\code\modules\antagonists\heretic\heretic_antag.dm"
@@ -9792,6 +9814,7 @@
#include "modular_zubbers\code\modules\storyteller\verbs.dm"
#include "modular_zubbers\code\modules\storyteller\_events\_event.dm"
#include "modular_zubbers\code\modules\storyteller\_events\_event_ui.dm"
+#include "modular_zubbers\code\modules\storyteller\_events\fleshmind_event.dm"
#include "modular_zubbers\code\modules\storyteller\event_defines\disabled_event_overrides.dm"
#include "modular_zubbers\code\modules\storyteller\event_defines\crewset\_antagonist_event.dm"
#include "modular_zubbers\code\modules\storyteller\event_defines\crewset\bloodsucker.dm"
diff --git a/tgui/packages/tgui/interfaces/AntagInfoWirePriest.tsx b/tgui/packages/tgui/interfaces/AntagInfoWirePriest.tsx
new file mode 100644
index 00000000000..7889f7cf898
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/AntagInfoWirePriest.tsx
@@ -0,0 +1,62 @@
+import { BlockQuote, Section, Stack } from 'tgui-core/components';
+
+import { Window } from '../layouts';
+
+const tipstyle = {
+ color: 'white',
+};
+
+const noticestyle = {
+ color: 'lightblue',
+};
+
+export const AntagInfoWirePriest = (props) => {
+ return (
+
+
+
+
+
+
+ You are the Wire Priest
+
+
+ You are part of the fleshmind, this means any fleshmind
+ entities, structures, mobs are your ally. You must not
+ attack them. You must roleplay that you are part of the
+ fleshmind. Your number one goal is converting other hosts
+ and spreading the flesh.
+
+
+
+
+ Create:
+ Create tech structures, basic structures, and wireweed itself
+ to help defend and spread the fleshmind. These structures
+ depend on the current level of the core.
+
+
+ Defend:
+ Call flesh reinforcements and use your knife to defend the
+ your core. Remember, if you die, you will no longer be loyal
+ to your precious fleshmind, protecct it with your life.
+
+
+ Convert:
+ Convert station dwellers with mechivers, rally them using the
+ call flesh reinforcements ability. You may be weak alone, but
+ with numbers you can easily overwhelm the crew.
+
+
+ Communicate:
+ Speak with your fellow enlightened ones using your flesh chat
+ ability. Communication is key afterall.
+
+
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/wire_priest.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/wire_priest.ts
new file mode 100644
index 00000000000..25e08476a94
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/wire_priest.ts
@@ -0,0 +1,15 @@
+import { type Antagonist, Category } from '../base';
+
+const Wire_Priest: Antagonist = {
+ key: 'wirepriest',
+ name: 'Wire Priest',
+ description: [
+ `
+ You are part of the fleshmind, this means any fleshmind entities, structures, mobs are your ally. You must not attack them.
+ You must roleplay that you are part of the fleshmind. Your number one goal is converting other hosts and spreading the flesh.
+ `,
+ ],
+ category: Category.Midround,
+};
+
+export default Wire_Priest;