Skip to content

Commit

Permalink
adds in idle trees and eating behaviours
Browse files Browse the repository at this point in the history
  • Loading branch information
dwasint committed Nov 19, 2024
1 parent 76e115a commit e36deba
Show file tree
Hide file tree
Showing 15 changed files with 169 additions and 56 deletions.
3 changes: 3 additions & 0 deletions code/__DEFINES/ai/hostile.dm
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@
#define BB_BASIC_MOB_CURRENT_TARGET "BB_basic_current_target"
#define BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION "BB_basic_current_target_hiding_location"
#define BB_TARGETTING_DATUM "targetting_datum"

///list of foods this mob likes
#define BB_BASIC_FOODS "BB_basic_foods"
29 changes: 18 additions & 11 deletions code/datums/ai/_ai_controller.dm
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ have ways of interacting with a specific atom and control it. They posses a blac
var/list/current_behaviors
///Current actions and their respective last time ran as an assoc list.
var/list/behavior_cooldowns = list()
///The idle behavior this AI performs when it has no actions.
var/datum/idle_behavior/idle_behavior = null
///Current status of AI (OFF/ON/IDLE)
var/ai_status
///Current movement target of the AI, generally set by decision making.
Expand Down Expand Up @@ -50,6 +52,9 @@ have ways of interacting with a specific atom and control it. They posses a blac
change_ai_movement_type(ai_movement)
init_subtrees()

if(idle_behavior)
idle_behavior = new idle_behavior()

PossessPawn(new_pawn)

/datum/ai_controller/Destroy(force, ...)
Expand Down Expand Up @@ -122,15 +127,21 @@ have ways of interacting with a specific atom and control it. They posses a blac
walk(pawn, 0) //stop moving
return //this should remove them from processing in the future through event-based stuff.

if(!LAZYLEN(current_behaviors))
PerformIdleBehavior(delta_time) //Do some stupid shit while we have nothing to do
if(!LAZYLEN(current_behaviors) && idle_behavior)
idle_behavior.perform_idle_behavior(delta_time, src) //Do some stupid shit while we have nothing to do
return

if(current_movement_target && get_dist(pawn, current_movement_target) > max_target_distance) //The distance is out of range
CancelActions()
return
for(var/i in current_behaviors)
var/datum/ai_behavior/current_behavior = i
if(current_movement_target)
if(!isatom(current_movement_target))
stack_trace("[pawn]'s current movement target is not an atom, rather a [current_movement_target.type]! Did you accidentally set it to a weakref?")
CancelActions()
return

if(get_dist(pawn, current_movement_target) > max_target_distance) //The distance is out of range
CancelActions()
return

for(var/datum/ai_behavior/current_behavior as anything in current_behaviors)
// Convert the current behaviour action cooldown to realtime seconds from deciseconds.current_behavior
// Then pick the max of this and the delta_time passed to ai_controller.process()
// Action cooldowns cannot happen faster than delta_time, so delta_time should be the value used in this scenario.
Expand Down Expand Up @@ -163,10 +174,6 @@ have ways of interacting with a specific atom and control it. They posses a blac
ProcessBehavior(action_delta_time, current_behavior)
return

///Perform some dumb idle behavior.
/datum/ai_controller/proc/PerformIdleBehavior(delta_time)
return

///This is where you decide what actions are taken by the AI.
/datum/ai_controller/proc/SelectBehaviors(delta_time)
SHOULD_NOT_SLEEP(TRUE) //Fuck you don't sleep in procs like this.
Expand Down
3 changes: 3 additions & 0 deletions code/datums/ai/_ai_idle_behavior.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/datum/idle_behavior
/datum/idle_behavior/proc/perform_idle_behavior(delta_time, datum/ai_controller/controller)
return
73 changes: 73 additions & 0 deletions code/datums/ai/behaviours/find_and_set.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**find and set
* Finds an item near themselves, sets a blackboard key as it. Very useful for ais that need to use machines or something.
* if you want to do something more complicated than find a single atom, change the search_tactic() proc
* cool tip: search_tactic() can set lists
*/
/datum/ai_behavior/find_and_set
action_cooldown = 2 SECONDS

