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