Skip to content

Commit

Permalink
Facehugger AI & autodoc now can extract embryos (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
xDanilcusx authored Feb 19, 2024
1 parent a078ba0 commit 0558482
Show file tree
Hide file tree
Showing 19 changed files with 218 additions and 108 deletions.
2 changes: 1 addition & 1 deletion code/__DEFINES/xeno.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
if(!.)
return

if(isfacehugger(checked_xeno))
return FALSE

var/mob/parent_mob = parent

var/captee_stat = parent_mob.stat
Expand Down
38 changes: 27 additions & 11 deletions code/game/machinery/medical_pod/autodoc.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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"]
Expand Down Expand Up @@ -481,8 +486,8 @@
H.updatehealth()
H.UpdateDamageIcon()

if("shrapnel")
if(prob(30)) visible_message("[icon2html(src, viewers(src))] \The <b>[src]</b> speaks: Beginning shrapnel removal.");
if("object")
if(prob(30)) visible_message("[icon2html(src, viewers(src))] \The <b>[src]</b> speaks: Beginning foreign object removal.");
if(S.unneeded)
sleep(UNNEEDED_DELAY)
visible_message("[icon2html(src, viewers(src))] \The <b>[src]</b> speaks: Procedure has been deemed unnecessary.");
Expand All @@ -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
Expand Down Expand Up @@ -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"
Expand All @@ -743,8 +753,8 @@
dat += "<a href='?src=\ref[src];internal=1'>Internal Bleeding Surgery</a><br>"
if(isnull(surgeryqueue["open"]))
dat += "<a href='?src=\ref[src];open=1'>Close Open Incisions</a><br>"
if(isnull(surgeryqueue["shrapnel"]))
dat += "<a href='?src=\ref[src];shrapnel=1'>Shrapnel Removal Surgery</a><br>"
if(isnull(surgeryqueue["object"]))
dat += "<a href='?src=\ref[src];object=1'>Foreign Object Removal Surgery</a><br>"
dat += "<b>Organ Surgeries</b>"
dat += "<br>"
if(isnull(surgeryqueue["eyes"]))
Expand Down Expand Up @@ -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"])
Expand Down
2 changes: 1 addition & 1 deletion code/modules/admin/game_master/game_master.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
39 changes: 8 additions & 31 deletions code/modules/cm_aliens/structures/egg.dm
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@
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()
var/status = EGG_GROWING //can be EGG_GROWING, EGG_GROWN, EGG_BURST, EGG_BURSTING, or EGG_DESTROYED; all mutually exclusive
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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
..()
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions code/modules/mob/living/carbon/xenomorph/Facehuggers.dm
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion code/modules/mob/living/carbon/xenomorph/Xenomorph.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@
freeze_time = 5
freeze_play_sound = FALSE
can_be_shield_blocked = TRUE

ai_prob_chance = 45
81 changes: 81 additions & 0 deletions code/modules/mob/living/carbon/xenomorph/ai/movement/facehugger.dm
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

Expand All @@ -59,5 +60,4 @@
travelling_turf = get_turf(moving_xeno.current_target)
return

#undef REENGAGE_COOLDOWN
#undef FIND_NEW_TRAVEL_TURF_LIMIT
4 changes: 2 additions & 2 deletions code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Expand Down
Loading

0 comments on commit 0558482

Please sign in to comment.