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"