From 6763d8c57bc0b016145dbcd9e4cbe9c36d9f53ad Mon Sep 17 00:00:00 2001 From: Morrow Date: Sat, 28 Oct 2023 21:36:49 -0400 Subject: [PATCH 1/3] woops! .stat moment --- code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8ffe258ca7..d0dedab470 100644 --- a/code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm +++ b/code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm @@ -67,7 +67,7 @@ GLOBAL_LIST_INIT(ai_target_limbs, list( var/stat_check = FALSE if(istype(current_target, /mob)) var/mob/current_target_mob = current_target - stat_check = (current_target_mob != CONSCIOUS) + stat_check = (current_target_mob.stat != CONSCIOUS) if(QDELETED(current_target) || stat_check || get_dist(current_target, src) > ai_range) current_target = get_target(ai_range) From 177ca6b0f053712dc60929e5cc96838e8c36bac0 Mon Sep 17 00:00:00 2001 From: morrowwolf Date: Sun, 29 Oct 2023 01:23:56 -0400 Subject: [PATCH 2/3] More complicated but hopefully better target acquisition (#34) --- code/__DEFINES/xeno_ai.dm | 39 ------ code/_globalvars/lists/object_lists.dm | 2 +- code/modules/defenses/defenses.dm | 6 +- .../mob/living/carbon/xenomorph/ai/xeno_ai.dm | 113 ++++++++++++------ 4 files changed, 81 insertions(+), 79 deletions(-) diff --git a/code/__DEFINES/xeno_ai.dm b/code/__DEFINES/xeno_ai.dm index defe61de11..1b4006d507 100644 --- a/code/__DEFINES/xeno_ai.dm +++ b/code/__DEFINES/xeno_ai.dm @@ -15,10 +15,6 @@ #define WALL_PENALTY 50 #define FIRE_PENALTY 25 -// Xeno AI flags -#define XENO_AI_NO_DESPAWN (1<<0) -#define XENO_AI_CHOOSE_RANDOM_STRAIN (1<<1) - /* PROBABILITY CALCULATIONS ARE HERE */ @@ -86,41 +82,6 @@ PROBABILITY CALCULATIONS ARE HERE #define PRAETORIAN_SPIT 80 #define PRAETORIAN_SPRAY 80 -/* - GAME DIRECTOR AI -*/ - -/datum/config_entry/number/ai_director - abstract_type = /datum/config_entry/number/ai_director - -#define IDEAL_T2_PERCENT 0.5 -#define IDEAL_T3_PERCENT 0.25 - -/// The maximum amount of xenomorphs that can spawn, scaled up by population. -/datum/config_entry/number/ai_director/max_xeno_per_player - config_entry_value = 1 - -// Xenos spawn -/datum/config_entry/number/ai_director/t2_spawn_at_percentage - config_entry_value = 0.25 - -/datum/config_entry/number/ai_director/t3_spawn_at_percentage - config_entry_value = 0.5 - -/// The minimum range at which a xeno can be spawned from a human -#define MIN_RANGE_TO_SPAWN_XENO 10 -/// The maximum range at which a xeno can be spawned from a human -#define MAX_RANGE_TO_SPAWN_XENO 25 - -/// When a xeno gets despawned if there is no human within a specific range. -#define RANGE_TO_DESPAWN_XENO 25 -/// When a xeno gets despawned if they can't find a target within a specific amount of time. -#define XENO_DESPAWN_NO_TARGET_PERIOD 200 SECONDS - -// Director flags -#define XENO_SPAWN_T1 (1<<0) -#define XENO_SPAWN_T2 (1<<1) -#define XENO_SPAWN_T3 (1<<2) /// Special blockers for pathfinding or obstacle handling #define XENO_AI_SPECIAL_BLOCKERS list(/obj/flamer_fire, /obj/vehicle/multitile) diff --git a/code/_globalvars/lists/object_lists.dm b/code/_globalvars/lists/object_lists.dm index a224d0761b..0bbd63cdcf 100644 --- a/code/_globalvars/lists/object_lists.dm +++ b/code/_globalvars/lists/object_lists.dm @@ -28,7 +28,7 @@ GLOBAL_LIST_EMPTY_TYPED(hijack_deletable_windows, /obj/structure/machinery/door/ GLOBAL_LIST_EMPTY_TYPED(hijack_bustable_ladders, /obj/structure/ladder/fragile_almayer) GLOBAL_LIST_EMPTY_TYPED(all_multi_vehicles, /obj/vehicle/multitile) -GLOBAL_LIST_EMPTY_TYPED(all_defenses, /obj/structure/machinery/defenses) +GLOBAL_LIST_EMPTY_TYPED(all_active_defenses, /obj/structure/machinery/defenses) GLOBAL_LIST_EMPTY_TYPED(lifeboat_almayer_docks, /obj/docking_port/stationary/lifeboat_dock) GLOBAL_LIST_EMPTY_TYPED(lifeboat_doors, /obj/structure/machinery/door/airlock/multi_tile/almayer/dropshiprear/lifeboat/blastdoor) diff --git a/code/modules/defenses/defenses.dm b/code/modules/defenses/defenses.dm index a43af36097..cf1ff5a278 100644 --- a/code/modules/defenses/defenses.dm +++ b/code/modules/defenses/defenses.dm @@ -101,14 +101,14 @@ power_on_action() update_icon() - GLOB.all_defenses += src + GLOB.all_active_defenses += src /obj/structure/machinery/defenses/proc/power_off() turned_on = FALSE power_off_action() update_icon() - GLOB.all_defenses -= src + GLOB.all_active_defenses -= src /** * Update state category for this structure. @@ -472,7 +472,7 @@ return /obj/structure/machinery/defenses/Destroy() - GLOB.all_defenses -= src + GLOB.all_active_defenses -= src if(owner_mob) owner_mob = null 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 d0dedab470..e6f1c24586 100644 --- a/code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm +++ b/code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm @@ -1,6 +1,5 @@ /mob/living/carbon/xenomorph // AI stuff - var/flags_ai = XENO_AI_NO_DESPAWN var/atom/movable/current_target var/next_path_generation = 0 @@ -206,61 +205,103 @@ GLOBAL_LIST_INIT(ai_target_limbs, list( return TRUE +#define EXTRA_CHECK_DISTANCE_MULTIPLIER 0.20 + /mob/living/carbon/xenomorph/proc/get_target(range) - var/list/viable_humans = list() - var/list/viable_vehicles = list() - var/list/viable_defenses = list() + var/list/viable_targets = list() + var/atom/movable/closest_target var/smallest_distance = INFINITY - for(var/mob/living/carbon/human/alive_human as anything in GLOB.alive_human_list) - if(alive_human.species.flags & IS_SYNTHETIC) + for(var/mob/living/carbon/human/potential_alive_human_target as anything in GLOB.alive_human_list) + if(z != potential_alive_human_target.z) continue - if(z != alive_human.z) + if(!check_mob_target(potential_alive_human_target)) continue - if(FACTION_XENOMORPH in alive_human.faction_group) + var/distance = get_dist(src, potential_alive_human_target) + + if(distance > ai_range) continue - var/distance = get_dist(src, alive_human) + viable_targets += potential_alive_human_target + + if(smallest_distance <= distance) + continue - if(distance < ai_range && alive_human.stat == CONSCIOUS) - viable_humans += alive_human - smallest_distance = min(distance, smallest_distance) + closest_target = potential_alive_human_target + smallest_distance = distance - for(var/l in GLOB.all_multi_vehicles) - var/obj/vehicle/multitile/V = l - if(z != V.z) + for(var/obj/vehicle/multitile/potential_vehicle_target as anything in GLOB.all_multi_vehicles) + if(z != potential_vehicle_target.z) continue - var/distance = get_dist(src, V) - if(distance < ai_range) - viable_vehicles += V - smallest_distance = min(distance, smallest_distance) + var/distance = get_dist(src, potential_vehicle_target) - for(var/l in GLOB.all_defenses) - var/obj/structure/machinery/defenses/S = l - if(z != S.z) + if(distance > ai_range) continue - var/distance = get_dist(src, S) - if(distance < ai_range) - viable_defenses += S - smallest_distance = min(distance, smallest_distance) + if(potential_vehicle_target.health <= 0) + var/skip_vehicle = TRUE + var/list/interior_living_mobs = potential_vehicle_target.interior.get_passengers() + for(var/mob/living/carbon/human/human_mob in interior_living_mobs) + if(!check_mob_target(human_mob)) + continue - if(smallest_distance > RANGE_TO_DESPAWN_XENO && !(XENO_AI_NO_DESPAWN & flags_ai)) - remove_ai() - qdel(src) - return + skip_vehicle = FALSE + + if(skip_vehicle) + continue + + viable_targets += potential_vehicle_target + + if(smallest_distance <= distance) + continue + + closest_target = potential_vehicle_target + smallest_distance = distance + + for(var/obj/structure/machinery/defenses/potential_defense_target as anything in GLOB.all_active_defenses) + if(z != potential_defense_target.z) + continue + + var/distance = get_dist(src, potential_defense_target) + + if(distance > ai_range) + continue - if(length(viable_humans)) - return pick(viable_humans) + viable_targets += potential_defense_target - if(length(viable_vehicles)) - return pick(viable_vehicles) + if(smallest_distance <= distance) + continue + + closest_target = potential_defense_target + smallest_distance = distance + + var/extra_check_distance = round(smallest_distance * EXTRA_CHECK_DISTANCE_MULTIPLIER) + + if(extra_check_distance < 1) + return closest_target + + var/list/extra_checked = orange(extra_check_distance, closest_target) + + var/list/final_targets = extra_checked & viable_targets + + return length(final_targets) ? pick(final_targets) : closest_target + +#undef EXTRA_CHECK_DISTANCE_MULTIPLIER - if(length(viable_defenses)) - return pick(viable_defenses) +/mob/living/carbon/xenomorph/proc/check_mob_target(mob/living/carbon/human/checked_human) + if(checked_human.species.flags & IS_SYNTHETIC) + return FALSE + + if(FACTION_XENOMORPH in checked_human.faction_group) + return FALSE + + if(checked_human.stat != CONSCIOUS) + return FALSE + + return TRUE /mob/living/carbon/xenomorph/proc/make_ai() SHOULD_CALL_PARENT(TRUE) From 837fbfa45518e4e98617ec5ebfdb12e918472824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BA=D1=82=D0=BE?= <65656972+xDanilcusx@users.noreply.github.com> Date: Sun, 29 Oct 2023 08:24:17 +0300 Subject: [PATCH 3/3] Lurker's scary AI (#23) Co-authored-by: morrowwolf --- .../signals/atom/mob/living/signals_xeno.dm | 9 + code/__DEFINES/xeno_ai.dm | 2 + code/modules/admin/game_master/game_master.dm | 2 +- .../game_master/game_master_submenu/vents.dm | 2 +- .../xenomorph/abilities/general_powers.dm | 2 + .../abilities/lurker/lurker_abilities.dm | 2 +- .../carbon/xenomorph/ai/movement/lurking.dm | 269 ++++++++++++++++++ .../mob/living/carbon/xenomorph/ai/xeno_ai.dm | 14 +- .../living/carbon/xenomorph/castes/Lurker.dm | 60 ++++ .../mob/living/carbon/xenomorph/life.dm | 2 + .../carbon/xenomorph/xeno_ai_interaction.dm | 6 + code/modules/projectiles/projectile.dm | 3 +- colonialmarines.dme | 1 + 13 files changed, 365 insertions(+), 9 deletions(-) create mode 100644 code/modules/mob/living/carbon/xenomorph/ai/movement/lurking.dm diff --git a/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm b/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm index e9862be49d..f66c264fde 100644 --- a/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm +++ b/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm @@ -62,3 +62,12 @@ /// For any additional things that should happen when a xeno's melee_attack_additional_effects_self() proc is called #define COMSIG_XENO_SLASH_ADDITIONAL_EFFECTS_SELF "xeno_slash_additional_effects_self" + +/// From /mob/living/carbon/xenomorph/proc/handle_crit() +#define COMSIG_XENO_HANDLE_CRIT "xeno_handle_crit" + +/// From /datum/action/xeno_action/activable/pounce/use_ability() +#define COMSIG_XENO_USED_POUNCE "xeno_used_pounce" + +/// From /mob/living/carbon/xenomorph/proc/handle_ai_shot() +#define COMSIG_XENO_HANDLE_AI_SHOT "xeno_handle_ai_shot" diff --git a/code/__DEFINES/xeno_ai.dm b/code/__DEFINES/xeno_ai.dm index 1b4006d507..936a4cc656 100644 --- a/code/__DEFINES/xeno_ai.dm +++ b/code/__DEFINES/xeno_ai.dm @@ -24,6 +24,8 @@ PROBABILITY CALCULATIONS ARE HERE #define RETREAT_AT_PLASMA_LEVEL 0.2 #define RETREAT_AT_HEALTH_LEVEL 0.4 +#define LURKING_IGNORE_SHOT_CHANCE 75 + // Warrior /// How likely should it be that you lunge when off cooldown? diff --git a/code/modules/admin/game_master/game_master.dm b/code/modules/admin/game_master/game_master.dm index 943199c9a8..de540b6451 100644 --- a/code/modules/admin/game_master/game_master.dm +++ b/code/modules/admin/game_master/game_master.dm @@ -26,7 +26,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_CRUSHER) +#define GAME_MASTER_AI_XENOS list(XENO_CASTE_DRONE, XENO_CASTE_RUNNER, XENO_CASTE_LURKER, XENO_CASTE_CRUSHER) #define DEFAULT_XENO_AMOUNT_TO_SPAWN 1 diff --git a/code/modules/admin/game_master/game_master_submenu/vents.dm b/code/modules/admin/game_master/game_master_submenu/vents.dm index 188de1f6f1..d2f56cd810 100644 --- a/code/modules/admin/game_master/game_master_submenu/vents.dm +++ b/code/modules/admin/game_master/game_master_submenu/vents.dm @@ -1,6 +1,6 @@ #define DEFAULT_SPAWN_XENO_STRING XENO_CASTE_DRONE -#define GAME_MASTER_VENT_AI_XENOS list(XENO_CASTE_DRONE, XENO_CASTE_RUNNER) +#define GAME_MASTER_VENT_AI_XENOS list(XENO_CASTE_DRONE, XENO_CASTE_RUNNER, XENO_CASTE_LURKER) #define DEFAULT_XENO_AMOUNT_TO_SPAWN 1 diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm index 7a8151d8aa..512a7478be 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm @@ -446,6 +446,8 @@ LM.pass_flags = pounce_pass_flags LM.collision_callbacks = pounce_callbacks + SEND_SIGNAL(owner, COMSIG_XENO_USED_POUNCE, A) + X.launch_towards(LM) X.update_icons() diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm index 0c9358119d..a769a75df3 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/lurker/lurker_abilities.dm @@ -8,7 +8,7 @@ // Config options distance = 6 knockdown = FALSE - knockdown_duration = 2.5 + knockdown_duration = 1.5 freeze_self = TRUE freeze_time = 15 can_be_shield_blocked = TRUE diff --git a/code/modules/mob/living/carbon/xenomorph/ai/movement/lurking.dm b/code/modules/mob/living/carbon/xenomorph/ai/movement/lurking.dm new file mode 100644 index 0000000000..2968ffb481 --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/ai/movement/lurking.dm @@ -0,0 +1,269 @@ +/datum/xeno_ai_movement/linger/lurking + + // Are we currently hiding? + var/ai_lurking = FALSE + + // Gradually increases the chance of AI to try and bait marines, annoyance accumulate when we lurk (stand invisible) and aware of our target + var/annoyance = 0 + + // Total baits this xeno has made + var/total_baits = 0 + + // Distance at which we want to stay from our spotted target + var/stalking_distance = 12 + + // List of turfs we see and register while lurking + var/list/registered_turfs = list() + + // Let's lower this a little bit cause we do some heavy checks while finding our "home" + home_locate_range = 10 + + max_distance_from_home = 10 + +#define AI_CHECK_ANNOYANCE_COOLDOWN 2.5 SECONDS + +/datum/xeno_ai_movement/linger/lurking/New(mob/living/carbon/xenomorph/parent) + . = ..() + + RegisterSignal(parent, COMSIG_XENO_HANDLE_AI_SHOT, PROC_REF(stop_lurking)) + RegisterSignal(parent, COMSIG_XENO_HANDLE_CRIT, PROC_REF(stop_lurking)) + RegisterSignal(parent, COMSIG_XENO_USED_POUNCE, PROC_REF(stop_lurking)) + + addtimer(CALLBACK(src, PROC_REF(check_annoyance)), AI_CHECK_ANNOYANCE_COOLDOWN, TIMER_UNIQUE|TIMER_LOOP|TIMER_DELETE_ME) + +#undef AI_CHECK_ANNOYANCE_COOLDOWN + +/datum/xeno_ai_movement/linger/lurking/ai_move_idle(delta_time) + var/mob/living/carbon/xenomorph/idle_xeno = parent + if(idle_xeno.throwing) + return + + if(home_turf) + if(get_dist(home_turf, idle_xeno) <= 0) + start_lurking() + return + + if(!idle_xeno.move_to_next_turf(home_turf)) + home_turf = null + return + + return + + if(next_home_search > world.time) + return + + var/turf/current_turf = get_turf(idle_xeno) + + if(!current_turf) + return + + next_home_search = world.time + home_search_delay + var/shortest_distance = INFINITY + var/turf/non_preferred_turf + for(var/turf/potential_home as anything in shuffle(RANGE_TURFS(home_locate_range, current_turf))) + if(potential_home.density) + continue + + var/blocked = FALSE + for(var/atom/potential_blocker as anything in potential_home) + if(potential_blocker != idle_xeno && (potential_blocker.can_block_movement || potential_blocker.density)) + blocked = TRUE + break + + if(blocked) + continue + + var/preferred = FALSE + for(var/turf/closed/touching_turf in orange(1, potential_home)) + preferred = TRUE + break + + var/atom/movable/our_target = idle_xeno.current_target + if(our_target) + var/potential_home_dir = get_dir(idle_xeno, potential_home) + var/current_target_dir = get_dir(idle_xeno, our_target) + + if(current_target_dir == potential_home_dir || current_target_dir == turn(potential_home_dir, 45) || current_target_dir == turn(potential_home_dir, -45)) + continue + + if(get_dist(potential_home, our_target) > stalking_distance) + continue + + var/xeno_to_potential_home_distance = get_dist(idle_xeno, potential_home) + if(xeno_to_potential_home_distance > shortest_distance) + continue + + if(preferred) + home_turf = potential_home + shortest_distance = xeno_to_potential_home_distance + continue + + if(xeno_to_potential_home_distance < get_dist(idle_xeno, non_preferred_turf)) + non_preferred_turf = potential_home + continue + + if(!home_turf) + home_turf = non_preferred_turf + return + +/datum/xeno_ai_movement/linger/lurking/ai_move_target(delta_time) + var/mob/living/carbon/xenomorph/moving_xeno = parent + if(moving_xeno.throwing) + return + + var/incapacitated_check = TRUE + if(istype(moving_xeno.current_target, /mob)) + var/mob/current_target_mob = moving_xeno.current_target + incapacitated_check = current_target_mob.is_mob_incapacitated() + + if(incapacitated_check) + return ..() + + var/turf/target_turf = get_turf(moving_xeno.current_target) + if(ai_lurking || get_dist(moving_xeno, target_turf) > world.view + 1) + if(get_dist(moving_xeno, target_turf) > stalking_distance) + home_turf = null + return moving_xeno.move_to_next_turf(target_turf) + return ai_move_idle(delta_time) + + annoyance = 0 + check_for_travelling_turf_change(moving_xeno) + + if(!moving_xeno.move_to_next_turf(travelling_turf)) + travelling_turf = target_turf + return TRUE + +/datum/xeno_ai_movement/linger/lurking/proc/check_annoyance() + var/mob/living/carbon/xenomorph/annoyed_xeno = parent + if(!annoyed_xeno.current_target || !ai_lurking) + return + + var/target_distance = get_dist(annoyed_xeno, annoyed_xeno.current_target) + + if(target_distance < world.view) + return + + if(target_distance > 10) + annoyance = 0 + total_baits = 0 + return + + annoyance++ + + if(prob(annoyance)) + try_bait() + +#define LURKER_BAIT_TYPES list("Taunt","Emote","Interact") +#define LURKER_BAIT_EMOTES list("growl","roar","hiss","needshelp") +#define LURKER_BAIT_TAUNTS list("Come here, little host","I won't bite","I see you","Safe to go, little one") +#define LURKER_BAITS_BEFORE_AMBUSH 3 + +/datum/xeno_ai_movement/linger/lurking/proc/try_bait(no_interact) + var/mob/living/carbon/xenomorph/baiting_xeno = parent + if(baiting_xeno.throwing) + return + + if(total_baits >= LURKER_BAITS_BEFORE_AMBUSH) + stop_lurking() + total_baits = 0 + return + + var/bait_types = LURKER_BAIT_TYPES + if(no_interact) + bait_types -= "Interact" + + var/bait = pick(bait_types) + switch(bait) + if("Emote") + baiting_xeno.emote(pick(LURKER_BAIT_EMOTES)) + if("Taunt") + baiting_xeno.say(pick(LURKER_BAIT_TAUNTS)) + if("Interact") + if(!interact_random(baiting_xeno)) + return try_bait(no_interact = TRUE) + + total_baits++ + annoyance = 0 + return bait + +#undef LURKER_BAIT_TYPES +#undef LURKER_BAIT_EMOTES +#undef LURKER_BAIT_TAUNTS +#undef LURKER_BAITS_BEFORE_AMBUSH + +/datum/xeno_ai_movement/linger/lurking/proc/interact_random(mob/living/carbon/xenomorph/X) + for(var/obj/potential_interaction in orange(1, X)) + if(istype(potential_interaction, /obj/structure/window_frame)) + continue + if(istype(potential_interaction, /obj/structure/pipes)) + continue + if(istype(potential_interaction, /obj/structure/sign)) + continue + if(!potential_interaction.xeno_ai_act(X)) + continue + return TRUE + return FALSE + +/datum/xeno_ai_movement/linger/lurking/proc/start_lurking() + SIGNAL_HANDLER + if(ai_lurking) + return + + var/mob/living/carbon/xenomorph/lurking_xeno = parent + animate(lurking_xeno, alpha = 20, time = 0.5 SECONDS, easing = QUAD_EASING) + lurking_xeno.set_movement_intent(MOVE_INTENT_WALK) + register_turf_signals() + ai_lurking = TRUE + + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(lurking_parent_moved)) + + var/datum/action/xeno_action/activable/pounce/lurker/LPA = get_xeno_action_by_type(lurking_xeno, /datum/action/xeno_action/activable/pounce/lurker) + if(LPA && istype(LPA)) + LPA.knockdown = TRUE + LPA.freeze_self = TRUE + + INVOKE_ASYNC(lurking_xeno, TYPE_PROC_REF(/mob, stop_pulling)) + +/datum/xeno_ai_movement/linger/lurking/proc/stop_lurking() + SIGNAL_HANDLER + if(!ai_lurking) + return + + var/mob/living/carbon/xenomorph/lurking_xeno = parent + animate(lurking_xeno, alpha = initial(lurking_xeno.alpha), time = 0.2 SECONDS, easing = QUAD_EASING) + lurking_xeno.set_movement_intent(MOVE_INTENT_RUN) + unregister_turf_signals() + ai_lurking = FALSE + + UnregisterSignal(parent, COMSIG_MOVABLE_MOVED) + + INVOKE_ASYNC(lurking_xeno, TYPE_PROC_REF(/mob, stop_pulling)) + +/datum/xeno_ai_movement/linger/lurking/proc/register_turf_signals() + for(var/turf/open/cycled_open_turf in view(world.view, parent)) + RegisterSignal(cycled_open_turf, COMSIG_TURF_ENTERED, PROC_REF(set_target)) + registered_turfs += cycled_open_turf + + var/mob/living/carbon/human/possible_target = locate() in cycled_open_turf + if(possible_target && (!parent.current_target || get_dist(parent, possible_target) < get_dist(parent, parent.current_target))) + parent.current_target = possible_target + +/datum/xeno_ai_movement/linger/lurking/proc/unregister_turf_signals() + for(var/turf/open/cycled_open_turf in registered_turfs) + UnregisterSignal(cycled_open_turf, COMSIG_TURF_ENTERED) + registered_turfs.Cut() + +/datum/xeno_ai_movement/linger/lurking/proc/set_target(turf/hooked, atom/movable/entering_atom) + SIGNAL_HANDLER + + if(!istype(entering_atom, /mob/living/carbon/human)) + return + + if(!parent.current_target || get_dist(parent, entering_atom) < get_dist(parent, parent.current_target)) + parent.current_target = entering_atom + +/datum/xeno_ai_movement/linger/lurking/proc/lurking_parent_moved(atom/movable/moving_atom, atom/oldloc, direction, Forced) + SIGNAL_HANDLER + + unregister_turf_signals() + register_turf_signals() 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 e6f1c24586..f52c5276c9 100644 --- a/code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm +++ b/code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm @@ -36,12 +36,16 @@ GLOBAL_LIST_INIT(ai_target_limbs, list( return new /datum/xeno_ai_movement(src) /mob/living/carbon/xenomorph/proc/handle_ai_shot(obj/projectile/P) - if(!current_target && P.firer) - var/distance = get_dist(src, P.firer) - if(distance > max_travel_distance) - return + SEND_SIGNAL(src, COMSIG_XENO_HANDLE_AI_SHOT, P) - SSxeno_pathfinding.calculate_path(src, P.firer, distance, src, CALLBACK(src, PROC_REF(set_path)), list(src, P.firer)) + if(current_target || !P.firer) + return + + var/distance = get_dist(src, P.firer) + if(distance > max_travel_distance) + return + + SSxeno_pathfinding.calculate_path(src, P.firer, distance, src, CALLBACK(src, PROC_REF(set_path)), list(src, P.firer)) /mob/living/carbon/xenomorph/proc/register_ai_action(datum/action/xeno_action/XA) if(XA.owner != src) diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm b/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm index c106eb3078..f85a0088fe 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Lurker.dm @@ -59,6 +59,66 @@ icon_xeno = 'icons/mob/xenos/lurker.dmi' icon_xenonid = 'icons/mob/xenonids/lurker.dmi' + var/pull_direction + +/mob/living/carbon/xenomorph/lurker/launch_towards(datum/launch_metadata/LM) + if(!current_target) + return ..() + + pull_direction = turn(get_dir(src, current_target), 180) + + if(!(pull_direction in GLOB.cardinals)) + if(abs(x - current_target.x) < abs(y - current_target.y)) + pull_direction &= (NORTH|SOUTH) + else + pull_direction &= (EAST|WEST) + return ..() + +/mob/living/carbon/xenomorph/lurker/init_movement_handler() + return new /datum/xeno_ai_movement/linger/lurking(src) + +/mob/living/carbon/xenomorph/lurker/ai_move_target(delta_time) + if(throwing) + return + + if(pulling) + if(!current_target || get_dist(src, current_target) > 10) + INVOKE_ASYNC(src, PROC_REF(stop_pulling)) + return ..() + if(can_move_and_apply_move_delay()) + if(!Move(get_step(loc, pull_direction), pull_direction)) + pull_direction = turn(pull_direction, pick(45, -45)) + current_path = null + return + + ..() + + if(get_dist(current_target, src) > 1) + return + + if(!istype(current_target, /mob)) + return + + var/mob/current_target_mob = current_target + + if(!current_target_mob.is_mob_incapacitated()) + return + + if(isxeno(current_target.pulledby)) + return + + if(!DT_PROB(RUNNER_GRAB, delta_time)) + return + + INVOKE_ASYNC(src, PROC_REF(start_pulling), current_target) + swap_hand() + +/mob/living/carbon/xenomorph/lurker/process_ai(delta_time) + if(get_active_hand()) + swap_hand() + zone_selected = pick(GLOB.ai_target_limbs) + return ..() + /datum/behavior_delegate/lurker_base name = "Base Lurker Behavior Delegate" diff --git a/code/modules/mob/living/carbon/xenomorph/life.dm b/code/modules/mob/living/carbon/xenomorph/life.dm index 65839e9c8c..da66c61bfc 100644 --- a/code/modules/mob/living/carbon/xenomorph/life.dm +++ b/code/modules/mob/living/carbon/xenomorph/life.dm @@ -539,6 +539,8 @@ Make sure their actual health updates immediately.*/ if(!lying) update_canmove() + SEND_SIGNAL(src, COMSIG_XENO_HANDLE_CRIT) + /mob/living/carbon/xenomorph/proc/handle_luminosity() var/new_luminosity = 0 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 90c0ccf805..587942b41a 100644 --- a/code/modules/mob/living/carbon/xenomorph/xeno_ai_interaction.dm +++ b/code/modules/mob/living/carbon/xenomorph/xeno_ai_interaction.dm @@ -4,6 +4,7 @@ /obj/structure/mineral_door/xeno_ai_act(mob/living/carbon/xenomorph/X) X.do_click(src, "", list()) + return TRUE /obj/structure/mineral_door/resin/xeno_ai_obstacle(mob/living/carbon/xenomorph/xeno) if(xeno.hivenumber != hivenumber) @@ -22,6 +23,7 @@ /obj/structure/machinery/door/xeno_ai_act(mob/living/carbon/xenomorph/X) X.do_click(src, "", list()) + return TRUE // OBJECTS /obj/structure/xeno_ai_obstacle(mob/living/carbon/xenomorph/X, direction) @@ -38,6 +40,7 @@ do_climb(X) return X.do_click(src, "", list()) + return TRUE // HUMANS @@ -50,6 +53,7 @@ if(status_flags & GODMODE) return ..() X.do_click(src, "", list()) + return TRUE // VEHICLES /obj/vehicle/xeno_ai_obstacle(mob/living/carbon/xenomorph/X, direction) @@ -57,6 +61,7 @@ /obj/vehicle/xeno_ai_act(mob/living/carbon/xenomorph/X) X.do_click(src, "", list()) + return TRUE // SENTRY /obj/structure/machinery/defenses/xeno_ai_obstacle(mob/living/carbon/xenomorph/X, direction) @@ -64,6 +69,7 @@ /obj/structure/machinery/defenses/xeno_ai_act(mob/living/carbon/xenomorph/X) X.do_click(src, "", list()) + return TRUE // WINDOW FRAME /obj/structure/window_frame/xeno_ai_obstacle(mob/living/carbon/xenomorph/X, direction) diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index b01203d0f4..48dc79bfa6 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -1025,7 +1025,8 @@ bullet_message(P) //Message us about the bullet, since damage was inflicted. - + if(mob_flags & AI_CONTROLLED) + handle_ai_shot(P) if(SEND_SIGNAL(src, COMSIG_XENO_BULLET_ACT, damage_result, ammo_flags, P) & COMPONENT_CANCEL_BULLET_ACT) return diff --git a/colonialmarines.dme b/colonialmarines.dme index c1c6de35b4..368466b1ff 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -1992,6 +1992,7 @@ #include "code\modules\mob\living\carbon\xenomorph\ai\movement\base_define.dm" #include "code\modules\mob\living\carbon\xenomorph\ai\movement\drone.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" #include "code\modules\mob\living\carbon\xenomorph\castes\Burrower.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\Carrier.dm"