From 834fca8213ea21ca04b66df8e09e8ee08122b82b Mon Sep 17 00:00:00 2001 From: Alexis Date: Tue, 28 Oct 2025 14:42:30 -0400 Subject: [PATCH] (IRIS PORT) Black Mesa Basic Mobs (#4632) ## About The Pull Request **_ALL THE CREDIT FOR THIS GOES TO NoNameGiven-05 FROM IRISSTATION FOR https://github.com/IrisSS13/IrisStation/pull/627._** EVERY Black Mesa mob has been refactored into a basic mob. Attached are videos of the new mob AI in action.
Bullsquid https://github.com/user-attachments/assets/a4e279be-c365-482f-b481-8cd5166d24c6
Gordon Freeman https://github.com/user-attachments/assets/b658641c-5bb0-4132-9020-81ded34ade1b
Headcrab https://github.com/user-attachments/assets/ee6219af-fa56-42b4-ba14-de949f9f463b
Houndeye https://github.com/user-attachments/assets/5ceea5b5-2f4d-4569-9d89-933f0ba668f2
Nihilanth https://github.com/user-attachments/assets/617f1f78-abee-457c-8dba-7d8767892d62
Vortigaunt https://github.com/user-attachments/assets/3ae36849-7788-40ff-8e14-036d0bf42301
## Why It's Good For The Game It makes the Half-Life gateway mobs actually *do* stuff. ## Proof Of Testing See above. ## Changelog :cl: refactor: Refactored ALL the Black Mesa mobs into basic mobs. /:cl: --- _maps/RandomZLevels/blackmesa.dmm | 30 +-- _maps/map_files/Blueshift/Blueshift.dmm | 6 +- code/__HELPERS/~skyrat_helpers/is_helpers.dm | 2 +- .../unit_tests/simple_animal_freeze.dm | 22 -- .../modules/black_mesa/code/mobs/bullsquid.dm | 50 ---- .../code/mobs/bullsquid/bullsquid.dm | 74 ++++++ .../code/mobs/bullsquid/bullsquid_ai.dm | 46 ++++ .../black_mesa/code/mobs/corpse_spawns.dm | 105 ++++++++ .../{ => gordon_freeman}/gordon_freeman.dm | 94 +++++++- .../mobs/gordon_freeman/gordon_freeman_ai.dm | 18 ++ .../modules/black_mesa/code/mobs/headcrab.dm | 136 ----------- .../black_mesa/code/mobs/headcrab/headcrab.dm | 174 ++++++++++++++ .../code/mobs/headcrab/headcrab_ai.dm | 64 +++++ .../black_mesa/code/mobs/headcrab_zombie.dm | 89 ------- .../mobs/headcrab_zombie/headcrab_zombie.dm | 130 ++++++++++ .../headcrab_zombie/headcrab_zombie_ai.dm | 19 ++ .../modules/black_mesa/code/mobs/houndeye.dm | 52 ---- .../black_mesa/code/mobs/houndeye/houndeye.dm | 85 +++++++ .../code/mobs/houndeye/houndeye_ai.dm | 138 +++++++++++ .../black_mesa/code/mobs/human_mobs.dm | 226 ------------------ .../code/mobs/human_mobs/human_mobs.dm | 216 +++++++++++++++++ .../code/mobs/human_mobs/human_mobs_ai.dm | 74 ++++++ .../black_mesa/code/mobs/mob_spawners.dm | 74 +++--- .../modules/black_mesa/code/mobs/nihilanth.dm | 67 ------ .../code/mobs/nihilanth/nihilanth.dm | 123 ++++++++++ .../code/mobs/nihilanth/nihilanth_ai.dm | 23 ++ .../black_mesa/code/mobs/vortigaunt.dm | 64 ----- .../code/mobs/vortigaunt/vortigaunt.dm | 60 +++++ .../code/mobs/vortigaunt/vortigaunt_ai.dm | 21 ++ .../modules/black_mesa/code/shield_pylon.dm | 37 +-- .../modules/black_mesa/code/turfs.dm | 2 +- .../modules/black_mesa/code/xen_crystal.dm | 4 +- .../modules/decay_subsystem/code/nests.dm | 2 +- tgstation.dme | 25 +- 34 files changed, 1560 insertions(+), 792 deletions(-) delete mode 100644 modular_skyrat/modules/black_mesa/code/mobs/bullsquid.dm create mode 100644 modular_skyrat/modules/black_mesa/code/mobs/bullsquid/bullsquid.dm create mode 100644 modular_skyrat/modules/black_mesa/code/mobs/bullsquid/bullsquid_ai.dm create mode 100644 modular_skyrat/modules/black_mesa/code/mobs/corpse_spawns.dm rename modular_skyrat/modules/black_mesa/code/mobs/{ => gordon_freeman}/gordon_freeman.dm (59%) create mode 100644 modular_skyrat/modules/black_mesa/code/mobs/gordon_freeman/gordon_freeman_ai.dm delete mode 100644 modular_skyrat/modules/black_mesa/code/mobs/headcrab.dm create mode 100644 modular_skyrat/modules/black_mesa/code/mobs/headcrab/headcrab.dm create mode 100644 modular_skyrat/modules/black_mesa/code/mobs/headcrab/headcrab_ai.dm delete mode 100644 modular_skyrat/modules/black_mesa/code/mobs/headcrab_zombie.dm create mode 100644 modular_skyrat/modules/black_mesa/code/mobs/headcrab_zombie/headcrab_zombie.dm create mode 100644 modular_skyrat/modules/black_mesa/code/mobs/headcrab_zombie/headcrab_zombie_ai.dm delete mode 100644 modular_skyrat/modules/black_mesa/code/mobs/houndeye.dm create mode 100644 modular_skyrat/modules/black_mesa/code/mobs/houndeye/houndeye.dm create mode 100644 modular_skyrat/modules/black_mesa/code/mobs/houndeye/houndeye_ai.dm delete mode 100644 modular_skyrat/modules/black_mesa/code/mobs/human_mobs.dm create mode 100644 modular_skyrat/modules/black_mesa/code/mobs/human_mobs/human_mobs.dm create mode 100644 modular_skyrat/modules/black_mesa/code/mobs/human_mobs/human_mobs_ai.dm delete mode 100644 modular_skyrat/modules/black_mesa/code/mobs/nihilanth.dm create mode 100644 modular_skyrat/modules/black_mesa/code/mobs/nihilanth/nihilanth.dm create mode 100644 modular_skyrat/modules/black_mesa/code/mobs/nihilanth/nihilanth_ai.dm delete mode 100644 modular_skyrat/modules/black_mesa/code/mobs/vortigaunt.dm create mode 100644 modular_skyrat/modules/black_mesa/code/mobs/vortigaunt/vortigaunt.dm create mode 100644 modular_skyrat/modules/black_mesa/code/mobs/vortigaunt/vortigaunt_ai.dm diff --git a/_maps/RandomZLevels/blackmesa.dmm b/_maps/RandomZLevels/blackmesa.dmm index 352f70347bc..3be8c5a6319 100644 --- a/_maps/RandomZLevels/blackmesa.dmm +++ b/_maps/RandomZLevels/blackmesa.dmm @@ -324,7 +324,7 @@ /turf/open/misc/dirt/planet/xen, /area/awaymission/black_mesa/xen/nihilanth_computer) "adL" = ( -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab, +/mob/living/basic/blackmesa/xen/headcrab, /turf/open/floor/mineral/titanium/white, /area/awaymission/black_mesa/entrance) "adM" = ( @@ -1800,7 +1800,7 @@ /area/awaymission/black_mesa/security_outpost) "axm" = ( /obj/structure/stone_tile/slab, -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/gordon_freeman, +/mob/living/basic/blackmesa/xen/gordon_freeman, /turf/open/misc/xen, /area/awaymission/black_mesa/xen/freeman_arena) "axn" = ( @@ -2724,7 +2724,7 @@ /obj/structure/chair/office{ dir = 8 }, -/mob/living/simple_animal/hostile/blackmesa/sec/ranged, +/mob/living/basic/blackmesa/human/sec/ranged, /turf/open/floor/iron, /area/awaymission/black_mesa/entrance_lobby) "aJd" = ( @@ -3609,7 +3609,7 @@ /turf/open/floor/mineral/titanium/white, /area/awaymission/black_mesa/tram_room) "aUu" = ( -/mob/living/simple_animal/hostile/blackmesa/xen/nihilanth, +/mob/living/basic/blackmesa/xen/nihilanth, /obj/structure/marker_beacon/green, /turf/open/floor/vault/alien, /area/awaymission/black_mesa/xen/nihilanth_arena) @@ -6126,7 +6126,7 @@ /turf/open/misc/xen, /area/awaymission/black_mesa/xen/acid_lake_hallway) "eXf" = ( -/mob/living/simple_animal/hostile/blackmesa/sec, +/mob/living/basic/blackmesa/human/sec, /turf/open/floor/iron/smooth_large, /area/awaymission/black_mesa/high_security_armory) "eXU" = ( @@ -6449,7 +6449,7 @@ /turf/open/floor/iron, /area/awaymission/black_mesa/entrance_internal_hall) "fwW" = ( -/mob/living/simple_animal/hostile/blackmesa/blackops/ranged, +/mob/living/basic/blackmesa/human/blackops/ranged, /turf/open/floor/iron/smooth, /area/awaymission/black_mesa/black_ops_button) "fxF" = ( @@ -7477,7 +7477,7 @@ /turf/open/misc/ironsand/black_mesa, /area/awaymission/black_mesa/hecu_zone_towards_facility) "hBp" = ( -/mob/living/simple_animal/hostile/blackmesa/sec/ranged, +/mob/living/basic/blackmesa/human/sec/ranged, /turf/open/floor/iron/textured, /area/awaymission/black_mesa/high_security_sec_point) "hBz" = ( @@ -9140,7 +9140,7 @@ }, /area/awaymission/black_mesa/black_ops_downstairs_atrium) "kMw" = ( -/mob/living/simple_animal/hostile/blackmesa/sec/ranged, +/mob/living/basic/blackmesa/human/sec/ranged, /turf/open/floor/iron/smooth_large, /area/awaymission/black_mesa/high_security_servers) "kNj" = ( @@ -9254,7 +9254,7 @@ /turf/closed/wall/r_wall, /area/awaymission/black_mesa/genetics_sec) "kWX" = ( -/mob/living/simple_animal/hostile/blackmesa/xen/bullsquid, +/mob/living/basic/blackmesa/xen/bullsquid, /turf/open/misc/xen, /area/awaymission/black_mesa/xen/acid_lake) "kXt" = ( @@ -9946,7 +9946,7 @@ "mnn" = ( /obj/structure/alien/weeds/xen, /obj/structure/cable, -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/hev, +/mob/living/basic/blackmesa/xen/headcrab_zombie/hev, /turf/open/floor/plating, /area/awaymission/black_mesa/resonant_chamber) "mnS" = ( @@ -13886,7 +13886,7 @@ /area/awaymission/black_mesa/xen/acid_lake_building) "uaK" = ( /obj/machinery/light/cold, -/mob/living/simple_animal/hostile/blackmesa/blackops/ranged, +/mob/living/basic/blackmesa/human/blackops/ranged, /turf/open/floor/iron/textured_large, /area/awaymission/black_mesa/black_ops_downstairs_atrium) "ubi" = ( @@ -14279,7 +14279,7 @@ /obj/structure/deployable_barricade/metal{ dir = 8 }, -/mob/living/simple_animal/hostile/blackmesa/blackops/ranged, +/mob/living/basic/blackmesa/human/blackops/ranged, /turf/open/misc/ironsand/black_mesa, /area/awaymission/black_mesa/hecu_zone_towards_facility) "uSX" = ( @@ -14351,7 +14351,7 @@ /area/awaymission/black_mesa/xen/acid_lake_hallway) "uWN" = ( /obj/structure/alien/weeds/xen, -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/scientist, +/mob/living/basic/blackmesa/xen/headcrab_zombie/scientist, /turf/open/floor/iron/textured, /area/awaymission/black_mesa/science_labs) "uWO" = ( @@ -15401,7 +15401,7 @@ dir = 8 }, /obj/structure/alien/weeds/xen, -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/scientist, +/mob/living/basic/blackmesa/xen/headcrab_zombie/scientist, /turf/open/floor/iron/textured, /area/awaymission/black_mesa/science_labs) "wUw" = ( @@ -15450,7 +15450,7 @@ /turf/open/floor/iron/smooth, /area/awaymission/black_mesa/genetics_elevator) "wYo" = ( -/mob/living/simple_animal/hostile/blackmesa/xen/bullsquid, +/mob/living/basic/blackmesa/xen/bullsquid, /turf/open/water/xen_acid, /area/awaymission/black_mesa/xen/acid_lake) "wYs" = ( diff --git a/_maps/map_files/Blueshift/Blueshift.dmm b/_maps/map_files/Blueshift/Blueshift.dmm index f54fd237da9..36dbdd7aaf4 100644 --- a/_maps/map_files/Blueshift/Blueshift.dmm +++ b/_maps/map_files/Blueshift/Blueshift.dmm @@ -59106,7 +59106,7 @@ /obj/effect/decal/cleanable/dirt{ icon_state = "dirt-flat-1" }, -/mob/living/simple_animal/hostile/blackmesa/xen/bullsquid, +/mob/living/basic/blackmesa/xen/bullsquid, /obj/structure/alien/weeds/xen, /turf/open/misc/asteroid/airless, /area/space/nearstation) @@ -77459,7 +77459,7 @@ icon_state = "dirt-flat-1" }, /obj/structure/alien/weeds/xen, -/mob/living/simple_animal/hostile/blackmesa/xen/houndeye, +/mob/living/basic/blackmesa/xen/houndeye, /turf/open/misc/asteroid/airless, /area/space/nearstation) "oMZ" = ( @@ -90929,7 +90929,7 @@ icon_state = "dirt-flat-1" }, /obj/structure/alien/weeds/xen, -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab, +/mob/living/basic/blackmesa/xen/headcrab, /turf/open/misc/asteroid/airless, /area/space/nearstation) "rpJ" = ( diff --git a/code/__HELPERS/~skyrat_helpers/is_helpers.dm b/code/__HELPERS/~skyrat_helpers/is_helpers.dm index 642119039d9..1be4d08e652 100644 --- a/code/__HELPERS/~skyrat_helpers/is_helpers.dm +++ b/code/__HELPERS/~skyrat_helpers/is_helpers.dm @@ -36,4 +36,4 @@ #define isavian(A) (isteshari(A) || isvox(A) || isvoxprimalis(A) || HAS_TRAIT(A, TRAIT_AVIAN)) // Xen mobs -#define isxenmob(A) (istype(A, /mob/living/simple_animal/hostile/blackmesa/xen)) +#define isxenmob(A) (istype(A, /mob/living/basic/blackmesa/xen)) diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 72f88e15265..83a70034448 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -62,28 +62,6 @@ // MODULAR SKYRAT ENTRIES // PLEASE REFACTOR THESE AS YOU CAN /mob/living/simple_animal/hostile/bigcrab, - /mob/living/simple_animal/hostile/blackmesa, - /mob/living/simple_animal/hostile/blackmesa/blackops, - /mob/living/simple_animal/hostile/blackmesa/blackops/ranged, - /mob/living/simple_animal/hostile/blackmesa/hecu, - /mob/living/simple_animal/hostile/blackmesa/hecu/ranged, - /mob/living/simple_animal/hostile/blackmesa/hecu/ranged/smg, - /mob/living/simple_animal/hostile/blackmesa/sec, - /mob/living/simple_animal/hostile/blackmesa/sec/ranged, - /mob/living/simple_animal/hostile/blackmesa/xen, - /mob/living/simple_animal/hostile/blackmesa/xen/bullsquid, - /mob/living/simple_animal/hostile/blackmesa/xen/headcrab, - /mob/living/simple_animal/hostile/blackmesa/xen/headcrab/fast, - /mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie, - /mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/gordon_freeman, - /mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/guard, - /mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/hecu, - /mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/hev, - /mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/scientist, - /mob/living/simple_animal/hostile/blackmesa/xen/houndeye, - /mob/living/simple_animal/hostile/blackmesa/xen/nihilanth, - /mob/living/simple_animal/hostile/blackmesa/xen/vortigaunt, - /mob/living/simple_animal/hostile/blackmesa/xen/vortigaunt/slave, /mob/living/simple_animal/hostile/cazador, /mob/living/simple_animal/hostile/cult, /mob/living/simple_animal/hostile/cult/assassin, diff --git a/modular_skyrat/modules/black_mesa/code/mobs/bullsquid.dm b/modular_skyrat/modules/black_mesa/code/mobs/bullsquid.dm deleted file mode 100644 index c8c77c8d2b8..00000000000 --- a/modular_skyrat/modules/black_mesa/code/mobs/bullsquid.dm +++ /dev/null @@ -1,50 +0,0 @@ -/mob/living/simple_animal/hostile/blackmesa/xen/bullsquid - name = "bullsquid" - desc = "Some highly aggressive alien creature. Thrives in toxic environments." - icon = 'modular_skyrat/modules/black_mesa/icons/mobs.dmi' - icon_state = "bullsquid" - icon_living = "bullsquid" - icon_dead = "bullsquid_dead" - icon_gib = null - mob_biotypes = MOB_ORGANIC|MOB_BEAST - environment_smash = ENVIRONMENT_SMASH_STRUCTURES - speak_chance = 1 - speak_emote = list("growls") - emote_taunt = list("growls", "snarls", "grumbles") - taunt_chance = 100 - turns_per_move = 7 - maxHealth = 110 - health = 110 - obj_damage = 50 - harm_intent_damage = 15 - melee_damage_lower = 15 - melee_damage_upper = 15 - ranged = TRUE - retreat_distance = 4 - minimum_distance = 4 - dodging = TRUE - projectiletype = /obj/projectile/bullsquid - projectilesound = 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/goo_attack3.ogg' - melee_damage_upper = 18 - attack_sound = 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/attack1.ogg' - gold_core_spawnable = HOSTILE_SPAWN - alert_sounds = list( - 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/detect1.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/detect2.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/detect3.ogg' - ) - -/obj/projectile/bullsquid - name = "nasty ball of ooze" - icon_state = "neurotoxin" - damage = 5 - damage_type = BURN - knockdown = 20 - armor_flag = BIO - impact_effect_type = /obj/effect/temp_visual/impact_effect/neurotoxin - hitsound = 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/splat1.ogg' - hitsound_wall = 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/splat1.ogg' - -/obj/projectile/bullsquid/on_hit(atom/target, blocked = 0, pierce_hit) - new /obj/effect/decal/cleanable/greenglow(target.loc) - return ..() diff --git a/modular_skyrat/modules/black_mesa/code/mobs/bullsquid/bullsquid.dm b/modular_skyrat/modules/black_mesa/code/mobs/bullsquid/bullsquid.dm new file mode 100644 index 00000000000..bd95f185604 --- /dev/null +++ b/modular_skyrat/modules/black_mesa/code/mobs/bullsquid/bullsquid.dm @@ -0,0 +1,74 @@ +/** + * Bullsquid (/mob/living/basic/blackmesa/xen/bullsquid) + * An aggressive alien creature from Xen that uses both melee and ranged attacks. + * + * A hostile mob that switches between biting nearby targets and spitting acid at distant ones. + * Uses standard melee attacks when adjacent and acid spit when at range. + * Each bullsquid is hostile to other bullsquids, creating organic free-for-all combat. + */ +/mob/living/basic/blackmesa/xen/bullsquid + name = "bullsquid" + desc = "Some highly aggressive alien creature. Thrives in toxic environments." + icon = 'modular_skyrat/modules/black_mesa/icons/mobs.dmi' + icon_state = "bullsquid" + icon_living = "bullsquid" + icon_dead = "bullsquid_dead" + + // Health and combat + maxHealth = 110 + health = 110 + melee_damage_lower = 15 + melee_damage_upper = 18 + obj_damage = 50 + combat_mode = TRUE + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + attack_sound = 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/attack1.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 1, OXY = 1) + + // Mob traits + mob_biotypes = MOB_ORGANIC | MOB_BEAST + environment_smash = ENVIRONMENT_SMASH_STRUCTURES + basic_mob_flags = FLAMMABLE_MOB + unsuitable_cold_damage = 15 + unsuitable_heat_damage = 15 + speak_emote = list("growls") + + // AI and behavior + ai_controller = /datum/ai_controller/basic_controller/bullsquid + gold_core_spawnable = HOSTILE_SPAWN + + alert_sounds = list( + 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/detect1.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/detect2.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/detect3.ogg' + ) + +/mob/living/basic/blackmesa/xen/bullsquid/Initialize(mapload) + . = ..() + AddComponent(/datum/component/ai_listen_to_weather) + // Add ranged attack component with proper targeting + AddComponent(/datum/component/ranged_attacks, projectile_type = /obj/projectile/bullsquid, projectile_sound = 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/goo_attack3.ogg', cooldown_time = 3 SECONDS) + +/obj/item/ammo_casing/bullsquid + name = "nasty ball of ooze" + projectile_type = /obj/projectile/bullsquid + +/obj/projectile/bullsquid + name = "nasty ball of ooze" + icon_state = "neurotoxin" + damage = 5 + damage_type = BURN + knockdown = 20 + armor_flag = BIO + impact_effect_type = /obj/effect/temp_visual/impact_effect/neurotoxin + hitsound = 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/splat1.ogg' + hitsound_wall = 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/splat1.ogg' + pass_flags = PASSTABLE + speed = 0.8 + +/// Creates toxic residue where the projectile hits +/obj/projectile/bullsquid/on_hit(atom/target, blocked = 0, pierce_hit) + new /obj/effect/decal/cleanable/greenglow(target.loc) + return ..() diff --git a/modular_skyrat/modules/black_mesa/code/mobs/bullsquid/bullsquid_ai.dm b/modular_skyrat/modules/black_mesa/code/mobs/bullsquid/bullsquid_ai.dm new file mode 100644 index 00000000000..fa0f802a250 --- /dev/null +++ b/modular_skyrat/modules/black_mesa/code/mobs/bullsquid/bullsquid_ai.dm @@ -0,0 +1,46 @@ +/datum/ai_controller/basic_controller/bullsquid + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_MELEE_ATTACK_RANGE = 1, + BB_BASIC_MOB_CURRENT_TARGET = null, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/bullsquid_attack, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/// Handles the bullsquid's acid spit attack based on distance. +/// Works alongside basic_melee_attack_subtree which handles biting. +/datum/ai_planning_subtree/bullsquid_attack + COOLDOWN_DECLARE(acid_cooldown) + /// Time between acid spit attacks + var/acid_cooldown_time = 3 SECONDS + /// Minimum range before we'll try to spit acid + var/min_spit_range = 2 + /// Maximum range at which we can spit acid + var/max_spit_range = 6 + +/datum/ai_planning_subtree/bullsquid_attack/SelectBehaviors(datum/ai_controller/controller, delta_time) + . = ..() + var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + if(!target) + return + + var/mob/living/basic/blackmesa/xen/bullsquid/pawn = controller.pawn + if(QDELETED(pawn)) + return + + var/distance = get_dist(pawn, target) + + // Let basic_melee_attack_subtree handle melee range combat + if(distance <= 1) + return + + // Try acid spit if in valid range and attack is ready + if(distance >= min_spit_range && distance <= max_spit_range && COOLDOWN_FINISHED(src, acid_cooldown)) + controller.queue_behavior(/datum/ai_behavior/basic_ranged_attack, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY) + COOLDOWN_START(src, acid_cooldown, acid_cooldown_time) diff --git a/modular_skyrat/modules/black_mesa/code/mobs/corpse_spawns.dm b/modular_skyrat/modules/black_mesa/code/mobs/corpse_spawns.dm new file mode 100644 index 00000000000..6965999b426 --- /dev/null +++ b/modular_skyrat/modules/black_mesa/code/mobs/corpse_spawns.dm @@ -0,0 +1,105 @@ +// Corpse definitions for Black Mesa NPCs + +/obj/effect/mob_spawn/corpse/human/hecu + name = "HECU Corpse" + hairstyle = "Bald" + facial_hairstyle = "Shaved" + outfit = /datum/outfit/hecu_corpse + +/obj/effect/mob_spawn/corpse/human/hecu_ranged + name = "HECU Ranged Corpse" + hairstyle = "Bald" + facial_hairstyle = "Shaved" + outfit = /datum/outfit/hecu_ranged_corpse + +/obj/effect/mob_spawn/corpse/human/hecu_smg + name = "HECU SMG Corpse" + hairstyle = "Bald" + facial_hairstyle = "Shaved" + outfit = /datum/outfit/hecu_smg_corpse + +/obj/effect/mob_spawn/corpse/human/security_guard + name = "Security Guard Corpse" + hairstyle = "Bald" + facial_hairstyle = "Shaved" + outfit = /datum/outfit/security_guard_corpse + +/obj/effect/mob_spawn/corpse/human/security_guard/ranged + name = "Armed Security Guard Corpse" + outfit = /datum/outfit/security_guard_ranged_corpse + +/obj/effect/mob_spawn/corpse/human/black_ops + name = "Black Ops Corpse" + hairstyle = "Bald" + facial_hairstyle = "Shaved" + outfit = /datum/outfit/black_ops_corpse + +/obj/effect/mob_spawn/corpse/human/black_ops/ranged + name = "Armed Black Ops Corpse" + outfit = /datum/outfit/black_ops_ranged_corpse + +// Outfits for corpses +/datum/outfit/hecu_corpse + name = "HECU Grunt" + uniform = /obj/item/clothing/under/rank/security/officer/hecu + suit = /obj/item/clothing/suit/armor/vest/hecu + shoes = /obj/item/clothing/shoes/combat + gloves = /obj/item/clothing/gloves/combat + mask = /obj/item/clothing/mask/gas + head = /obj/item/clothing/head/helmet/hecu + +/datum/outfit/hecu_ranged_corpse + name = "HECU Ranged" + uniform = /obj/item/clothing/under/rank/security/officer/hecu + suit = /obj/item/clothing/suit/armor/vest/hecu + shoes = /obj/item/clothing/shoes/combat + gloves = /obj/item/clothing/gloves/combat + mask = /obj/item/clothing/mask/gas + head = /obj/item/clothing/head/helmet/hecu + l_pocket = /obj/item/ammo_box/magazine/m50 + +/datum/outfit/hecu_smg_corpse + name = "HECU SMG" + uniform = /obj/item/clothing/under/rank/security/officer/hecu + suit = /obj/item/clothing/suit/armor/vest/hecu + shoes = /obj/item/clothing/shoes/combat + gloves = /obj/item/clothing/gloves/combat + mask = /obj/item/clothing/mask/gas + head = /obj/item/clothing/head/helmet/hecu + l_pocket = /obj/item/ammo_box/magazine/smgm45 + +/datum/outfit/security_guard_corpse + name = "Black Mesa Security" + uniform = /obj/item/clothing/under/rank/security/officer/blueshirt + suit = /obj/item/clothing/suit/armor/vest/blueshirt + shoes = /obj/item/clothing/shoes/jackboots + head = /obj/item/clothing/head/helmet/blueshirt + +/datum/outfit/security_guard_ranged_corpse + name = "Armed Black Mesa Security" + uniform = /obj/item/clothing/under/rank/security/officer/blueshirt + suit = /obj/item/clothing/suit/armor/vest/blueshirt + shoes = /obj/item/clothing/shoes/jackboots + head = /obj/item/clothing/head/helmet/blueshirt + l_pocket = /obj/item/ammo_box/magazine/c35sol_pistol + +/datum/outfit/black_ops_corpse + name = "Black Ops" + uniform = /obj/item/clothing/under/syndicate/combat + suit = /obj/item/clothing/suit/armor/vest/alt + shoes = /obj/item/clothing/shoes/combat + gloves = /obj/item/clothing/gloves/combat + mask = /obj/item/clothing/mask/gas/sechailer + head = /obj/item/clothing/head/helmet/swat + back = /obj/item/storage/backpack/security + +/datum/outfit/black_ops_ranged_corpse + name = "Armed Black Ops" + uniform = /obj/item/clothing/under/syndicate/combat + suit = /obj/item/clothing/suit/armor/vest/alt + shoes = /obj/item/clothing/shoes/combat + gloves = /obj/item/clothing/gloves/combat + mask = /obj/item/clothing/mask/gas/sechailer + head = /obj/item/clothing/head/helmet/swat + back = /obj/item/storage/backpack/security + l_pocket = /obj/item/ammo_box/magazine/c40sol_rifle diff --git a/modular_skyrat/modules/black_mesa/code/mobs/gordon_freeman.dm b/modular_skyrat/modules/black_mesa/code/mobs/gordon_freeman/gordon_freeman.dm similarity index 59% rename from modular_skyrat/modules/black_mesa/code/mobs/gordon_freeman.dm rename to modular_skyrat/modules/black_mesa/code/mobs/gordon_freeman/gordon_freeman.dm index 9b05d8c0c45..88bff4ec50e 100644 --- a/modular_skyrat/modules/black_mesa/code/mobs/gordon_freeman.dm +++ b/modular_skyrat/modules/black_mesa/code/mobs/gordon_freeman/gordon_freeman.dm @@ -6,39 +6,109 @@ * Once destroyed, the shield falls, and the mob can be killed. */ - -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/gordon_freeman +/mob/living/basic/blackmesa/xen/gordon_freeman name = "\improper Gordon Freeman" desc = "Gordon Freeman in the flesh. Or in the zombified form, it seems." + icon = 'modular_skyrat/modules/black_mesa/icons/mobs.dmi' icon_state = "gordon_freeman" - speed = -2 - environment_smash = ENVIRONMENT_SMASH_RWALLS - health = 1000 + base_icon_state = "gordon_freeman" + icon_living = "gordon_freeman" + + // Stats maxHealth = 1000 + health = 1000 melee_damage_lower = 45 melee_damage_upper = 45 - wander = FALSE attack_sound = 'modular_skyrat/master_files/sound/weapons/crowbar2.ogg' - loot = list(/obj/item/crowbar/freeman/ultimate, /obj/item/keycard/freeman_boss_exit) + attack_verb_continuous = "smashes" + attack_verb_simple = "smash" + + // Movement + speed = -2 + ai_controller = /datum/ai_controller/basic_controller/gordon_freeman + basic_mob_flags = DEL_ON_DEATH + + // Boss properties + environment_smash = ENVIRONMENT_SMASH_RWALLS gold_core_spawnable = NO_SPAWN + // Death drops + butcher_results = list( + /obj/item/crowbar/freeman/ultimate = 1, + /obj/item/keycard/freeman_boss_exit = 1 + ) + +/mob/living/basic/blackmesa/xen/gordon_freeman/Initialize(mapload) + . = ..() + if(mapload) + find_and_register_pylons() + +/// Finds all pylons in range and registers with them +/mob/living/basic/blackmesa/xen/gordon_freeman/proc/find_and_register_pylons() + var/found_pylons = 0 + for(var/obj/structure/xen_pylon/freeman/pylon in orange(30, src)) + if(pylon.register_mob(src)) + found_pylons++ + if(!found_pylons) + stack_trace("[src] failed to find any pylons on initialization") + +/// Update our appearance when shield status changes +/mob/living/basic/blackmesa/xen/gordon_freeman/update_overlays() + . = ..() + if(!shielded) + return + . += mutable_appearance(icon, "gordon_freeman_shield", layer = ABOVE_MOB_LAYER) + +/mob/living/basic/blackmesa/xen/gordon_freeman/death(gibbed) + // Cleanup shield effects and beams + shielded = FALSE + shield_count = 0 + + // Handle death drops + var/turf/T = get_turf(src) + if(T) + for(var/item_type in butcher_results) + var/amount = butcher_results[item_type] + for(var/i in 1 to amount) + new item_type(T) + + // Create death effect + T.visible_message(span_warning("[src] violently dissolves into a pile of flesh!")) + new /obj/effect/gibspawner/generic(T) + + // Clean up the mob + qdel(src) + return ..(TRUE) // Force gibbed to true + /obj/structure/xen_pylon/freeman shield_range = 30 max_integrity = 300 -/obj/structure/xen_pylon/freeman/register_mob(mob/living/simple_animal/hostile/blackmesa/xen/mob_to_register) - if(!istype(mob_to_register, /mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/gordon_freeman)) - return +/obj/structure/xen_pylon/freeman/register_mob(mob/living/basic/blackmesa/xen/mob_to_register) + if(!istype(mob_to_register, /mob/living/basic/blackmesa/xen/gordon_freeman)) + return FALSE if(mob_to_register in shielded_mobs) - return + return FALSE + if(QDELETED(mob_to_register)) + return FALSE + shielded_mobs += mob_to_register mob_to_register.shielded = TRUE mob_to_register.shield_count++ mob_to_register.update_appearance() + var/datum/beam/created_beam = Beam(mob_to_register, icon_state = "red_lightning", time = 10 MINUTES, maxdistance = shield_range) + if(!created_beam) // Beam creation failed + shielded_mobs -= mob_to_register + mob_to_register.shielded = FALSE + mob_to_register.shield_count-- + mob_to_register.update_appearance() + return FALSE + shielded_mobs[mob_to_register] = created_beam RegisterSignal(created_beam, COMSIG_QDELETING, PROC_REF(beam_died)) RegisterSignal(mob_to_register, COMSIG_QDELETING, PROC_REF(mob_died)) + return TRUE /obj/machinery/door/puzzle/keycard/xen/freeman_boss_entry name = "entry door" @@ -73,7 +143,7 @@ /obj/effect/freeman_blocker/CanPass(atom/blocker, movement_dir, blocker_opinion) . = ..() - if(istype(blocker, /mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/gordon_freeman)) + if(istype(blocker, /mob/living/basic/blackmesa/xen/gordon_freeman)) return FALSE return TRUE diff --git a/modular_skyrat/modules/black_mesa/code/mobs/gordon_freeman/gordon_freeman_ai.dm b/modular_skyrat/modules/black_mesa/code/mobs/gordon_freeman/gordon_freeman_ai.dm new file mode 100644 index 00000000000..f00558efc25 --- /dev/null +++ b/modular_skyrat/modules/black_mesa/code/mobs/gordon_freeman/gordon_freeman_ai.dm @@ -0,0 +1,18 @@ +/** + * AI Controllers + */ + +/// AI controller for Gordon Freeman boss +/datum/ai_controller/basic_controller/gordon_freeman + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/target_retaliate + ) diff --git a/modular_skyrat/modules/black_mesa/code/mobs/headcrab.dm b/modular_skyrat/modules/black_mesa/code/mobs/headcrab.dm deleted file mode 100644 index 13de30c3a3c..00000000000 --- a/modular_skyrat/modules/black_mesa/code/mobs/headcrab.dm +++ /dev/null @@ -1,136 +0,0 @@ -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab - name = "headcrab" - desc = "Don't let it latch onto your hea-... hey, that's kinda cool." - icon = 'modular_skyrat/modules/black_mesa/icons/mobs.dmi' - icon_state = "headcrab" - icon_living = "headcrab" - icon_dead = "headcrab_dead" - icon_gib = null - mob_biotypes = MOB_ORGANIC|MOB_BEAST - speak_chance = 1 - speak_emote = list("growls") - speed = 1 - emote_taunt = list("growls", "snarls", "grumbles") - ranged_message = "jumps" - taunt_chance = 100 - turns_per_move = 7 - maxHealth = 50 - health = 50 - speed = 3 - ranged = TRUE - dodging = TRUE - ranged_cooldown_time = 4 SECONDS - harm_intent_damage = 15 - melee_damage_lower = 5 - melee_damage_upper = 5 - retreat_distance = 5 - minimum_distance = 5 - attack_sound = 'sound/items/weapons/bite.ogg' - gold_core_spawnable = HOSTILE_SPAWN - loot = list(/obj/item/stack/sheet/bone) - alert_sounds = list( - 'modular_skyrat/modules/black_mesa/sound/mobs/headcrab/alert1.ogg' - ) - var/is_zombie = FALSE - var/mob/living/carbon/human/oldguy - /// Charging ability - var/datum/action/cooldown/mob_cooldown/charge/basic_charge/charge - var/throw_at_range = 10 - var/throw_at_speed = 2 - -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab/Initialize(mapload) - . = ..() - charge = new(src) - charge.Grant(src) - charge.cooldown_time = 0 - -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab/Shoot(atom/targeted_atom) - throw_at(targeted_atom, throw_at_range, throw_at_speed) - playsound( - src, - pick('modular_skyrat/modules/black_mesa/sound/mobs/headcrab/attack1.ogg', 'modular_skyrat/modules/black_mesa/sound/mobs/headcrab/attack2.ogg', 'modular_skyrat/modules/black_mesa/sound/mobs/headcrab/attack3.ogg'), - 100 - ) - -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab/death(gibbed) - . = ..() - playsound(src, pick(list( - 'modular_skyrat/modules/black_mesa/sound/mobs/headcrab/die1.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/headcrab/die2.ogg' - )), 100) - -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - . = ..() - if(!hit_atom || stat == DEAD) - return - if(!isliving(hit_atom)) - return - playsound(src, 'modular_skyrat/modules/black_mesa/sound/mobs/headcrab/headbite.ogg', 100) - var/mob/living/hit_mob = hit_atom - hit_mob.apply_damage(melee_damage_upper, BRUTE) - if(!ishuman(hit_atom)) - return - var/mob/living/carbon/human/human_to_dunk = hit_atom - if(!human_to_dunk.get_item_by_slot(ITEM_SLOT_HEAD) && prob(50) && zombify(human_to_dunk)) - to_chat(human_to_dunk, span_userdanger("[src] latches onto your head as it pierces your skull, instantly killing you!")) - human_to_dunk.investigate_log("was headcrab latched by [src].", INVESTIGATE_DEATHS) - human_to_dunk.death(FALSE) - -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab/proc/zombify(mob/living/carbon/human/zombified_human) - if(is_zombie) - return FALSE - is_zombie = TRUE - if(zombified_human.wear_suit) - var/obj/item/clothing/suit/armor/zombie_suit = zombified_human.wear_suit - maxHealth += zombie_suit.get_armor_rating(MELEE) //That zombie's got armor, I want armor! - maxHealth += 40 - health = maxHealth - name = "zombie" - desc = "A shambling corpse animated by a headcrab!" - mob_biotypes |= MOB_HUMANOID - melee_damage_lower += 8 - melee_damage_upper += 11 - obj_damage = 21 //now that it has a corpse to puppet, it can properly attack structures - ranged = FALSE - dodging = FALSE - retreat_distance = 0 - minimum_distance = 0 - AddElement(/datum/element/wall_smasher, strength_flag = ENVIRONMENT_SMASH_STRUCTURES) - movement_type = GROUND - icon_state = "" - zombified_human.set_hairstyle(null, update = FALSE) - zombified_human.update_body_parts() - zombified_human.forceMove(src) - oldguy = zombified_human - update_appearance() - visible_message(span_warning("The corpse of [zombified_human.name] suddenly rises!")) - return TRUE - -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab/Destroy() - if(oldguy) - oldguy.forceMove(get_turf(src)) - oldguy = null - return ..() - -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab/death(gibbed) - . = ..() - if(oldguy) - oldguy.forceMove(loc) - oldguy = null - if(is_zombie) - if(prob(30)) - new /mob/living/simple_animal/hostile/blackmesa/xen/headcrab(loc) //OOOO it unlached! - qdel(src) - return - cut_overlays() - update_appearance() - -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab/update_overlays() - . = ..() - if(is_zombie) - copy_overlays(oldguy, TRUE) - var/mutable_appearance/blob_head_overlay = mutable_appearance('modular_skyrat/modules/black_mesa/icons/mobs.dmi', "headcrab_zombie") - add_overlay(blob_head_overlay) - -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab/fast - speed = -2 diff --git a/modular_skyrat/modules/black_mesa/code/mobs/headcrab/headcrab.dm b/modular_skyrat/modules/black_mesa/code/mobs/headcrab/headcrab.dm new file mode 100644 index 00000000000..f9ee7131864 --- /dev/null +++ b/modular_skyrat/modules/black_mesa/code/mobs/headcrab/headcrab.dm @@ -0,0 +1,174 @@ +/** + * Headcrab (/mob/living/basic/blackmesa/xen/headcrab) + * Parasitic alien that jumps at targets and can zombify humans. + * + * A classic Half-Life enemy that uses jumping attacks and can turn humans into zombies. + * - Jumps at targets from range + * - Can zombify unprotected humans on headshot + * - Detaches from zombies on death with a chance to survive + */ +/mob/living/basic/blackmesa/xen/headcrab + name = "headcrab" + desc = "Don't let it latch onto your hea-... hey, that's kinda cool." + icon = 'modular_skyrat/modules/black_mesa/icons/mobs.dmi' + icon_state = "headcrab" + icon_living = "headcrab" + icon_dead = "headcrab_dead" + base_icon_state = "headcrab" + + // Health and combat + maxHealth = 50 + health = 50 + melee_damage_lower = 0 // No melee attacks, only jump attacks + melee_damage_upper = 0 + combat_mode = TRUE + + // Mob traits + mob_biotypes = MOB_ORGANIC | MOB_BEAST + basic_mob_flags = NONE + ai_controller = /datum/ai_controller/basic_controller/headcrab + + // Movement + speed = 3 + move_force = MOVE_FORCE_WEAK + move_resist = MOVE_FORCE_WEAK + pull_force = MOVE_FORCE_WEAK + pixel_x = -8 + base_pixel_x = -8 + faction = list(FACTION_XEN) // Friendly to other Xen creatures + can_be_shielded = FALSE // Headcrabs don't use shield pylons + + // Spawning and loot + gold_core_spawnable = HOSTILE_SPAWN + butcher_results = list( + /obj/item/stack/sheet/bone = 1 + ) + + /// Maximum distance this headcrab can jump in tiles + var/throw_at_range = 10 + + /// Base speed at which this headcrab jumps (actual speed varies with distance) + var/throw_at_speed = 2 + + /// Track if we've attached to a human, to prevent multiple zombifications + var/is_zombie = FALSE + +/mob/living/basic/blackmesa/xen/headcrab + alert_sounds = list('modular_skyrat/modules/black_mesa/sound/mobs/headcrab/alert1.ogg') + +/mob/living/basic/blackmesa/xen/headcrab/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_MOVABLE_IMPACT, PROC_REF(handle_impact)) + ai_controller.set_blackboard_key(BB_TARGET_MINIMUM_STAT, HARD_CRIT) // Allow targeting unconscious people + +/// Execute the jump after the telegraph +/datum/ai_planning_subtree/headcrab_hunt/proc/execute_jump(mob/living/basic/blackmesa/xen/headcrab/jumper, atom/target, distance, speed) + if(QDELETED(jumper) || QDELETED(target) || jumper.stat == DEAD) + return + + // Make it spin during the jump! The faster the jump, the faster the spin + var/spin_speed = speed * 2 + jumper.throw_at(target, distance, speed, jumper, TRUE, TRUE, null, 0.1, FALSE, spin_speed) + +/// Handle leap impacts +/mob/living/basic/blackmesa/xen/headcrab/proc/handle_impact(datum/source, atom/hit_atom, datum/thrownthing/throwingdatum) + SIGNAL_HANDLER + if(!hit_atom || stat == DEAD) + return + if(!isliving(hit_atom)) + return + + playsound(src, 'modular_skyrat/modules/black_mesa/sound/mobs/headcrab/attack1.ogg', 100, FALSE) + var/mob/living/hit_mob = hit_atom + + // More damage if we hit them at high speed + var/damage = 8 + if(throwingdatum.speed >= 3) + damage = 12 + + hit_mob.apply_damage(damage, BRUTE) + + if(!ishuman(hit_atom)) + return + + var/mob/living/carbon/human/human_target = hit_atom + + // Regular damage if the target is conscious + if(human_target.stat < UNCONSCIOUS) + human_target.apply_damage(damage, BRUTE, BODY_ZONE_HEAD) + return + + // Check for head protection on unconscious targets + var/obj/item/clothing/head/head_protection = human_target.get_item_by_slot(ITEM_SLOT_HEAD) + if(head_protection) + head_protection.take_damage(15) + return + + // Zombify unprotected unconscious targets + if(zombify(human_target)) + // Visual and sound feedback + playsound(src, 'modular_skyrat/modules/black_mesa/sound/mobs/headcrab/attack1.ogg', 100, FALSE) + do_sparks(3, TRUE, human_target) + + // Log the zombification + human_target.investigate_log("was zombified by [src] while unconscious.", INVESTIGATE_DEATHS) + human_target.death(FALSE) + +/mob/living/basic/blackmesa/xen/headcrab/death(gibbed) + // Handle death sound if not gibbed + if(!gibbed) + playsound(src, pick(list( + 'modular_skyrat/modules/black_mesa/sound/mobs/headcrab/die1.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/headcrab/die2.ogg' + )), 100) + return ..() + +/// Transforms a human into a headcrab zombie +/mob/living/basic/blackmesa/xen/headcrab/proc/zombify(mob/living/carbon/human/target_human) + // Sanity checks + if(is_zombie || !target_human) + return FALSE + + // Create the zombie at our location + var/mob/living/basic/blackmesa/xen/headcrab_zombie/new_zombie = new(get_turf(src)) + new_zombie.name = "[target_human.name] zombie" + + // Copy the human's appearance + target_human.set_hairstyle(null, update = FALSE) + target_human.update_body_parts() + new_zombie.copy_overlays(target_human) + + // Add the headcrab overlay + var/mutable_appearance/blob_head_overlay = mutable_appearance('modular_skyrat/modules/black_mesa/icons/mobs.dmi', "headcrab_zombie") + new_zombie.add_overlay(blob_head_overlay) + + // Store the human inside the zombie + target_human.forceMove(new_zombie) + new_zombie.zombified_human = target_human + + // If they have armor, apply it to the zombie + var/obj/item/clothing/suit/armor/zombie_suit = target_human.wear_suit + if(istype(zombie_suit)) + new_zombie.maxHealth += zombie_suit.get_armor_rating(MELEE) //That zombie's got armor, I want armor! + new_zombie.health = new_zombie.maxHealth + + // Visual and sound feedback for zombie creation + playsound(new_zombie, 'modular_skyrat/modules/black_mesa/sound/mobs/headcrab/attack1.ogg', 100, FALSE) + visible_message(span_warning("The corpse of [target_human.name] suddenly rises, a headcrab controlling its lifeless body!")) + + // Delete the original headcrab + qdel(src) + return TRUE + +/** + * Fast Headcrab (/mob/living/basic/blackmesa/xen/headcrab/fast) + * A variant of the standard headcrab that moves significantly faster. + * + * This version appears in later stages of Half-Life and represents a more dangerous variant. + * - Moves much quicker than standard headcrabs + * - Same jumping and zombification mechanics + * - More dangerous due to increased mobility + */ +/mob/living/basic/blackmesa/xen/headcrab/fast + speed = -2 + desc = "Don't let it latch onto your hea-... hey, that's kinda cool. This one looks faster than usual." diff --git a/modular_skyrat/modules/black_mesa/code/mobs/headcrab/headcrab_ai.dm b/modular_skyrat/modules/black_mesa/code/mobs/headcrab/headcrab_ai.dm new file mode 100644 index 00000000000..feca2d9d030 --- /dev/null +++ b/modular_skyrat/modules/black_mesa/code/mobs/headcrab/headcrab_ai.dm @@ -0,0 +1,64 @@ +/datum/ai_controller/basic_controller/headcrab + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items, + BB_BASIC_MOB_CURRENT_TARGET = null, + BB_BASIC_MOB_MELEE_ATTACK_RANGE = 0, // No melee attacks + BB_BASIC_MOB_CURRENT_TARGET_HIDING = FALSE, + BB_TARGET_MINIMUM_STAT = HARD_CRIT // Allow targeting of unconscious people + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/headcrab_hunt + ) + +/// Handles the headcrab's hunting behavior, trying to leap at targets when in range +/datum/ai_planning_subtree/headcrab_hunt + /// Time between jump attempts + var/jump_cooldown_time = 1 SECONDS // Reduced cooldown for more frequent jumps + COOLDOWN_DECLARE(jump_cooldown) + + /// Preferred range to start jumping + var/preferred_jump_range = 4 + /// Maximum range at which we can leap + var/max_jump_range = 10 // Increased range for better mobility + +/datum/ai_planning_subtree/headcrab_hunt/SelectBehaviors(datum/ai_controller/controller, delta_time) + . = ..() + var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + if(!target) + return + + var/mob/living/living_pawn = controller.pawn + if(QDELETED(living_pawn)) + return + + if(!COOLDOWN_FINISHED(src, jump_cooldown)) + return + + var/distance = get_dist(living_pawn, target) + + // Step back to avoid attacking from standing on the corpse. + if(target.stat >= HARD_CRIT && distance == 0) + var/turf/away = get_step_away(living_pawn, target) + if(away) + living_pawn.Move(away) + distance = get_dist(living_pawn, target) + + // Always jump at target if in range + if(distance <= max_jump_range) + COOLDOWN_START(src, jump_cooldown, jump_cooldown_time) + // Telegraph the jump with sound + playsound(living_pawn, 'modular_skyrat/modules/black_mesa/sound/mobs/headcrab/attack2.ogg', 50, TRUE) + + // Calculate jump speed based on distance - faster at close range for better accuracy + var/jump_speed = 3 + if(distance <= 3) + jump_speed = 4 // Faster at close range + else if(distance >= 7) + jump_speed = 2 // Slower at long range + + addtimer(CALLBACK(src, PROC_REF(execute_jump), living_pawn, target, distance, jump_speed), 0.3 SECONDS) diff --git a/modular_skyrat/modules/black_mesa/code/mobs/headcrab_zombie.dm b/modular_skyrat/modules/black_mesa/code/mobs/headcrab_zombie.dm deleted file mode 100644 index 5479c376132..00000000000 --- a/modular_skyrat/modules/black_mesa/code/mobs/headcrab_zombie.dm +++ /dev/null @@ -1,89 +0,0 @@ -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie - name = "headcrab zombie" - desc = "This unlucky person has had a headcrab latch onto their head. Ouch." - icon = 'modular_skyrat/modules/black_mesa/icons/mobs.dmi' - icon_state = "zombie" - icon_living = "zombie" - maxHealth = 110 - health = 110 - icon_gib = null - icon_dead = "zombie_dead" - speak_chance = 1 - speak_emote = list("growls") - speed = 1 - emote_taunt = list("growls", "snarls", "grumbles") - taunt_chance = 100 - melee_damage_lower = 21 - melee_damage_upper = 21 - attack_sound = 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/claw_strike.ogg' - gold_core_spawnable = HOSTILE_SPAWN - alert_cooldown_time = 8 SECONDS - alert_sounds = list( - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/alert1.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/alert2.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/alert3.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/alert4.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/alert5.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/alert6.ogg', - ) - -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/death(gibbed) - new /obj/effect/gibspawner/human(get_turf(src)) - return ..() - -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/scientist - name = "zombified scientist" - desc = "Even after death, I still have to wear this horrible tie!" - icon_state = "scientist_zombie" - icon_living = "scientist_zombie" - loot = list(/obj/effect/mob_spawn/corpse/human/scientist_zombie) - del_on_death = 1 - - -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/guard - name = "zombified guard" - desc = "About that brain I owed ya!" - icon_state = "security_zombie" - icon_living = "security_zombie" - maxHealth = 140 // Armor! - health = 140 - loot = list(/obj/effect/mob_spawn/corpse/human/guard_zombie) - del_on_death = 1 - -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/hecu - name = "zombified marine" - desc = "MY. ASS. IS. DEAD." - icon_state = "hecu_zombie" - icon_living = "hecu_zombie" - maxHealth = 190 // More armor! - health = 190 - loot = list(/obj/effect/mob_spawn/corpse/human/hecu_zombie) - del_on_death = 1 - -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/hev - name = "zombified hazardous environment specialist" - desc = "User death... surpassed." - icon_state = "hev_zombie" - icon_living = "hev_zombie" - maxHealth = 250 - health = 250 - alert_sounds = list( - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv1.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv2.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv3.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv4.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv5.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv6.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv7.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv8.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv9.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv10.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv11.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv12.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv13.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv14.ogg', - ) - - - - diff --git a/modular_skyrat/modules/black_mesa/code/mobs/headcrab_zombie/headcrab_zombie.dm b/modular_skyrat/modules/black_mesa/code/mobs/headcrab_zombie/headcrab_zombie.dm new file mode 100644 index 00000000000..618db26ad32 --- /dev/null +++ b/modular_skyrat/modules/black_mesa/code/mobs/headcrab_zombie/headcrab_zombie.dm @@ -0,0 +1,130 @@ +/** + * Headcrab Zombies + * Various types of zombies created by headcrabs latching onto different personnel. + * + * Base zombie type with common behaviors and properties. + * Each variant (scientist, guard, HECU, HEV) has different health values and corpses. + */ +/mob/living/basic/blackmesa/xen/headcrab_zombie + name = "headcrab zombie" + desc = "This unlucky person has had a headcrab latch onto their head. Ouch." + icon = 'modular_skyrat/modules/black_mesa/icons/mobs.dmi' + icon_state = "zombie" + base_icon_state = "zombie" + icon_dead = "zombie_dead" + + // Health and combat + maxHealth = 110 + health = 110 + melee_damage_lower = 21 + melee_damage_upper = 21 + attack_sound = 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/claw_strike.ogg' + obj_damage = 21 + combat_mode = TRUE + + // Movement and behavior + speed = 1 + move_force = MOVE_FORCE_STRONG + move_resist = MOVE_FORCE_STRONG + pull_force = MOVE_FORCE_STRONG + + // Mob traits + mob_biotypes = MOB_ORGANIC | MOB_HUMANOID + basic_mob_flags = NONE + faction = list(FACTION_XEN) + ai_controller = /datum/ai_controller/basic_controller/headcrab_zombie + gold_core_spawnable = HOSTILE_SPAWN + can_be_shielded = FALSE // Zombies are corrupted humans, not true Xen allies + + /// What type of corpse to spawn when this zombie dies + var/corpse_type = null + /// The human that was zombified (for freeing on death) + var/mob/living/carbon/human/zombified_human = null + // Alert Sound System - Inherited from /mob/living/basic/blackmesa + alert_sounds = list( + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/alert1.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/alert2.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/alert3.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/alert4.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/alert5.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/alert6.ogg' + ) + +/mob/living/basic/blackmesa/xen/headcrab_zombie/Initialize(mapload) + . = ..() + AddElement(/datum/element/wall_smasher, strength_flag = ENVIRONMENT_SMASH_STRUCTURES) + +/mob/living/basic/blackmesa/xen/headcrab_zombie/death(gibbed) + if(!gibbed && prob(30)) + new /mob/living/basic/blackmesa/xen/headcrab(loc) // Headcrab detaches and survives! + if(zombified_human) + zombified_human.forceMove(get_turf(src)) + zombified_human = null + qdel(src) // To ensure the non-player body gets deleted instead of there being two. + else + return ..() + +/mob/living/basic/blackmesa/xen/headcrab_zombie/Destroy() + if(zombified_human) + zombified_human.forceMove(get_turf(src)) + zombified_human = null + return ..() + +/** + * Zombie Variants + * Different types of zombies with varying stats and corpses. + */ + +/// Scientist zombie - baseline zombie with standard stats +/mob/living/basic/blackmesa/xen/headcrab_zombie/scientist + name = "zombified scientist" + desc = "Even after death, I still have to wear this horrible tie!" + icon_state = "scientist_zombie" + base_icon_state = "scientist_zombie" + corpse_type = /obj/effect/mob_spawn/corpse/human/scientist_zombie + +/// Security guard zombie - medium armor +/mob/living/basic/blackmesa/xen/headcrab_zombie/guard + name = "zombified guard" + desc = "About that brain I owed ya!" + icon_state = "security_zombie" + base_icon_state = "security_zombie" + maxHealth = 140 + health = 140 + corpse_type = /obj/effect/mob_spawn/corpse/human/guard_zombie + +/// HECU Marine zombie - heavy armor +/mob/living/basic/blackmesa/xen/headcrab_zombie/hecu + name = "zombified marine" + desc = "MY. ASS. IS. DEAD." + icon_state = "hecu_zombie" + base_icon_state = "hecu_zombie" + maxHealth = 190 + health = 190 + corpse_type = /obj/effect/mob_spawn/corpse/human/hecu_zombie + +/// HEV zombie - extremely tough due to the HEV suit +/mob/living/basic/blackmesa/xen/headcrab_zombie/hev + name = "zombified hazardous environment specialist" + desc = "User death... surpassed." + icon_state = "hev_zombie" + base_icon_state = "hev_zombie" + maxHealth = 250 + health = 250 + + alert_sounds = list( + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv1.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv2.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv3.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv4.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv5.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv6.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv7.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv8.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv9.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv10.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv11.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv12.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv13.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/zombies/hzv14.ogg' + ) diff --git a/modular_skyrat/modules/black_mesa/code/mobs/headcrab_zombie/headcrab_zombie_ai.dm b/modular_skyrat/modules/black_mesa/code/mobs/headcrab_zombie/headcrab_zombie_ai.dm new file mode 100644 index 00000000000..5aa7a12d2a8 --- /dev/null +++ b/modular_skyrat/modules/black_mesa/code/mobs/headcrab_zombie/headcrab_zombie_ai.dm @@ -0,0 +1,19 @@ +/** + * AI Controller + * Handles the zombie's behavior and targeting + */ +/datum/ai_controller/basic_controller/headcrab_zombie + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_CURRENT_TARGET = null, + BB_BASIC_MOB_CURRENT_TARGET_HIDING = FALSE, + BB_TARGET_MINIMUM_STAT = HARD_CRIT + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/target_retaliate + ) diff --git a/modular_skyrat/modules/black_mesa/code/mobs/houndeye.dm b/modular_skyrat/modules/black_mesa/code/mobs/houndeye.dm deleted file mode 100644 index d078d1e4dfe..00000000000 --- a/modular_skyrat/modules/black_mesa/code/mobs/houndeye.dm +++ /dev/null @@ -1,52 +0,0 @@ -/mob/living/simple_animal/hostile/blackmesa/xen/houndeye - name = "houndeye" - desc = "Some highly aggressive alien creature. Thrives in toxic environments." - icon = 'modular_skyrat/modules/black_mesa/icons/mobs.dmi' - icon_state = "houndeye" - icon_living = "houndeye" - icon_dead = "houndeye_dead" - icon_gib = null - mob_biotypes = MOB_ORGANIC|MOB_BEAST - environment_smash = ENVIRONMENT_SMASH_STRUCTURES - speak_chance = 1 - speak_emote = list("growls") - speed = 1 - emote_taunt = list("growls", "snarls", "grumbles") - taunt_chance = 100 - turns_per_move = 7 - maxHealth = 100 - health = 100 - obj_damage = 50 - harm_intent_damage = 10 - melee_damage_lower = 20 - melee_damage_upper = 20 - attack_sound = 'sound/items/weapons/bite.ogg' - gold_core_spawnable = HOSTILE_SPAWN - //Since those can survive on Xen, I'm pretty sure they can thrive on any atmosphere - - minbodytemp = 0 - maxbodytemp = 1500 - loot = list(/obj/item/stack/sheet/bluespace_crystal) - alert_sounds = list( - 'modular_skyrat/modules/black_mesa/sound/mobs/houndeye/he_alert1.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/houndeye/he_alert2.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/houndeye/he_alert3.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/houndeye/he_alert4.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/houndeye/he_alert5.ogg' - ) - /// Charging ability - var/datum/action/cooldown/mob_cooldown/charge/basic_charge/charge - -/mob/living/simple_animal/hostile/blackmesa/xen/houndeye/Initialize(mapload) - . = ..() - charge = new(src) - charge.Grant(src) - -/mob/living/simple_animal/hostile/blackmesa/xen/houndeye/Destroy() - charge = null - return ..() - -/mob/living/simple_animal/hostile/blackmesa/xen/houndeye/OpenFire() - if(client) - return - charge.Trigger(target) diff --git a/modular_skyrat/modules/black_mesa/code/mobs/houndeye/houndeye.dm b/modular_skyrat/modules/black_mesa/code/mobs/houndeye/houndeye.dm new file mode 100644 index 00000000000..33c2f9ac4fd --- /dev/null +++ b/modular_skyrat/modules/black_mesa/code/mobs/houndeye/houndeye.dm @@ -0,0 +1,85 @@ +/mob/living/basic/blackmesa/xen/houndeye + name = "houndeye" + desc = "Some highly aggressive alien creature that attacks using sonic waves." + icon = 'modular_skyrat/modules/black_mesa/icons/mobs.dmi' + icon_state = "houndeye" + base_icon_state = "houndeye" + icon_living = "houndeye" + icon_dead = "houndeye_dead" + + // Stats + maxHealth = 100 + health = 100 + melee_damage_lower = 15 + melee_damage_upper = 15 + obj_damage = 50 + attack_sound = 'sound/items/tools/welder.ogg' + attack_verb_continuous = "headbutts" + attack_verb_simple = "headbutt" + + // Sonic blast vars + /// Whether we're currently charging up a sonic blast + var/charging_sonic = FALSE + /// World time when we can next use sonic blast + var/next_sonic_blast = 0 + /// Sounds to play when charging sonic blast + var/static/list/charge_sounds = list( + 'modular_skyrat/modules/black_mesa/sound/mobs/houndeye/charge1.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/houndeye/charge2.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/houndeye/charge3.ogg' + ) + + // Movement + speed = 1 + ai_controller = /datum/ai_controller/basic_controller/houndeye + basic_mob_flags = NONE + + // Mob properties + mob_biotypes = list(MOB_ORGANIC, MOB_BEAST) + environment_smash = ENVIRONMENT_SMASH_STRUCTURES + gold_core_spawnable = HOSTILE_SPAWN + + // Environmental resistances + minimum_survivable_temperature = 0 + maximum_survivable_temperature = 1500 + + // Alert sounds from parent type will be used + alert_sounds = list( + 'modular_skyrat/modules/black_mesa/sound/mobs/houndeye/he_alert1.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/houndeye/he_alert2.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/houndeye/he_alert3.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/houndeye/he_alert4.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/houndeye/he_alert5.ogg' + ) + +/** + * Visual effect for sonic blast charging + * + * Creates a charging effect that follows the houndeye + */ +/obj/effect/temp_visual/spirit_hound_charge + icon = 'icons/effects/effects.dmi' + icon_state = "lightning" + duration = 1.5 SECONDS + randomdir = FALSE + layer = BELOW_MOB_LAYER + plane = GAME_PLANE + /// The mob we're attached to for following + var/mob/attached_mob + +/obj/effect/temp_visual/spirit_hound_charge/Initialize(mapload) + . = ..() + if(attached_mob) + loc = attached_mob.loc + +/** + * Visual effect for the sonic blast wave + * + * Creates a visible indicator of the sonic blast's area of effect + */ +/obj/effect/temp_visual/sonicblast + icon = 'icons/effects/effects.dmi' + icon_state = "shieldsparkles" + duration = 5 + layer = ABOVE_MOB_LAYER + plane = GAME_PLANE diff --git a/modular_skyrat/modules/black_mesa/code/mobs/houndeye/houndeye_ai.dm b/modular_skyrat/modules/black_mesa/code/mobs/houndeye/houndeye_ai.dm new file mode 100644 index 00000000000..93536330820 --- /dev/null +++ b/modular_skyrat/modules/black_mesa/code/mobs/houndeye/houndeye_ai.dm @@ -0,0 +1,138 @@ +/** + * AI controller for houndeye basic mob + * Handles charging behavior and basic targeting + */ +/datum/ai_controller/basic_controller/houndeye + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_LOW_PRIORITY_HUNTING_TARGET = null, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree/houndeye, + /datum/ai_planning_subtree/target_retaliate + ) + +/** + * Custom melee attack subtree for houndeye that includes charging + */ +/datum/ai_planning_subtree/basic_melee_attack_subtree/houndeye + melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/houndeye + +/** + * Custom melee attack behavior that includes sonic blast + */ +/datum/ai_behavior/basic_melee_attack/houndeye + action_cooldown = 50 // Longer cooldown between sonic blasts + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT + required_distance = 5 + +/datum/ai_behavior/basic_melee_attack/houndeye/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) + var/mob/living/basic/blackmesa/xen/houndeye/living_pawn = controller.pawn + var/atom/target = controller.blackboard[target_key] + + if(QDELETED(target)) + finish_action(controller, FALSE) + return + + if(living_pawn.charging_sonic || world.time < living_pawn.next_sonic_blast) + return + + // Attempt to use sonic blast if possible + if(can_sonic_blast(living_pawn, target)) + living_pawn.charging_sonic = TRUE + living_pawn.next_sonic_blast = world.time + action_cooldown + do_sonic_blast(living_pawn, target) + finish_action(controller, TRUE) + return + + return ..() // Otherwise do normal melee attack + +/** + * Check if we can use sonic blast on the target + */ +/datum/ai_behavior/basic_melee_attack/houndeye/proc/can_sonic_blast(mob/living/basic/blackmesa/xen/houndeye/living_pawn, atom/target) + if(!isliving(target)) + return FALSE + + var/dist = get_dist(living_pawn, target) + if(dist <= 5 && dist > 1) // Use sonic blast at medium range + return TRUE + return FALSE + +/** + * Perform the sonic blast attack + */ +/datum/ai_behavior/basic_melee_attack/houndeye/proc/do_sonic_blast(mob/living/basic/blackmesa/xen/houndeye/living_pawn, atom/target) + // Start charging and prevent movement + living_pawn.face_atom(target) + living_pawn.set_anchored(TRUE) + + living_pawn.visible_message(span_warning("[living_pawn] begins to charge up energy!")) + playsound(living_pawn, pick(living_pawn.charge_sounds), 100, TRUE) + + // Visual effect for charging + var/obj/effect/temp_visual/spirit_hound_charge/charge_effect = new(living_pawn.loc) + charge_effect.attached_mob = living_pawn + + // After a delay, release the blast + addtimer(CALLBACK(src, PROC_REF(release_sonic_blast), living_pawn, target), 1.5 SECONDS) + + // Reset anchoring and state after the blast + addtimer(CALLBACK(src, PROC_REF(reset_movement), living_pawn), 1.5 SECONDS) + +/** + * Helper proc to reset movement restrictions + */ +/datum/ai_behavior/basic_melee_attack/houndeye/proc/reset_movement(mob/living/basic/blackmesa/xen/houndeye/living_pawn) + if(QDELETED(living_pawn)) + return + living_pawn.set_anchored(FALSE) + living_pawn.charging_sonic = FALSE + +/** + * Release the charged up sonic blast + */ +/datum/ai_behavior/basic_melee_attack/houndeye/proc/release_sonic_blast(mob/living/basic/blackmesa/xen/houndeye/living_pawn, atom/target) + if(QDELETED(living_pawn) || QDELETED(target)) + return + + // Create the blast effect + var/list/turfs_to_hit = list() + + // Get turfs in a cone shape in front of the houndeye + for(var/turf/T in view(4, living_pawn)) + if(get_dist(living_pawn, T) > 4) + continue + if(angle2dir_cardinal(get_angle(living_pawn, T)) != living_pawn.dir) + continue + turfs_to_hit += T + new /obj/effect/temp_visual/sonicblast(T) + + // Deal damage to everything in the blast + for(var/turf/T in turfs_to_hit) + for(var/mob/living/L in T) + if(L == living_pawn) + continue + var/dist_mod = 1 - (get_dist(living_pawn, L) / 5) // Less damage at max range + var/damage = round(15 * dist_mod) // Base 15 damage, reduced by distance + L.apply_damage(damage, BRUTE) + L.apply_damage(damage, STAMINA) // Add stamina damage + if(prob(50)) + L.Knockdown(10) // Shorter knockdown, 50% chance + to_chat(L, span_warning("You're hit by [living_pawn]'s sonic blast!")) + + // Break windows and weak objects, but less severely + for(var/obj/O in T) + if(istype(O, /obj/structure/window)) + O.take_damage(30) + else if(istype(O, /obj/structure/table) || istype(O, /obj/structure/rack)) + O.take_damage(10) + + // Play the blast sound + playsound(living_pawn, 'sound/effects/explosion/explosioncreak2.ogg', 100, TRUE) + living_pawn.visible_message(span_danger("[living_pawn] releases a powerful sonic blast!")) diff --git a/modular_skyrat/modules/black_mesa/code/mobs/human_mobs.dm b/modular_skyrat/modules/black_mesa/code/mobs/human_mobs.dm deleted file mode 100644 index 444c5d97f5f..00000000000 --- a/modular_skyrat/modules/black_mesa/code/mobs/human_mobs.dm +++ /dev/null @@ -1,226 +0,0 @@ -/* -* HECU -*/ - -/mob/living/simple_animal/hostile/blackmesa/hecu - name = "HECU Grunt" - desc = "I didn't sign on for this shit. Monsters, sure, but civilians? Who ordered this operation anyway?" - icon = 'modular_skyrat/modules/black_mesa/icons/mobs.dmi' - icon_state = "hecu_melee" - icon_living = "hecu_melee" - icon_dead = "hecu_dead" - icon_gib = "syndicate_gib" - mob_biotypes = MOB_ORGANIC|MOB_HUMANOID - sentience_type = SENTIENCE_HUMANOID - speak_chance = 10 - speak = list("Stop right there!") - turns_per_move = 5 - speed = 0 - stat_attack = HARD_CRIT - robust_searching = 1 - maxHealth = 150 - health = 150 - harm_intent_damage = 5 - melee_damage_lower = 10 - melee_damage_upper = 10 - attack_verb_continuous = "punches" - attack_verb_simple = "punch" - attack_sound = 'sound/items/weapons/punch1.ogg' - combat_mode = TRUE - loot = list(/obj/effect/gibspawner/human, /obj/item/melee/baton) - atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) - unsuitable_atmos_damage = 7.5 - faction = list(FACTION_HECU) - check_friendly_fire = 1 - status_flags = CANPUSH - del_on_death = 1 - dodging = TRUE - rapid_melee = 2 - footstep_type = FOOTSTEP_MOB_SHOE - alert_sounds = list( - 'modular_skyrat/modules/black_mesa/sound/mobs/hecu/hg_alert01.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/hecu/hg_alert03.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/hecu/hg_alert04.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/hecu/hg_alert05.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/hecu/hg_alert06.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/hecu/hg_alert07.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/hecu/hg_alert08.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/hecu/hg_alert10.ogg' - ) - - -/mob/living/simple_animal/hostile/blackmesa/hecu/ranged - ranged = TRUE - retreat_distance = 5 - minimum_distance = 5 - icon_state = "hecu_ranged" - icon_living = "hecu_ranged" - casingtype = /obj/item/ammo_casing/a50ae - projectilesound = 'sound/items/weapons/gun/pistol/shot.ogg' - loot = list(/obj/effect/gibspawner/human, /obj/effect/spawner/random/hecu_deagle) - dodging = TRUE - rapid_melee = 1 - -/mob/living/simple_animal/hostile/blackmesa/hecu/ranged/smg - rapid = 3 - icon_state = "hecu_ranged_smg" - icon_living = "hecu_ranged_smg" - casingtype = /obj/item/ammo_casing/c34 - projectilesound = 'sound/items/weapons/gun/smg/shot.ogg' - loot = list(/obj/effect/gibspawner/human, /obj/effect/spawner/random/hecu_smg) - -/mob/living/simple_animal/hostile/blackmesa/sec - name = "Security Guard" - desc = "About that beer I owe'd ya!" - icon = 'modular_skyrat/modules/black_mesa/icons/mobs.dmi' - icon_state = "security_guard_melee" - icon_living = "security_guard_melee" - icon_dead = "security_guard_dead" - icon_gib = "syndicate_gib" - mob_biotypes = MOB_ORGANIC|MOB_HUMANOID - sentience_type = SENTIENCE_HUMANOID - turns_per_move = 5 - speed = 0 - stat_attack = HARD_CRIT - robust_searching = 1 - maxHealth = 100 - health = 100 - harm_intent_damage = 5 - melee_damage_lower = 7 - melee_damage_upper = 7 - attack_verb_continuous = "punches" - attack_verb_simple = "punch" - attack_sound = 'sound/items/weapons/punch1.ogg' - loot = list(/obj/effect/gibspawner/human, /obj/item/clothing/suit/armor/vest/blueshirt) - atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) - unsuitable_atmos_damage = 7.5 - faction = list(FACTION_STATION, FACTION_NEUTRAL) - check_friendly_fire = 1 - status_flags = CANPUSH - del_on_death = TRUE - combat_mode = TRUE - dodging = TRUE - rapid_melee = 2 - footstep_type = FOOTSTEP_MOB_SHOE - alert_sounds = list( - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance01.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance02.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance02.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance03.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance04.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance05.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance06.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance07.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance08.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance09.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance10.ogg' - ) - var/list/follow_sounds = list( - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/leadon01.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/leadon02.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/leadon03.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/leadtheway01.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/leadtheway02.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/leadtheway03.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/leadtheway04.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/leadtheway05.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/leadtheway06.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/leadtheway07.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/leadtheway08.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/letsgo01.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/letsgo02.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/letsgo03.ogg', - ) - var/list/unfollow_sounds = list( - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/holddownspot01.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/holddownspot02.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/holddownspot03.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/holddownspot04.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/holddownspot05.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/illstayhere01.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/illstayhere02.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/illstayhere03.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/imstickinghere01.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/imstickinghere02.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/imstickinghere03.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/imstickinghere04.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/imstickinghere05.ogg', - ) - var/follow_speed = 2 - var/follow_distance = 2 - -/mob/living/simple_animal/hostile/blackmesa/sec/Initialize(mapload) - . = ..() - AddComponent(/datum/component/follow, follow_sounds, unfollow_sounds, follow_distance, follow_speed) - - -/mob/living/simple_animal/hostile/blackmesa/sec/ranged - ranged = TRUE - retreat_distance = 5 - minimum_distance = 5 - icon_state = "security_guard_ranged" - icon_living = "security_guard_ranged" - casingtype = /obj/item/ammo_casing/c9mm - projectilesound = 'sound/items/weapons/gun/pistol/shot.ogg' - loot = list(/obj/effect/gibspawner/human, /obj/item/clothing/suit/armor/vest/blueshirt, /obj/item/gun/ballistic/automatic/pistol/sol) - rapid_melee = 1 - -/mob/living/simple_animal/hostile/blackmesa/blackops - name = "black operative" - desc = "Why do we always have to clean up a mess the grunts can't handle?" - icon = 'modular_skyrat/modules/black_mesa/icons/mobs.dmi' - icon_state = "blackops" - icon_living = "blackops" - icon_dead = "blackops" - icon_gib = "syndicate_gib" - mob_biotypes = MOB_ORGANIC|MOB_HUMANOID - sentience_type = SENTIENCE_HUMANOID - speak_chance = 10 - speak = list("Got a visual!") - turns_per_move = 5 - speed = 0 - stat_attack = HARD_CRIT - robust_searching = 1 - maxHealth = 200 - health = 200 - harm_intent_damage = 25 - melee_damage_lower = 30 - melee_damage_upper = 30 - attack_verb_continuous = "strikes" - attack_verb_simple = "strikes" - attack_sound = 'sound/effects/woodhit.ogg' - combat_mode = TRUE - loot = list(/obj/effect/gibspawner/human) - atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) - unsuitable_atmos_damage = 7.5 - faction = list(FACTION_BLACKOPS) - check_friendly_fire = 1 - status_flags = CANPUSH - del_on_death = 1 - dodging = TRUE - rapid_melee = 2 - footstep_type = FOOTSTEP_MOB_SHOE - alert_sounds = list( - 'modular_skyrat/modules/black_mesa/sound/mobs/blackops/bo_alert01.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/blackops/bo_alert02.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/blackops/bo_alert03.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/blackops/bo_alert04.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/blackops/bo_alert05.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/blackops/bo_alert06.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/blackops/bo_alert07.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/blackops/bo_alert08.ogg' - ) - - -/mob/living/simple_animal/hostile/blackmesa/blackops/ranged - ranged = TRUE - rapid = 2 - retreat_distance = 5 - minimum_distance = 5 - icon_state = "blackops_ranged" - icon_living = "blackops_ranged" - casingtype = /obj/item/ammo_casing/c40sol - projectilesound = 'modular_skyrat/modules/modular_weapons/sounds/rifle_heavy.ogg' - attack_sound = 'sound/items/weapons/punch1.ogg' - loot = list(/obj/effect/gibspawner/human, /obj/item/ammo_box/magazine/c40sol_rifle/standard) - rapid_melee = 1 diff --git a/modular_skyrat/modules/black_mesa/code/mobs/human_mobs/human_mobs.dm b/modular_skyrat/modules/black_mesa/code/mobs/human_mobs/human_mobs.dm new file mode 100644 index 00000000000..42435516198 --- /dev/null +++ b/modular_skyrat/modules/black_mesa/code/mobs/human_mobs/human_mobs.dm @@ -0,0 +1,216 @@ +/** + * Black Mesa Human NPCs + * Various human NPCs from Black Mesa including HECU, Security, and Black Ops. + */ + +/// Base type for all Black Mesa human NPCs +/mob/living/basic/blackmesa/human + // Mob traits + mob_biotypes = MOB_ORGANIC|MOB_HUMANOID + sentience_type = SENTIENCE_HUMANOID + basic_mob_flags = DEL_ON_DEATH // Handle death manually for corpse spawning + status_flags = CANPUSH + + // Combat stats + speed = 0 + combat_mode = TRUE + attack_verb_continuous = "punches" + attack_verb_simple = "punch" + attack_sound = 'sound/items/weapons/punch1.ogg' + + // Environmental resistances + unsuitable_atmos_damage = 7.5 + habitable_atmos = list( + "min_oxy" = 5, "max_oxy" = 0, + "min_tox" = 0, "max_tox" = 1, + "min_co2" = 0, "max_co2" = 5, + "min_n2" = 0, "max_n2" = 0 + ) + minimum_survivable_temperature = T0C - 50 + maximum_survivable_temperature = T0C + 50 + + // AI behavior + ai_controller = /datum/ai_controller/basic_controller + + // Death handling + /// The chance (0-100) to spawn a corpse instead of gibbing + var/corpse_chance = 20 + /// The type of corpse to spawn on death + var/obj/effect/mob_spawn/corpse/human/corpse + /// The item to hold in the right hand + var/obj/item/r_hand + + +/mob/living/basic/blackmesa/human/death(gibbed) + if(!gibbed) + var/turf/T = get_turf(src) + if(T) + // Clean up any components with active timers first + new /obj/effect/gibspawner/human(T) + + if(prob(corpse_chance) && corpse) + new corpse(T) + + // Clean up any casings at our feet + for(var/obj/item/ammo_casing/casing in T) + qdel(casing) + + qdel(src) + return + return ..() + +/mob/living/basic/blackmesa/human/hecu + name = "HECU Grunt" + desc = "I didn't sign on for this shit. Monsters, sure, but civilians? Who ordered this operation anyway?" + icon = 'modular_skyrat/modules/black_mesa/icons/mobs.dmi' + icon_state = "hecu_melee" + icon_living = "hecu_melee" + icon_dead = "hecu_dead" + icon_gib = "syndicate_gib" + maxHealth = 150 + health = 150 + melee_damage_lower = 10 + melee_damage_upper = 10 + faction = list(FACTION_HECU) + ai_controller = /datum/ai_controller/basic_controller/hecu + corpse = /obj/effect/mob_spawn/corpse/human/hecu + alert_sounds = list( + 'modular_skyrat/modules/black_mesa/sound/mobs/hecu/hg_alert01.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/hecu/hg_alert03.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/hecu/hg_alert04.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/hecu/hg_alert05.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/hecu/hg_alert06.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/hecu/hg_alert07.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/hecu/hg_alert08.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/hecu/hg_alert10.ogg' + ) + + +/mob/living/basic/blackmesa/human/hecu/ranged + icon_state = "hecu_ranged" + icon_living = "hecu_ranged" + ai_controller = /datum/ai_controller/basic_controller/hecu/ranged + corpse = /obj/effect/mob_spawn/corpse/human/hecu_ranged + r_hand = /obj/item/gun/ballistic/automatic/pistol/deagle + +/mob/living/basic/blackmesa/human/hecu/ranged/Initialize(mapload) + . = ..() + AddComponent(\ + /datum/component/ranged_attacks,\ + casing_type = /obj/item/ammo_casing/a50ae,\ + projectile_sound = 'sound/items/weapons/gun/pistol/shot.ogg',\ + cooldown_time = 5 SECONDS,\ + burst_shots = 1\ + ) + +/mob/living/basic/blackmesa/human/hecu/ranged/smg + icon_state = "hecu_ranged_smg" + icon_living = "hecu_ranged_smg" + ai_controller = /datum/ai_controller/basic_controller/hecu/ranged + corpse = /obj/effect/mob_spawn/corpse/human/hecu_smg + r_hand = /obj/item/gun/ballistic/automatic/c20r + +/mob/living/basic/blackmesa/human/hecu/ranged/smg/Initialize(mapload) + . = ..() + // Override parent's pistol component with SMG settings + qdel(GetComponent(/datum/component/ranged_attacks)) + AddComponent(\ + /datum/component/ranged_attacks,\ + casing_type = /obj/item/ammo_casing/c45,\ + projectile_sound = 'sound/items/weapons/gun/smg/shot.ogg',\ + cooldown_time = 3 SECONDS,\ + burst_shots = 3\ + ) + +/mob/living/basic/blackmesa/human/sec + name = "Security Guard" + desc = "About that beer I owe'd ya!" + icon = 'modular_skyrat/modules/black_mesa/icons/mobs.dmi' + icon_state = "security_guard_melee" + icon_living = "security_guard_melee" + icon_dead = "security_guard_dead" + icon_gib = "syndicate_gib" + maxHealth = 100 + health = 100 + melee_damage_lower = 7 + melee_damage_upper = 7 + faction = list(FACTION_STATION, FACTION_NEUTRAL) + ai_controller = /datum/ai_controller/basic_controller/sec + corpse = /obj/effect/mob_spawn/corpse/human/security_guard + alert_sounds = list( + 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance01.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance02.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance03.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance04.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance05.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance06.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance07.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance08.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance09.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/security_guard/annoyance10.ogg' + ) + +/mob/living/basic/blackmesa/human/sec/ranged + icon_state = "security_guard_ranged" + icon_living = "security_guard_ranged" + ai_controller = /datum/ai_controller/basic_controller/sec/ranged + corpse = /obj/effect/mob_spawn/corpse/human/security_guard/ranged + r_hand = /obj/item/gun/ballistic/automatic/pistol/sol + +/mob/living/basic/blackmesa/human/sec/ranged/Initialize(mapload) + . = ..() + AddComponent(\ + /datum/component/ranged_attacks,\ + casing_type = /obj/item/ammo_casing/c35sol,\ + projectile_sound = 'sound/items/weapons/gun/pistol/shot.ogg',\ + cooldown_time = 5 SECONDS,\ + burst_shots = 1\ + ) + +/mob/living/basic/blackmesa/human/blackops + name = "black operative" + desc = "Why do we always have to clean up a mess the grunts can't handle?" + icon = 'modular_skyrat/modules/black_mesa/icons/mobs.dmi' + icon_state = "blackops" + icon_living = "blackops" + icon_dead = "blackops" + icon_gib = "syndicate_gib" + maxHealth = 200 + health = 200 + melee_damage_lower = 30 + melee_damage_upper = 30 + attack_verb_continuous = "strikes" + attack_verb_simple = "strikes" + attack_sound = 'sound/effects/woodhit.ogg' + faction = list(FACTION_BLACKOPS) + ai_controller = /datum/ai_controller/basic_controller/blackops + corpse = /obj/effect/mob_spawn/corpse/human/black_ops + alert_sounds = list( + 'modular_skyrat/modules/black_mesa/sound/mobs/blackops/bo_alert01.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/blackops/bo_alert02.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/blackops/bo_alert03.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/blackops/bo_alert04.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/blackops/bo_alert05.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/blackops/bo_alert06.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/blackops/bo_alert07.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/blackops/bo_alert08.ogg' + ) + + +/mob/living/basic/blackmesa/human/blackops/ranged + icon_state = "blackops_ranged" + icon_living = "blackops_ranged" + ai_controller = /datum/ai_controller/basic_controller/blackops/ranged + attack_sound = 'sound/items/weapons/punch1.ogg' + corpse = /obj/effect/mob_spawn/corpse/human/black_ops/ranged + r_hand = /obj/item/gun/ballistic/automatic/sol_rifle + +/mob/living/basic/blackmesa/human/blackops/ranged/Initialize(mapload) + . = ..() + AddComponent(\ + /datum/component/ranged_attacks,\ + casing_type = /obj/item/ammo_casing/c40sol,\ + projectile_sound = 'sound/items/weapons/gun/rifle/shot.ogg',\ + cooldown_time = 4 SECONDS,\ + burst_shots = 3\ + ) diff --git a/modular_skyrat/modules/black_mesa/code/mobs/human_mobs/human_mobs_ai.dm b/modular_skyrat/modules/black_mesa/code/mobs/human_mobs/human_mobs_ai.dm new file mode 100644 index 00000000000..e76d01024f0 --- /dev/null +++ b/modular_skyrat/modules/black_mesa/code/mobs/human_mobs/human_mobs_ai.dm @@ -0,0 +1,74 @@ +/** + * AI Controllers + */ + +/// AI controller for HECU melee troops +/datum/ai_controller/basic_controller/hecu + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/target_retaliate + ) + +/// AI controller for HECU ranged troops +/datum/ai_controller/basic_controller/hecu/ranged + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper, + /datum/ai_planning_subtree/target_retaliate + ) + +/// AI controller for security guards +/datum/ai_controller/basic_controller/sec + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_BASIC_MOB_HATED_FACTIONS = list(FACTION_HECU, FACTION_BLACKOPS) + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/target_retaliate + ) + +/// AI controller for ranged security guards +/datum/ai_controller/basic_controller/sec/ranged + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper, + /datum/ai_planning_subtree/target_retaliate + ) + +/// AI controller for black ops +/datum/ai_controller/basic_controller/blackops + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/target_retaliate + ) + +/// AI controller for ranged black ops +/datum/ai_controller/basic_controller/blackops/ranged + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper, + /datum/ai_planning_subtree/target_retaliate + ) + diff --git a/modular_skyrat/modules/black_mesa/code/mobs/mob_spawners.dm b/modular_skyrat/modules/black_mesa/code/mobs/mob_spawners.dm index 317c73caecc..9943f4fe094 100644 --- a/modular_skyrat/modules/black_mesa/code/mobs/mob_spawners.dm +++ b/modular_skyrat/modules/black_mesa/code/mobs/mob_spawners.dm @@ -1,20 +1,40 @@ -/mob/living/simple_animal/hostile/blackmesa +/mob/living/basic/blackmesa + /// List of sounds to play when spotting enemies var/list/alert_sounds + /// Time between alert sounds var/alert_cooldown = 3 SECONDS + /// World time when we can make an alert sound again var/alert_cooldown_time -/mob/living/simple_animal/hostile/blackmesa/xen - faction = list(FACTION_XEN) - atmos_requirements = null - minbodytemp = 0 - maxbodytemp = 1500 - -/mob/living/simple_animal/hostile/blackmesa/Aggro() +/mob/living/basic/blackmesa/Initialize(mapload) + . = ..() + AddElement(/datum/element/ai_retaliate) if(alert_sounds) - if(!(world.time <= alert_cooldown_time)) - playsound(src, pick(alert_sounds), 70) - alert_cooldown_time = world.time + alert_cooldown + RegisterSignal(src, COMSIG_AI_BLACKBOARD_KEY_SET(BB_BASIC_MOB_CURRENT_TARGET), PROC_REF(handle_alert)) +/// Play a sound when spotting an enemy +/mob/living/basic/blackmesa/proc/handle_alert(datum/source, key, value) + SIGNAL_HANDLER + if(world.time <= alert_cooldown_time) + return + if(prob(75)) // Don't always make noise + playsound(src, pick(alert_sounds), 70) + alert_cooldown_time = world.time + alert_cooldown + +/mob/living/basic/blackmesa/xen + faction = list(FACTION_XEN) + minimum_survivable_temperature = 0 + maximum_survivable_temperature = 1500 + unsuitable_cold_damage = 15 + unsuitable_heat_damage = 15 + habitable_atmos = null // Can survive in any atmos + + /// Can we be shielded by pylons? + var/can_be_shielded = TRUE + /// If we have support pylons, this is true. + var/shielded = FALSE + /// How many shields we have protecting us + var/shield_count = 0 #define MOB_PLACER_RANGE 16 // One more tile than the biggest viewrange we have. @@ -22,7 +42,7 @@ name = "mob placer" icon = 'modular_skyrat/modules/black_mesa/icons/mapping_helpers.dmi' icon_state = "mobspawner" - var/list/possible_mobs = list(/mob/living/simple_animal/hostile/blackmesa/xen/headcrab) + var/list/possible_mobs = list(/mob/living/basic/blackmesa/xen/headcrab) /obj/effect/random_mob_placer/Initialize(mapload) . = ..() @@ -47,49 +67,49 @@ /obj/effect/random_mob_placer/xen icon_state = "spawn_xen" possible_mobs = list( - /mob/living/simple_animal/hostile/blackmesa/xen/headcrab, - /mob/living/simple_animal/hostile/blackmesa/xen/houndeye, - /mob/living/simple_animal/hostile/blackmesa/xen/bullsquid, + /mob/living/basic/blackmesa/xen/headcrab, + /mob/living/basic/blackmesa/xen/houndeye, + /mob/living/basic/blackmesa/xen/bullsquid, ) /obj/effect/random_mob_placer/xen/zombie icon_state = "spawn_zombie" possible_mobs = list( - /mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/scientist, - /mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/guard, - /mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/hecu, + /mob/living/basic/blackmesa/xen/headcrab_zombie/scientist, + /mob/living/basic/blackmesa/xen/headcrab_zombie/guard, + /mob/living/basic/blackmesa/xen/headcrab_zombie/hecu, ) /obj/effect/random_mob_placer/blackops icon_state = "spawn_blackops" possible_mobs = list( - /mob/living/simple_animal/hostile/blackmesa/blackops, - /mob/living/simple_animal/hostile/blackmesa/blackops/ranged, + /mob/living/basic/blackmesa/human/blackops, + /mob/living/basic/blackmesa/human/blackops/ranged, ) /obj/effect/random_mob_placer/hev_zombie icon_state = "spawn_hev" - possible_mobs = list(/mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/hev) + possible_mobs = list(/mob/living/basic/blackmesa/xen/headcrab_zombie/hev) /obj/effect/random_mob_placer/scientist_zombie icon_state = "spawn_zombiescientist" - possible_mobs = list(/mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/scientist) + possible_mobs = list(/mob/living/basic/blackmesa/xen/headcrab_zombie/scientist) -/obj/effect/random_mob_placer/scientist_zombie +/obj/effect/random_mob_placer/guard_zombie icon_state = "spawn_zombiesec" - possible_mobs = list(/mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/guard) + possible_mobs = list(/mob/living/basic/blackmesa/xen/headcrab_zombie/guard) /obj/effect/random_mob_placer/security_guard icon_state = "spawn_guard" - possible_mobs = list(/mob/living/simple_animal/hostile/blackmesa/sec, /mob/living/simple_animal/hostile/blackmesa/sec/ranged) + possible_mobs = list(/mob/living/basic/blackmesa/human/sec, /mob/living/basic/blackmesa/human/sec/ranged,) /obj/effect/random_mob_placer/vortigaunt_hostile icon_state = "spawn_vortigaunt_slave" - possible_mobs = list(/mob/living/simple_animal/hostile/blackmesa/xen/vortigaunt/slave) + possible_mobs = list(/mob/living/basic/blackmesa/xen/vortigaunt/slave) /obj/effect/random_mob_placer/vortigaunt icon_state = "spawn_vortigaunt" - possible_mobs = list(/mob/living/simple_animal/hostile/blackmesa/xen/vortigaunt) + possible_mobs = list(/mob/living/basic/blackmesa/xen/vortigaunt) /obj/effect/mob_spawn/corpse/human/hecu_zombie name = "HECU" diff --git a/modular_skyrat/modules/black_mesa/code/mobs/nihilanth.dm b/modular_skyrat/modules/black_mesa/code/mobs/nihilanth.dm deleted file mode 100644 index 1b7c94ab66f..00000000000 --- a/modular_skyrat/modules/black_mesa/code/mobs/nihilanth.dm +++ /dev/null @@ -1,67 +0,0 @@ -/mob/living/simple_animal/hostile/blackmesa/xen/nihilanth - name = "nihilanth" - desc = "Holy shit." - icon = 'modular_skyrat/modules/black_mesa/icons/nihilanth.dmi' - icon_state = "nihilanth" - icon_living = "nihilanth" - SET_BASE_PIXEL(-32, -32) - speed = 3 - bound_height = 64 - bound_width = 64 - icon_dead = "bullsquid_dead" - maxHealth = 3000 - health = 3000 - atmos_requirements = null - projectilesound = 'sound/items/weapons/lasercannonfire.ogg' - projectiletype = /obj/projectile/nihilanth - ranged = TRUE - rapid = 3 - alert_cooldown = 2 MINUTES - harm_intent_damage = 50 - melee_damage_lower = 30 - melee_damage_upper = 40 - attack_verb_continuous = "lathes" - attack_verb_simple = "lathe" - attack_sound = 'sound/items/weapons/punch1.ogg' - status_flags = NONE - del_on_death = TRUE - wander = TRUE - loot = list(/obj/effect/gibspawner/xeno, /obj/item/stack/sheet/bluespace_crystal/fifty, /obj/item/key/gateway, /obj/item/uber_teleporter) - movement_type = FLYING - -/obj/item/stack/sheet/bluespace_crystal/fifty - amount = 50 - -/obj/projectile/nihilanth - name = "portal energy" - icon_state = "seedling" - damage = 20 - damage_type = BURN - light_range = 2 - armor_flag = ENERGY - light_color = LIGHT_COLOR_BRIGHT_YELLOW - hitsound = 'sound/items/weapons/sear.ogg' - hitsound_wall = 'sound/items/weapons/effects/searwall.ogg' - nondirectional_sprite = TRUE - -/mob/living/simple_animal/hostile/blackmesa/xen/nihilanth/Aggro() - . = ..() - if(!(world.time <= alert_cooldown_time)) - alert_cooldown_time = world.time + alert_cooldown - switch(health) - if(0 to 999) - playsound(src, pick(list('modular_skyrat/modules/black_mesa/sound/mobs/nihilanth/nihilanth_pain01.ogg', 'modular_skyrat/modules/black_mesa/sound/mobs/nihilanth/nihilanth_freeeemmaan01.ogg')), 100) - if(1000 to 2999) - playsound(src, pick(list('modular_skyrat/modules/black_mesa/sound/mobs/nihilanth/nihilanth_youalldie01.ogg', 'modular_skyrat/modules/black_mesa/sound/mobs/nihilanth/nihilanth_foryouhewaits01.ogg')), 100) - if(3000 to 6000) - playsound(src, pick(list('modular_skyrat/modules/black_mesa/sound/mobs/nihilanth/nihilanth_whathavedone01.ogg', 'modular_skyrat/modules/black_mesa/sound/mobs/nihilanth/nihilanth_deceiveyou01.ogg')), 100) - else - playsound(src, pick(list('modular_skyrat/modules/black_mesa/sound/mobs/nihilanth/nihilanth_thetruth01.ogg', 'modular_skyrat/modules/black_mesa/sound/mobs/nihilanth/nihilanth_iamthelast01.ogg')), 100) - set_combat_mode(TRUE) - -/mob/living/simple_animal/hostile/blackmesa/xen/nihilanth/death(gibbed) - . = ..() - -/mob/living/simple_animal/hostile/blackmesa/xen/nihilanth/LoseAggro() - . = ..() - set_combat_mode(FALSE) diff --git a/modular_skyrat/modules/black_mesa/code/mobs/nihilanth/nihilanth.dm b/modular_skyrat/modules/black_mesa/code/mobs/nihilanth/nihilanth.dm new file mode 100644 index 00000000000..a14cd98af14 --- /dev/null +++ b/modular_skyrat/modules/black_mesa/code/mobs/nihilanth/nihilanth.dm @@ -0,0 +1,123 @@ +/** + * Nihilanth - The final boss of Black Mesa + * + * A massive floating entity that serves as the master of the Xen forces. + * Uses powerful ranged attacks and becomes more desperate as its health decreases. + */ +/mob/living/basic/blackmesa/xen/nihilanth + name = "nihilanth" + desc = "Holy shit." + COOLDOWN_DECLARE(voice_cooldown) + icon = 'modular_skyrat/modules/black_mesa/icons/nihilanth.dmi' + icon_state = "nihilanth" + icon_living = "nihilanth" + pixel_x = -32 + base_pixel_x = -32 + pixel_y = -16 // Adjusted to move hitbox to bottom + base_pixel_y = -16 // Adjusted to move hitbox to bottom + density = TRUE + move_force = MOVE_FORCE_OVERPOWERING + move_resist = MOVE_FORCE_OVERPOWERING + pull_force = MOVE_FORCE_OVERPOWERING + mob_size = MOB_SIZE_LARGE + layer = LARGE_MOB_LAYER + maptext_height = 96 + maptext_width = 96 //Keeps it above walls and structures + mouse_opacity = MOUSE_OPACITY_OPAQUE // Easier to click on + icon_dead = "bullsquid_dead" + appearance_flags = TILE_BOUND | PIXEL_SCALE | KEEP_TOGETHER + maxHealth = 3000 + health = 3000 + obj_damage = 400 + melee_damage_lower = 30 + melee_damage_upper = 40 + armour_penetration = 40 + attack_verb_continuous = "lathes" + attack_verb_simple = "lathe" + attack_sound = 'sound/items/weapons/punch1.ogg' + status_flags = NONE + lighting_cutoff_red = 25 + lighting_cutoff_green = 15 + lighting_cutoff_blue = 35 + mob_biotypes = MOB_ORGANIC|MOB_SPECIAL|MOB_MINING + flags_1 = PREVENT_CONTENTS_EXPLOSION_1 + basic_mob_flags = DEL_ON_DEATH + ai_controller = /datum/ai_controller/basic_controller/nihilanth + + /// Items to drop on death + var/static/list/death_loot = list( + /obj/effect/gibspawner/xeno = 1, + /obj/item/stack/sheet/bluespace_crystal/fifty = 1, + /obj/item/key/gateway = 1, + /obj/item/uber_teleporter = 1 + ) + +/obj/item/stack/sheet/bluespace_crystal/fifty + amount = 50 + +/obj/projectile/nihilanth + name = "portal energy" + icon_state = "seedling" + damage = 20 + damage_type = BURN + light_range = 2 + armor_flag = ENERGY + light_color = LIGHT_COLOR_BRIGHT_YELLOW + hitsound = 'sound/items/weapons/sear.ogg' + hitsound_wall = 'sound/items/weapons/effects/searwall.ogg' + nondirectional_sprite = TRUE + +/mob/living/basic/blackmesa/xen/nihilanth/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_IMMOBILIZED, INNATE_TRAIT) // Prevent unnecessary movement processing + ADD_TRAIT(src, TRAIT_NO_FLOATING_ANIM, INNATE_TRAIT) // Prevent unnecessary animation processing + AddElement(/datum/element/death_drops, death_loot) + AddElement(/datum/element/simple_flying) + RegisterSignal(src, COMSIG_LIVING_DEATH, PROC_REF(on_death), override = TRUE) + AddComponent(\ + /datum/component/ranged_attacks,\ + projectile_type = /obj/projectile/nihilanth,\ + projectile_sound = 'sound/items/weapons/lasercannonfire.ogg',\ + cooldown_time = 3 SECONDS,\ + burst_shots = 3\ + ) + update_appearance(UPDATE_ICON) + +/// Called when nihilanth dies - play death sound +/mob/living/basic/blackmesa/xen/nihilanth/proc/on_death(datum/source) + SIGNAL_HANDLER + playsound(src, pick(list( + 'modular_skyrat/modules/black_mesa/sound/mobs/nihilanth/nihilanth_pain01.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/nihilanth/nihilanth_freeeemmaan01.ogg' + )), 100) + +/mob/living/basic/blackmesa/xen/nihilanth/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) + . = ..() + if(amount <= 0 || !COOLDOWN_FINISHED(src, voice_cooldown)) // Don't play sounds for healing or during cooldown + return + var/list/sound_options + switch(maxHealth - health) // Use remaining health instead of current health + if(2001 to INFINITY) // 0-33% health remaining + sound_options = list( + 'modular_skyrat/modules/black_mesa/sound/mobs/nihilanth/nihilanth_pain01.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/nihilanth/nihilanth_freeeemmaan01.ogg' + ) + if(1001 to 2000) // 33-66% health remaining + sound_options = list( + 'modular_skyrat/modules/black_mesa/sound/mobs/nihilanth/nihilanth_youalldie01.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/nihilanth/nihilanth_foryouhewaits01.ogg' + ) + if(1 to 1000) // 66-99% health remaining + sound_options = list( + 'modular_skyrat/modules/black_mesa/sound/mobs/nihilanth/nihilanth_whathavedone01.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/nihilanth/nihilanth_deceiveyou01.ogg' + ) + else // Full health + sound_options = list( + 'modular_skyrat/modules/black_mesa/sound/mobs/nihilanth/nihilanth_thetruth01.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/nihilanth/nihilanth_iamthelast01.ogg' + ) + var/sound_to_play = pick(sound_options) + var/sound_length = SSsounds.get_sound_length(sound_to_play) + playsound(src, sound_to_play, 100) + COOLDOWN_START(src, voice_cooldown, sound_length + 1 SECONDS) diff --git a/modular_skyrat/modules/black_mesa/code/mobs/nihilanth/nihilanth_ai.dm b/modular_skyrat/modules/black_mesa/code/mobs/nihilanth/nihilanth_ai.dm new file mode 100644 index 00000000000..f973e6e77dd --- /dev/null +++ b/modular_skyrat/modules/black_mesa/code/mobs/nihilanth/nihilanth_ai.dm @@ -0,0 +1,23 @@ +/** + * AI controller for the Nihilanth boss mob + * + * Handles ranged combat behavior and targeting + */ +/datum/ai_controller/basic_controller/nihilanth + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_RANGED_SKIRMISH_MIN_DISTANCE = 3, + BB_RANGED_SKIRMISH_MAX_DISTANCE = 5, + BB_MAX_PATHING_ATTEMPTS = 2, + BB_TARGETING_TIMEOUT = 30 SECONDS // Don't waste time searching if no targets found + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/ranged_skirmish, + /datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper, + /datum/ai_planning_subtree/target_retaliate + ) diff --git a/modular_skyrat/modules/black_mesa/code/mobs/vortigaunt.dm b/modular_skyrat/modules/black_mesa/code/mobs/vortigaunt.dm deleted file mode 100644 index 00d4be49c2e..00000000000 --- a/modular_skyrat/modules/black_mesa/code/mobs/vortigaunt.dm +++ /dev/null @@ -1,64 +0,0 @@ -/mob/living/simple_animal/hostile/blackmesa/xen/vortigaunt - name = "vortigaunt" - desc = "There is no distance between us. No false veils of time or space may intervene." - icon = 'modular_skyrat/modules/black_mesa/icons/mobs.dmi' - icon_state = "vortigaunt" - icon_living = "vortigaunt" - icon_dead = "vortigaunt_dead" - icon_gib = null - gender = MALE - faction = list(FACTION_STATION, FACTION_NEUTRAL) - mob_biotypes = MOB_ORGANIC|MOB_BEAST - speak_chance = 1 - speak_emote = list("galungs") - speed = 1 - emote_taunt = list("galalungas", "galungas", "gungs") - projectiletype = /obj/projectile/beam/emitter/hitscan - projectilesound = 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/attack_shoot4.ogg' - ranged_cooldown_time = 5 SECONDS - ranged_message = "fires" - taunt_chance = 100 - turns_per_move = 7 - maxHealth = 130 - health = 130 - speed = 3 - ranged = TRUE - dodging = TRUE - harm_intent_damage = 15 - melee_damage_lower = 10 - melee_damage_upper = 10 - retreat_distance = 5 - minimum_distance = 5 - attack_sound = 'sound/items/weapons/bite.ogg' - gold_core_spawnable = FRIENDLY_SPAWN - loot = list(/obj/item/stack/sheet/bone) - alert_sounds = list( - 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/alert01.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/alert01b.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/alert02.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/alert03.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/alert04.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/alert05.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/alert06.ogg', - ) - /// SOunds we play when asked to follow/unfollow. - var/list/follow_sounds = list( - 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/village_argue01.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/village_argue02.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/village_argue03.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/village_argue04.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/village_argue05.ogg', - 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/village_argue05a.ogg', - ) - var/follow_speed = 1 - var/follow_distance = 2 - -/mob/living/simple_animal/hostile/blackmesa/xen/vortigaunt/Initialize(mapload) - . = ..() - AddComponent(/datum/component/follow, follow_sounds, follow_sounds, follow_distance, follow_speed) - -/mob/living/simple_animal/hostile/blackmesa/xen/vortigaunt/slave - name = "slave vortigaunt" - desc = "Bound by the shackles of a sinister force. He does not want to hurt you." - icon_state = "vortigaunt_slave" - faction = list(FACTION_XEN) diff --git a/modular_skyrat/modules/black_mesa/code/mobs/vortigaunt/vortigaunt.dm b/modular_skyrat/modules/black_mesa/code/mobs/vortigaunt/vortigaunt.dm new file mode 100644 index 00000000000..3e3447d1a25 --- /dev/null +++ b/modular_skyrat/modules/black_mesa/code/mobs/vortigaunt/vortigaunt.dm @@ -0,0 +1,60 @@ +/** + * Vortigaunt - Allied alien being capable of ranged attacks + * + * A friendly alien that uses beam attacks and maintains distance from enemies. + * Uses ranged skirmish AI to maintain optimal distance while attacking. + */ +/mob/living/basic/blackmesa/xen/vortigaunt + name = "vortigaunt" + desc = "There is no distance between us. No false veils of time or space may intervene." + icon = 'modular_skyrat/modules/black_mesa/icons/mobs.dmi' + icon_state = "vortigaunt" + icon_living = "vortigaunt" + icon_dead = "vortigaunt_dead" + gender = MALE + faction = list(FACTION_STATION, FACTION_NEUTRAL) + mob_biotypes = list(MOB_ORGANIC, MOB_BEAST) + + maxHealth = 130 + health = 130 + melee_damage_lower = 10 + melee_damage_upper = 10 + attack_sound = 'sound/items/weapons/bite.ogg' + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) + + ai_controller = /datum/ai_controller/basic_controller/vortigaunt + gold_core_spawnable = FRIENDLY_SPAWN + + /// Sounds played when spotting enemies + alert_sounds = list( + 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/alert01.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/alert01b.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/alert02.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/alert03.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/alert04.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/alert05.ogg', + 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/alert06.ogg', + ) + +/** + * Initialize the vortigaunt with ranged attack capabilities + */ +/mob/living/basic/blackmesa/xen/vortigaunt/Initialize(mapload) + . = ..() + // Add ranged attack component + AddComponent(\ + /datum/component/ranged_attacks,\ + projectile_type = /obj/projectile/beam/emitter/hitscan,\ + projectile_sound = 'modular_skyrat/modules/black_mesa/sound/mobs/vortigaunt/attack_shoot4.ogg',\ + cooldown_time = 5 SECONDS,\ + burst_shots = 1\ + ) + +/** + * Slave Vortigaunt - Hostile variant that fights for the Xen forces + */ +/mob/living/basic/blackmesa/xen/vortigaunt/slave + name = "slave vortigaunt" + desc = "Bound by the shackles of a sinister force. He does not want to hurt you." + icon_state = "vortigaunt_slave" + faction = list(FACTION_XEN) diff --git a/modular_skyrat/modules/black_mesa/code/mobs/vortigaunt/vortigaunt_ai.dm b/modular_skyrat/modules/black_mesa/code/mobs/vortigaunt/vortigaunt_ai.dm new file mode 100644 index 00000000000..e4ff75c497f --- /dev/null +++ b/modular_skyrat/modules/black_mesa/code/mobs/vortigaunt/vortigaunt_ai.dm @@ -0,0 +1,21 @@ +/** + * AI controller for vortigaunt mobs + * + * Handles ranged combat behavior and targeting + */ +/datum/ai_controller/basic_controller/vortigaunt + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_RANGED_SKIRMISH_MIN_DISTANCE = 3, + BB_RANGED_SKIRMISH_MAX_DISTANCE = 6 + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/ranged_skirmish, + /datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper, + /datum/ai_planning_subtree/target_retaliate + ) diff --git a/modular_skyrat/modules/black_mesa/code/shield_pylon.dm b/modular_skyrat/modules/black_mesa/code/shield_pylon.dm index bf2f8a673dc..d7abbe309d2 100644 --- a/modular_skyrat/modules/black_mesa/code/shield_pylon.dm +++ b/modular_skyrat/modules/black_mesa/code/shield_pylon.dm @@ -1,23 +1,28 @@ -/mob/living/simple_animal/hostile/blackmesa/xen - /// Can we be shielded by pylons? - var/can_be_shielded = TRUE - /// If we have support pylons, this is true. - var/shielded = FALSE - /// How many shields we have protecting us - var/shield_count = 0 - -/mob/living/simple_animal/hostile/blackmesa/xen/update_overlays() +/mob/living/basic/blackmesa/xen/update_overlays() . = ..() if(shielded) . += mutable_appearance('icons/effects/effects.dmi', "shield-yellow", MOB_SHIELD_LAYER) -/mob/living/simple_animal/hostile/blackmesa/xen/proc/lose_shield() +/mob/living/basic/blackmesa/xen/proc/lose_shield() shield_count-- if(shield_count <= 0) shielded = FALSE update_appearance() -/mob/living/simple_animal/hostile/blackmesa/xen/apply_damage(damage = 0, damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, exposed_wound_bonus = 0, sharpness = NONE, attack_direction = null, attacking_item) +/mob/living/basic/blackmesa/xen/apply_damage( + damage = 0, + damagetype = BRUTE, + def_zone = null, + blocked = 0, + forced = FALSE, + spread_damage = FALSE, + wound_bonus = 0, + exposed_wound_bonus = 0, + sharpness = NONE, + attack_direction = null, + attacking_item, + wound_clothing = TRUE, + ) if(shielded) balloon_alert_to_viewers("ineffective!") return FALSE @@ -38,7 +43,7 @@ /obj/structure/xen_pylon/Initialize(mapload) . = ..() - for(var/mob/living/simple_animal/hostile/blackmesa/xen/iterating_mob in range(shield_range, src)) + for(var/mob/living/basic/blackmesa/xen/iterating_mob in range(shield_range, src)) if(!iterating_mob.can_be_shielded) continue register_mob(iterating_mob) @@ -49,12 +54,12 @@ SIGNAL_HANDLER if(!isxenmob(entered_atom)) return - var/mob/living/simple_animal/hostile/blackmesa/xen/entered_xen_mob = entered_atom + var/mob/living/basic/blackmesa/xen/entered_xen_mob = entered_atom if(!entered_xen_mob.can_be_shielded) return register_mob(entered_xen_mob) -/obj/structure/xen_pylon/proc/register_mob(mob/living/simple_animal/hostile/blackmesa/xen/mob_to_register) +/obj/structure/xen_pylon/proc/register_mob(mob/living/basic/blackmesa/xen/mob_to_register) if(mob_to_register in shielded_mobs) return if(!istype(mob_to_register)) @@ -77,14 +82,14 @@ /obj/structure/xen_pylon/proc/beam_died(datum/beam/beam_to_kill) SIGNAL_HANDLER - for(var/mob/living/simple_animal/hostile/blackmesa/xen/iterating_mob as anything in shielded_mobs) + for(var/mob/living/basic/blackmesa/xen/iterating_mob as anything in shielded_mobs) if(shielded_mobs[iterating_mob] == beam_to_kill) iterating_mob.lose_shield() shielded_mobs[iterating_mob] = null shielded_mobs -= iterating_mob /obj/structure/xen_pylon/Destroy() - for(var/mob/living/simple_animal/hostile/blackmesa/xen/iterating_mob as anything in shielded_mobs) + for(var/mob/living/basic/blackmesa/xen/iterating_mob as anything in shielded_mobs) iterating_mob.lose_shield() var/datum/beam/beam = shielded_mobs[iterating_mob] QDEL_NULL(beam) diff --git a/modular_skyrat/modules/black_mesa/code/turfs.dm b/modular_skyrat/modules/black_mesa/code/turfs.dm index 634e6e12afc..30962c5814b 100644 --- a/modular_skyrat/modules/black_mesa/code/turfs.dm +++ b/modular_skyrat/modules/black_mesa/code/turfs.dm @@ -82,7 +82,7 @@ /turf/open/water/xen_acid/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) . = ..() - if(isliving(arrived) && !istype(arrived, /mob/living/simple_animal/hostile/blackmesa/xen/bullsquid)) // Bull squid territory! + if(isliving(arrived) && !istype(arrived, /mob/living/basic/blackmesa/xen/bullsquid)) // Bull squid territory! var/mob/living/unlucky_mob = arrived unlucky_mob.adjustFireLoss(acid_damage) playsound(unlucky_mob, 'sound/items/weapons/sear.ogg', 100, TRUE) diff --git a/modular_skyrat/modules/black_mesa/code/xen_crystal.dm b/modular_skyrat/modules/black_mesa/code/xen_crystal.dm index edb6305d7a8..030ea3699af 100644 --- a/modular_skyrat/modules/black_mesa/code/xen_crystal.dm +++ b/modular_skyrat/modules/black_mesa/code/xen_crystal.dm @@ -52,8 +52,8 @@ var/factions = list(FACTION_STATION, "neutral") /// Mobs in this list will not be affected by this grenade. var/list/blacklisted_mobs = list( - /mob/living/simple_animal/hostile/blackmesa/xen/headcrab_zombie/gordon_freeman, - /mob/living/simple_animal/hostile/blackmesa/xen/nihilanth, + /mob/living/basic/blackmesa/xen/gordon_freeman, + /mob/living/basic/blackmesa/xen/nihilanth, ) /obj/item/grenade/xen_crystal/detonate(mob/living/lanced_by) diff --git a/modular_skyrat/modules/decay_subsystem/code/nests.dm b/modular_skyrat/modules/decay_subsystem/code/nests.dm index 59413eda7d1..d4a36300ca9 100644 --- a/modular_skyrat/modules/decay_subsystem/code/nests.dm +++ b/modular_skyrat/modules/decay_subsystem/code/nests.dm @@ -13,7 +13,7 @@ faction = list(NEST_FACTION) var/spawn_delay = 0 /// What mob to spawn - var/list/monster_types = list(/mob/living/simple_animal/hostile/blackmesa/xen/headcrab) + var/list/monster_types = list(/mob/living/basic/blackmesa/xen/headcrab) /// How many mobs can we spawn? var/max_mobs = 3 var/spawned_mobs = 0 diff --git a/tgstation.dme b/tgstation.dme index 3bfb850d2d8..799e80d4912 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -7469,15 +7469,24 @@ #include "modular_skyrat\modules\black_mesa\code\armaments\armament_miscellaneous.dm" #include "modular_skyrat\modules\black_mesa\code\armaments\armament_utility.dm" #include "modular_skyrat\modules\black_mesa\code\armaments\hecu_armament_station.dm" -#include "modular_skyrat\modules\black_mesa\code\mobs\bullsquid.dm" -#include "modular_skyrat\modules\black_mesa\code\mobs\gordon_freeman.dm" -#include "modular_skyrat\modules\black_mesa\code\mobs\headcrab.dm" -#include "modular_skyrat\modules\black_mesa\code\mobs\headcrab_zombie.dm" -#include "modular_skyrat\modules\black_mesa\code\mobs\houndeye.dm" -#include "modular_skyrat\modules\black_mesa\code\mobs\human_mobs.dm" +#include "modular_skyrat\modules\black_mesa\code\mobs\corpse_spawns.dm" #include "modular_skyrat\modules\black_mesa\code\mobs\mob_spawners.dm" -#include "modular_skyrat\modules\black_mesa\code\mobs\nihilanth.dm" -#include "modular_skyrat\modules\black_mesa\code\mobs\vortigaunt.dm" +#include "modular_skyrat\modules\black_mesa\code\mobs\bullsquid\bullsquid.dm" +#include "modular_skyrat\modules\black_mesa\code\mobs\bullsquid\bullsquid_ai.dm" +#include "modular_skyrat\modules\black_mesa\code\mobs\gordon_freeman\gordon_freeman.dm" +#include "modular_skyrat\modules\black_mesa\code\mobs\gordon_freeman\gordon_freeman_ai.dm" +#include "modular_skyrat\modules\black_mesa\code\mobs\headcrab\headcrab.dm" +#include "modular_skyrat\modules\black_mesa\code\mobs\headcrab\headcrab_ai.dm" +#include "modular_skyrat\modules\black_mesa\code\mobs\headcrab_zombie\headcrab_zombie.dm" +#include "modular_skyrat\modules\black_mesa\code\mobs\headcrab_zombie\headcrab_zombie_ai.dm" +#include "modular_skyrat\modules\black_mesa\code\mobs\houndeye\houndeye.dm" +#include "modular_skyrat\modules\black_mesa\code\mobs\houndeye\houndeye_ai.dm" +#include "modular_skyrat\modules\black_mesa\code\mobs\human_mobs\human_mobs.dm" +#include "modular_skyrat\modules\black_mesa\code\mobs\human_mobs\human_mobs_ai.dm" +#include "modular_skyrat\modules\black_mesa\code\mobs\nihilanth\nihilanth.dm" +#include "modular_skyrat\modules\black_mesa\code\mobs\nihilanth\nihilanth_ai.dm" +#include "modular_skyrat\modules\black_mesa\code\mobs\vortigaunt\vortigaunt.dm" +#include "modular_skyrat\modules\black_mesa\code\mobs\vortigaunt\vortigaunt_ai.dm" #include "modular_skyrat\modules\blastwave_outfits\code\cargo_packs.dm" #include "modular_skyrat\modules\blastwave_outfits\code\clothing\blastwave_head.dm" #include "modular_skyrat\modules\blastwave_outfits\code\clothing\blastwave_mask.dm"