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 defe61de11..f06c164ec6 100644 --- a/code/__DEFINES/xeno_ai.dm +++ b/code/__DEFINES/xeno_ai.dm @@ -28,6 +28,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 89cc7b849a..6fc64bd248 100644 --- a/code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm +++ b/code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm @@ -37,12 +37,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"