diff --git a/code/__DEFINES/ai/creatures.dm b/code/__DEFINES/ai/creatures.dm index f49bcb80783..2b8031cebf6 100644 --- a/code/__DEFINES/ai/creatures.dm +++ b/code/__DEFINES/ai/creatures.dm @@ -2,8 +2,3 @@ #define BB_DRAGON_ADULTBREATH "BB" - -//Chicken egglaying - -#define BB_CHICKEN_LAYEGG - diff --git a/code/__DEFINES/ai/hostile.dm b/code/__DEFINES/ai/hostile.dm index 995173ddb54..1cd82689cea 100644 --- a/code/__DEFINES/ai/hostile.dm +++ b/code/__DEFINES/ai/hostile.dm @@ -31,9 +31,6 @@ //Hunting defines #define SUCCESFUL_HUNT_COOLDOWN 5 SECONDS -///Hunting BB keys -#define BB_CURRENT_HUNTING_TARGET "BB_current_hunting_target" -#define BB_HUNTING_COOLDOWN "BB_HUNTING_COOLDOWN" ///Basic Mob Keys @@ -59,3 +56,19 @@ ///List of mobs who have damaged us #define BB_BASIC_MOB_RETALIATE_LIST "BB_basic_mob_shitlist" + +///Blackboard key for a whitelist typecache of "things we can target while trying to move" +#define BB_OBSTACLE_TARGETING_WHITELIST "BB_targeting_whitelist" + +///Hunting BB keys +#define BB_CURRENT_HUNTING_TARGET "BB_current_hunting_target" +#define BB_HUNTING_COOLDOWN "BB_hunting_cooldown" + +///Travel BB key +#define BB_TRAVEL_DESTINATION "BB_travel_destination" + +///Reinforcements BB key +#define BB_BASIC_MOB_REINFORCEMENT_TARGET "BB_basic_mob_reinforcement_target" +#define BB_REINFORCEMENTS_SAY "BB_basic_mob_reinforcement_say" +#define BB_REINFORCEMENTS_EMOTE "BB_basic_mob_reinforcement_emote" +#define BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN "BB_basic_mob_reinforcement_cooldown" diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 738bda4f149..90bf941f843 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -11,6 +11,8 @@ #define TEXT_EAST "[EAST]" #define TEXT_WEST "[WEST]" +///Returns true if the dir is diagonal, false otherwise +#define ISDIAGONALDIR(d) (d&(d-1)) //Human Overlays Indexes///////// #define MUTATIONS_LAYER 49 //mutations. Tk headglows, cold resistance glow, etc diff --git a/code/datums/ai/behaviors/travel_towards.dm b/code/datums/ai/behaviors/travel_towards.dm new file mode 100644 index 00000000000..6eb7c36dadd --- /dev/null +++ b/code/datums/ai/behaviors/travel_towards.dm @@ -0,0 +1,52 @@ +/** + * # Travel Towards + * Moves towards the atom in the passed blackboard key. + * Planning continues during this action so it can be interrupted by higher priority actions. + */ +/datum/ai_behavior/travel_towards + required_distance = 0 + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + /// If true we will get rid of our target on completion + var/clear_target = FALSE + ///should we use a different movement type? + var/new_movement_type + +/datum/ai_behavior/travel_towards/setup(datum/ai_controller/controller, target_key) + . = ..() + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + set_movement_target(controller, target, new_movement_type) + +/datum/ai_behavior/travel_towards/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + finish_action(controller, TRUE, target_key) + +/datum/ai_behavior/travel_towards/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + if (clear_target) + controller.clear_blackboard_key(target_key) + if(new_movement_type) + controller.change_ai_movement_type(initial(controller.ai_movement)) + +/datum/ai_behavior/travel_towards/stop_on_arrival + clear_target = TRUE + +/** + * # Travel Towards Atom + * Travel towards an atom you pass directly from the controller rather than a blackboard key. + * You might need to do this to avoid repeating some checks in both a controller and an action. + */ +/datum/ai_behavior/travel_towards_atom + required_distance = 0 + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT + +/datum/ai_behavior/travel_towards_atom/setup(datum/ai_controller/controller, atom/target_atom) + . = ..() + if(isnull(target_atom)) + return FALSE + set_movement_target(controller, target_atom) + +/datum/ai_behavior/travel_towards_atom/perform(seconds_per_tick, datum/ai_controller/controller, atom/target_atom) + . = ..() + finish_action(controller, TRUE) diff --git a/code/datums/ai/controllers/orc.dm b/code/datums/ai/controllers/orc.dm index f09a93146f8..246ead1d02e 100644 --- a/code/datums/ai/controllers/orc.dm +++ b/code/datums/ai/controllers/orc.dm @@ -4,13 +4,15 @@ ai_movement = /datum/ai_movement/basic_avoidance blackboard = list( - BB_TARGETTING_DATUM = new /datum/targetting_datum/basic() + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), + BB_REINFORCEMENTS_SAY = "Intruders!" ) planning_subtrees = list( /datum/ai_planning_subtree/simple_find_target/closest, /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic + /datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic, + /datum/ai_planning_subtree/call_reinforcements ) idle_behavior = /datum/idle_behavior/idle_random_walk diff --git a/code/datums/ai/subtrees/attack_obstacle_in_path.dm b/code/datums/ai/subtrees/attack_obstacle_in_path.dm index 593252c8837..643b29174d4 100644 --- a/code/datums/ai/subtrees/attack_obstacle_in_path.dm +++ b/code/datums/ai/subtrees/attack_obstacle_in_path.dm @@ -1 +1,81 @@ -//todo +/// If there's something between us and our target then we need to queue a behaviour to make it not be there +/datum/ai_planning_subtree/attack_obstacle_in_path + /// Blackboard key containing current target + var/target_key = BB_BASIC_MOB_CURRENT_TARGET + /// The action to execute, extend to add a different cooldown or something + var/attack_behaviour = /datum/ai_behavior/attack_obstructions + +/datum/ai_planning_subtree/attack_obstacle_in_path/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + . = ..() + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return + + var/turf/next_step = get_step_towards(controller.pawn, target) + if (!next_step.is_blocked_turf(exclude_mobs = TRUE, source_atom = controller.pawn)) + return + + controller.queue_behavior(attack_behaviour, target_key) + // Don't cancel future planning, maybe we can move now + +/// Something is in our way, get it outta here +/datum/ai_behavior/attack_obstructions + action_cooldown = 2 SECONDS + /// If we should attack walls, be prepared for complaints about breaches + var/can_attack_turfs = FALSE + /// For if you want your mob to be able to attack dense objects + var/can_attack_dense_objects = FALSE + +/datum/ai_behavior/attack_obstructions/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + var/mob/living/simple_animal/basic_mob = controller.pawn + var/atom/target = controller.blackboard[target_key] + + if (QDELETED(target)) + finish_action(controller, succeeded = FALSE) + return + + var/turf/next_step = get_step_towards(basic_mob, target) + var/dir_to_next_step = get_dir(basic_mob, next_step) + // If moving diagonally we need to punch both ways, or more accurately the one we are blocked in + var/list/dirs_to_move = list() + if (ISDIAGONALDIR(dir_to_next_step)) + for(var/direction in GLOB.cardinals) + if(direction & dir_to_next_step) + dirs_to_move += direction + else + dirs_to_move += dir_to_next_step + + for (var/direction in dirs_to_move) + if (attack_in_direction(controller, basic_mob, direction)) + return + finish_action(controller, succeeded = TRUE) + +/datum/ai_behavior/attack_obstructions/proc/attack_in_direction(datum/ai_controller/controller, mob/living/simple_animal/basic_mob, direction) + var/turf/next_step = get_step(basic_mob, direction) + if (!next_step.is_blocked_turf(exclude_mobs = TRUE, source_atom = controller.pawn)) + return FALSE + + for (var/obj/object as anything in next_step.contents) + if (!can_smash_object(basic_mob, object)) + continue + basic_mob.ClickOn(object, list()) + return TRUE + + if (can_attack_turfs) + basic_mob.ClickOn(next_step) + return TRUE + return FALSE + +/datum/ai_behavior/attack_obstructions/proc/can_smash_object(mob/living/simple_animal/basic_mob, obj/object) + if (!object.density && !can_attack_dense_objects) + return FALSE + if (object.IsObscured()) + return FALSE + if (basic_mob.see_invisible < object.invisibility) + return FALSE + var/list/whitelist = basic_mob.ai_controller.blackboard[BB_OBSTACLE_TARGETING_WHITELIST] + if(whitelist && !is_type_in_typecache(object, whitelist)) + return FALSE + + return TRUE // It's in our way, let's get it out of our way diff --git a/code/datums/ai/subtrees/call_reinforcements.dm b/code/datums/ai/subtrees/call_reinforcements.dm new file mode 100644 index 00000000000..f236421d1d2 --- /dev/null +++ b/code/datums/ai/subtrees/call_reinforcements.dm @@ -0,0 +1,50 @@ +#define REINFORCEMENTS_COOLDOWN (30 SECONDS) + +/// Calls all nearby mobs that share a faction to give backup in combat +/datum/ai_planning_subtree/call_reinforcements + /// Blackboard key containing something to say when calling reinforcements (takes precedence over emotes) + var/say_key = BB_REINFORCEMENTS_SAY + /// Blackboard key containing an emote to perform when calling reinforcements + var/emote_key = BB_REINFORCEMENTS_EMOTE + /// Reinforcement-calling behavior to use + var/call_type = /datum/ai_behavior/call_reinforcements + +/datum/ai_planning_subtree/call_reinforcements/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + . = ..() + if (!decide_to_call(controller) || controller.blackboard[BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN] > world.time) + return + + var/call_say = controller.blackboard[BB_REINFORCEMENTS_SAY] + var/call_emote = controller.blackboard[BB_REINFORCEMENTS_EMOTE] + + if(!isnull(call_say)) + controller.queue_behavior(/datum/ai_behavior/perform_speech, call_say) + else if(!isnull(call_emote)) + controller.queue_behavior(/datum/ai_behavior/perform_emote, call_emote) + else + controller.queue_behavior(/datum/ai_behavior/perform_emote, "cries for help!") + + controller.queue_behavior(call_type) + +/// Decides when to call reinforcements, can be overridden for alternate behavior +/datum/ai_planning_subtree/call_reinforcements/proc/decide_to_call(datum/ai_controller/controller) + return controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET) && istype(controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET], /mob) + +/// Call out to all mobs in the specified range for help +/datum/ai_behavior/call_reinforcements + /// Range to call reinforcements from + var/reinforcements_range = 12 + +/datum/ai_behavior/call_reinforcements/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/pawn_mob = controller.pawn + for(var/mob/other_mob in oview(reinforcements_range, pawn_mob)) + if(faction_check(pawn_mob, other_mob) && !isnull(other_mob.ai_controller)) + // Add our current target to their retaliate list so that they'll attack our aggressor + other_mob.ai_controller.insert_blackboard_key_lazylist(BB_BASIC_MOB_RETALIATE_LIST, controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) + other_mob.ai_controller.set_blackboard_key(BB_BASIC_MOB_REINFORCEMENT_TARGET, pawn_mob) + + controller.set_blackboard_key(BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN, world.time + REINFORCEMENTS_COOLDOWN) + finish_action(controller, TRUE) + return //AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +#undef REINFORCEMENTS_COOLDOWN diff --git a/code/datums/ai/subtrees/travel_to_point.dm b/code/datums/ai/subtrees/travel_to_point.dm new file mode 100644 index 00000000000..0b5e5d4776f --- /dev/null +++ b/code/datums/ai/subtrees/travel_to_point.dm @@ -0,0 +1,21 @@ +/// Simply walk to a location +/datum/ai_planning_subtree/travel_to_point + /// Blackboard key where we travel a place we walk to + var/location_key = BB_TRAVEL_DESTINATION + /// What do we do in order to travel + var/travel_behaviour = /datum/ai_behavior/travel_towards + +/datum/ai_planning_subtree/travel_to_point/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + . = ..() + var/atom/target = controller.blackboard[location_key] + if (QDELETED(target)) + return + controller.queue_behavior(travel_behaviour, location_key) + return SUBTREE_RETURN_FINISH_PLANNING + + +/datum/ai_planning_subtree/travel_to_point/and_clear_target + travel_behaviour = /datum/ai_behavior/travel_towards/stop_on_arrival + +/datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce + location_key = BB_BASIC_MOB_REINFORCEMENT_TARGET diff --git a/roguetown.dme b/roguetown.dme index 22238f0480d..03590a4013c 100644 --- a/roguetown.dme +++ b/roguetown.dme @@ -1991,6 +1991,8 @@ #include "code\datums\ai\behaviors\resist.dm" #include "code\datums\ai\behaviors\recuperate.dm" #include "code\datums\ai\behaviors\run_from_target.dm" +#include "code\datums\ai\behaviors\travel_towards.dm" + #include "code\datums\ai\behaviors\use_in_hand.dm" #include "code\datums\ai\behaviors\use_on_object.dm" #include "code\datums\ai\behaviors\use_targeted_ability.dm" @@ -2023,12 +2025,12 @@ #include "code\datums\ai\subtrees\attack_adjacent_target.dm" #include "code\datums\ai\subtrees\attack_obstacle_in_path.dm" +#include "code\datums\ai\subtrees\call_reinforcements.dm" #include "code\datums\ai\subtrees\find_food.dm" #include "code\datums\ai\subtrees\flee_nearest_target.dm" #include "code\datums\ai\subtrees\flee_target.dm" #include "code\datums\ai\subtrees\eat_body.dm" #include "code\datums\ai\subtrees\eat_food.dm" - #include "code\datums\ai\subtrees\move_to_cardinal.dm" #include "code\datums\ai\subtrees\melee_spacing.dm" #include "code\datums\ai\subtrees\random_speech.dm" @@ -2037,7 +2039,7 @@ #include "code\datums\ai\subtrees\simple_find_target.dm" #include "code\datums\ai\subtrees\simple_melee_subtree.dm" #include "code\datums\ai\subtrees\targeted_ability_use.dm" - +#include "code\datums\ai\subtrees\travel_to_point.dm" #include "code\datums\ai\targetting_datum\simple_targetting_datum.dm" #include "code\datums\ai\targetting_datum\simple_targetting_allow_item.dm"