/datum/ai_behavior/find_and_set/perform(delta_time, datum/ai_controller/controller, set_key, locate_path, search_range)
. = ..()
var/find_this_thing = search_tactic(controller, locate_path, search_range)
if(find_this_thing)
controller.blackboard[set_key] = WEAKREF(find_this_thing)
finish_action(controller, TRUE)
else
finish_action(controller, FALSE)

/datum/ai_behavior/find_and_set/proc/search_tactic(datum/ai_controller/controller, locate_path, search_range)
return locate(locate_path) in oview(search_range, controller.pawn)

/**
* Variant of find and set that fails if the living pawn doesn't hold something
*/
/datum/ai_behavior/find_and_set/pawn_must_hold_item

/datum/ai_behavior/find_and_set/pawn_must_hold_item/search_tactic(datum/ai_controller/controller)
var/mob/living/living_pawn = controller.pawn
if(!living_pawn.get_inactive_held_item() && !living_pawn.get_active_held_item())
return //we want to fail the search if we don't have something held
return ..()

/**
* Variant of find and set that also requires the item to be edible. checks hands too
*/
/datum/ai_behavior/find_and_set/edible

/datum/ai_behavior/find_and_set/edible/search_tactic(datum/ai_controller/controller, locate_path, search_range)
var/mob/living/living_pawn = controller.pawn
var/list/food_candidates = list()
for(var/held_candidate as anything in living_pawn.held_items)
if(!held_candidate || !istype(held_candidate, /obj/item/reagent_containers/food))
continue
food_candidates += held_candidate
var/list/local_results = locate(locate_path) in oview(search_range, controller.pawn)
for(var/local_candidate in local_results)
if(!istype(local_candidate, /obj/item/reagent_containers/food))
continue
food_candidates += local_candidate
if(food_candidates.len)
return pick(food_candidates)

/**
* Variant of find and set that only checks in hands, search range should be excluded for this
*/
/datum/ai_behavior/find_and_set/in_hands

/datum/ai_behavior/find_and_set/in_hands/search_tactic(datum/ai_controller/controller, locate_path)
var/mob/living/living_pawn = controller.pawn
return locate(locate_path) in living_pawn.held_items

/**
* Variant of find and set that takes a list of things to find.
*/
/datum/ai_behavior/find_and_set/in_list

/datum/ai_behavior/find_and_set/in_list/search_tactic(datum/ai_controller/controller, locate_paths, search_range)
var/list/found = list()
for(var/locate_path in locate_paths)
var/single_locate = ..(controller, locate_path, search_range)
if(single_locate)
found += single_locate
if(found.len)
return pick(found)
24 changes: 0 additions & 24 deletions code/datums/ai/behaviours/hostile/find_and_set.dm

This file was deleted.

4 changes: 2 additions & 2 deletions code/datums/ai/behaviours/hostile/find_potential_targets.dm
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@
return

var/atom/target = pick(filtered_targets)
controller.blackboard[target_key] = target
controller.blackboard[target_key] = WEAKREF(target)

var/atom/potential_hiding_location = targetting_datum.find_hidden_mobs(living_mob, target)

if(potential_hiding_location) //If they're hiding inside of something, we need to know so we can go for that instead initially.
controller.blackboard[hiding_location_key] = potential_hiding_location
controller.blackboard[hiding_location_key] = WEAKREF(potential_hiding_location)

finish_action(controller, TRUE)

Expand Down
25 changes: 19 additions & 6 deletions code/datums/ai/behaviours/hostile/melee_attack.dm
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@

/datum/ai_behavior/basic_melee_attack/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
. = ..()
controller.current_movement_target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] //Hiding location is priority
//Hiding location is priority
var/datum/weakref/weak_target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
if(!target)
return FALSE
controller.current_movement_target = target

/datum/ai_behavior/basic_melee_attack/perform(delta_time, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
. = ..()
var/mob/living/simple_animal/basic_mob = controller.pawn
var/atom/target = controller.blackboard[target_key]
//targetting datum will kill the action if not real anymore
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key]

if(!targetting_datum.can_attack(basic_mob, target))
Expand Down Expand Up @@ -39,23 +46,29 @@

