From 0558482a8a7a6b2e90e0f1a42403e360421cc9f5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=BA=D1=82=D0=BE?=
<65656972+xDanilcusx@users.noreply.github.com>
Date: Mon, 19 Feb 2024 23:30:01 +0300
Subject: [PATCH] Facehugger AI & autodoc now can extract embryos (#17)
---
code/__DEFINES/xeno.dm | 2 +-
.../capture_override_behavior.dm | 3 +
code/game/machinery/medical_pod/autodoc.dm | 38 ++++++---
code/modules/admin/game_master/game_master.dm | 2 +-
.../game_master/game_master_submenu/ambush.dm | 2 +-
code/modules/cm_aliens/structures/egg.dm | 39 ++-------
.../living/carbon/xenomorph/Facehuggers.dm | 4 +-
.../mob/living/carbon/xenomorph/Xenomorph.dm | 2 +-
.../abilities/carrier/carrier_abilities.dm | 2 +
.../abilities/carrier/carrier_powers.dm | 7 ++
.../facehugger/facehugger_abilities.dm | 2 +
.../xenomorph/ai/movement/facehugger.dm | 81 +++++++++++++++++++
.../carbon/xenomorph/ai/movement/linger.dm | 6 +-
.../mob/living/carbon/xenomorph/ai/xeno_ai.dm | 4 +-
.../living/carbon/xenomorph/castes/Carrier.dm | 65 +++++++--------
.../carbon/xenomorph/castes/Facehugger.dm | 24 ++----
.../carbon/xenomorph/xeno_ai_interaction.dm | 40 +++++++--
.../living/carbon/xenomorph/xeno_defines.dm | 2 +-
colonialmarines.dme | 1 +
19 files changed, 218 insertions(+), 108 deletions(-)
create mode 100644 code/modules/mob/living/carbon/xenomorph/ai/movement/facehugger.dm
diff --git a/code/__DEFINES/xeno.dm b/code/__DEFINES/xeno.dm
index c77dac05fc..878a591357 100644
--- a/code/__DEFINES/xeno.dm
+++ b/code/__DEFINES/xeno.dm
@@ -220,7 +220,7 @@
#define XENO_EXPLOSIVE_ARMOR_TIER_10 100
// Health bands
-#define XENO_HEALTH_LARVA 35 * XENO_UNIVERSAL_HPMULT
+#define XENO_HEALTH_LARVA 45 * XENO_UNIVERSAL_HPMULT
#define XENO_HEALTH_LESSER_DRONE 160 * XENO_UNIVERSAL_HPMULT
#define XENO_HEALTH_RUNNER 230 * XENO_UNIVERSAL_HPMULT // Killed by 1 PB
#define XENO_HEALTH_TIER_1 250 * XENO_UNIVERSAL_HPMULT
diff --git a/code/datums/components/xeno/ai_behavior_overrides/capture_override_behavior.dm b/code/datums/components/xeno/ai_behavior_overrides/capture_override_behavior.dm
index 505dc6cdfa..96eaa7d8fb 100644
--- a/code/datums/components/xeno/ai_behavior_overrides/capture_override_behavior.dm
+++ b/code/datums/components/xeno/ai_behavior_overrides/capture_override_behavior.dm
@@ -19,6 +19,9 @@
if(!.)
return
+ if(isfacehugger(checked_xeno))
+ return FALSE
+
var/mob/parent_mob = parent
var/captee_stat = parent_mob.stat
diff --git a/code/game/machinery/medical_pod/autodoc.dm b/code/game/machinery/medical_pod/autodoc.dm
index 5b40f70bc4..6ac6da3def 100644
--- a/code/game/machinery/medical_pod/autodoc.dm
+++ b/code/game/machinery/medical_pod/autodoc.dm
@@ -248,6 +248,11 @@
if(H.disfigured)
surgery_list += create_autodoc_surgery(L,LIMB_SURGERY,"facial")
+ if(istype(L,/obj/limb/chest))
+ var/obj/limb/chest/C = L
+ if(M.status_flags & XENO_HOST)
+ surgery_list += create_autodoc_surgery(C,LIMB_SURGERY,"object")
+
if(L.status & LIMB_BROKEN)
surgery_list += create_autodoc_surgery(L,LIMB_SURGERY,"broken")
if(L.status & LIMB_DESTROYED)
@@ -256,7 +261,7 @@
if(L.implants.len)
for(var/I in L.implants)
if(!is_type_in_list(I,known_implants))
- surgery_list += create_autodoc_surgery(L,LIMB_SURGERY,"shrapnel")
+ surgery_list += create_autodoc_surgery(L,LIMB_SURGERY,"object")
if(M.incision_depths[L.name] != SURGERY_DEPTH_SURFACE)
surgery_list += create_autodoc_surgery(L,LIMB_SURGERY,"open")
var/datum/internal_organ/I = M.internal_organs_by_name["eyes"]
@@ -481,8 +486,8 @@
H.updatehealth()
H.UpdateDamageIcon()
- if("shrapnel")
- if(prob(30)) visible_message("[icon2html(src, viewers(src))] \The [src] speaks: Beginning shrapnel removal.");
+ if("object")
+ if(prob(30)) visible_message("[icon2html(src, viewers(src))] \The [src] speaks: Beginning foreign object removal.");
if(S.unneeded)
sleep(UNNEEDED_DELAY)
visible_message("[icon2html(src, viewers(src))] \The [src] speaks: Procedure has been deemed unnecessary.");
@@ -500,6 +505,11 @@
S.limb_ref.implants -= I
H.embedded_items -= I
qdel(I)
+ var/obj/item/larva_ref = locate(/obj/item/alien_embryo) in H.contents
+ if(S.limb_ref.name == "chest" && larva_ref)
+ sleep(REMOVE_OBJECT_MAX_DURATION*surgery_mod)
+ H.contents -= larva_ref
+ qdel(larva_ref)
if(S.limb_ref.name == "chest" || S.limb_ref.name == "head")
close_encased(H,S.limb_ref)
if(!surgery) break
@@ -716,9 +726,9 @@
if("missing")
surgeryqueue["missing"] = 1
dat += "Limb Replacement Surgery"
- if("shrapnel")
- surgeryqueue["shrapnel"] = 1
- dat += "Shrapnel Removal Surgery"
+ if("object")
+ surgeryqueue["object"] = 1
+ dat += "Foreign Object Removal Surgery"
if("facial")
surgeryqueue["facial"] = 1
dat += "Facial Reconstruction Surgery"
@@ -743,8 +753,8 @@
dat += "Internal Bleeding Surgery
"
if(isnull(surgeryqueue["open"]))
dat += "Close Open Incisions
"
- if(isnull(surgeryqueue["shrapnel"]))
- dat += "Shrapnel Removal Surgery
"
+ if(isnull(surgeryqueue["object"]))
+ dat += "Foreign Object Removal Surgery
"
dat += "Organ Surgeries"
dat += "
"
if(isnull(surgeryqueue["eyes"]))
@@ -852,17 +862,23 @@
N.fields["autodoc_manual"] += create_autodoc_surgery(null,LIMB_SURGERY,"missing",1)
updateUsrDialog()
- if(href_list["shrapnel"])
+ if(href_list["object"])
var/known_implants = list(/obj/item/implant/chem, /obj/item/implant/death_alarm, /obj/item/implant/loyalty, /obj/item/implant/tracking, /obj/item/implant/neurostim)
for(var/obj/limb/L in connected.occupant.limbs)
if(L)
+ if(istype(L,/obj/limb/chest))
+ var/obj/limb/chest/C = L
+ if(connected.occupant.status_flags & XENO_HOST)
+ N.fields["autodoc_manual"] += create_autodoc_surgery(C,LIMB_SURGERY,"object")
+ needed++
+ continue
if(L.implants.len)
for(var/I in L.implants)
if(!is_type_in_list(I,known_implants))
- N.fields["autodoc_manual"] += create_autodoc_surgery(L,LIMB_SURGERY,"shrapnel")
+ N.fields["autodoc_manual"] += create_autodoc_surgery(L,LIMB_SURGERY,"object")
needed++
if(!needed)
- N.fields["autodoc_manual"] += create_autodoc_surgery(null,LIMB_SURGERY,"shrapnel",1)
+ N.fields["autodoc_manual"] += create_autodoc_surgery(null,LIMB_SURGERY,"object",1)
updateUsrDialog()
if(href_list["facial"])
diff --git a/code/modules/admin/game_master/game_master.dm b/code/modules/admin/game_master/game_master.dm
index 0ec9ee7e3e..b3502c3322 100644
--- a/code/modules/admin/game_master/game_master.dm
+++ b/code/modules/admin/game_master/game_master.dm
@@ -29,7 +29,7 @@ GLOBAL_VAR_INIT(radio_communication_clarity, 100)
// Spawn stuff
#define DEFAULT_SPAWN_XENO_STRING XENO_CASTE_DRONE
-#define GAME_MASTER_AI_XENOS list(XENO_CASTE_DRONE, XENO_CASTE_RUNNER, XENO_CASTE_LURKER, XENO_CASTE_CRUSHER)
+#define GAME_MASTER_AI_XENOS list(XENO_CASTE_DRONE, XENO_CASTE_RUNNER, XENO_CASTE_LURKER, XENO_CASTE_CRUSHER, XENO_CASTE_FACEHUGGER)
#define DEFAULT_SPAWN_HIVE_STRING XENO_HIVE_NORMAL
#define DEFAULT_XENO_AMOUNT_TO_SPAWN 1
diff --git a/code/modules/admin/game_master/game_master_submenu/ambush.dm b/code/modules/admin/game_master/game_master_submenu/ambush.dm
index 80709d99cf..2af5bd0f00 100644
--- a/code/modules/admin/game_master/game_master_submenu/ambush.dm
+++ b/code/modules/admin/game_master/game_master_submenu/ambush.dm
@@ -1,6 +1,6 @@
#define DEFAULT_SPAWN_XENO_STRING XENO_CASTE_DRONE
-#define GAME_MASTER_AMBUSH_AI_XENOS list(XENO_CASTE_DRONE, XENO_CASTE_RUNNER, XENO_CASTE_LURKER)
+#define GAME_MASTER_AMBUSH_AI_XENOS list(XENO_CASTE_DRONE, XENO_CASTE_RUNNER, XENO_CASTE_LURKER, XENO_CASTE_FACEHUGGER)
#define DEFAULT_SPAWN_HIVE_STRING XENO_HIVE_NORMAL
#define DEFAULT_XENO_AMOUNT_TO_SPAWN 1
diff --git a/code/modules/cm_aliens/structures/egg.dm b/code/modules/cm_aliens/structures/egg.dm
index c23f4f3e2b..9a5bc8b932 100644
--- a/code/modules/cm_aliens/structures/egg.dm
+++ b/code/modules/cm_aliens/structures/egg.dm
@@ -8,7 +8,7 @@
icon_state = "Egg Growing"
density = FALSE
anchored = TRUE
- layer = LYING_BETWEEN_MOB_LAYER //to stop hiding eggs under corpses
+ layer = RESIN_STRUCTURE_LAYER //so facehuggers will be above the eggs
health = 80
plane = GAME_PLANE
var/list/egg_triggers = list()
@@ -16,10 +16,10 @@
var/on_fire = FALSE
var/hivenumber = XENO_HIVE_NORMAL
var/flags_embryo = NO_FLAGS
+ var/trigger_radius = 2
/obj/effect/alien/egg/Initialize(mapload, hive)
. = ..()
- create_egg_triggers()
if (hive)
hivenumber = hive
@@ -93,21 +93,10 @@
update_icon()
deploy_egg_triggers()
-/obj/effect/alien/egg/proc/create_egg_triggers()
- for(var/i in 1 to 8)
- egg_triggers += new /obj/effect/egg_trigger(src, src)
-
/obj/effect/alien/egg/proc/deploy_egg_triggers()
- var/i = 1
- var/x_coords = list(-1,-1,-1,0,0,1,1,1)
- var/y_coords = list(1,0,-1,1,-1,1,0,-1)
- var/turf/target_turf
- for(var/trigger in egg_triggers)
- var/obj/effect/egg_trigger/ET = trigger
- target_turf = locate(x+x_coords[i],y+y_coords[i], z)
- if(target_turf)
- ET.forceMove(target_turf)
- i++
+ var/list/observed_turfs = orange(trigger_radius, src)
+ for(var/target_turf in observed_turfs)
+ egg_triggers += new /obj/effect/egg_trigger(target_turf, src)
/obj/effect/alien/egg/proc/hide_egg_triggers()
for(var/trigger in egg_triggers)
@@ -136,20 +125,8 @@
status = EGG_BURST
if(is_hugger_player_controlled)
return //Don't need to spawn a hugger, a player controls it already!
- var/obj/item/clothing/mask/facehugger/child = new(loc, hivenumber)
-
- child.flags_embryo = flags_embryo
- flags_embryo = NO_FLAGS // Lose the embryo flags when passed on
- if(X && X.caste.can_hold_facehuggers && (!X.l_hand || !X.r_hand)) //sanity checks
- X.put_in_hands(child)
- return
-
- if(instant_trigger)
- if(!child.leap_at_nearest_target())
- child.return_to_egg(src)
- else
- child.go_idle()
+ new /mob/living/carbon/xenomorph/facehugger(loc, null, hivenumber)
/obj/effect/alien/egg/bullet_act(obj/projectile/P)
..()
@@ -286,9 +263,9 @@
/obj/effect/egg_trigger/Crossed(atom/movable/AM)
if(!linked_egg && !linked_eggmorph) //something went very wrong.
qdel(src)
- else if(linked_egg && (get_dist(src, linked_egg) != 1 || !isturf(linked_egg.loc))) //something went wrong
+ else if(linked_egg && (get_dist(src, linked_egg) > linked_egg.trigger_radius || !isturf(linked_egg.loc))) //something went wrong
forceMove(linked_egg)
- else if(linked_eggmorph && (get_dist(src, linked_eggmorph) != 1 || !isturf(linked_eggmorph.loc))) //something went wrong
+ else if(linked_eggmorph && (get_dist(src, linked_eggmorph) > linked_egg.trigger_radius || !isturf(linked_eggmorph.loc))) //something went wrong
forceMove(linked_eggmorph)
else if(iscarbon(AM))
var/mob/living/carbon/C = AM
diff --git a/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm b/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm
index 18bb56b632..5524adbde1 100644
--- a/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm
+++ b/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm
@@ -1,5 +1,5 @@
-#define MIN_IMPREGNATION_TIME 10 SECONDS //Time it takes to impregnate someone
-#define MAX_IMPREGNATION_TIME 15 SECONDS
+#define MIN_IMPREGNATION_TIME 30 SECONDS //Time it takes to impregnate someone
+#define MAX_IMPREGNATION_TIME 40 SECONDS
#define MIN_ACTIVE_TIME 5 SECONDS //Time between being dropped and going idle
#define MAX_ACTIVE_TIME 15 SECONDS
diff --git a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm
index c916bef3da..078ad96536 100644
--- a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm
+++ b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm
@@ -1107,7 +1107,7 @@
to_chat(src, SPAN_WARNING("[current_airlock] is locked down tight. You can't squeeze underneath!"))
return FALSE
visible_message(SPAN_WARNING("[src] scuttles underneath [current_structure]!"), \
- SPAN_WARNING("You squeeze and scuttle underneath [current_structure]."), max_distance = 5)
+ SPAN_WARNING("You squeeze and scuttle underneath [current_structure]."), max_distance = 2)
forceMove(current_structure.loc)
return TRUE
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/carrier/carrier_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/carrier/carrier_abilities.dm
index 566fe0eaea..cbc2163dfe 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/carrier/carrier_abilities.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/carrier/carrier_abilities.dm
@@ -6,6 +6,8 @@
action_type = XENO_ACTION_CLICK
ability_primacy = XENO_PRIMARY_ACTION_3
+ default_ai_action = TRUE
+
/datum/action/xeno_action/activable/throw_hugger/action_cooldown_check()
if(owner)
var/mob/living/carbon/xenomorph/carrier/X = owner
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/carrier/carrier_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/carrier/carrier_powers.dm
index 1ee32225a4..da85b94502 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/carrier/carrier_powers.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/carrier/carrier_powers.dm
@@ -3,6 +3,13 @@
X.throw_hugger(A)
return ..()
+/datum/action/xeno_action/activable/throw_hugger/process_ai(mob/living/carbon/xenomorph/X, delta_time)
+ var/distance = get_dist(X, X.current_target)
+ if(!DT_PROB(ai_prob_chance, delta_time) || distance < 3 || distance > 8)
+ return
+
+ use_ability_async(X.current_target)
+
/datum/action/xeno_action/activable/retrieve_egg/use_ability(atom/A)
var/mob/living/carbon/xenomorph/carrier/X = owner
X.retrieve_egg(A)
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_abilities.dm
index 91bda707ec..ccb31820db 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_abilities.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_abilities.dm
@@ -17,3 +17,5 @@
freeze_time = 5
freeze_play_sound = FALSE
can_be_shield_blocked = TRUE
+
+ ai_prob_chance = 45
diff --git a/code/modules/mob/living/carbon/xenomorph/ai/movement/facehugger.dm b/code/modules/mob/living/carbon/xenomorph/ai/movement/facehugger.dm
new file mode 100644
index 0000000000..f82a55026c
--- /dev/null
+++ b/code/modules/mob/living/carbon/xenomorph/ai/movement/facehugger.dm
@@ -0,0 +1,81 @@
+/datum/xeno_ai_movement/linger/facehugger
+ linger_range = 8
+ reengage_interval = (12 SECONDS)
+
+/datum/xeno_ai_movement/linger/facehugger/New(mob/living/carbon/xenomorph/parent)
+ . = ..()
+ var/turf/current_turf = get_turf(parent)
+ if(. != INITIALIZE_HINT_QDEL && (locate(/obj/effect/alien/egg) in current_turf))
+ home_turf = current_turf
+
+/datum/xeno_ai_movement/linger/facehugger/ai_move_idle(delta_time)
+ var/mob/living/carbon/xenomorph/facehugger/idle_xeno = parent
+ if(idle_xeno.throwing)
+ return
+
+ if(next_home_search < world.time && (!home_turf || !valid_shelter(home_turf) || get_dist(home_turf, idle_xeno) > max_distance_from_home))
+ var/turf/T = get_turf(idle_xeno.loc)
+ next_home_search = world.time + home_search_delay
+ if(valid_shelter(T))
+ home_turf = T
+ else
+ var/shortest_distance = INFINITY
+ for(var/i in RANGE_TURFS(home_locate_range, T))
+ var/turf/potential_home = i
+ if(valid_shelter(potential_home) && get_dist(idle_xeno, potential_home) < shortest_distance)
+ home_turf = potential_home
+ shortest_distance = get_dist(idle_xeno, potential_home)
+
+ if(!home_turf)
+ return
+
+ if(idle_xeno.move_to_next_turf(home_turf, home_locate_range))
+ var/shelter = valid_shelter(home_turf)
+ if(get_dist(home_turf, idle_xeno) <= 0 && shelter)
+ idle_xeno.climb_in(shelter)
+ return
+ home_turf = null
+
+/datum/xeno_ai_movement/linger/facehugger/proc/valid_shelter(turf/checked_turf)
+ var/mob/living/carbon/xenomorph/carrier/carrier = locate(/mob/living/carbon/xenomorph/carrier) in checked_turf
+ if(carrier && carrier.huggers_cur < carrier.huggers_max)
+ return carrier
+ var/obj/effect/alien/egg/eggy = locate(/obj/effect/alien/egg) in checked_turf
+ if(eggy && eggy.status == EGG_BURST)
+ return eggy
+
+/datum/xeno_ai_movement/linger/facehugger/ai_move_target(delta_time)
+ var/mob/living/carbon/xenomorph/moving_xeno = parent
+
+ if(moving_xeno.action_busy)
+ return
+
+ return ..()
+
+/mob/living/carbon/xenomorph/facehugger/proc/climb_in(shelter)
+ set waitfor = FALSE
+
+ if(!do_after(src, 1 SECONDS, INTERRUPT_ALL, BUSY_ICON_GENERIC, shelter, INTERRUPT_NONE))
+ return
+
+ if(!shelter)
+ return
+
+ if(iscarrier(shelter))
+ var/mob/living/carbon/xenomorph/carrier/horsey = shelter
+ if(horsey.huggers_cur >= horsey.huggers_max)
+ return
+ visible_message(SPAN_XENOWARNING("[src] crawls onto [horsey]!"))
+ horsey.huggers_cur = min(horsey.huggers_max, horsey.huggers_cur + 1)
+ horsey.update_hugger_overlays()
+
+ else
+ var/obj/effect/alien/egg/eggy = shelter
+ if(eggy.status != EGG_BURST)
+ return
+ visible_message(SPAN_XENOWARNING("[src] crawls back into [eggy]!"))
+ eggy.status = EGG_GROWN
+ eggy.icon_state = "Egg"
+ eggy.deploy_egg_triggers()
+
+ qdel(src)
diff --git a/code/modules/mob/living/carbon/xenomorph/ai/movement/linger.dm b/code/modules/mob/living/carbon/xenomorph/ai/movement/linger.dm
index bbaebfda6e..1dce132e6e 100644
--- a/code/modules/mob/living/carbon/xenomorph/ai/movement/linger.dm
+++ b/code/modules/mob/living/carbon/xenomorph/ai/movement/linger.dm
@@ -12,6 +12,8 @@
/// The cooldown for how long the xeno will wait out of view before attempting to re-engage
COOLDOWN_DECLARE(reengage_cooldown)
+ var/reengage_interval = (2 SECONDS)
+
/datum/xeno_ai_movement/linger/ai_move_target(delta_time)
var/mob/living/carbon/xenomorph/moving_xeno = parent
if(moving_xeno.throwing)
@@ -32,13 +34,12 @@
travelling_turf = get_turf(moving_xeno.current_target)
return TRUE
-#define REENGAGE_COOLDOWN (2 SECONDS)
#define FIND_NEW_TRAVEL_TURF_LIMIT 5
/datum/xeno_ai_movement/linger/proc/check_for_travelling_turf_change(mob/living/carbon/xenomorph/moving_xeno)
if(!(moving_xeno in view(world.view, moving_xeno.current_target)) && COOLDOWN_FINISHED(src, reengage_cooldown))
travelling_turf = get_turf(moving_xeno.current_target)
- COOLDOWN_START(src, reengage_cooldown, REENGAGE_COOLDOWN)
+ COOLDOWN_START(src, reengage_cooldown, reengage_interval)
moving_xeno.emote("growl")
return
@@ -59,5 +60,4 @@
travelling_turf = get_turf(moving_xeno.current_target)
return
-#undef REENGAGE_COOLDOWN
#undef FIND_NEW_TRAVEL_TURF_LIMIT
diff --git a/code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm b/code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm
index b10bc7f7f1..2c522b58d2 100644
--- a/code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm
+++ b/code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm
@@ -250,7 +250,7 @@
var/smallest_distance = INFINITY
for(var/mob/living/carbon/potential_target as anything in GLOB.alive_mob_list)
- if(!iscarbon(potential_target))
+ if(!istype(potential_target))
continue
if(z != potential_target.z)
@@ -339,7 +339,7 @@
#undef EXTRA_CHECK_DISTANCE_MULTIPLIER
/mob/living/carbon/proc/ai_can_target(mob/living/carbon/xenomorph/ai_xeno)
- if(!ai_check_stat())
+ if(!ai_check_stat(ai_xeno))
return FALSE
if(ai_xeno.can_not_harm(src))
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm b/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm
index 07f161f4c6..0b5388fa6c 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm
@@ -17,7 +17,7 @@
evolution_allowed = FALSE
deevolves_to = list(XENO_CASTE_DRONE)
- throwspeed = SPEED_AVERAGE
+ throwspeed = SPEED_FAST
can_hold_facehuggers = 1
can_hold_eggs = CAN_HOLD_ONE_HAND
weed_level = WEED_LEVEL_STANDARD
@@ -33,7 +33,7 @@
tacklestrength_max = 5
aura_strength = 2
- hugger_delay = 20
+ hugger_delay = 10
egg_cooldown = 250
minimum_evolve_time = 5 MINUTES
@@ -176,6 +176,8 @@
. = ..()
hugger_overlays_icon = mutable_appearance('icons/mob/xenos/overlay_effects64x64.dmi',"empty")
eggsac_overlays_icon = mutable_appearance('icons/mob/xenos/overlay_effects64x64.dmi',"empty")
+ huggers_cur = rand(3,16)
+ update_hugger_overlays()
/mob/living/carbon/xenomorph/carrier/death(cause, gibbed)
. = ..(cause, gibbed)
@@ -187,11 +189,11 @@
if(huggers_cur)
//Hugger explosion, like an egg morpher
- var/obj/item/clothing/mask/facehugger/hugger
+ var/mob/living/carbon/xenomorph/facehugger/hugger
visible_message(SPAN_XENOWARNING("The chittering mass of tiny aliens is trying to escape [src]!"))
for(var/i in 1 to huggers_cur)
if(prob(chance))
- hugger = new(loc, hivenumber)
+ hugger = new(loc, null, hivenumber)
step_away(hugger, src, 1)
var/eggs_dropped = FALSE
@@ -255,6 +257,8 @@
if(!check_state())
return
+ face_atom(T)
+
//target a hugger on the ground to store it directly
if(istype(T, /obj/item/clothing/mask/facehugger))
var/obj/item/clothing/mask/facehugger/F = T
@@ -282,42 +286,39 @@
store_huggers_from_egg_morpher(morpher)
return
- var/obj/item/clothing/mask/facehugger/F = get_active_hand()
- if(!F) //empty active hand
- //if no hugger in active hand, we take one from our storage
- if(huggers_cur <= 0)
- to_chat(src, SPAN_WARNING("You don't have any facehuggers to use!"))
- return
-
- if(on_fire)
- to_chat(src, SPAN_WARNING("Retrieving a stored facehugger while you're on fire would burn it!"))
- return
+ //We throw a hugger from our storage
+ if(huggers_cur <= 0)
+ to_chat(src, SPAN_WARNING("You don't have any facehuggers to use!"))
+ return
- F = new(src, hivenumber)
- huggers_cur--
- put_in_active_hand(F)
- to_chat(src, SPAN_XENONOTICE("You grab one of the facehugger in your storage. Now sheltering: [huggers_cur] / [huggers_max]."))
- update_icons()
+ if(threw_a_hugger)
return
- if(!istype(F)) //something else in our hand
- to_chat(src, SPAN_WARNING("You need a facehugger in your hand to throw one!"))
+ threw_a_hugger = TRUE
+ for(var/X in actions)
+ var/datum/action/A = X
+ A.update_button_icon()
+
+ if(!do_after(src, 1 SECONDS, INTERRUPT_INCAPACITATED, BUSY_ICON_HOSTILE))
return
- if(!threw_a_hugger)
- threw_a_hugger = TRUE
+ var/turf/target_turf = get_turf(T)
+ var/mob/living/carbon/xenomorph/facehugger/child = new(loc, null, hivenumber)
+
+ huggers_cur--
+ update_icons()
+
+ visible_message(SPAN_XENOWARNING("\The [src] throws something towards \the [target_turf]!"), \
+ SPAN_XENOWARNING("You throw a facehugger towards \the [target_turf]!"))
+
+ playsound(loc, get_sfx("alien_tail_swipe"), 120, 1)
+ child.throw_atom(target_turf, 6, caste.throwspeed)
+
+ spawn(caste.hugger_delay)
+ threw_a_hugger = 0
for(var/X in actions)
var/datum/action/A = X
A.update_button_icon()
- drop_inv_item_on_ground(F)
- F.throw_atom(T, 4, caste.throwspeed)
- visible_message(SPAN_XENOWARNING("\The [src] throws something towards \the [T]!"), \
- SPAN_XENOWARNING("You throw a facehugger towards \the [T]!"))
- spawn(caste.hugger_delay)
- threw_a_hugger = 0
- for(var/X in actions)
- var/datum/action/A = X
- A.update_button_icon()
/mob/living/carbon/xenomorph/carrier/proc/store_egg(obj/item/xeno_egg/E)
if(E.hivenumber != hivenumber)
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm b/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm
index 16ce2ed9d5..71448bc7af 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm
@@ -7,7 +7,7 @@
melee_damage_upper = 5
max_health = XENO_HEALTH_LARVA
caste_desc = "Ewwww, that's disgusting!"
- speed = XENO_SPEED_TIER_8
+ speed = XENO_SPEED_TIER_10
evolution_allowed = FALSE
can_be_revived = FALSE
@@ -24,12 +24,12 @@
pixel_y = -6
old_x = -8
old_y = -6
- layer = MOB_LAYER
+ layer = XENO_HIDING_LAYER
mob_flags = NOBIOSCAN
see_in_dark = 8
tier = 0 //Facehuggers don't count towards Pop limits
acid_blood_damage = 5
- crit_health = 0
+ crit_health = -25
crit_grace_time = 0
mob_size = MOB_SIZE_SMALL
death_fontsize = 2
@@ -55,29 +55,21 @@
/mob/living/carbon/xenomorph/proc/vent_crawl,
)
mutation_type = "Normal"
+ claw_type = 0 // No claws at all
icon_xeno = 'icons/mob/xenos/facehugger.dmi'
icon_xenonid = 'icons/mob/xenonids/facehugger.dmi'
+ ai_range = 24
+
/mob/living/carbon/xenomorph/facehugger/initialize_pass_flags(datum/pass_flags_container/PF)
..()
if (PF)
PF.flags_pass = PASS_MOB_THRU|PASS_FLAGS_CRAWLER
PF.flags_can_pass_all = PASS_ALL^PASS_OVER_THROW_ITEM
-/mob/living/carbon/xenomorph/facehugger/Life(delta_time)
- if(stat == DEAD)
- return ..()
-
- if(body_position == STANDING_UP && !(mutation_type == FACEHUGGER_WATCHER) && !(locate(/obj/effect/alien/weeds) in get_turf(src)))
- adjustBruteLoss(1)
- return ..()
-
- if(!client && !aghosted && away_timer > XENO_FACEHUGGER_LEAVE_TIMER)
- // Become a npc once again
- new /obj/item/clothing/mask/facehugger(loc, hivenumber)
- qdel(src)
- return ..()
+/mob/living/carbon/xenomorph/facehugger/init_movement_handler()
+ return new /datum/xeno_ai_movement/linger/facehugger(src)
/mob/living/carbon/xenomorph/facehugger/update_icons(is_pouncing)
if(!caste)
diff --git a/code/modules/mob/living/carbon/xenomorph/xeno_ai_interaction.dm b/code/modules/mob/living/carbon/xenomorph/xeno_ai_interaction.dm
index 5d36c4efde..5aaf3b0684 100644
--- a/code/modules/mob/living/carbon/xenomorph/xeno_ai_interaction.dm
+++ b/code/modules/mob/living/carbon/xenomorph/xeno_ai_interaction.dm
@@ -27,7 +27,7 @@ At bare minimum, make sure the relevant checks from parent types gets copied in
if(!density)
return 0
- if(unslashable && !climbable)
+ if((unslashable || isfacehugger(X)) && !climbable)
return
return OBJECT_PENALTY
@@ -106,13 +106,26 @@ At bare minimum, make sure the relevant checks from parent types gets copied in
if(locked || welded || isElectrified())
return INFINITY
+ if(isfacehugger(X))
+ return -1 // We LOVE going under doors!
+
return DOOR_PENALTY
+/////////////////////////////
+// TABLES //
+/////////////////////////////
+/obj/structure/surface/table/xeno_ai_obstacle(mob/living/carbon/xenomorph/X, direction, turf/target)
+ . = ..()
+ if(isfacehugger(X))
+ return -1 // We also love to skiddle under the tables!
+
+ return
+
/////////////////////////////
// MOBS //
/////////////////////////////
-/mob/living/ai_check_stat()
+/mob/living/ai_check_stat(mob/living/carbon/xenomorph/X)
return stat == CONSCIOUS
@@ -134,7 +147,7 @@ At bare minimum, make sure the relevant checks from parent types gets copied in
. = ..()
-/mob/living/carbon/human/ai_can_target(mob/living/carbon/xenomorph/ai_xeno)
+/mob/living/carbon/human/ai_can_target(mob/living/carbon/xenomorph/X)
. = ..()
if(!.)
return FALSE
@@ -145,8 +158,20 @@ At bare minimum, make sure the relevant checks from parent types gets copied in
if(HAS_TRAIT(src, TRAIT_NESTED))
return FALSE
+ if(isfacehugger(X))
+ if(status_flags & XENO_HOST)
+ return FALSE
+
+ if(istype(wear_mask, /obj/item/clothing/mask/facehugger))
+ return FALSE
+
return TRUE
+/mob/living/carbon/human/ai_check_stat(mob/living/carbon/xenomorph/X)
+ . = ..()
+ if(isfacehugger(X))
+ return stat != DEAD
+
/////////////////////////////
// XENOS //
@@ -167,17 +192,20 @@ At bare minimum, make sure the relevant checks from parent types gets copied in
. = ..()
-/mob/living/carbon/xenomorph/ai_can_target(mob/living/carbon/xenomorph/ai_xeno)
+/mob/living/carbon/xenomorph/ai_can_target(mob/living/carbon/xenomorph/X)
. = ..()
if(!.)
return FALSE
- if(IS_SAME_HIVENUMBER(ai_xeno, src))
+ if(isfacehugger(X))
+ return FALSE
+
+ if(IS_SAME_HIVENUMBER(X, src))
return FALSE
return TRUE
-/mob/living/carbon/xenomorph/ai_check_stat()
+/mob/living/carbon/xenomorph/ai_check_stat(mob/living/carbon/xenomorph/X)
return stat != DEAD // Should slash enemy xenos, even if they are critted out
diff --git a/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm b/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm
index 1d286adf3b..1369b1fa53 100644
--- a/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm
+++ b/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm
@@ -282,7 +282,7 @@
var/datum/mutator_set/hive_mutators/mutators = new
var/tier_slot_multiplier = 1
- var/larva_gestation_multiplier = 1
+ var/larva_gestation_multiplier = 0.25
var/bonus_larva_spawn_chance = 1
var/hijack_burrowed_surge = FALSE //at hijack, start spawning lots of burrowed
/// how many burrowed is going to spawn during larva surge
diff --git a/colonialmarines.dme b/colonialmarines.dme
index f3f5d9a012..276767ade3 100644
--- a/colonialmarines.dme
+++ b/colonialmarines.dme
@@ -2021,6 +2021,7 @@
#include "code\modules\mob\living\carbon\xenomorph\ai\movement\base_define.dm"
#include "code\modules\mob\living\carbon\xenomorph\ai\movement\crusher.dm"
#include "code\modules\mob\living\carbon\xenomorph\ai\movement\drone.dm"
+#include "code\modules\mob\living\carbon\xenomorph\ai\movement\facehugger.dm"
#include "code\modules\mob\living\carbon\xenomorph\ai\movement\linger.dm"
#include "code\modules\mob\living\carbon\xenomorph\ai\movement\lurking.dm"
#include "code\modules\mob\living\carbon\xenomorph\castes\Boiler.dm"