Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix monkeys not being able to eat food then getting stuck forever #1106

Merged
merged 2 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions code/__DEFINES/ai.dm
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
#define AI_BEHAVIOR_KEEP_MOVING_TOWARDS_TARGET_ON_FINISH (1<<3)
///Does this behavior NOT block planning?
#define AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION (1<<4)
///Does this require the current_movement_target to be adjacent and in reach?
#define AI_BEHAVIOR_REQUIRE_REACH (1<<1)

///AI flags
#define STOP_MOVING_WHEN_PULLED (1<<0)
Expand Down
76 changes: 34 additions & 42 deletions code/datums/ai/_ai_controller.dm
Original file line number Diff line number Diff line change
Expand Up @@ -161,52 +161,42 @@ multiple modular subtrees with behaviors
SSmove_manager.stop_looping(pawn) //stop moving
return //this should remove them from processing in the future through event-based stuff.

if(!LAZYLEN(current_behaviors) && default_behavior)
if(behavior_cooldowns[GET_AI_BEHAVIOR(default_behavior)] > world.time)
return

queue_behavior(default_behavior)

if(current_movement_target && 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.
var/action_delta_time = max(current_behavior.action_cooldown * 0.1, delta_time)
// Then pick the max of this and the seconds_per_tick passed to ai_controller.process()
// Action cooldowns cannot happen faster than seconds_per_tick, so seconds_per_tick should be the value used in this scenario.
var/action_seconds_per_tick = max(current_behavior.get_cooldown(src) * 0.1, delta_time)

if(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT) //Might need to move closer
if(!current_movement_target)
stack_trace("[pawn] wants to perform action type [current_behavior.type] which requires movement, but has no current movement target!")
return //This can cause issues, so don't let these slide.

if(current_behavior.required_distance >= get_dist(pawn, current_movement_target)) ///Are we close enough to engage?
if(ai_movement.moving_controllers[src] == current_movement_target) //We are close enough, if we're moving stop.
ai_movement.stop_moving_towards(src)
if(!(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT))
if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown
continue
ProcessBehavior(action_seconds_per_tick, current_behavior)
return

if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown
continue
if(isnull(current_movement_target))
fail_behavior(current_behavior)
return

ProcessBehavior(action_delta_time, current_behavior)
return
///Stops pawns from performing such actions that should require the target to be adjacent.
var/atom/movable/moving_pawn = pawn
var/can_reach = !(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_REACH) || moving_pawn.CanReach(current_movement_target)
if(can_reach && current_behavior.required_distance >= get_dist(moving_pawn, current_movement_target)) ///Are we close enough to engage?
if(ai_movement.moving_controllers[src] == current_movement_target) //We are close enough, if we're moving stop.
ai_movement.stop_moving_towards(src)

else if(ai_movement.moving_controllers[src] != current_movement_target) //We're too far, if we're not already moving start doing it.
ai_movement.start_moving_towards(src, current_movement_target, current_behavior.required_distance) //Then start moving
if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown
continue
ProcessBehavior(action_seconds_per_tick, current_behavior)
return

if(current_behavior.behavior_flags & AI_BEHAVIOR_MOVE_AND_PERFORM) //If we can move and perform then do so.
if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown
continue
ProcessBehavior(action_delta_time, current_behavior)
return
if(ai_movement.moving_controllers[src] != current_movement_target) //We're too far, if we're not already moving start doing it.
ai_movement.start_moving_towards(src, current_movement_target, current_behavior.required_distance) //Then start moving

else //No movement required
if(current_behavior.behavior_flags & AI_BEHAVIOR_MOVE_AND_PERFORM) //If we can move and perform then do so.
if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown
continue

ProcessBehavior(action_delta_time, current_behavior)
ProcessBehavior(action_seconds_per_tick, current_behavior)
return

///This is where you decide what actions are taken by the AI.
Expand Down Expand Up @@ -320,13 +310,15 @@ multiple modular subtrees with behaviors
/datum/ai_controller/proc/CancelActions()
if(!LAZYLEN(current_behaviors))
return
for(var/i in current_behaviors)
var/datum/ai_behavior/current_behavior = i
var/list/arguments = list(src, FALSE)
var/list/stored_arguments = behavior_args[current_behavior.type]
if(stored_arguments)
arguments += stored_arguments
current_behavior.finish_action(arglist(arguments))
for(var/datum/ai_behavior/current_behavior as anything in current_behaviors)
fail_behavior(current_behavior)

/datum/ai_controller/proc/fail_behavior(datum/ai_behavior/current_behavior)
var/list/arguments = list(src, FALSE)
var/list/stored_arguments = behavior_args[current_behavior.type]
if(stored_arguments)
arguments += stored_arguments
current_behavior.finish_action(arglist(arguments))

/datum/ai_controller/proc/get_movement_delay()
if(isliving(pawn))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/datum/ai_behavior/basic_melee_attack
action_cooldown = 0.2 SECONDS // We gotta check unfortunately often because we're in a race condition with nextmove
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT //| AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
///do we finish this action after hitting once?
var/terminate_after_action = FALSE

Expand Down
13 changes: 6 additions & 7 deletions code/datums/ai/generic/generic_behaviors.dm
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
/// Use the currently held item, or unarmed, on a weakref to an object in the world
/datum/ai_behavior/use_on_object
required_distance = 1
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH

/datum/ai_behavior/use_on_object/setup(datum/ai_controller/controller, target_key)
. = ..()
Expand Down Expand Up @@ -105,7 +105,7 @@

/datum/ai_behavior/give
required_distance = 1
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH


/datum/ai_behavior/give/setup(datum/ai_controller/controller, target_key)
Expand Down Expand Up @@ -142,7 +142,7 @@

/datum/ai_behavior/consume
required_distance = 1
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH
action_cooldown = 2 SECONDS

/datum/ai_behavior/consume/setup(datum/ai_controller/controller, target_key)
Expand All @@ -157,7 +157,7 @@
if(QDELETED(target))
return BEHAVIOR_PERFORM_COOLDOWN | BEHAVIOR_PERFORM_FAILURE

if(!(target in living_pawn.held_items))
if(!(living_pawn.is_holding(target)))
if(!living_pawn.put_in_hands(target))
return BEHAVIOR_PERFORM_COOLDOWN | BEHAVIOR_PERFORM_FAILURE

Expand Down Expand Up @@ -255,19 +255,18 @@
/datum/ai_behavior/drop_item

/datum/ai_behavior/drop_item/perform(delta_time, datum/ai_controller/controller)
. = ..()
var/mob/living/living_pawn = controller.pawn
var/obj/item/best_held = GetBestWeapon(controller, null, living_pawn.held_items)
for(var/obj/item/held as anything in living_pawn.held_items)
if(!held || held == best_held)
continue
living_pawn.dropItemToGround(held)

return BEHAVIOR_PERFORM_COOLDOWN
return BEHAVIOR_PERFORM_SUCCESS

/// This behavior involves attacking a target.
/datum/ai_behavior/attack
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_REQUIRE_REACH
required_distance = 1

/datum/ai_behavior/attack/perform(delta_time, datum/ai_controller/controller)
Expand Down
19 changes: 13 additions & 6 deletions code/datums/ai/generic/generic_subtrees.dm
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,24 @@
* * BB_NEXT_HUNGRY - set by this subtree, is when the controller is next hungry
*/
/datum/ai_planning_subtree/generic_hunger/SelectBehaviors(datum/ai_controller/controller, delta_time)
//inits the blackboard timer
if(!controller.blackboard[BB_NEXT_HUNGRY])
controller.set_blackboard_key(BB_NEXT_HUNGRY, world.time + rand(0, 30 SECONDS))
var/next_eat = controller.blackboard[BB_NEXT_HUNGRY]
if(!next_eat)
//inits the blackboard timer
next_eat = world.time + rand(0, 30 SECONDS)
controller.set_blackboard_key(BB_NEXT_HUNGRY, next_eat)

if(world.time < controller.blackboard[BB_NEXT_HUNGRY])
if(world.time < next_eat)
return

if(!controller.blackboard[BB_FOOD_TARGET])
var/atom/movable/food_target = controller.blackboard[BB_FOOD_TARGET]
if(!food_target)
controller.queue_behavior(/datum/ai_behavior/find_and_set/edible, BB_FOOD_TARGET, /obj/item, 2)
return

controller.queue_behavior(/datum/ai_behavior/drop_item)

var/mob/living/pawn = controller.pawn
if(isitem(food_target) && !pawn.is_holding(food_target) && !pawn.get_empty_held_index())
controller.queue_behavior(/datum/ai_behavior/drop_item)

controller.queue_behavior(/datum/ai_behavior/consume, BB_FOOD_TARGET, BB_NEXT_HUNGRY)
return SUBTREE_RETURN_FINISH_PLANNING
2 changes: 1 addition & 1 deletion code/datums/ai/hunting_behavior/hunting_behaviors.dm
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
return BEHAVIOR_PERFORM_COOLDOWN | BEHAVIOR_PERFORM_FAILURE

/datum/ai_behavior/hunt_target
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH

/datum/ai_behavior/hunt_target/setup(datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key)
. = ..()
Expand Down
2 changes: 1 addition & 1 deletion code/datums/ai/monkey/monkey_behaviors.dm
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
screeches = list("roar","screech")

/datum/ai_behavior/monkey_equip
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH

/datum/ai_behavior/monkey_equip/finish_action(datum/ai_controller/controller, success)
. = ..()
Expand Down
Loading