/datum/ai_behavior/basic_ranged_attack/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
. = ..()
controller.current_movement_target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] //Hiding location is priority
var/datum/weakref/weak_target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
if(!target)
return FALSE
controller.current_movement_target = target


/datum/ai_behavior/basic_ranged_attack/perform(delta_time, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
. = ..()
var/mob/living/simple_animal/basic_mob = controller.pawn
var/atom/target = controller.blackboard[target_key]
//targetting datum will kill the action if not real anymore
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key]


if(!targetting_datum.can_attack(basic_mob, target))
finish_action(controller, FALSE, target_key)
return

var/hiding_target = targetting_datum.find_hidden_mobs(basic_mob, target) //If this is valid, theyre hidden in something!
var/atom/hiding_target = targetting_datum.find_hidden_mobs(basic_mob, target) //If this is valid, theyre hidden in something!

controller.blackboard[hiding_location_key] = hiding_target
controller.blackboard[hiding_location_key] = WEAKREF(hiding_target)

basic_mob.face_atom()
if(hiding_target) //Shoot it!
Expand Down
14 changes: 5 additions & 9 deletions code/datums/ai/hostile/_hostile_controller.dm
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
)
ai_movement = /datum/ai_movement/basic_avoidance

idle_behavior = /datum/idle_behavior/idle_random_walk/hostile_tameable

var/ride_penalty_movement = 1 SECONDS

COOLDOWN_DECLARE(command_cooldown)
Expand Down Expand Up @@ -60,15 +62,6 @@

return simple_pawn.lock_hashes

/datum/ai_controller/hostile_friend/PerformIdleBehavior(delta_time)
var/mob/living/living_pawn = pawn
if(!isturf(living_pawn.loc) || living_pawn.pulledby || length(living_pawn.buckled_mobs))
return

if(prob(5 * delta_time) && (living_pawn.mobility_flags & MOBILITY_MOVE))
var/move_dir = pick(GLOB.alldirs)
living_pawn.Move(get_step(living_pawn, move_dir), move_dir)

/datum/ai_controller/hostile_friend/proc/on_ridden_driver_move(atom/movable/movable_parent, mob/living/user, direction)
SIGNAL_HANDLER
PauseAi(ride_penalty_movement)
Expand Down Expand Up @@ -192,3 +185,6 @@
pawn.visible_message(span_danger("[pawn] [blackboard[BB_HOSTILE_ATTACK_WORD]] at [commander]'s command, and [pawn.p_they()] growl[pawn.p_s()] intensely.")) // imagine getting intimidated by a corgi
CancelActions()
blackboard[BB_HOSTILE_ORDER_MODE] = HOSTILE_COMMAND_ATTACK

/datum/idle_behavior/idle_random_walk/hostile_tameable
walk_chance = 5
4 changes: 4 additions & 0 deletions code/datums/ai/hostile/testing_orc.dm
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
/datum/ai_planning_subtree/basic_melee_attack_subtree,
)

idle_behavior = /datum/idle_behavior/idle_random_walk

/datum/ai_controller/testing_orc_ranged
move_delay = 0.5 SECONDS

Expand All @@ -25,3 +27,5 @@
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/basic_ranged_attack_subtree,
)

idle_behavior = /datum/idle_behavior/idle_random_walk
10 changes: 10 additions & 0 deletions code/datums/ai/idle_behaviors/random_walk.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/datum/idle_behavior/idle_random_walk
///Chance that the mob random walks per second
var/walk_chance = 10

/datum/idle_behavior/idle_random_walk/perform_idle_behavior(delta_time, datum/ai_controller/controller)
. = ..()
var/mob/living/living_pawn = controller.pawn
if(prob(walk_chance * delta_time) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !living_pawn.pulledby)
var/move_dir = pick(GLOB.alldirs)
living_pawn.Move(get_step(living_pawn, move_dir), move_dir)
10 changes: 10 additions & 0 deletions code/datums/ai/subtrees/find_food.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// similar to finding a target but looks for food types in the
/datum/ai_planning_subtree/find_food

