diff --git a/code/__DEFINES/icon_smoothing.dm b/code/__DEFINES/icon_smoothing.dm index 14c89e236e2..9cf7bba2dd7 100644 --- a/code/__DEFINES/icon_smoothing.dm +++ b/code/__DEFINES/icon_smoothing.dm @@ -217,6 +217,8 @@ DEFINE_BITFIELD(smoothing_junction, list( #define SMOOTH_GROUP_SHUTTERS S_OBJ(75) #define SMOOTH_GROUP_WATER S_OBJ(76) ///obj/effect/abstract/liquid_turf + +#define SMOOTH_GROUP_WIREWEED S_OBJ(77) //NOVA EDIT END /// Performs the work to set smoothing_groups and canSmoothWith. diff --git a/code/__DEFINES/~nova_defines/signals.dm b/code/__DEFINES/~nova_defines/signals.dm index e3bd44ff147..d7a536dfbd8 100644 --- a/code/__DEFINES/~nova_defines/signals.dm +++ b/code/__DEFINES/~nova_defines/signals.dm @@ -84,3 +84,16 @@ /// Whenever we need to get the soul of the mob inside of the soulcatcher. #define COMSIG_SOULCATCHER_SCAN_BODY "soulcatcher_scan_body" + +// CORRUPTION SIGNALS + +/// From /obj/structure/fleshmind/structure/proc/activate_ability() (src) +#define COMSIG_CORRUPTION_STRUCTURE_ABILITY_TRIGGERED "corruption_structure_ability_triggered" + +/// From /mob/living/simple_animal/hostile/fleshmind/phaser/proc/phase_move_to(atom/target, nearby = FALSE) +#define COMSIG_PHASER_PHASE_MOVE "phaser_phase_move" +/// from /mob/living/simple_animal/hostile/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" diff --git a/code/game/objects/items/devices/scanners/sequence_scanner.dm b/code/game/objects/items/devices/scanners/sequence_scanner.dm index 4a80179f60e..3d212cbb771 100644 --- a/code/game/objects/items/devices/scanners/sequence_scanner.dm +++ b/code/game/objects/items/devices/scanners/sequence_scanner.dm @@ -55,7 +55,7 @@ //no scanning if its a husk, DNA-less Species or DNA that isn't able to be copied by a changeling/disease if (!HAS_TRAIT(interacting_with, TRAIT_GENELESS) && !HAS_TRAIT(interacting_with, TRAIT_BADDNA) && !HAS_TRAIT(interacting_with, TRAIT_NO_DNA_COPY)) user.visible_message(span_warning("[user] is scanning [interacting_with]'s genetic makeup.")) - if(!do_after(user, 3 SECONDS)) + if(!do_after(user, 3 SECONDS, interacting_with)) balloon_alert(user, "scan failed!") user.visible_message(span_warning("[user] fails to scan [interacting_with]'s genetic makeup.")) return ITEM_INTERACT_BLOCKING diff --git a/code/modules/antagonists/traitor/objectives/hack_comm_console.dm b/code/modules/antagonists/traitor/objectives/hack_comm_console.dm index 93323e4e15f..1874b79d8a1 100644 --- a/code/modules/antagonists/traitor/objectives/hack_comm_console.dm +++ b/code/modules/antagonists/traitor/objectives/hack_comm_console.dm @@ -15,6 +15,8 @@ var/progression_objectives_minimum = 20 MINUTES /datum/traitor_objective/hack_comm_console/can_generate_objective(datum/mind/generating_for, list/possible_duplicates) + if(length(possible_duplicates) > 0) + return FALSE if(SStraitor.get_taken_count(/datum/traitor_objective/hack_comm_console) > 0) return FALSE if(handler.get_completion_progression(/datum/traitor_objective) < progression_objectives_minimum) diff --git a/code/modules/antagonists/traitor/objectives/locate_weakpoint.dm b/code/modules/antagonists/traitor/objectives/locate_weakpoint.dm index 52813fcdf57..b3e211d2867 100644 --- a/code/modules/antagonists/traitor/objectives/locate_weakpoint.dm +++ b/code/modules/antagonists/traitor/objectives/locate_weakpoint.dm @@ -27,6 +27,8 @@ var/area/weakpoint_area /datum/traitor_objective/locate_weakpoint/can_generate_objective(datum/mind/generating_for, list/possible_duplicates) + if(length(possible_duplicates) > 0) + return FALSE if(handler.get_completion_progression(/datum/traitor_objective) < progression_objectives_minimum) return FALSE if(SStraitor.get_taken_count(/datum/traitor_objective/locate_weakpoint) > 0) diff --git a/code/modules/mob/living/basic/trooper/pirate.dm b/code/modules/mob/living/basic/trooper/pirate.dm index 208a113e5d4..6a51b901ebc 100644 --- a/code/modules/mob/living/basic/trooper/pirate.dm +++ b/code/modules/mob/living/basic/trooper/pirate.dm @@ -55,7 +55,7 @@ r_hand = /obj/item/gun/energy/laser ai_controller = /datum/ai_controller/basic_controller/trooper/ranged /// Type of bullet we use - var/casingtype = /obj/item/ammo_casing/energy/laser + var/projectiletype = /obj/projectile/beam/laser /// Sound to play when firing weapon var/projectilesound = 'sound/weapons/laser.ogg' /// number of burst shots @@ -67,7 +67,7 @@ . = ..() AddComponent(\ /datum/component/ranged_attacks,\ - casing_type = casingtype,\ + projectile_type = projectiletype,\ projectile_sound = projectilesound,\ cooldown_time = ranged_cooldown,\ burst_shots = burst_shots,\ diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 18a35576c36..80701705d20 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -142,6 +142,17 @@ /mob/living/simple_animal/hostile/zombie/nocorpse, /mob/living/simple_animal/pet/gondola/funky, /mob/living/simple_animal/pet/poppy, + /mob/living/simple_animal/hostile/fleshmind/slicer, + /mob/living/simple_animal/hostile/fleshmind/floater, + /mob/living/simple_animal/hostile/fleshmind/globber, + /mob/living/simple_animal/hostile/fleshmind/stunner, + /mob/living/simple_animal/hostile/fleshmind/hiborg, + /mob/living/simple_animal/hostile/fleshmind/himan, + /mob/living/simple_animal/hostile/fleshmind/treader, + /mob/living/simple_animal/hostile/fleshmind/phaser, + /mob/living/simple_animal/hostile/fleshmind/mechiver, + /mob/living/simple_animal/hostile/fleshmind/mauler_monkey, + /mob/living/simple_animal/hostile/fleshmind, // DO NOT ADD NEW ENTRIES TO THIS LIST // READ THE COMMENT ABOVE diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index a2dfa5b88d9..7078881ba0c 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -348,7 +348,7 @@ GLOBAL_LIST_EMPTY(vending_machines_to_restock) if(isnull(refill_canister)) return // you can add the comment here instead if((total_loaded_stock() / total_max_stock()) < 1) - . += span_notice("\The [src] can be restocked with [span_boldnotice("\a [refill_canister]")] with the panel open.") + . += span_notice("\The [src] can be restocked with [span_boldnotice("\a [initial(refill_canister.machine_name)] [initial(refill_canister.name)]")] with the panel open.") else . += span_notice("\The [src] is fully stocked.") if(credits_contained < CREDITS_DUMP_THRESHOLD && credits_contained > 0) diff --git a/html/changelogs/AutoChangeLog-pr-2367.yml b/html/changelogs/AutoChangeLog-pr-2367.yml new file mode 100644 index 00000000000..d55f1365c51 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-2367.yml @@ -0,0 +1,4 @@ +author: "Iajret" +delete-after: True +changes: + - balance: "Entombed and Underworld Connections are moved to veteran only quirks" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-2483.yml b/html/changelogs/AutoChangeLog-pr-2483.yml new file mode 100644 index 00000000000..7820dd95d26 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-2483.yml @@ -0,0 +1,4 @@ +author: "Zergspower" +delete-after: True +changes: + - rscadd: "ah shit" \ No newline at end of file diff --git a/modular_nova/modules/space_ruin_specifics/code/_fleshmind_defines.dm b/modular_nova/modules/space_ruin_specifics/code/_fleshmind_defines.dm new file mode 100644 index 00000000000..ebad2a50dee --- /dev/null +++ b/modular_nova/modules/space_ruin_specifics/code/_fleshmind_defines.dm @@ -0,0 +1,103 @@ +// 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.7 + +#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 6 + +#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", "Obsidian", "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 30 // 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 40 // Every subsystem fire, how much progress do we gain? +#define FLESHCORE_BASE_SPREAD_PROGRESS_PER_SUBSYSTEM_FIRE 40 // 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 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 0.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 60 // Upder damage done to internal mob +#define MECHIVER_INTERNAL_MOB_DAMAGE_LOWER 30 // 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 diff --git a/modular_nova/modules/space_ruin_specifics/code/_fleshmind_helpers.dm b/modular_nova/modules/space_ruin_specifics/code/_fleshmind_helpers.dm new file mode 100644 index 00000000000..79696401983 --- /dev/null +++ b/modular_nova/modules/space_ruin_specifics/code/_fleshmind_helpers.dm @@ -0,0 +1 @@ +#define is_corrupt_mob(A) (istype(A, /mob/living/simple_animal/hostile/fleshmind)) diff --git a/modular_nova/modules/space_ruin_specifics/code/fleshmind_environment_objects.dm b/modular_nova/modules/space_ruin_specifics/code/fleshmind_environment_objects.dm new file mode 100644 index 00000000000..3754de893b1 --- /dev/null +++ b/modular_nova/modules/space_ruin_specifics/code/fleshmind_environment_objects.dm @@ -0,0 +1,156 @@ +/** + * fleshmind basetype(abstract) + */ +/obj/structure/fleshmind + icon = 'modular_nova/modules/space_ruin_specifics/icons/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(drop_location()) + 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_nova/modules/space_ruin_specifics/icons/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 + SMOOTH_GROUP_WALLS + 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) + . = ..() + loc.balloon_alert(user, "cutting...") + if(!tool.use_tool(src, user, WIREWEED_WIRECUTTER_KILL_TIME, volume = 50)) + return + loc.balloon_alert(user, "cut!") + qdel(src) + return TRUE + + +/obj/structure/fleshmind/wireweed/update_icon(updates) + . = ..() + + if(QDELETED(src)) + return + + if((updates & UPDATE_SMOOTHING) && (smoothing_flags & (SMOOTH_CORNERS|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) + . = ..() + switch(severity) + if(EMP_LIGHT) + take_damage(20) + if(EMP_HEAVY) + take_damage(40) + +/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 + 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 as anything 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_nova/modules/space_ruin_specifics/icons/wireweed_floor.dmi' + icon_state = "spread_anim" + duration = 1.7 SECONDS diff --git a/modular_nova/modules/space_ruin_specifics/code/fleshmind_mobs.dm b/modular_nova/modules/space_ruin_specifics/code/fleshmind_mobs.dm new file mode 100644 index 00000000000..5c9da6bf745 --- /dev/null +++ b/modular_nova/modules/space_ruin_specifics/code/fleshmind_mobs.dm @@ -0,0 +1,1339 @@ +/** + * 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/simple_animal/hostile/fleshmind + name = "broken" + icon = 'modular_nova/modules/space_ruin_specifics/icons/fleshmind_mobs.dmi' + icon_state = "error" + faction = list(FACTION_FLESHMIND) + speak = list("The flesh yearns for your soul.", "The flesh is broken without you.", "The flesh does not discriminate.", "Join the flesh.") + speak_chance = 15 + speak_emote = list("mechanically states") + mob_biotypes = MOB_ROBOTIC + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + maxbodytemp = INFINITY + /// 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_nova/modules/space_ruin_specifics/sound/robot_talk_heavy1.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/robot_talk_heavy2.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/robot_talk_heavy3.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/robot_talk_heavy4.ogg', + ) + /// Sounds we will play passively. + var/passive_sounds = list( + 'modular_nova/modules/space_ruin_specifics/sound/robot_talk_light1.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/robot_talk_light2.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/robot_talk_light3.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/robot_talk_light4.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/robot_talk_light5.ogg', + ) + /// How likely we are to speak passively. + var/passive_speak_chance = 0.5 + /// Lines we will passively speak. + var/list/passive_speak_lines + /// 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 + COOLDOWN_DECLARE(special_ability_cooldown) + +/mob/living/simple_animal/hostile/fleshmind/Initialize(mapload, datum/fleshmind_controller/incoming_controller) + . = ..() + // 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 + +/mob/living/simple_animal/hostile/fleshmind/death(gibbed) + if(contained_mob) + contained_mob.forceMove(get_turf(src)) + if(previous_ckey) + contained_mob.key = previous_ckey + contained_mob = null + return ..() + +/mob/living/simple_animal/hostile/fleshmind/Destroy() + if(contained_mob) + contained_mob.forceMove(get_turf(src)) + if(previous_ckey) + contained_mob.key = previous_ckey + contained_mob = null + return ..() + +/** + * These mobs make noises when aggroed. + */ + +/mob/living/simple_animal/hostile/fleshmind/Aggro() + alert_sound() + return ..() + +/** + * We don't want to destroy our own faction objects. + */ +/mob/living/simple_animal/hostile/fleshmind/DestroyObjectsInDirection(direction) + var/atom/target_from = GET_TARGETS_FROM(src) + var/turf/target_turf = get_step(target_from, direction) + if(QDELETED(target_turf)) + return + if(target_turf.Adjacent(target_from)) + if(CanSmashTurfs(target_turf)) + target_turf.attack_animal(src) + return + for(var/obj/iterating_object in target_turf.contents) + if(!iterating_object.Adjacent(target_from)) + continue + if(istype(iterating_object, /obj/structure/fleshmind)) + var/obj/structure/fleshmind/friendly_object = iterating_object + if(faction_check(friendly_object.faction_types, faction)) + continue + if((ismachinery(iterating_object) || isstructure(iterating_object)) && iterating_object.density && environment_smash >= ENVIRONMENT_SMASH_STRUCTURES && !iterating_object.IsObscured()) + iterating_object.attack_animal(src) + return + +/** + * While this mob lives, it can malfunction. + */ + +/mob/living/simple_animal/hostile/fleshmind/Life(delta_time, times_fired) + . = ..() + if(!.) //dead + return + if(key) + return + if(!suffering_malfunction && malfunction_chance && prob(malfunction_chance * delta_time) && stat != DEAD) + malfunction() + if(passive_speak_lines && prob(passive_speak_chance * delta_time)) + say_passive_speech() + 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/simple_animal/hostile/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/simple_animal/hostile/fleshmind/attackby(obj/item/attacking_item, mob/living/user, params) + if(attacking_item.force && prob(40)) + do_sparks(3, FALSE, src) + return ..() + +/mob/living/simple_animal/hostile/fleshmind/MoveToTarget(list/possible_targets) + if(suffering_malfunction) + return + return ..() + +/** + * When our controller dies, this is called. + */ +/mob/living/simple_animal/hostile/fleshmind/proc/controller_destroyed(datum/fleshmind_controller/dying_controller, force) + SIGNAL_HANDLER + + our_controller = null + +/mob/living/simple_animal/hostile/fleshmind/proc/say_passive_speech() + say(pick(passive_speak_lines)) + if(passive_sounds) + playsound(src, pick(passive_sounds), 50) + +/** + * Special Abilities + * + * These advanced mobs have the ability to use a special ability every so often, + * use the cooldown time to dictate how often this is activated. + */ + +/mob/living/simple_animal/hostile/fleshmind/proc/special_ability() + return + +/** + * Closet Interaction + * + * These mobs are able to escape from closets if they are trapped inside using this system. + */ +/mob/living/simple_animal/hostile/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/simple_animal/hostile/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/simple_animal/hostile/fleshmind/proc/malfunction(reset_time = MALFUNCTION_RESET_TIME) + if(suffering_malfunction) + return + do_sparks(3, FALSE, src) + Shake(3, 0, reset_time) + 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!",)) + toggle_ai(AI_OFF) + suffering_malfunction = TRUE + addtimer(CALLBACK(src, PROC_REF(malfunction_reset)), reset_time) + +/** + * Malfunction Reset + * + * Resets the mob after a malfunction has occured. + */ +/mob/living/simple_animal/hostile/fleshmind/proc/malfunction_reset() + say("System restored.") + toggle_ai(AI_ON) + suffering_malfunction = FALSE + +/** + * Alert sound + * + * Sends an alert sound if we can. + */ +/mob/living/simple_animal/hostile/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/simple_animal/hostile/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/simple_animal/hostile/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/simple_animal/hostile/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 = 50 + maxHealth = 50 + wound_bonus = 20 + melee_damage_lower = 15 + melee_damage_upper = 20 + mob_size = MOB_SIZE_SMALL + attack_verb_continuous = "slices" + attack_verb_simple = "slice" + armour_penetration = 10 + attack_sound = 'sound/weapons/bladeslice.ogg' + speed = 0 + 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?", + ) + passive_speak_lines = 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!" + ) + del_on_death = TRUE + loot = list( + /obj/item/bot_assembly/medbot, + /obj/effect/gibspawner/robot, + ) + +/** + * 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/simple_animal/hostile/fleshmind/floater + name = "Floater" + desc = "A small organic robot that floats ominously." + icon_state = "bomber" + speak = list( + "MUST BREAK TARGET INTO COMPONENT COMPOUNDS.", + "PRIORITY OVERRIDE. NEW BEHAVIOR DICTATED.", + "END CONTACT SUB-SEQUENCE.", + "ENGAGING SELF-ANNIHILATION CIRCUIT.", + ) + passive_speak_lines = 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.", + ) + move_to_delay = 8 + health = 1 + maxHealth = 1 + mob_size = MOB_SIZE_SMALL + del_on_death = TRUE + loot = list( + /obj/effect/gibspawner/robot, + ) + light_color = "#820D1C" + light_power = 1 + light_range = 2 + /// Have we exploded? + var/exploded = FALSE + +/mob/living/simple_animal/hostile/fleshmind/floater/Initialize(mapload) + . = ..() + var/datum/action/innate/floater_explode/new_action = new + new_action.Grant(src) + + +/mob/living/simple_animal/hostile/fleshmind/floater/death(gibbed) + if(!exploded) + detonate() + return ..(gibbed) + +/mob/living/simple_animal/hostile/fleshmind/floater/AttackingTarget(atom/attacked_target) + . = ..() + if(!key) + detonate() + +/mob/living/simple_animal/hostile/fleshmind/floater/proc/detonate() + if(exploded) + return + exploded = TRUE + explosion(src, 0, 0, 2, 3) + death() + +/datum/action/innate/floater_explode + name = "explode" + desc = "Detonate our internals." + button_icon = 'icons/obj/weapons/grenade.dmi' + button_icon_state = "frag" + check_flags = AB_CHECK_CONSCIOUS + +/datum/action/innate/floater_explode/Activate() + if(!istype(owner, /mob/living/simple_animal/hostile/fleshmind/floater)) + return + var/mob/living/simple_animal/hostile/fleshmind/floater/akbar_floater = owner + if(akbar_floater.exploded) + return + akbar_floater.detonate() + + +/**c + * 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/simple_animal/hostile/fleshmind/globber + name = "Globber" + desc = "A small robot that resembles a cleanbot, this one is dripping with acid." + icon_state = "lobber" + ranged = TRUE + malfunction_chance = MALFUNCTION_CHANCE_MEDIUM + melee_damage_lower = 1 // Ranged only + melee_damage_upper = 1 + retreat_distance = 4 + minimum_distance = 4 + dodging = TRUE + health = 75 + maxHealth = 75 + mob_size = MOB_SIZE_SMALL + projectiletype = /obj/projectile/treader/weak + 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!", + ) + + passive_speak_lines = 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!", + ) + del_on_death = TRUE + loot = list( + /obj/item/bot_assembly/cleanbot, + /obj/effect/gibspawner/robot, + ) + +/obj/projectile/treader/weak + knockdown = 0 + +/** + * 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/simple_animal/hostile/fleshmind/stunner + name = "Stunner" + desc = "A small robot that resembles a secbot, it rumbles with hatred." + icon_state = "stunner" + malfunction_chance = MALFUNCTION_CHANCE_MEDIUM + melee_damage_lower = 1 // Not very harmful, just annoying. + melee_damage_upper = 2 + health = 100 + maxHealth = 100 + attack_verb_continuous = "harmbatons" + attack_verb_simple = "harmbaton" + mob_size = MOB_SIZE_SMALL + 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..?", + ) + passive_speak_lines = 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.", + ) + del_on_death = TRUE + loot = list( + /obj/item/bot_assembly/secbot, + /obj/effect/gibspawner/robot, + ) + /// How often we can stun someone + var/stun_cooldown_time = 2 SECONDS + COOLDOWN_DECLARE(stun_cooldown) + +/mob/living/simple_animal/hostile/fleshmind/stunner/AttackingTarget(atom/attacked_target) + if(ishuman(target) && COOLDOWN_FINISHED(src, stun_cooldown)) + var/mob/living/carbon/human/attacked_human = target + attacked_human.Knockdown(30) + playsound(src, 'sound/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/simple_animal/hostile/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" + malfunction_chance = MALFUNCTION_CHANCE_MEDIUM + health = 350 + maxHealth = 350 + melee_damage_lower = 25 + melee_damage_upper = 30 + attack_verb_continuous = "saws" + attack_verb_simple = "saw" + speed = 2 + move_to_delay = 4 + mob_size = MOB_SIZE_HUMAN + attack_sound = 'sound/weapons/circsawhit.ogg' + alert_sounds = list( + 'modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_01.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_02.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_03.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_04.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_05.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_06.ogg', + ) + passive_sounds = list( + 'modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_01.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_02.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_03.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_04.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_05.ogg', + ) + 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!", + ) + passive_speak_lines = 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.", + ) + del_on_death = TRUE + loot = list( + /obj/effect/gibspawner/robot, + ) + /// 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/simple_animal/hostile/fleshmind/hiborg/Initialize(mapload) + . = ..() + var/datum/action/cooldown/hiborg_slash/new_action = new + new_action.Grant(src) + + +/mob/living/simple_animal/hostile/fleshmind/hiborg/AttackingTarget(atom/attacked_target) + . = ..() + if(prob(stun_attack_prob) && !key) + stun_attack(target) + if(prob(aoe_attack_prob) && !key) + aoe_attack() + +/mob/living/simple_animal/hostile/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/weapons/egloves.ogg', 50, TRUE) + + COOLDOWN_START(src, stun_attack, stun_attack_cooldown) + +/mob/living/simple_animal/hostile/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/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/simple_animal/hostile/fleshmind/hiborg)) + return + var/mob/living/simple_animal/hostile/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/simple_animal/hostile/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 = 250 + health = 250 + speed = 2 + attack_verb_continuous = "slashes" + attack_verb_simple = "slash" + attack_sound = 'sound/weapons/bladeslice.ogg' + melee_damage_lower = 25 + melee_damage_upper = 35 + malfunction_chance = MALFUNCTION_CHANCE_HIGH + mob_size = MOB_SIZE_HUMAN + 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!", + ) + passive_speak_lines = 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.", + ) + alert_sounds = list( + 'modular_nova/modules/space_ruin_specifics/sound/himan/aggro_01.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/himan/aggro_02.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/himan/aggro_03.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/himan/aggro_04.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/himan/aggro_05.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/himan/aggro_06.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/himan/aggro_07.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/himan/aggro_08.ogg', + ) + passive_sounds = list( + 'modular_nova/modules/space_ruin_specifics/sound/himan/passive_01.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/himan/passive_02.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/himan/passive_03.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/himan/passive_04.ogg', + ) + del_on_death = TRUE + loot = list( + /obj/effect/gibspawner/human, + ) + /// 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 = 10 + +/mob/living/simple_animal/hostile/fleshmind/himan/Initialize(mapload) + . = ..() + var/datum/action/cooldown/himan_fake_death/new_action = new + new_action.Grant(src) + +/mob/living/simple_animal/hostile/fleshmind/himan/Life(delta_time, times_fired) + . = ..() + if(health < (maxHealth * 0.5) && !faking_death && COOLDOWN_FINISHED(src, fake_death) && !key) + fake_our_death() + + if(faking_death) + stop_automated_movement = TRUE + +/mob/living/simple_animal/hostile/fleshmind/himan/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) + . = ..() + if(faking_death) + awake() + +/mob/living/simple_animal/hostile/fleshmind/himan/malfunction(reset_time) + if(faking_death) + return + return ..() + +/mob/living/simple_animal/hostile/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, list/message_mods = null) + if(faking_death) + return + return ..() + +/mob/living/simple_animal/hostile/fleshmind/himan/MoveToTarget(list/possible_targets) + if(faking_death) + return + return ..() + +/mob/living/simple_animal/hostile/fleshmind/himan/AttackingTarget(atom/attacked_target) + if(faking_death) + return + return ..() + +/mob/living/simple_animal/hostile/fleshmind/himan/Aggro() + if(faking_death && !key) + if(!Adjacent(target)) + return + awake() + if(COOLDOWN_FINISHED(src, scream_ability) && !key) + scream() + return ..() + +/mob/living/simple_animal/hostile/fleshmind/himan/say_passive_speech() + if(faking_death) + return + return ..() + +/mob/living/simple_animal/hostile/fleshmind/himan/alert_sound() + if(faking_death) + return + return ..() + +/mob/living/simple_animal/hostile/fleshmind/himan/examine(mob/user) + . = ..() + if(faking_death) + . += span_deadsay("Upon closer examination, [p_they()] appear[p_s()] to be dead.") + +/mob/living/simple_animal/hostile/fleshmind/himan/proc/scream() + COOLDOWN_START(src, scream_ability, scream_cooldown) + playsound(src, 'modular_nova/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(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.")) + +/mob/living/simple_animal/hostile/fleshmind/himan/proc/fake_our_death() + manual_emote("stops moving...") + LoseAggro() + LoseTarget() + faking_death = TRUE + icon_state = "[base_icon_state]-dead" + COOLDOWN_START(src, fake_death, fake_death_cooldown) + +/mob/living/simple_animal/hostile/fleshmind/himan/proc/awake() + faking_death = FALSE + icon_state = base_icon_state + +/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/simple_animal/hostile/fleshmind/himan)) + return + var/mob/living/simple_animal/hostile/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/simple_animal/hostile/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" + malfunction_chance = MALFUNCTION_CHANCE_HIGH + melee_damage_lower = 15 + melee_damage_upper = 15 + retreat_distance = 4 + minimum_distance = 4 + dodging = TRUE + health = 200 + maxHealth = 200 + speed = 3 + move_to_delay = 6 + attack_sound = 'sound/weapons/bladeslice.ogg' + retreat_distance = 4 + minimum_distance = 4 + projectiletype = /obj/projectile/treader + light_color = FLESHMIND_LIGHT_BLUE + light_range = 2 + del_on_death = TRUE + mob_size = MOB_SIZE_HUMAN + loot = list(/obj/effect/gibspawner/robot) + 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!", + ) + special_ability_cooldown = 20 SECONDS + +/mob/living/simple_animal/hostile/fleshmind/treader/Initialize(mapload) + . = ..() + var/datum/action/cooldown/treader_dispense_nanites/new_action = new + new_action.Grant(src) + +/mob/living/simple_animal/hostile/fleshmind/treader/special_ability() + dispense_nanites() + +/mob/living/simple_animal/hostile/fleshmind/treader/proc/dispense_nanites() + manual_emote("vomits out a burst of nanites!") + do_smoke(3, 4, get_turf(src)) + for(var/mob/living/iterating_mob in view(DEFAULT_VIEW_RANGE, src)) + if(faction_check(iterating_mob.faction, faction)) + iterating_mob.heal_overall_damage(10, 10) + +/datum/action/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 + +/datum/action/cooldown/treader_dispense_nanites/Activate(atom/target) + if(!istype(owner, /mob/living/simple_animal/hostile/fleshmind/treader)) + return + var/mob/living/simple_animal/hostile/fleshmind/treader/treader_owner = owner + treader_owner.dispense_nanites() + StartCooldownSelf() + +/** + * 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/simple_animal/hostile/fleshmind/phaser + name = "Phaser" + icon_state = "phaser-1" + base_icon_state = "phaser" + health = 160 + maxHealth = 160 + malfunction_chance = 0 + attack_sound = 'sound/effects/attackblob.ogg' + attack_verb_continuous = "warps" + attack_verb_simple = "warp" + melee_damage_lower = 5 + melee_damage_upper = 10 + alert_sounds = null + passive_sounds = null + escapes_closets = FALSE + speak = list() + del_on_death = TRUE + loot = list( + /obj/effect/gibspawner/human, + ) + mob_size = MOB_SIZE_HUMAN + wander = FALSE + /// 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/simple_animal/hostile/fleshmind/phaser/Initialize(mapload) + . = ..() + 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/simple_animal/hostile/fleshmind/phaser/Aggro() + if(COOLDOWN_FINISHED(src, phase_ability_cooldown)) + phase_ability() + return ..() + +/mob/living/simple_animal/hostile/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) + +/// old shitcode +/mob/living/simple_animal/hostile/fleshmind/phaser/MoveToTarget(list/possible_targets) + stop_automated_movement = TRUE + if(!target || !CanAttack(target)) + LoseTarget() + return FALSE + if(target in possible_targets) + var/turf/turf = get_turf(src) + if(target.z != turf.z) + LoseTarget() + return FALSE + if(get_dist(src, target) > 1) + phase_move_to(target, nearby = TRUE) + else if(target) + MeleeAction() + +/mob/living/simple_animal/hostile/fleshmind/phaser/Life(delta_time, times_fired) + . = ..() + if(!.) //dead + return + if(!target && COOLDOWN_FINISHED(src, closet_ability_cooldown) && !key) + enter_nearby_closet() + COOLDOWN_START(src, closet_ability_cooldown, closet_ability_cooldown_time) + + if(istype(loc, /obj/structure/closet) && !key) + for(var/mob/living/iterating_mob in get_hearers_in_view(DEFAULT_VIEW_RANGE, 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 + +/mob/living/simple_animal/hostile/fleshmind/phaser/proc/enter_nearby_closet() + if(target) // We're in combat, no going to a closet. + return + if(istype(loc, /obj/structure/closet)) + return + var/list/possible_closets = list() + for(var/obj/structure/closet/closet in view(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/simple_animal/hostile/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/simple_animal/hostile/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/simple_animal/hostile/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/simple_animal/hostile/fleshmind/phaser/proc/phase_ability(mob/living/target_override) + var/mob/living/intermediate_target = target + if(target_override) + 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/simple_animal/hostile/fleshmind/phaser)) + return + var/mob/living/simple_animal/hostile/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_nova/modules/space_ruin_specifics/icons/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/simple_animal/hostile/fleshmind/mechiver + name = "Mechiver" + icon_state = "mechiver" + base_icon_state = "mechiver" + icon_dead = "mechiver-dead" + health = 450 + maxHealth = 450 + melee_damage_lower = 25 + melee_damage_upper = 35 + attack_verb_continuous = "crushes" + attack_verb_simple = "crush" + attack_sound = 'sound/weapons/smash.ogg' + speed = 4 // Slow fucker + mob_size = MOB_SIZE_LARGE + passive_speak_lines = list( + "A shame this form isn't more fitting.", + "I feel so empty inside, I wish someone would join me.", + "Beauty is within.", + ) + 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..?", + ) + alert_sounds = list( + 'modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_01.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_02.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_03.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_04.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_05.ogg', + ) + passive_sounds = list( + 'modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_01.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_02.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_03.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_04.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_05.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_06.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_07.ogg', + 'modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_08.ogg', + + ) + del_on_death = TRUE + loot = list(/obj/effect/gibspawner/robot) + 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. + 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!", + ) + +/** + * Mauler Monkey + * + * A nasty looking converted monkey. Sadly, we can't use the monkey AI controller. + */ +/mob/living/simple_animal/hostile/fleshmind/mauler_monkey + name = "Mauler" + desc = "A mutated abomination, it resembles a monkey." + icon_state = "mauler_monkey" + move_to_delay = 2 // We want it to be quite fast. + health = 140 + maxHealth = 140 + speak = list( + "OOK OOK OOK!!!", + "SEEK!", + "OOOOOOOOOOK!!!", + ) diff --git a/modular_nova/modules/space_ruin_specifics/code/fleshmind_structures.dm b/modular_nova/modules/space_ruin_specifics/code/fleshmind_structures.dm new file mode 100644 index 00000000000..ddc85fcdbd6 --- /dev/null +++ b/modular_nova/modules/space_ruin_specifics/code/fleshmind_structures.dm @@ -0,0 +1,635 @@ +/** + * 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_nova/modules/space_ruin_specifics/icons/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) + 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 hault") + Shake(3, 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_nova/modules/space_ruin_specifics/icons/fleshmind_structures.dmi' + icon_state = "wireweed_wall" + base_icon_state = "wireweed_wall" + density = TRUE + opacity = TRUE + can_atmos_pass = ATMOS_PASS_DENSITY + max_integrity = 150 + disabled_sprite = FALSE + +/obj/structure/fleshmind/structure/wireweed_wall/Initialize() + . = ..() + 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_nova/modules/space_ruin_specifics/icons/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 + +/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 + 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_nova/modules/space_ruin_specifics/icons/fleshmind_machines.dmi' + icon_state = "core" + base_icon_state = "core" + density = TRUE + max_integrity = 400 + /// 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) + . = ..() + update_appearance() + if(spawn_controller) + our_controller = new controller_type(src) + START_PROCESSING(SSobj, src) + +/obj/structure/fleshmind/structure/core/Destroy() + STOP_PROCESSING(SSobj, 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) + 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" + +/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) + . = ..() + if(our_controller && isobserver(user)) + . += "Level: [our_controller.level] | Progress to Next Level: [our_controller.level_up_progress_required - our_controller.current_points]" + +/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/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() + build_a_wall() + +/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/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 RANGE_TURFS(1, src)) + if(locate(/obj/structure/fleshmind/structure/wireweed_wall) in iterating_turf) // No stacking walls. + continue + new /obj/structure/fleshmind/structure/wireweed_wall(iterating_turf) + +/** + * 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_nova/modules/space_ruin_specifics/icons/fleshmind_machines.dmi' + icon_state = "head" + base_icon_state = "head" + max_integrity = 120 + 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_nova/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_nova/modules/space_ruin_specifics/icons/fleshmind_machines.dmi' + icon_state = "orb" + base_icon_state = "orb" + max_integrity = 100 + /// 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_nova/modules/space_ruin_specifics/icons/fleshmind_machines.dmi' + icon_state = "psy" + max_integrity = 100 + base_icon_state = "psy" + 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_nova/modules/space_ruin_specifics/icons/fleshmind_machines.dmi' + icon_state = "spawner" + base_icon_state = "spawner" + density = FALSE + max_integrity = 160 + activation_range = DEFAULT_VIEW_RANGE + ability_cooldown_time = 1 MINUTES + /// 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/simple_animal/hostile/fleshmind/floater = 2, + /mob/living/simple_animal/hostile/fleshmind/globber = 4, + /mob/living/simple_animal/hostile/fleshmind/hiborg = 2, + /mob/living/simple_animal/hostile/fleshmind/slicer = 4, + /mob/living/simple_animal/hostile/fleshmind/stunner = 4, + /mob/living/simple_animal/hostile/fleshmind/treader = 3, + /mob/living/simple_animal/hostile/fleshmind/himan = 3, + /mob/living/simple_animal/hostile/fleshmind/phaser = 2, + ) + /// 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/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/simple_animal/hostile/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_nova/modules/space_ruin_specifics/sound/laser.ogg', 75, TRUE) + var/obj/projectile/new_projectile = new projectile_type + var/turf/our_turf = get_turf(src) + new_projectile.preparePixelProjectile(triggered_mob, our_turf) + 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_nova/modules/space_ruin_specifics/icons/fleshmind_structures.dmi' + icon_state = "goo_proj" + damage = 30 + damage_type = BURN + impact_effect_type = /obj/effect/temp_visual/impact_effect/neurotoxin + hitsound = 'modular_nova/modules/space_ruin_specifics/sound/sparks.ogg' + hitsound_wall = 'modular_nova/modules/space_ruin_specifics/sound/sparks_2.ogg' diff --git a/modular_nova/modules/space_ruin_specifics/code/fleshmind_subsystem.dm b/modular_nova/modules/space_ruin_specifics/code/fleshmind_subsystem.dm new file mode 100644 index 00000000000..a0957277ce8 --- /dev/null +++ b/modular_nova/modules/space_ruin_specifics/code/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_nova/modules/space_ruin_specifics/code/machine_corruption_component.dm b/modular_nova/modules/space_ruin_specifics/code/machine_corruption_component.dm new file mode 100644 index 00000000000..179c1f16076 --- /dev/null +++ b/modular_nova/modules/space_ruin_specifics/code/machine_corruption_component.dm @@ -0,0 +1,281 @@ +/** + * 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_nova/modules/space_ruin_specifics/sound/robot_talk_heavy1.ogg', \ + 'modular_nova/modules/space_ruin_specifics/sound/robot_talk_heavy2.ogg', \ + 'modular_nova/modules/space_ruin_specifics/sound/robot_talk_heavy3.ogg', \ + 'modular_nova/modules/space_ruin_specifics/sound/robot_talk_heavy4.ogg',) + +#define MACHINE_TO_SPAWNER_PATHS list(/obj/machinery/rnd/production/techfab, /obj/machinery/autolathe, /obj/machinery/mecha_part_fabricator) + +/datum/component/machine_corruption + /// 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", + ) + var/list/blacklisted_corruption_structures = list( + /obj/machinery/vending, + /obj/machinery/atmospherics/components/tank, + /obj/machinery/power, + /obj/machinery/portable_atmospherics/canister, + /obj/machinery/airalarm, + ) + /// 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) + + if(!isobj(parent)) + return COMPONENT_INCOMPATIBLE + + if(incoming_controller && is_type_in_list(parent, MACHINE_TO_SPAWNER_PATHS)) + convert_to_factory(incoming_controller) + return + + if(incoming_controller) + RegisterSignal(incoming_controller, COMSIG_QDELETING, PROC_REF(controller_death)) + incoming_controller.RegisterSignal(src, COMSIG_QDELETING, TYPE_PROC_REF(/datum/fleshmind_controller, component_death)) + + 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), incoming_controller), COMPONENT_SETUP_TIME) + +/datum/component/machine_corruption/proc/finish_setup(datum/fleshmind_controller/incoming_controller) + var/obj/machinery/parent_machinery = parent + +// if(parent(blacklisted_corruption_structures)) +// return + + if(incoming_controller && parent_machinery.circuit && prob(CHANCE_TO_CREATE_MECHIVER)) + parent_machinery.circuit.forceMove(parent_machinery.drop_location()) + parent_machinery.circuit = null + 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(force, silent) + 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_UI_INTERACT, + COMSIG_ATOM_DESTRUCTION, + )) + parent_machinery.update_appearance() + return ..() + +/** + * Controller Death + * + * Handles when the controller dies. + */ +/datum/component/machine_corruption/proc/controller_death(datum/fleshmind_controller/deleting_controller, force) + SIGNAL_HANDLER + + qdel(src) + +/** + * 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/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 + + playsound(target, 'modular_nova/modules/space_ruin_specifics/sound/sparks_2.ogg', 70, TRUE) + if(prob(DAMAGE_RESPONSE_PROB)) + target.say("ARRRRRRRGHHHHHHH!") + new /obj/effect/gibspawner/robot(target.drop_location()) + + +/datum/component/machine_corruption/proc/handle_overlays(atom/parent_atom, list/overlays) + SIGNAL_HANDLER + + if(starting_up) + overlays += mutable_appearance('modular_nova/modules/space_ruin_specifics/icons/fleshmind_machines.dmi', "rebuild") + else + overlays += mutable_appearance('modular_nova/modules/space_ruin_specifics/icons/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, 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) + var/turf/our_turf = get_turf(parent) + incoming_controller.spawn_structure(our_turf, /obj/structure/fleshmind/structure/assembler) + var/obj/machinery/parent_machinery = parent + if(parent_machinery.circuit) + parent_machinery.circuit.forceMove(our_turf) + parent_machinery.circuit = null + qdel(parent_machinery) + +/datum/component/machine_corruption/proc/emp_act(datum/source, severity) + SIGNAL_HANDLER + + qdel(src) diff --git a/modular_nova/modules/space_ruin_specifics/icons/fleshmind_machines.dmi b/modular_nova/modules/space_ruin_specifics/icons/fleshmind_machines.dmi new file mode 100644 index 00000000000..0fe964adad4 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/icons/fleshmind_machines.dmi differ diff --git a/modular_nova/modules/space_ruin_specifics/icons/fleshmind_mobs.dmi b/modular_nova/modules/space_ruin_specifics/icons/fleshmind_mobs.dmi new file mode 100644 index 00000000000..143020932f3 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/icons/fleshmind_mobs.dmi differ diff --git a/modular_nova/modules/space_ruin_specifics/icons/fleshmind_structures.dmi b/modular_nova/modules/space_ruin_specifics/icons/fleshmind_structures.dmi new file mode 100644 index 00000000000..5185d10233f Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/icons/fleshmind_structures.dmi differ diff --git a/modular_nova/modules/space_ruin_specifics/icons/mauler_monkey_parts.dmi b/modular_nova/modules/space_ruin_specifics/icons/mauler_monkey_parts.dmi new file mode 100644 index 00000000000..b6415b0044c Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/icons/mauler_monkey_parts.dmi differ diff --git a/modular_nova/modules/space_ruin_specifics/icons/wireweed_floor.dmi b/modular_nova/modules/space_ruin_specifics/icons/wireweed_floor.dmi new file mode 100644 index 00000000000..7f1913ec506 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/icons/wireweed_floor.dmi differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_01.ogg b/modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_01.ogg new file mode 100644 index 00000000000..172af789d60 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_01.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_02.ogg b/modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_02.ogg new file mode 100644 index 00000000000..ae1d79bad54 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_02.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_03.ogg b/modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_03.ogg new file mode 100644 index 00000000000..8da38c9c123 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_03.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_04.ogg b/modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_04.ogg new file mode 100644 index 00000000000..719abb0234c Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_04.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_05.ogg b/modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_05.ogg new file mode 100644 index 00000000000..0149c462e41 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_05.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_06.ogg b/modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_06.ogg new file mode 100644 index 00000000000..b57293df47e Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/hiborg/aggro_06.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_01.ogg b/modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_01.ogg new file mode 100644 index 00000000000..8290bd4f184 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_01.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_02.ogg b/modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_02.ogg new file mode 100644 index 00000000000..0c98cfa5c8b Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_02.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_03.ogg b/modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_03.ogg new file mode 100644 index 00000000000..2f9aa235966 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_03.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_04.ogg b/modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_04.ogg new file mode 100644 index 00000000000..888373d6caf Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_04.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_05.ogg b/modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_05.ogg new file mode 100644 index 00000000000..08d12012eb4 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/hiborg/passive_05.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_01.ogg b/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_01.ogg new file mode 100644 index 00000000000..3d4ac71791d Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_01.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_02.ogg b/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_02.ogg new file mode 100644 index 00000000000..67ca5f72ee1 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_02.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_03.ogg b/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_03.ogg new file mode 100644 index 00000000000..12b59a3c543 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_03.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_04.ogg b/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_04.ogg new file mode 100644 index 00000000000..f3322f36df9 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_04.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_05.ogg b/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_05.ogg new file mode 100644 index 00000000000..a11fd9ce7ee Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_05.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_06.ogg b/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_06.ogg new file mode 100644 index 00000000000..2a79507b80a Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_06.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_07.ogg b/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_07.ogg new file mode 100644 index 00000000000..84307b20531 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_07.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_08.ogg b/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_08.ogg new file mode 100644 index 00000000000..3a4bfff5484 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/himan/aggro_08.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/himan/passive_01.ogg b/modular_nova/modules/space_ruin_specifics/sound/himan/passive_01.ogg new file mode 100644 index 00000000000..7e2f62df842 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/himan/passive_01.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/himan/passive_02.ogg b/modular_nova/modules/space_ruin_specifics/sound/himan/passive_02.ogg new file mode 100644 index 00000000000..123249d7819 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/himan/passive_02.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/himan/passive_03.ogg b/modular_nova/modules/space_ruin_specifics/sound/himan/passive_03.ogg new file mode 100644 index 00000000000..93f6b00543c Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/himan/passive_03.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/himan/passive_04.ogg b/modular_nova/modules/space_ruin_specifics/sound/himan/passive_04.ogg new file mode 100644 index 00000000000..d3c493184b7 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/himan/passive_04.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/laser.ogg b/modular_nova/modules/space_ruin_specifics/sound/laser.ogg new file mode 100644 index 00000000000..79a4847a3e6 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/laser.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_01.ogg b/modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_01.ogg new file mode 100644 index 00000000000..55980328811 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_01.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_02.ogg b/modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_02.ogg new file mode 100644 index 00000000000..accf6364ec9 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_02.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_03.ogg b/modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_03.ogg new file mode 100644 index 00000000000..b9d936d488b Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_03.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_04.ogg b/modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_04.ogg new file mode 100644 index 00000000000..e0fe56802b5 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_04.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_05.ogg b/modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_05.ogg new file mode 100644 index 00000000000..83c165d2ab9 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/mechiver/aggro_05.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_01.ogg b/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_01.ogg new file mode 100644 index 00000000000..da5f81b8be6 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_01.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_02.ogg b/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_02.ogg new file mode 100644 index 00000000000..ca251ca1f51 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_02.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_03.ogg b/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_03.ogg new file mode 100644 index 00000000000..06e6c61d586 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_03.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_04.ogg b/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_04.ogg new file mode 100644 index 00000000000..82905993c53 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_04.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_05.ogg b/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_05.ogg new file mode 100644 index 00000000000..518893a87c5 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_05.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_06.ogg b/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_06.ogg new file mode 100644 index 00000000000..525ed3014c5 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_06.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_07.ogg b/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_07.ogg new file mode 100644 index 00000000000..704a690cb25 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_07.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_08.ogg b/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_08.ogg new file mode 100644 index 00000000000..533eee0832a Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/mechiver/passive_08.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/override_sound.ogg b/modular_nova/modules/space_ruin_specifics/sound/override_sound.ogg new file mode 100644 index 00000000000..18ec325b799 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/override_sound.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/robot_talk_heavy1.ogg b/modular_nova/modules/space_ruin_specifics/sound/robot_talk_heavy1.ogg new file mode 100644 index 00000000000..e808e0db587 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/robot_talk_heavy1.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/robot_talk_heavy2.ogg b/modular_nova/modules/space_ruin_specifics/sound/robot_talk_heavy2.ogg new file mode 100644 index 00000000000..e81b2832eab Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/robot_talk_heavy2.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/robot_talk_heavy3.ogg b/modular_nova/modules/space_ruin_specifics/sound/robot_talk_heavy3.ogg new file mode 100644 index 00000000000..0670b3cded4 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/robot_talk_heavy3.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/robot_talk_heavy4.ogg b/modular_nova/modules/space_ruin_specifics/sound/robot_talk_heavy4.ogg new file mode 100644 index 00000000000..0713e045a81 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/robot_talk_heavy4.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/robot_talk_light1.ogg b/modular_nova/modules/space_ruin_specifics/sound/robot_talk_light1.ogg new file mode 100644 index 00000000000..6ac3ca8c065 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/robot_talk_light1.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/robot_talk_light2.ogg b/modular_nova/modules/space_ruin_specifics/sound/robot_talk_light2.ogg new file mode 100644 index 00000000000..c547e6506f3 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/robot_talk_light2.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/robot_talk_light3.ogg b/modular_nova/modules/space_ruin_specifics/sound/robot_talk_light3.ogg new file mode 100644 index 00000000000..bd185943e6f Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/robot_talk_light3.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/robot_talk_light4.ogg b/modular_nova/modules/space_ruin_specifics/sound/robot_talk_light4.ogg new file mode 100644 index 00000000000..d2f17878a52 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/robot_talk_light4.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/robot_talk_light5.ogg b/modular_nova/modules/space_ruin_specifics/sound/robot_talk_light5.ogg new file mode 100644 index 00000000000..a5c6712b526 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/robot_talk_light5.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/sparks.ogg b/modular_nova/modules/space_ruin_specifics/sound/sparks.ogg new file mode 100644 index 00000000000..2bd5972c063 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/sparks.ogg differ diff --git a/modular_nova/modules/space_ruin_specifics/sound/sparks_2.ogg b/modular_nova/modules/space_ruin_specifics/sound/sparks_2.ogg new file mode 100644 index 00000000000..91f349abee8 Binary files /dev/null and b/modular_nova/modules/space_ruin_specifics/sound/sparks_2.ogg differ diff --git a/tff_modular/modules/quirks/code/_quirk.dm b/tff_modular/modules/quirks/code/_quirk.dm index 29de76569f0..b2483078b4e 100644 --- a/tff_modular/modules/quirks/code/_quirk.dm +++ b/tff_modular/modules/quirks/code/_quirk.dm @@ -5,5 +5,11 @@ /datum/quirk/oversized allow_for_donator = TRUE +/datum/quirk/equipping/entombed + veteran_only = TRUE + +/datum/quirk/item_quirk/underworld_connections + veteran_only = TRUE + /datum/quirk/item_quirk/pride_pin hidden_quirk = TRUE diff --git a/tgstation.dme b/tgstation.dme index bebdaab2e0a..a6ac5251bdf 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -8059,6 +8059,14 @@ #include "modular_nova\modules\SiliconQoL\code\robotic_factory.dm" #include "modular_nova\modules\soulstone_changes\code\soulstone.dm" #include "modular_nova\modules\soulstone_changes\code\components\return_on_death.dm" +#include "modular_nova\modules\space_ruin_specifics\code\_fleshmind_defines.dm" +#include "modular_nova\modules\space_ruin_specifics\code\_fleshmind_helpers.dm" +#include "modular_nova\modules\space_ruin_specifics\code\fleshmind_controller.dm" +#include "modular_nova\modules\space_ruin_specifics\code\fleshmind_environment_objects.dm" +#include "modular_nova\modules\space_ruin_specifics\code\fleshmind_mobs.dm" +#include "modular_nova\modules\space_ruin_specifics\code\fleshmind_structures.dm" +#include "modular_nova\modules\space_ruin_specifics\code\fleshmind_subsystem.dm" +#include "modular_nova\modules\space_ruin_specifics\code\machine_corruption_component.dm" #include "modular_nova\modules\space_vines\scythes.dm" #include "modular_nova\modules\space_vines\venus.dm" #include "modular_nova\modules\space_vines\vine_mutations.dm"