diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm index e198951b43c5..e4a7e9bace01 100644 --- a/code/__DEFINES/ai.dm +++ b/code/__DEFINES/ai.dm @@ -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) diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm index ea221dab6aac..beb51ccec55f 100644 --- a/code/datums/ai/_ai_controller.dm +++ b/code/datums/ai/_ai_controller.dm @@ -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. @@ -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)) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm index 87dbb2b47bda..0e67ad8fb3ae 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm @@ -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 diff --git a/code/datums/ai/generic/generic_behaviors.dm b/code/datums/ai/generic/generic_behaviors.dm index a5756ffc9cf2..ad9924fbec62 100644 --- a/code/datums/ai/generic/generic_behaviors.dm +++ b/code/datums/ai/generic/generic_behaviors.dm @@ -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) . = ..() @@ -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) @@ -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) @@ -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 @@ -255,7 +255,6 @@ /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) @@ -263,11 +262,11 @@ 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) diff --git a/code/datums/ai/generic/generic_subtrees.dm b/code/datums/ai/generic/generic_subtrees.dm index 3a1ab9928fbe..ee674f2eb988 100644 --- a/code/datums/ai/generic/generic_subtrees.dm +++ b/code/datums/ai/generic/generic_subtrees.dm @@ -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 diff --git a/code/datums/ai/hunting_behavior/hunting_behaviors.dm b/code/datums/ai/hunting_behavior/hunting_behaviors.dm index a2bca55636d8..a639913f3807 100644 --- a/code/datums/ai/hunting_behavior/hunting_behaviors.dm +++ b/code/datums/ai/hunting_behavior/hunting_behaviors.dm @@ -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) . = ..() diff --git a/code/datums/ai/monkey/monkey_behaviors.dm b/code/datums/ai/monkey/monkey_behaviors.dm index e6172103baa6..e3f2004626ff 100644 --- a/code/datums/ai/monkey/monkey_behaviors.dm +++ b/code/datums/ai/monkey/monkey_behaviors.dm @@ -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) . = ..()