/datum/ai_planning_subtree/find_food/SelectBehaviors(datum/ai_controller/controller, delta_time)
. = ..()
var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
if(target && !QDELETED(target))
return
var/list/wanted = controller.blackboard[BB_BASIC_FOODS]
controller.queue_behavior(/datum/ai_behavior/find_and_set/in_list, BB_BASIC_MOB_CURRENT_TARGET, wanted)
4 changes: 3 additions & 1 deletion code/datums/ai/subtrees/simple_find_target.dm
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

/datum/ai_planning_subtree/simple_find_target/SelectBehaviors(datum/ai_controller/controller, delta_time)
. = ..()
var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/datum/weakref/weak_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/atom/target = weak_target?.resolve()

if(target && !QDELETED(target))
return
controller.queue_behavior(/datum/ai_behavior/find_potential_targets, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
7 changes: 5 additions & 2 deletions code/datums/ai/subtrees/simple_melee_subtree.dm
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

/datum/ai_planning_subtree/basic_melee_attack_subtree/SelectBehaviors(datum/ai_controller/controller, delta_time)
. = ..()
var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/datum/weakref/weak_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/atom/target = weak_target?.resolve()
if(!target || QDELETED(target))
return
controller.queue_behavior(melee_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
Expand All @@ -15,7 +16,9 @@

/datum/ai_planning_subtree/basic_ranged_attack_subtree/SelectBehaviors(datum/ai_controller/controller, delta_time)
. = ..()
var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/datum/weakref/weak_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/atom/target = weak_target?.resolve()

if(!target || QDELETED(target))
return
controller.queue_behavior(ranged_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/// Subtype more forgiving for items.
/// Careful, this can go wrong and keep a mob hyperfocused on an item it can't lose aggro on
/datum/targetting_datum/basic/allow_items

/datum/targetting_datum/basic/allow_items/can_attack(mob/living/living_mob, atom/the_target)
. = ..()
if(isitem(the_target))
// trust fall exercise
return TRUE
6 changes: 5 additions & 1 deletion stonekeep.dme
Original file line number Diff line number Diff line change
Expand Up @@ -400,12 +400,14 @@
#include "code\datums\achievements\misc_achievements.dm"
#include "code\datums\ai\_ai_behaviour.dm"
#include "code\datums\ai\_ai_controller.dm"
#include "code\datums\ai\_ai_idle_behavior.dm"
#include "code\datums\ai\ai_movement\_ai_movement.dm"
#include "code\datums\ai\ai_movement\_ai_planning_subtree.dm"
#include "code\datums\ai\ai_movement\astar_movement.dm"
#include "code\datums\ai\ai_movement\basic_avoidance.dm"
#include "code\datums\ai\ai_movement\dumb_movement.dm"
#include "code\datums\ai\behaviours\consume.dm"
#include "code\datums\ai\behaviours\find_and_set.dm"
#include "code\datums\ai\behaviours\follow.dm"
#include "code\datums\ai\behaviours\perform_emote.dm"
#include "code\datums\ai\behaviours\perform_speech.dm"
Expand All @@ -414,15 +416,17 @@
#include "code\datums\ai\behaviours\use_on_object.dm"
#include "code\datums\ai\behaviours\wak_to_target.dm"
#include "code\datums\ai\behaviours\hostile\attack.dm"
#include "code\datums\ai\behaviours\hostile\find_and_set.dm"
#include "code\datums\ai\behaviours\hostile\find_potential_targets.dm"
#include "code\datums\ai\behaviours\hostile\melee_attack.dm"
#include "code\datums\ai\hostile\_hostile_controller.dm"
#include "code\datums\ai\hostile\testing_orc.dm"
#include "code\datums\ai\idle_behaviors\random_walk.dm"
#include "code\datums\ai\subtrees\find_food.dm"
#include "code\datums\ai\subtrees\random_speech.dm"
#include "code\datums\ai\subtrees\simple_find_target.dm"
#include "code\datums\ai\subtrees\simple_melee_subtree.dm"
#include "code\datums\ai\targetting_datum\simpe_targetting_datum.dm"
#include "code\datums\ai\targetting_datum\simple_targetting_allow_item.dm"
#include "code\datums\atmosphere\_atmosphere.dm"
#include "code\datums\atmosphere\planetary.dm"
#include "code\datums\brain_damage\brain_trauma.dm"
Expand Down

0 comments on commit e36deba

Please sign in to comment.