From bdc2b2d3c9ed59439f7f41f108262a642409113d Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Thu, 29 Jul 2021 12:11:03 +0200 Subject: [PATCH] [MIRROR] Revives PR #58579; Sligh refactor to AI datums that allows for basic support of subtrees (#7214) * Revives PR #58579; Sligh refactor to AI datums that allows for basic support of subtrees (#60249) Co-authored-by: Mothblocks <35135081+Mothblocks@ users.noreply.github.com> Co-authored-by: coiax Co-authored-by: Watermelon914 <37270891+Watermelon914@ users.noreply.github.com> Co-authored-by: Rohesie Co-authored-by: Matthew J. <12817816+ZephyrTFA@ users.noreply.github.com> Co-authored-by: AnturK Co-authored-by: Jonathan Rubenstein Co-authored-by: Kylerace Co-authored-by: Watermelon914 <3052169-Watermelon914@ users.noreply.gitlab.com> Co-authored-by: tralezab <40974010+tralezab@ users.noreply.github.com> Co-authored-by: Jordan Brown Co-authored-by: Fikou Co-authored-by: Emmanuel S. * Revives PR #58579; Sligh refactor to AI datums that allows for basic support of subtrees Co-authored-by: ma44 Co-authored-by: Mothblocks <35135081+Mothblocks@ users.noreply.github.com> Co-authored-by: coiax Co-authored-by: Watermelon914 <37270891+Watermelon914@ users.noreply.github.com> Co-authored-by: Rohesie Co-authored-by: Matthew J. <12817816+ZephyrTFA@ users.noreply.github.com> Co-authored-by: AnturK Co-authored-by: Jonathan Rubenstein Co-authored-by: Kylerace Co-authored-by: Watermelon914 <3052169-Watermelon914@ users.noreply.gitlab.com> Co-authored-by: tralezab <40974010+tralezab@ users.noreply.github.com> Co-authored-by: Jordan Brown Co-authored-by: Fikou Co-authored-by: Emmanuel S. --- code/__DEFINES/ai.dm | 10 +- code/__DEFINES/subsystems.dm | 1 + code/controllers/subsystem/ai_controllers.dm | 33 +++++++ .../subsystem/processing/ai_behaviors.dm | 20 ++++ .../subsystem/processing/ai_controllers.dm | 23 ----- code/datums/ai/_ai_behavior.dm | 2 +- code/datums/ai/_ai_controller.dm | 67 +++++++++++--- code/datums/ai/_ai_planning_subtree.dm | 6 ++ code/datums/ai/bane/bane_controller.dm | 17 +--- code/datums/ai/bane/bane_subtrees.dm | 14 +++ code/datums/ai/cursed/cursed_controller.dm | 18 +--- code/datums/ai/cursed/cursed_subtrees.dm | 14 +++ code/datums/ai/dog/dog_controller.dm | 60 ++---------- code/datums/ai/dog/dog_subtrees.dm | 40 ++++++++ code/datums/ai/hauntium/haunted_controller.dm | 25 +---- code/datums/ai/hauntium/hauntium_subtrees.dm | 22 +++++ code/datums/ai/hostile/hostile_controller.dm | 8 +- code/datums/ai/monkey/monkey_controller.dm | 91 +------------------ code/datums/ai/monkey/monkey_subtrees.dm | 84 +++++++++++++++++ code/datums/ai/movement/ai_movement_jps.dm | 7 +- .../vending_machine_controller.dm | 4 +- .../robot_customer_controller.dm | 30 +----- .../robot_customer/robot_customer_subtrees.dm | 25 +++++ tgstation.dme | 10 +- 24 files changed, 356 insertions(+), 275 deletions(-) create mode 100644 code/controllers/subsystem/ai_controllers.dm create mode 100644 code/controllers/subsystem/processing/ai_behaviors.dm delete mode 100644 code/controllers/subsystem/processing/ai_controllers.dm create mode 100644 code/datums/ai/_ai_planning_subtree.dm create mode 100644 code/datums/ai/bane/bane_subtrees.dm create mode 100644 code/datums/ai/cursed/cursed_subtrees.dm create mode 100644 code/datums/ai/dog/dog_subtrees.dm create mode 100644 code/datums/ai/hauntium/hauntium_subtrees.dm create mode 100644 code/datums/ai/monkey/monkey_subtrees.dm create mode 100644 code/datums/ai/robot_customer/robot_customer_subtrees.dm diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm index 66caa5bdf63..d6018a8d8e1 100644 --- a/code/__DEFINES/ai.dm +++ b/code/__DEFINES/ai.dm @@ -1,4 +1,4 @@ -#define GET_AI_BEHAVIOR(behavior_type) SSai_controllers.ai_behaviors[behavior_type] +#define GET_AI_BEHAVIOR(behavior_type) SSai_behaviors.ai_behaviors[behavior_type] #define HAS_AI_CONTROLLER_TYPE(thing, type) istype(thing?.ai_controller, type) #define AI_STATUS_ON 1 @@ -12,6 +12,10 @@ ///For JPS pathing, the maximum length of a path we'll try to generate. Should be modularized depending on what we're doing later on #define AI_MAX_PATH_LENGTH 30 // 30 is possibly overkill since by default we lose interest after 14 tiles of distance, but this gives wiggle room for weaving around obstacles +///Cooldown on planning if planning failed last time + +#define AI_FAILED_PLANNING_COOLDOWN 1.5 SECONDS + ///Flags for ai_behavior new() #define AI_CONTROLLER_INCOMPATIBLE (1<<0) @@ -20,6 +24,10 @@ ///Does this task let you perform the action while you move closer? (Things like moving and shooting) #define AI_BEHAVIOR_MOVE_AND_PERFORM (1<<1) +///Subtree defines + +///This subtree should cancel any further planning, (Including from other subtrees) +#define SUBTREE_RETURN_FINISH_PLANNING 1 // Monkey AI controller blackboard keys diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 5e0c6ed17cd..97b24203e53 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -171,6 +171,7 @@ #define FIRE_PRIORITY_AIR 20 #define FIRE_PRIORITY_NPC 20 #define FIRE_PRIORITY_NPC_MOVEMENT 21 +#define FIRE_PRIORITY_NPC_ACTIONS 22 #define FIRE_PRIORITY_PROCESS 25 #define FIRE_PRIORITY_THROWING 25 #define FIRE_PRIORITY_REAGENTS 26 diff --git a/code/controllers/subsystem/ai_controllers.dm b/code/controllers/subsystem/ai_controllers.dm new file mode 100644 index 00000000000..5319d7316fb --- /dev/null +++ b/code/controllers/subsystem/ai_controllers.dm @@ -0,0 +1,33 @@ +/// The subsystem used to tick [/datum/ai_controllers] instances. Handling the re-checking of plans. +SUBSYSTEM_DEF(ai_controllers) + name = "AI Controller Ticker" + flags = SS_POST_FIRE_TIMING|SS_BACKGROUND + priority = FIRE_PRIORITY_NPC + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + init_order = INIT_ORDER_AI_CONTROLLERS + wait = 0.5 SECONDS //Plan every half second if required, not great not terrible. + + ///List of all ai_subtree singletons, key is the typepath while assigned value is a newly created instance of the typepath. See setup_subtrees() + var/list/ai_subtrees = list() + ///List of all ai controllers currently running + var/list/active_ai_controllers = list() + +/datum/controller/subsystem/ai_controllers/Initialize(timeofday) + setup_subtrees() + return ..() + +/datum/controller/subsystem/ai_controllers/proc/setup_subtrees() + ai_subtrees = list() + for(var/subtree_type in subtypesof(/datum/ai_planning_subtree)) + var/datum/ai_planning_subtree/subtree = new subtree_type + ai_subtrees[subtree_type] = subtree + +/datum/controller/subsystem/ai_controllers/fire(resumed) + for(var/datum/ai_controller/ai_controller as anything in active_ai_controllers) + if(!COOLDOWN_FINISHED(ai_controller, failed_planning_cooldown)) + continue + + if(!LAZYLEN(ai_controller.current_behaviors)) + ai_controller.SelectBehaviors(wait * 0.1) + if(!LAZYLEN(ai_controller.current_behaviors)) //Still no plan + COOLDOWN_START(ai_controller, failed_planning_cooldown, AI_FAILED_PLANNING_COOLDOWN) diff --git a/code/controllers/subsystem/processing/ai_behaviors.dm b/code/controllers/subsystem/processing/ai_behaviors.dm new file mode 100644 index 00000000000..4c98567405c --- /dev/null +++ b/code/controllers/subsystem/processing/ai_behaviors.dm @@ -0,0 +1,20 @@ +/// The subsystem used to tick [/datum/ai_behavior] instances. Handling the individual actions an AI can take like punching someone in the fucking NUTS +PROCESSING_SUBSYSTEM_DEF(ai_behaviors) + name = "AI Behavior Ticker" + flags = SS_POST_FIRE_TIMING|SS_BACKGROUND + priority = FIRE_PRIORITY_NPC_ACTIONS + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + init_order = INIT_ORDER_AI_CONTROLLERS + wait = 1 + ///List of all ai_behavior singletons, key is the typepath while assigned value is a newly created instance of the typepath. See SetupAIBehaviors() + var/list/ai_behaviors + +/datum/controller/subsystem/processing/ai_behaviors/Initialize(timeofday) + SetupAIBehaviors() + return ..() + +/datum/controller/subsystem/processing/ai_behaviors/proc/SetupAIBehaviors() + ai_behaviors = list() + for(var/behavior_type in subtypesof(/datum/ai_behavior)) + var/datum/ai_behavior/ai_behavior = new behavior_type + ai_behaviors[behavior_type] = ai_behavior diff --git a/code/controllers/subsystem/processing/ai_controllers.dm b/code/controllers/subsystem/processing/ai_controllers.dm deleted file mode 100644 index fbdb7adb37b..00000000000 --- a/code/controllers/subsystem/processing/ai_controllers.dm +++ /dev/null @@ -1,23 +0,0 @@ -/// The subsystem used to tick [/datum/ai_controllers] instances. Handling the re-checking of plans. -PROCESSING_SUBSYSTEM_DEF(ai_controllers) - name = "AI behavior" - flags = SS_POST_FIRE_TIMING|SS_BACKGROUND - priority = FIRE_PRIORITY_NPC - runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME - init_order = INIT_ORDER_AI_CONTROLLERS - wait = 8 //Uses the value of CLICK_CD_MELEE because that seemed like a nice standard for the speed of AI behavior - - ///an assoc list of all ai_behaviors by type, to - var/list/ai_behaviors - -/datum/controller/subsystem/processing/ai_controllers/Initialize(timeofday) - SetupAIBehaviors() - return ..() - -/datum/controller/subsystem/processing/ai_controllers/proc/SetupAIBehaviors() - ai_behaviors = list() - for(var/i in subtypesof(/datum/ai_behavior)) - var/datum/ai_behavior/ai_behavior = new i - ai_behaviors[i] = ai_behavior - - diff --git a/code/datums/ai/_ai_behavior.dm b/code/datums/ai/_ai_behavior.dm index 0ce84505a1f..e441236db2c 100644 --- a/code/datums/ai/_ai_behavior.dm +++ b/code/datums/ai/_ai_behavior.dm @@ -19,7 +19,7 @@ ///Called when the action is finished. /datum/ai_behavior/proc/finish_action(datum/ai_controller/controller, succeeded) - controller.current_behaviors.Remove(src) + LAZYREMOVE(controller.current_behaviors, src) controller.behavior_args -= type if(behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT) //If this was a movement task, reset our movement target. controller.current_movement_target = null diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm index d2aee869ccd..bcbc063aa40 100644 --- a/code/datums/ai/_ai_controller.dm +++ b/code/datums/ai/_ai_controller.dm @@ -1,6 +1,7 @@ /* AI controllers are a datumized form of AI that simulates the input a player would otherwise give to a atom. What this means is that these datums -have ways of interacting with a specific atom and control it. They posses a blackboard with the information the AI knows and has, and will plan behaviors it will try to execute. +have ways of interacting with a specific atom and control it. They posses a blackboard with the information the AI knows and has, and will plan behaviors it will try to execute through +multiple modular subtrees with behaviors */ /datum/ai_controller @@ -9,7 +10,7 @@ have ways of interacting with a specific atom and control it. They posses a blac ///Bitfield of traits for this AI to handle extra behavior var/ai_traits ///Current actions being performed by the AI. - var/list/current_behaviors = list() + var/list/current_behaviors ///Current actions and their respective last time ran as an assoc list. var/list/behavior_cooldowns = list() ///Current status of AI (OFF/ON) @@ -26,12 +27,20 @@ have ways of interacting with a specific atom and control it. They posses a blac var/continue_processing_when_client = FALSE ///distance to give up on target var/max_target_distance = 14 + ///Cooldown for new plans, to prevent AI from going nuts if it can't think of new plans and looping on end + COOLDOWN_DECLARE(failed_planning_cooldown) + ///All subtrees this AI has available, will run them in order, so make sure they're in the order you want them to run. On initialization of this type, it will start as a typepath(s) and get converted to references of ai_subtrees found in SSai_controllers when init_subtrees() is called + var/list/planning_subtrees + + // Movement related things here ///Reference to the movement datum we use. Is a type on initialize but becomes a ref afterwards. var/datum/ai_movement/ai_movement = /datum/ai_movement/dumb ///Cooldown until next movement COOLDOWN_DECLARE(movement_cooldown) ///Delay between movements. This is on the controller so we can keep the movement datum singleton var/movement_delay = 0.1 SECONDS + + // The variables below are fucking stupid and should be put into the blackboard at some point. ///A list for the path we're currently following, if we're using JPS pathing var/list/movement_path ///Cooldown for JPS movement, how often we're allowed to try making a new path @@ -40,7 +49,9 @@ have ways of interacting with a specific atom and control it. They posses a blac var/paused_until = 0 /datum/ai_controller/New(atom/new_pawn) - ai_movement = SSai_movement.movement_types[ai_movement] + change_ai_movement_type(ai_movement) + init_subtrees() + PossessPawn(new_pawn) /datum/ai_controller/Destroy(force, ...) @@ -48,6 +59,25 @@ have ways of interacting with a specific atom and control it. They posses a blac UnpossessPawn(FALSE) return ..() +///Overrides the current ai_movement of this controller with a new one +/datum/ai_controller/proc/change_ai_movement_type(datum/ai_movement/new_movement) + ai_movement = SSai_movement.movement_types[new_movement] + +///Completely replaces the planning_subtrees with a new set based on argument provided, list provided must contain specifically typepaths +/datum/ai_controller/proc/replace_planning_subtrees(list/typepaths_of_new_subtrees) + planning_subtrees = typepaths_of_new_subtrees + init_subtrees() + +///Loops over the subtrees in planning_subtrees and looks at the ai_controllers to grab a reference, ENSURE planning_subtrees ARE TYPEPATHS AND NOT INSTANCES/REFERENCES BEFORE EXECUTING THIS +/datum/ai_controller/proc/init_subtrees() + if(!LAZYLEN(planning_subtrees)) + return + var/list/temp_subtree_list = list() + for(var/subtree in planning_subtrees) + var/subtree_instance = SSai_controllers.ai_subtrees[subtree] + temp_subtree_list += subtree_instance + planning_subtrees = temp_subtree_list + ///Proc to move from one pawn to another, this will destroy the target's existing controller. /datum/ai_controller/proc/PossessPawn(atom/new_pawn) if(pawn) //Reset any old signals @@ -93,17 +123,16 @@ have ways of interacting with a specific atom and control it. They posses a blac return FALSE return TRUE -/// Generates a plan and see if our existing one is still valid. + +///Runs any actions that are currently running /datum/ai_controller/process(delta_time) if(!able_to_run()) walk(pawn, 0) //stop moving return //this should remove them from processing in the future through event-based stuff. - if(!current_behaviors?.len) - SelectBehaviors(delta_time) - if(!current_behaviors?.len) - PerformIdleBehavior(delta_time) //Do some stupid shit while we have nothing to do - return + if(!LAZYLEN(current_behaviors)) + PerformIdleBehavior(delta_time) //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() @@ -144,7 +173,15 @@ have ways of interacting with a specific atom and control it. They posses a blac ///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. - return + if(!COOLDOWN_FINISHED(src, failed_planning_cooldown)) + return FALSE + + LAZYINITLIST(current_behaviors) + + if(LAZYLEN(planning_subtrees)) + for(var/datum/ai_planning_subtree/subtree as anything in planning_subtrees) + if(subtree.SelectBehaviors(src, delta_time) == SUBTREE_RETURN_FINISH_PLANNING) + break ///This proc handles changing ai status, and starts/stops processing if required. /datum/ai_controller/proc/set_ai_status(new_ai_status) @@ -154,9 +191,11 @@ have ways of interacting with a specific atom and control it. They posses a blac ai_status = new_ai_status switch(ai_status) if(AI_STATUS_ON) - START_PROCESSING(SSai_controllers, src) + SSai_controllers.active_ai_controllers += src + START_PROCESSING(SSai_behaviors, src) if(AI_STATUS_OFF) - STOP_PROCESSING(SSai_controllers, src) + STOP_PROCESSING(SSai_behaviors, src) + SSai_controllers.active_ai_controllers -= src CancelActions() /datum/ai_controller/proc/PauseAi(time) @@ -170,7 +209,7 @@ have ways of interacting with a specific atom and control it. They posses a blac arguments[1] = src if(!behavior.setup(arglist(arguments))) return - current_behaviors += behavior + LAZYADD(current_behaviors, behavior) arguments.Cut(1, 2) if(length(arguments)) behavior_args[behavior_type] = arguments @@ -183,6 +222,8 @@ have ways of interacting with a specific atom and control it. They posses a blac behavior.perform(arglist(arguments)) /datum/ai_controller/proc/CancelActions() + if(!LAZYLEN(current_behaviors)) + return for(var/i in current_behaviors) var/datum/ai_behavior/current_behavior = i current_behavior.finish_action(src, FALSE) diff --git a/code/datums/ai/_ai_planning_subtree.dm b/code/datums/ai/_ai_planning_subtree.dm new file mode 100644 index 00000000000..8f186d586a4 --- /dev/null +++ b/code/datums/ai/_ai_planning_subtree.dm @@ -0,0 +1,6 @@ +///A subtree is attached to a controller and is occasionally called by /ai_controller/SelectBehaviors(), this mainly exists to act as a way to subtype and modify SelectBehaviors() without needing to subtype the ai controller itself +/datum/ai_planning_subtree + +///Determines what behaviors should the controller try processing; if this returns SUBTREE_RETURN_FINISH_PLANNING then the controller won't go through the other subtrees should multiple exist in controller.planning_subtrees +/datum/ai_planning_subtree/proc/SelectBehaviors(datum/ai_controller/controller, delta_time) + return diff --git a/code/datums/ai/bane/bane_controller.dm b/code/datums/ai/bane/bane_controller.dm index 1c9b4d9ce59..8d6820a800b 100644 --- a/code/datums/ai/bane/bane_controller.dm +++ b/code/datums/ai/bane/bane_controller.dm @@ -5,6 +5,7 @@ And the only victory you achieved was a lie. Now you understand Gotham is beyond /datum/ai_controller/bane movement_delay = 0.4 SECONDS blackboard = list(BB_BANE_BATMAN = null) + planning_subtrees = list(/datum/ai_planning_subtree/bane_hunting) /datum/ai_controller/bane/TryPossessPawn(atom/new_pawn) if(!isliving(new_pawn)) @@ -16,19 +17,3 @@ And the only victory you achieved was a lie. Now you understand Gotham is beyond if(IS_DEAD_OR_INCAP(living_pawn)) return FALSE return ..() - -/datum/ai_controller/bane/SelectBehaviors(delta_time) - current_behaviors = list() - var/mob/living/batman = blackboard[BB_BANE_BATMAN] - if(!batman) - for(var/mob/living/possibly_the_dark_knight in oview(7, pawn)) - if(IS_DEAD_OR_INCAP(possibly_the_dark_knight)) //I HAVE BROKEN THE BAT - continue - blackboard[BB_BANE_BATMAN] = possibly_the_dark_knight - batman = possibly_the_dark_knight - break - if(batman) - current_movement_target = batman - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/break_spine/bane) - - diff --git a/code/datums/ai/bane/bane_subtrees.dm b/code/datums/ai/bane/bane_subtrees.dm new file mode 100644 index 00000000000..cd94da40b88 --- /dev/null +++ b/code/datums/ai/bane/bane_subtrees.dm @@ -0,0 +1,14 @@ +///The bat is broken! +/datum/ai_planning_subtree/bane_hunting/SelectBehaviors(datum/ai_controller/controller, delta_time) + var/mob/living/batman = controller.blackboard[BB_BANE_BATMAN] + if(!batman) + for(var/mob/living/possibly_the_dark_knight in oview(7, controller.pawn)) + if(IS_DEAD_OR_INCAP(possibly_the_dark_knight)) //I HAVE BROKEN THE BAT + continue + controller.blackboard[BB_BANE_BATMAN] = possibly_the_dark_knight + batman = possibly_the_dark_knight + break + if(batman) + controller.current_movement_target = batman + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/break_spine/bane)) + return SUBTREE_RETURN_FINISH_PLANNING diff --git a/code/datums/ai/cursed/cursed_controller.dm b/code/datums/ai/cursed/cursed_controller.dm index f0a33de05a0..bc8ee10d625 100644 --- a/code/datums/ai/cursed/cursed_controller.dm +++ b/code/datums/ai/cursed/cursed_controller.dm @@ -12,6 +12,7 @@ BB_TARGET_SLOT, BB_CURSED_THROW_ATTEMPT_COUNT ) + planning_subtrees = list(/datum/ai_planning_subtree/cursed) /datum/ai_controller/cursed/TryPossessPawn(atom/new_pawn) if(!isitem(new_pawn)) @@ -24,23 +25,6 @@ UnregisterSignal(pawn, list(COMSIG_MOVABLE_IMPACT, COMSIG_ITEM_EQUIPPED)) return ..() //Run parent at end -/datum/ai_controller/cursed/SelectBehaviors(delta_time) - current_behaviors = list() - var/obj/item/item_pawn = pawn - - - //make sure we have a target - var/mob/living/carbon/curse_target = blackboard[BB_CURSE_TARGET] - if(!curse_target) - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/find_and_set/cursed) - return - //make sure attack is valid - if(get_dist(curse_target, item_pawn) > CURSED_VIEW_RANGE) - blackboard[BB_CURSE_TARGET] = null - return - current_movement_target = curse_target - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/item_move_close_and_attack/cursed) - /datum/ai_controller/cursed/PerformIdleBehavior(delta_time) var/obj/item/item_pawn = pawn if(ismob(item_pawn.loc)) //Being held. dont teleport diff --git a/code/datums/ai/cursed/cursed_subtrees.dm b/code/datums/ai/cursed/cursed_subtrees.dm new file mode 100644 index 00000000000..a6920654775 --- /dev/null +++ b/code/datums/ai/cursed/cursed_subtrees.dm @@ -0,0 +1,14 @@ +/datum/ai_planning_subtree/cursed/SelectBehaviors(datum/ai_controller/controller, delta_time) + var/obj/item/item_pawn = controller.pawn + + //make sure we have a target + var/mob/living/carbon/curse_target = controller.blackboard[BB_CURSE_TARGET] + if(!curse_target) + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/find_and_set/cursed)) + return + //make sure attack is valid + if(get_dist(curse_target, item_pawn) > CURSED_VIEW_RANGE) + controller.blackboard[BB_CURSE_TARGET] = null + return + controller.current_movement_target = curse_target + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/item_move_close_and_attack/cursed)) diff --git a/code/datums/ai/dog/dog_controller.dm b/code/datums/ai/dog/dog_controller.dm index da2ed7b44eb..0fc6f0d6dbb 100644 --- a/code/datums/ai/dog/dog_controller.dm +++ b/code/datums/ai/dog/dog_controller.dm @@ -9,10 +9,10 @@ BB_DOG_PLAYING_DEAD = FALSE,\ BB_DOG_HARASS_TARGET = null) ai_movement = /datum/ai_movement/jps + planning_subtrees = list(/datum/ai_planning_subtree/dog) COOLDOWN_DECLARE(heel_cooldown) COOLDOWN_DECLARE(command_cooldown) - COOLDOWN_DECLARE(reset_ignore_cooldown) /datum/ai_controller/dog/process(delta_time) @@ -55,52 +55,6 @@ return simple_pawn.access_card -/datum/ai_controller/dog/SelectBehaviors(delta_time) - current_behaviors = list() - var/mob/living/living_pawn = pawn - - // occasionally reset our ignore list - if(COOLDOWN_FINISHED(src, reset_ignore_cooldown) && length(blackboard[BB_FETCH_IGNORE_LIST])) - COOLDOWN_START(src, reset_ignore_cooldown, AI_FETCH_IGNORE_DURATION) - blackboard[BB_FETCH_IGNORE_LIST] = list() - - // if we were just ordered to heel, chill out for a bit - if(!COOLDOWN_FINISHED(src, heel_cooldown)) - return - - // if we're not already carrying something and we have a fetch target (and we're not already doing something with it), see if we can eat/equip it - if(!blackboard[BB_SIMPLE_CARRY_ITEM] && blackboard[BB_FETCH_TARGET]) - var/atom/movable/interact_target = blackboard[BB_FETCH_TARGET] - if(in_range(living_pawn, interact_target) && (isturf(interact_target.loc))) - current_movement_target = interact_target - if(IS_EDIBLE(interact_target)) - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/eat_snack) - else if(isitem(interact_target)) - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/simple_equip) - else - blackboard[BB_FETCH_TARGET] = null - blackboard[BB_FETCH_DELIVER_TO] = null - return - - // if we're carrying something and we have a destination to deliver it, do that - if(blackboard[BB_SIMPLE_CARRY_ITEM] && blackboard[BB_FETCH_DELIVER_TO]) - var/atom/return_target = blackboard[BB_FETCH_DELIVER_TO] - if(!can_see(pawn, return_target, length=AI_DOG_VISION_RANGE)) - // if the return target isn't in sight, we'll just forget about it and carry the thing around - blackboard[BB_FETCH_DELIVER_TO] = null - return - current_movement_target = return_target - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/deliver_item) - return - - // occasionally see if there's any loose snacks in sight nearby - if(DT_PROB(40, delta_time)) - for(var/obj/item/potential_snack in oview(living_pawn,2)) - if(IS_EDIBLE(potential_snack) && (isturf(potential_snack.loc) || ishuman(potential_snack.loc))) - current_movement_target = potential_snack - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/eat_snack) - return - /datum/ai_controller/dog/PerformIdleBehavior(delta_time) var/mob/living/living_pawn = pawn if(!isturf(living_pawn.loc) || living_pawn.pulledby) @@ -150,7 +104,7 @@ current_movement_target = thrown_thing blackboard[BB_FETCH_TARGET] = thrown_thing blackboard[BB_FETCH_DELIVER_TO] = throwing_datum.thrower - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/fetch) + LAZYADD(current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/fetch)) /// Someone's interacting with us by hand, see if they're being nice or mean /datum/ai_controller/dog/proc/on_attack_hand(datum/source, mob/living/user) @@ -299,7 +253,7 @@ if(COMMAND_DIE) blackboard[BB_DOG_ORDER_MODE] = DOG_COMMAND_NONE CancelActions() - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/play_dead) + LAZYADD(current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/play_dead)) /// Someone we like is pointing at something, see if it's something we might want to interact with (like if they might want us to fetch something for them) /datum/ai_controller/dog/proc/check_point(mob/pointing_friend, atom/movable/pointed_movable) @@ -330,12 +284,12 @@ blackboard[BB_FETCH_TARGET] = pointed_movable blackboard[BB_FETCH_DELIVER_TO] = pointing_friend if(living_pawn.buckled) - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/resist)//in case they are in bed or something - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/fetch) + LAZYADD(current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/resist))//in case they are in bed or something + LAZYADD(current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/fetch)) if(DOG_COMMAND_ATTACK) pawn.visible_message(span_notice("[pawn] follows [pointing_friend]'s gesture towards [pointed_movable] and growls intensely!")) current_movement_target = pointed_movable blackboard[BB_DOG_HARASS_TARGET] = WEAKREF(pointed_movable) if(living_pawn.buckled) - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/resist)//in case they are in bed or something - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/harass) + LAZYADD(current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/resist))//in case they are in bed or something + LAZYADD(current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/harass)) diff --git a/code/datums/ai/dog/dog_subtrees.dm b/code/datums/ai/dog/dog_subtrees.dm new file mode 100644 index 00000000000..1eab7b87251 --- /dev/null +++ b/code/datums/ai/dog/dog_subtrees.dm @@ -0,0 +1,40 @@ +/datum/ai_planning_subtree/dog + COOLDOWN_DECLARE(heel_cooldown) + COOLDOWN_DECLARE(reset_ignore_cooldown) + +/datum/ai_planning_subtree/dog/SelectBehaviors(datum/ai_controller/dog/controller, delta_time) + var/mob/living/living_pawn = controller.pawn + + // occasionally reset our ignore list + if(COOLDOWN_FINISHED(src, reset_ignore_cooldown) && length(controller.blackboard[BB_FETCH_IGNORE_LIST])) + COOLDOWN_START(src, reset_ignore_cooldown, AI_FETCH_IGNORE_DURATION) + controller.blackboard[BB_FETCH_IGNORE_LIST] = list() + + // if we were just ordered to heel, chill out for a bit + if(!COOLDOWN_FINISHED(src, heel_cooldown)) + return + + // if we're not already carrying something and we have a fetch target (and we're not already doing something with it), see if we can eat/equip it + if(!controller.blackboard[BB_SIMPLE_CARRY_ITEM] && controller.blackboard[BB_FETCH_TARGET]) + var/atom/movable/interact_target = controller.blackboard[BB_FETCH_TARGET] + if(in_range(living_pawn, interact_target) && (isturf(interact_target.loc))) + controller.current_movement_target = interact_target + if(IS_EDIBLE(interact_target)) + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/eat_snack)) + else if(isitem(interact_target)) + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/simple_equip)) + else + controller.blackboard[BB_FETCH_TARGET] = null + controller.blackboard[BB_FETCH_DELIVER_TO] = null + return + + // if we're carrying something and we have a destination to deliver it, do that + if(controller.blackboard[BB_SIMPLE_CARRY_ITEM] && controller.blackboard[BB_FETCH_DELIVER_TO]) + var/atom/return_target = controller.blackboard[BB_FETCH_DELIVER_TO] + if(!can_see(controller.pawn, return_target, length=AI_DOG_VISION_RANGE)) + // if the return target isn't in sight, we'll just forget about it and carry the thing around + controller.blackboard[BB_FETCH_DELIVER_TO] = null + return + controller.current_movement_target = return_target + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/deliver_item)) + return diff --git a/code/datums/ai/hauntium/haunted_controller.dm b/code/datums/ai/hauntium/haunted_controller.dm index 87f4ebfc21d..6e9670a85da 100644 --- a/code/datums/ai/hauntium/haunted_controller.dm +++ b/code/datums/ai/hauntium/haunted_controller.dm @@ -4,6 +4,7 @@ blackboard = list(BB_TO_HAUNT_LIST = list(), BB_HAUNT_TARGET, BB_HAUNTED_THROW_ATTEMPT_COUNT) + planning_subtrees = list(/datum/ai_planning_subtree/haunted) /datum/ai_controller/haunted/TryPossessPawn(atom/new_pawn) if(!isitem(new_pawn)) @@ -15,30 +16,6 @@ UnregisterSignal(pawn, COMSIG_ITEM_EQUIPPED) return ..() //Run parent at end -/datum/ai_controller/haunted/SelectBehaviors(delta_time) - current_behaviors = list() - var/obj/item/item_pawn = pawn - - if(ismob(item_pawn.loc)) //We're being held, maybe escape? - if(DT_PROB(HAUNTED_ITEM_ESCAPE_GRASP_CHANCE, delta_time)) - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/item_escape_grasp) - return - - if(!DT_PROB(HAUNTED_ITEM_ATTACK_HAUNT_CHANCE, delta_time)) - return - - var/list/to_haunt_list = blackboard[BB_TO_HAUNT_LIST] - - for(var/i in to_haunt_list) - if(to_haunt_list[i] <= 0) - continue - var/mob/living/potential_target = i - if(get_dist(potential_target, item_pawn) <= 7) - blackboard[BB_HAUNT_TARGET] = potential_target - current_movement_target = potential_target - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/item_move_close_and_attack/haunted) - return - /datum/ai_controller/haunted/PerformIdleBehavior(delta_time) var/obj/item/item_pawn = pawn if(ismob(item_pawn.loc)) //Being held. dont teleport diff --git a/code/datums/ai/hauntium/hauntium_subtrees.dm b/code/datums/ai/hauntium/hauntium_subtrees.dm new file mode 100644 index 00000000000..a1b043897bb --- /dev/null +++ b/code/datums/ai/hauntium/hauntium_subtrees.dm @@ -0,0 +1,22 @@ +/datum/ai_planning_subtree/haunted/SelectBehaviors(datum/ai_controller/controller, delta_time) + var/obj/item/item_pawn = controller.pawn + + if(ismob(item_pawn.loc)) //We're being held, maybe escape? + if(DT_PROB(HAUNTED_ITEM_ESCAPE_GRASP_CHANCE, delta_time)) + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/item_escape_grasp)) + return SUBTREE_RETURN_FINISH_PLANNING + + if(!DT_PROB(HAUNTED_ITEM_ATTACK_HAUNT_CHANCE, delta_time)) + return + + var/list/to_haunt_list = controller.blackboard[BB_TO_HAUNT_LIST] + + for(var/i in to_haunt_list) + if(to_haunt_list[i] <= 0) + continue + var/mob/living/potential_target = i + if(get_dist(potential_target, item_pawn) <= 7) + controller.blackboard[BB_HAUNT_TARGET] = potential_target + controller.current_movement_target = potential_target + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/item_move_close_and_attack/haunted)) + return SUBTREE_RETURN_FINISH_PLANNING diff --git a/code/datums/ai/hostile/hostile_controller.dm b/code/datums/ai/hostile/hostile_controller.dm index ab15d97b661..d4c68c86a65 100644 --- a/code/datums/ai/hostile/hostile_controller.dm +++ b/code/datums/ai/hostile/hostile_controller.dm @@ -187,8 +187,8 @@ current_movement_target = commander var/mob/living/living_pawn = pawn if(living_pawn.buckled) - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/resist)//in case they are in bed or something - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/follow) + LAZYADD(current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/resist))//in case they are in bed or something + LAZYADD(current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/follow)) // attack: harass whoever the commander points to if(COMMAND_ATTACK) 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 @@ -218,5 +218,5 @@ current_movement_target = pointed_movable blackboard[BB_ATTACK_TARGET] = WEAKREF(pointed_movable) if(living_pawn.buckled) - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/resist)//in case they are in bed or something - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/attack) + LAZYADD(current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/resist))//in case they are in bed or something + LAZYADD(current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/attack)) diff --git a/code/datums/ai/monkey/monkey_controller.dm b/code/datums/ai/monkey/monkey_controller.dm index 9641b497d92..8060cb101b5 100644 --- a/code/datums/ai/monkey/monkey_controller.dm +++ b/code/datums/ai/monkey/monkey_controller.dm @@ -6,6 +6,7 @@ have ways of interacting with a specific mob and control it. /datum/ai_controller/monkey movement_delay = 0.4 SECONDS + planning_subtrees = list(/datum/ai_planning_subtree/monkey_tree) blackboard = list( BB_MONKEY_AGRESSIVE = FALSE, BB_MONKEY_BEST_FORCE_FOUND = 0, @@ -78,92 +79,6 @@ have ways of interacting with a specific mob and control it. if(IS_DEAD_OR_INCAP(living_pawn)) return FALSE -/datum/ai_controller/monkey/SelectBehaviors(delta_time) - current_behaviors = list() - var/mob/living/living_pawn = pawn - - if(SHOULD_RESIST(living_pawn) && DT_PROB(MONKEY_RESIST_PROB, delta_time)) - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/resist) //BRO IM ON FUCKING FIRE BRO - return //IM NOT DOING ANYTHING ELSE BUT EXTUINGISH MYSELF, GOOD GOD HAVE MERCY. - - var/list/enemies = blackboard[BB_MONKEY_ENEMIES] - - if(HAS_TRAIT(pawn, TRAIT_PACIFISM)) //Not a pacifist? lets try some combat behavior. - return - - var/mob/living/selected_enemy - if(length(enemies) || blackboard[BB_MONKEY_AGRESSIVE]) //We have enemies or are pissed - var/list/valids = list() - for(var/mob/living/possible_enemy in view(MONKEY_ENEMY_VISION, living_pawn)) - if(possible_enemy == living_pawn || (!enemies[possible_enemy] && (!blackboard[BB_MONKEY_AGRESSIVE] || HAS_AI_CONTROLLER_TYPE(possible_enemy, /datum/ai_controller/monkey)))) //Are they an enemy? (And do we even care?) - continue - // Weighted list, so the closer they are the more likely they are to be chosen as the enemy - valids[possible_enemy] = CEILING(100 / (get_dist(living_pawn, possible_enemy) || 1), 1) - - selected_enemy = pickweight(valids) - - if(selected_enemy) - if(!selected_enemy.stat) //He's up, get him! - if(living_pawn.health < MONKEY_FLEE_HEALTH) //Time to skeddadle - blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] = selected_enemy - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/monkey_flee) - return //I'm running fuck you guys - - if(TryFindWeapon()) //Getting a weapon is higher priority if im not fleeing. - return - - blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] = selected_enemy - current_movement_target = selected_enemy - if(blackboard[BB_MONKEY_RECRUIT_COOLDOWN] < world.time) - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/recruit_monkeys) - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/battle_screech/monkey) - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/monkey_attack_mob) - return //Focus on this - - else //He's down, can we disposal him? - var/obj/machinery/disposal/bodyDisposal = locate(/obj/machinery/disposal/) in view(MONKEY_ENEMY_VISION, living_pawn) - if(bodyDisposal) - blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] = selected_enemy - blackboard[BB_MONKEY_TARGET_DISPOSAL] = bodyDisposal - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/disposal_mob) - return - - if(prob(5)) - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/use_in_hand) - - if(selected_enemy || !DT_PROB(MONKEY_SHENANIGAN_PROB, delta_time)) - return - - if(world.time >= blackboard[BB_MONKEY_NEXT_HUNGRY] && TryFindFood()) - return - - if(prob(50)) - var/list/possible_targets = list() - for(var/atom/thing in view(2, living_pawn)) - if(!thing.mouse_opacity) - continue - if(thing.IsObscured()) - continue - possible_targets += thing - var/atom/target = pick(possible_targets) - if(target) - current_movement_target = target - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/use_on_object) - return - - if(prob(5) && (locate(/obj/item) in living_pawn.held_items)) - var/list/possible_receivers = list() - for(var/mob/living/candidate in oview(2, pawn)) - possible_receivers += candidate - - if(length(possible_receivers)) - var/mob/living/target = pick(possible_receivers) - current_movement_target = target - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/give) - return - - TryFindWeapon() - ///re-used behavior pattern by monkeys for finding a weapon /datum/ai_controller/monkey/proc/TryFindWeapon() var/mob/living/living_pawn = pawn @@ -196,9 +111,9 @@ have ways of interacting with a specific mob and control it. blackboard[BB_MONKEY_PICKUPTARGET] = weapon current_movement_target = weapon if(pickpocket) - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/monkey_equip/pickpocket) + LAZYADD(current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/monkey_equip/pickpocket)) else - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/monkey_equip/ground) + LAZYADD(current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/monkey_equip/ground)) return TRUE /// Returns either the best weapon from the given choices or null if held weapons are better diff --git a/code/datums/ai/monkey/monkey_subtrees.dm b/code/datums/ai/monkey/monkey_subtrees.dm new file mode 100644 index 00000000000..faeb2391aed --- /dev/null +++ b/code/datums/ai/monkey/monkey_subtrees.dm @@ -0,0 +1,84 @@ +/datum/ai_planning_subtree/monkey_tree/SelectBehaviors(datum/ai_controller/monkey/controller, delta_time) + var/mob/living/living_pawn = controller.pawn + + if(SHOULD_RESIST(living_pawn) && DT_PROB(MONKEY_RESIST_PROB, delta_time)) + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/resist)) //BRO IM ON FUCKING FIRE BRO + return SUBTREE_RETURN_FINISH_PLANNING //IM NOT DOING ANYTHING ELSE BUT EXTUINGISH MYSELF, GOOD GOD HAVE MERCY. + + var/list/enemies = controller.blackboard[BB_MONKEY_ENEMIES] + + if(HAS_TRAIT(controller.pawn, TRAIT_PACIFISM)) //Not a pacifist? lets try some combat behavior. + return + + var/mob/living/selected_enemy + if(length(enemies) || controller.blackboard[BB_MONKEY_AGRESSIVE]) //We have enemies or are pissed + var/list/valids = list() + for(var/mob/living/possible_enemy in view(MONKEY_ENEMY_VISION, living_pawn)) + if(possible_enemy == living_pawn || (!enemies[possible_enemy] && (!controller.blackboard[BB_MONKEY_AGRESSIVE] || HAS_AI_CONTROLLER_TYPE(possible_enemy, /datum/ai_controller/monkey)))) //Are they an enemy? (And do we even care?) + continue + // Weighted list, so the closer they are the more likely they are to be chosen as the enemy + valids[possible_enemy] = CEILING(100 / (get_dist(living_pawn, possible_enemy) || 1), 1) + + selected_enemy = pickweight(valids) + + if(selected_enemy) + if(!selected_enemy.stat) //He's up, get him! + if(living_pawn.health < MONKEY_FLEE_HEALTH) //Time to skeddadle + controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] = selected_enemy + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/monkey_flee)) + return //I'm running fuck you guys + + if(controller.TryFindWeapon()) //Getting a weapon is higher priority if im not fleeing. + return SUBTREE_RETURN_FINISH_PLANNING + + controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] = selected_enemy + controller.current_movement_target = selected_enemy + if(controller.blackboard[BB_MONKEY_RECRUIT_COOLDOWN] < world.time) + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/recruit_monkeys)) + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/battle_screech/monkey)) + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/monkey_attack_mob)) + return SUBTREE_RETURN_FINISH_PLANNING //Focus on this + + else //He's down, can we disposal him? + var/obj/machinery/disposal/bodyDisposal = locate(/obj/machinery/disposal/) in view(MONKEY_ENEMY_VISION, living_pawn) + if(bodyDisposal) + controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] = selected_enemy + controller.blackboard[BB_MONKEY_TARGET_DISPOSAL] = bodyDisposal + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/disposal_mob)) + return SUBTREE_RETURN_FINISH_PLANNING + + if(prob(5)) + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/use_in_hand)) + + if(selected_enemy || !DT_PROB(MONKEY_SHENANIGAN_PROB, delta_time)) + return + + if(world.time >= controller.blackboard[BB_MONKEY_NEXT_HUNGRY] && controller.TryFindFood()) + return + + if(prob(50)) + var/list/possible_targets = list() + for(var/atom/thing in view(2, living_pawn)) + if(!thing.mouse_opacity) + continue + if(thing.IsObscured()) + continue + possible_targets += thing + var/atom/target = pick(possible_targets) + if(target) + controller.current_movement_target = target + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/use_on_object)) + return + + if(prob(5) && (locate(/obj/item) in living_pawn.held_items)) + var/list/possible_receivers = list() + for(var/mob/living/candidate in oview(2, controller.pawn)) + possible_receivers += candidate + + if(length(possible_receivers)) + var/mob/living/target = pick(possible_receivers) + controller.current_movement_target = target + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/give)) + return + + controller.TryFindWeapon() diff --git a/code/datums/ai/movement/ai_movement_jps.dm b/code/datums/ai/movement/ai_movement_jps.dm index 015fea1f242..ea05b0fc899 100644 --- a/code/datums/ai/movement/ai_movement_jps.dm +++ b/code/datums/ai/movement/ai_movement_jps.dm @@ -19,9 +19,10 @@ // right now I'm just taking the shortest minimum distance of our current behaviors, at some point in the future // we should let whatever sets the current_movement_target also set the min distance and max path length // (or at least cache it on the controller) - for(var/datum/ai_behavior/iter_behavior as anything in controller.current_behaviors) - if(iter_behavior.required_distance < minimum_distance) - minimum_distance = iter_behavior.required_distance + if(LAZYLEN(controller.current_behaviors)) + for(var/datum/ai_behavior/iter_behavior as anything in controller.current_behaviors) + if(iter_behavior.required_distance < minimum_distance) + minimum_distance = iter_behavior.required_distance if(get_dist(movable_pawn, controller.current_movement_target) <= minimum_distance) continue diff --git a/code/datums/ai/objects/vending_machines/vending_machine_controller.dm b/code/datums/ai/objects/vending_machines/vending_machine_controller.dm index 86b617b4966..66a3ff660c9 100644 --- a/code/datums/ai/objects/vending_machines/vending_machine_controller.dm +++ b/code/datums/ai/objects/vending_machines/vending_machine_controller.dm @@ -34,7 +34,7 @@ if(vendor_pawn.tilted) //We're tilted, try to untilt if(blackboard[BB_VENDING_UNTILT_COOLDOWN] > world.time) return - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/vendor_rise_up) + LAZYADD(current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/vendor_rise_up)) return else //Not tilted, try to find target to tilt onto. if(blackboard[BB_VENDING_TILT_COOLDOWN] > world.time) @@ -44,6 +44,6 @@ continue current_movement_target = living_target blackboard[BB_VENDING_CURRENT_TARGET] = living_target - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/vendor_crush) + LAZYADD(current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/vendor_crush)) return blackboard[BB_VENDING_TILT_COOLDOWN] = world.time + search_for_enemy_cooldown diff --git a/code/datums/ai/robot_customer/robot_customer_controller.dm b/code/datums/ai/robot_customer/robot_customer_controller.dm index 1ab1e7d5359..2c59fe5d94c 100644 --- a/code/datums/ai/robot_customer/robot_customer_controller.dm +++ b/code/datums/ai/robot_customer/robot_customer_controller.dm @@ -9,7 +9,7 @@ BB_CUSTOMER_LEAVING = FALSE, BB_CUSTOMER_ATTENDING_VENUE = null, BB_CUSTOMER_SAID_CANT_FIND_SEAT_LINE = FALSE) - + planning_subtrees = list(/datum/ai_planning_subtree/robot_customer) /datum/ai_controller/robot_customer/TryPossessPawn(atom/new_pawn) if(!istype(new_pawn, /mob/living/simple_animal/robot_customer)) @@ -23,34 +23,6 @@ UnregisterSignal(pawn, list(COMSIG_PARENT_ATTACKBY, COMSIG_LIVING_GET_PULLED, COMSIG_ATOM_ATTACK_HAND)) return ..() //Run parent at end -/datum/ai_controller/robot_customer/SelectBehaviors(delta_time) - current_behaviors = list() - if(blackboard[BB_CUSTOMER_LEAVING]) - var/datum/venue/attending_venue = blackboard[BB_CUSTOMER_ATTENDING_VENUE] - current_movement_target = attending_venue.restaurant_portal - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/leave_venue) - return - - if(blackboard[BB_CUSTOMER_CURRENT_TARGET]) - current_movement_target = blackboard[BB_CUSTOMER_CURRENT_TARGET] - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/break_spine/robot_customer) - return - - var/obj/my_seat = blackboard[BB_CUSTOMER_MY_SEAT] - - if(!my_seat) //We havn't got a seat yet! find one! - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/find_seat) - return - - current_movement_target = my_seat - - if(!blackboard[BB_CUSTOMER_CURRENT_ORDER]) //We havn't ordered yet even ordered yet. go on! go over there and go do it! - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/order_food) - return - else - current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/wait_for_food) - - /datum/ai_controller/robot_customer/proc/on_attackby(datum/source, obj/item/I, mob/living/user) SIGNAL_HANDLER var/datum/venue/attending_venue = blackboard[BB_CUSTOMER_ATTENDING_VENUE] diff --git a/code/datums/ai/robot_customer/robot_customer_subtrees.dm b/code/datums/ai/robot_customer/robot_customer_subtrees.dm new file mode 100644 index 00000000000..d87643dae71 --- /dev/null +++ b/code/datums/ai/robot_customer/robot_customer_subtrees.dm @@ -0,0 +1,25 @@ +/datum/ai_planning_subtree/robot_customer/SelectBehaviors(datum/ai_controller/controller, delta_time) + if(controller.blackboard[BB_CUSTOMER_LEAVING]) + var/datum/venue/attending_venue = controller.blackboard[BB_CUSTOMER_ATTENDING_VENUE] + controller.current_movement_target = attending_venue.restaurant_portal + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/leave_venue)) + return SUBTREE_RETURN_FINISH_PLANNING + + if(controller.blackboard[BB_CUSTOMER_CURRENT_TARGET]) + controller.current_movement_target = controller.blackboard[BB_CUSTOMER_CURRENT_TARGET] + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/break_spine/robot_customer)) + return SUBTREE_RETURN_FINISH_PLANNING + + var/obj/my_seat = controller.blackboard[BB_CUSTOMER_MY_SEAT] + + if(!my_seat) //We havn't got a seat yet! find one! + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/find_seat)) + return SUBTREE_RETURN_FINISH_PLANNING + + controller.current_movement_target = my_seat + + if(!controller.blackboard[BB_CUSTOMER_CURRENT_ORDER]) //We havn't ordered yet even ordered yet. go on! go over there and go do it! + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/order_food)) + return SUBTREE_RETURN_FINISH_PLANNING + else + LAZYADD(controller.current_behaviors, GET_AI_BEHAVIOR(/datum/ai_behavior/wait_for_food)) diff --git a/tgstation.dme b/tgstation.dme index f652fbe8b74..c94fe70040e 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -343,6 +343,7 @@ #include "code\controllers\subsystem\achievements.dm" #include "code\controllers\subsystem\addiction.dm" #include "code\controllers\subsystem\adjacent_air.dm" +#include "code\controllers\subsystem\ai_controllers.dm" #include "code\controllers\subsystem\air.dm" #include "code\controllers\subsystem\ambience.dm" #include "code\controllers\subsystem\assets.dm" @@ -414,7 +415,7 @@ #include "code\controllers\subsystem\vote.dm" #include "code\controllers\subsystem\weather.dm" #include "code\controllers\subsystem\processing\acid.dm" -#include "code\controllers\subsystem\processing\ai_controllers.dm" +#include "code\controllers\subsystem\processing\ai_behaviors.dm" #include "code\controllers\subsystem\processing\ai_movement.dm" #include "code\controllers\subsystem\processing\clock_component.dm" #include "code\controllers\subsystem\processing\fastprocess.dm" @@ -487,19 +488,25 @@ #include "code\datums\actions\beam_rifle.dm" #include "code\datums\ai\_ai_behavior.dm" #include "code\datums\ai\_ai_controller.dm" +#include "code\datums\ai\_ai_planning_subtree.dm" #include "code\datums\ai\_item_behaviors.dm" #include "code\datums\ai\generic_actions.dm" #include "code\datums\ai\telegraph_effects.dm" #include "code\datums\ai\bane\bane_behaviors.dm" #include "code\datums\ai\bane\bane_controller.dm" +#include "code\datums\ai\bane\bane_subtrees.dm" #include "code\datums\ai\cursed\cursed_behaviors.dm" #include "code\datums\ai\cursed\cursed_controller.dm" +#include "code\datums\ai\cursed\cursed_subtrees.dm" #include "code\datums\ai\dog\dog_behaviors.dm" #include "code\datums\ai\dog\dog_controller.dm" +#include "code\datums\ai\dog\dog_subtrees.dm" #include "code\datums\ai\hauntium\haunted_controller.dm" +#include "code\datums\ai\hauntium\hauntium_subtrees.dm" #include "code\datums\ai\hostile\hostile_controller.dm" #include "code\datums\ai\monkey\monkey_behaviors.dm" #include "code\datums\ai\monkey\monkey_controller.dm" +#include "code\datums\ai\monkey\monkey_subtrees.dm" #include "code\datums\ai\movement\_ai_movement.dm" #include "code\datums\ai\movement\ai_movement_basic_avoidance.dm" #include "code\datums\ai\movement\ai_movement_dumb.dm" @@ -508,6 +515,7 @@ #include "code\datums\ai\objects\vending_machines\vending_machine_controller.dm" #include "code\datums\ai\robot_customer\robot_customer_behaviors.dm" #include "code\datums\ai\robot_customer\robot_customer_controller.dm" +#include "code\datums\ai\robot_customer\robot_customer_subtrees.dm" #include "code\datums\announcers\_announcer.dm" #include "code\datums\announcers\default_announcer.dm" #include "code\datums\announcers\intern_announcer.dm"