Skip to content

Commit

Permalink
Lurker's scary AI (#23)
Browse files Browse the repository at this point in the history
Co-authored-by: morrowwolf <[email protected]>
  • Loading branch information
xDanilcusx and morrowwolf authored Oct 29, 2023
1 parent 177ca6b commit 837fbfa
Show file tree
Hide file tree
Showing 13 changed files with 365 additions and 9 deletions.
9 changes: 9 additions & 0 deletions code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 2 additions & 0 deletions code/__DEFINES/xeno_ai.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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?
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 @@ -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

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_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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
269 changes: 269 additions & 0 deletions code/modules/mob/living/carbon/xenomorph/ai/movement/lurking.dm
Original file line number Diff line number Diff line change
@@ -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()
14 changes: 9 additions & 5 deletions code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 837fbfa

Please sign in to comment.