Fleshmind - Attempt 3 (#4914)

## About This Pull Request
Brought back based off of
https://github.com/Bubberstation/Bubberstation/pull/2007, made
functional again by myself.
Original Code: https://github.com/Skyrat-SS13/Skyrat-tg/pull/14023

Fleshmind is a PVE event that incorporates some PVP elements in the
later stages of its growth. You defeat it by destroying all its
processors. It has six stages of growth with each stage expanding on its
abilities and the structures and mobs it can deploy. It will announce to
the entire station each time it gains a level or loses a level. Each
level means it has a new processor.

- **Level 1:** The start when it spawns in. It will spawn some basic
structures.
- **Level 2:** It will begin to spawn screamers and whisperers
- **Level 3:** The wireweed is much more situated, and this is the point
where it spawns a very dangerous mech. This is the point where it
becomes very critical. It also starts to spawn Babblers (structures that
spew propaganda into comms) and Modulators. (Gives you hallucinations)
- **Level 4** No new special abilities.
- **Level 5** No new special abilities
- **Level 6** It automatically calls the emergency shuttle to hijack and
spawns a deathsquad on Centcomm. This ends the round.

**Counters:**
- Fight in groups. Carry a melee weapon. (You can still melee the
mechivers and try to kill them before they convert you)
- Wirecutters cut wireweed.
- Ion Weaponry is extremely useful. It also stuns all the mobs.
- Target the processors. If you can pick them off in space, it has
practically zero EVA abilities.
- If someone gets assimilated, should get them to medical if you kill
them.

To run it requires 35 active players and a minimum of an hour to have
passed.
## Why It's Good For The Game
With mold being disabled, there is a missing PVE element in the game,
this fills that niche while also playing around with the idea of
temporary conversion antags in a way that I think works really well.

Wireweed/fleshmind also distracts the crew and security enough to
provide clever antagonists with an opening to commit crimes and get away
with them while the crew has their hands full.

## Proof of Testing
It's been test merged for two months now, it works.
## Changelog
🆑 xPokee, StrangeWeirdKitten, Gandalf2k15
add: New midround event: Fleshmind
/🆑

---------

Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
Co-authored-by: Artur Lang <24881678+Arturlang@users.noreply.github.com>
Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com>
This commit is contained in:
Alexis
2026-02-17 15:38:34 +01:00
committed by GitHub
co-authored by LT3 Artur Lang Roxy
parent 5ca3d6ef58
commit 88d7efe291
132 changed files with 4692 additions and 5 deletions
+3
View File
@@ -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.
@@ -0,0 +1 @@
#define COSMIG_CONTROLLER_SET_TARGET "fleshmind_set_target"
@@ -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"
@@ -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"
@@ -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"
+42 -2
View File
@@ -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
+1
View File
@@ -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,
Binary file not shown.

After

Width:  |  Height:  |  Size: 657 B

@@ -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
@@ -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]
@@ -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)
@@ -0,0 +1 @@
#define is_corrupt_mob(A) (istype(A, /mob/living/basic/fleshmind))
@@ -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!"))
@@ -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
@@ -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()
@@ -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)
@@ -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
@@ -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("<b>FLESHMIND ([owner]):</b> [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()
File diff suppressed because it is too large Load Diff
@@ -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"
@@ -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'
@@ -0,0 +1,5 @@
PROCESSING_SUBSYSTEM_DEF(corruption)
name = "Fleshmind Subsystem"
wait = 1 SECONDS
/// Only starts firing when there is corruption
can_fire = FALSE
@@ -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
@@ -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 += "<b>[p_They()] have strange wires wrappped around [p_them()]!</b>"
/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)
@@ -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 += "<b>It has strange wires wrappped around it!</b>"
/**
* 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
@@ -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
@@ -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
@@ -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 ..()
@@ -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
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 877 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More