-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
/tg/ AI controllers, part 1: core implementation. (#28065)
* /tg/ AI controllers, part 1: core implementation. * lewc review * remove unused arg * lewc review 2 * lint fix
- Loading branch information
1 parent
d7c4822
commit 81b3a20
Showing
23 changed files
with
1,486 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
#define GET_AI_BEHAVIOR(behavior_type) SSai_behaviors.ai_behaviors[behavior_type] | ||
#define GET_TARGETING_STRATEGY(targeting_type) SSai_behaviors.targeting_strategies[targeting_type] | ||
#define HAS_AI_CONTROLLER_TYPE(thing, type) istype(thing?.ai_controller, type) | ||
|
||
//AI controller flags | ||
//If you add a new status, be sure to add it to the ai_controllers subsystem's ai_controllers_by_status list. | ||
/// The AI is currently active. | ||
#define AI_STATUS_ON "ai_on" | ||
/// The AI is currently offline for any reason. | ||
#define AI_STATUS_OFF "ai_off" | ||
/// The AI is currently in idle mode. | ||
#define AI_STATUS_IDLE "ai_idle" | ||
|
||
// How far should we, by default, be looking for interesting things to de-idle? | ||
#define AI_DEFAULT_INTERESTING_DIST 10 | ||
|
||
/// 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) | ||
|
||
// Return flags for ai_behavior/perform() | ||
|
||
/// Update this behavior's cooldown | ||
#define AI_BEHAVIOR_DELAY (1<<0) | ||
/// Finish the behavior successfully | ||
#define AI_BEHAVIOR_SUCCEEDED (1<<1) | ||
/// Finish the behavior unsuccessfully | ||
#define AI_BEHAVIOR_FAILED (1<<2) | ||
|
||
#define AI_BEHAVIOR_INSTANT (NONE) | ||
|
||
/// Does this task require movement from the AI before it can be performed? | ||
#define AI_BEHAVIOR_REQUIRE_MOVEMENT (1<<0) | ||
/// Does this require the current_movement_target to be adjacent and in reach? | ||
#define AI_BEHAVIOR_REQUIRE_REACH (1<<1) | ||
/// Does this task let you perform the action while you move closer? (Things like moving and shooting) | ||
#define AI_BEHAVIOR_MOVE_AND_PERFORM (1<<2) | ||
/// Does finishing this task not null the current movement target? | ||
#define AI_BEHAVIOR_KEEP_MOVE_TARGET_ON_FINISH (1<<3) | ||
/// Does this behavior NOT block planning? | ||
#define AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION (1<<4) | ||
|
||
// AI flags | ||
|
||
/// Don't move if being pulled | ||
#define AI_FLAG_STOP_MOVING_WHEN_PULLED (1<<0) | ||
/// Continue processing even if dead | ||
#define AI_FLAG_CAN_ACT_WHILE_DEAD (1<<1) | ||
/// Stop processing while in a progress bar | ||
#define AI_FLAG_PAUSE_DURING_DO_AFTER (1<<2) | ||
/// Continue processing while in stasis | ||
#define AI_FLAG_CAN_ACT_IN_STASIS (1<<3) | ||
|
||
// Base Subtree defines | ||
|
||
/// This subtree should cancel any further planning, (Including from other subtrees) | ||
#define SUBTREE_RETURN_FINISH_PLANNING 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
// Generic blackboard keys. | ||
// This file will get a lot larger as AI subtrees are added. | ||
|
||
#define BB_CURRENT_MIN_MOVE_DISTANCE "min_move_distance" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
///sent from ai controllers when a behavior is inserted into the queue: (list/new_arguments) | ||
#define AI_CONTROLLER_BEHAVIOR_QUEUED(type) "ai_controller_behavior_queued_[type]" | ||
|
||
/// Signal sent when a blackboard key is set to a new value | ||
#define COMSIG_AI_BLACKBOARD_KEY_SET(blackboard_key) "ai_blackboard_key_set_[blackboard_key]" | ||
|
||
///Signal sent before a blackboard key is cleared | ||
#define COMSIG_AI_BLACKBOARD_KEY_PRECLEAR(blackboard_key) "ai_blackboard_key_pre_clear_[blackboard_key]" | ||
|
||
/// Signal sent when a blackboard key is cleared | ||
#define COMSIG_AI_BLACKBOARD_KEY_CLEARED(blackboard_key) "ai_blackboard_key_clear_[blackboard_key]" | ||
|
||
//from base of atom/attack_basic_mob(): (/mob/user) | ||
#define COMSIG_ATOM_ATTACK_BASIC_MOB "attack_basic_mob" | ||
|
||
///sent from ai controllers when they possess a pawn: (datum/ai_controller/source_controller) | ||
#define COMSIG_AI_CONTROLLER_POSSESSED_PAWN "ai_controller_possessed_pawn" | ||
///sent from ai controllers when they pick behaviors: (list/datum/ai_behavior/old_behaviors, list/datum/ai_behavior/new_behaviors) | ||
#define COMSIG_AI_CONTROLLER_PICKED_BEHAVIORS "ai_controller_picked_behaviors" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
/// 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 | ||
init_order = INIT_ORDER_AI_CONTROLLERS | ||
wait = 0.5 SECONDS //Plan every half second if required, not great not terrible. | ||
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME | ||
|
||
///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/datum/ai_planning_subtree/ai_subtrees = list() | ||
///Assoc List of all AI statuses and all AI controllers with that status. | ||
var/list/ai_controllers_by_status = list( | ||
AI_STATUS_ON = list(), | ||
AI_STATUS_OFF = list(), | ||
AI_STATUS_IDLE = list(), | ||
) | ||
///Assoc List of all AI controllers and the Z level they are on, which we check when someone enters/leaves a Z level to turn them on/off. | ||
var/list/ai_controllers_by_zlevel = list() | ||
/// The tick cost of all active AI, calculated on fire. | ||
var/cost_on | ||
/// The tick cost of all idle AI, calculated on fire. | ||
var/cost_idle | ||
|
||
|
||
/datum/controller/subsystem/ai_controllers/Initialize() | ||
setup_subtrees() | ||
|
||
/datum/controller/subsystem/ai_controllers/stat_entry(msg) | ||
var/list/active_list = ai_controllers_by_status[AI_STATUS_ON] | ||
var/list/inactive_list = ai_controllers_by_status[AI_STATUS_OFF] | ||
var/list/idle_list = ai_controllers_by_status[AI_STATUS_IDLE] | ||
msg = "Active AIs:[length(active_list)]/[round(cost_on,1)]%|Inactive:[length(inactive_list)]|Idle:[length(idle_list)]/[round(cost_idle,1)]%" | ||
return ..() | ||
|
||
/datum/controller/subsystem/ai_controllers/fire(resumed) | ||
var/timer = TICK_USAGE_REAL | ||
cost_idle = MC_AVERAGE(cost_idle, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer)) | ||
|
||
timer = TICK_USAGE_REAL | ||
for(var/datum/ai_controller/ai_controller as anything in ai_controllers_by_status[AI_STATUS_ON]) | ||
if(!COOLDOWN_FINISHED(ai_controller, failed_planning_cooldown)) | ||
continue | ||
|
||
if(!ai_controller.able_to_plan()) | ||
continue | ||
ai_controller.select_behaviors(wait / (1 SECONDS)) | ||
if(!LAZYLEN(ai_controller.current_behaviors)) //Still no plan | ||
COOLDOWN_START(ai_controller, failed_planning_cooldown, AI_FAILED_PLANNING_COOLDOWN) | ||
|
||
cost_on = MC_AVERAGE(cost_on, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer)) | ||
|
||
///Creates all instances of ai_subtrees and assigns them to the ai_subtrees list. | ||
/datum/controller/subsystem/ai_controllers/proc/setup_subtrees() | ||
for(var/subtree_type in subtypesof(/datum/ai_planning_subtree)) | ||
var/datum/ai_planning_subtree/subtree = new subtree_type | ||
ai_subtrees[subtree_type] = subtree | ||
|
||
///Called when the max Z level was changed, updating our coverage. | ||
/datum/controller/subsystem/ai_controllers/proc/on_max_z_changed() | ||
if(!islist(ai_controllers_by_zlevel)) | ||
ai_controllers_by_zlevel = new /list(world.maxz, 0) | ||
while(SSai_controllers.ai_controllers_by_zlevel.len < world.maxz) | ||
SSai_controllers.ai_controllers_by_zlevel.len++ | ||
SSai_controllers.ai_controllers_by_zlevel[ai_controllers_by_zlevel.len] = list() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/// The subsystem used to tick [/datum/ai_movement] instances. Handling the movement of individual AI instances | ||
MOVEMENT_SUBSYSTEM_DEF(ai_movement) | ||
name = "AI movement" | ||
flags = SS_BACKGROUND|SS_TICKER | ||
priority = FIRE_PRIORITY_NPC_MOVEMENT | ||
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME | ||
init_order = INIT_ORDER_AI_MOVEMENT | ||
|
||
///an assoc list of all ai_movement types. Assoc type to instance | ||
var/list/movement_types | ||
|
||
/datum/controller/subsystem/movement/ai_movement/Initialize() | ||
setup_ai_movement_instances() | ||
|
||
/datum/controller/subsystem/movement/ai_movement/proc/setup_ai_movement_instances() | ||
movement_types = list() | ||
for(var/key as anything in subtypesof(/datum/ai_movement)) | ||
var/datum/ai_movement/ai_movement = new key | ||
movement_types[key] = ai_movement |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/// 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 setup_ai_behaviors(). | ||
var/list/ai_behaviors | ||
/// List of all targeting_strategy singletons, key is the typepath while assigned | ||
/// value is a newly created instance of the typepath. See setup_targeting_strats(). | ||
var/list/targeting_strategies | ||
|
||
/datum/controller/subsystem/processing/ai_behaviors/Initialize() | ||
setup_ai_behaviors() | ||
setup_targeting_strats() | ||
|
||
/datum/controller/subsystem/processing/ai_behaviors/proc/setup_ai_behaviors() | ||
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 | ||
|
||
/datum/controller/subsystem/processing/ai_behaviors/proc/setup_targeting_strats() | ||
targeting_strategies = list() | ||
for(var/target_type in subtypesof(/datum/targeting_strategy)) | ||
var/datum/targeting_strategy/target_start = new target_type | ||
targeting_strategies[target_type] = target_start |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/// Abstract class for an action an AI can take. Can range from movement to grabbing a nearby weapon. | ||
/datum/ai_behavior | ||
/// What distance you need to be from the target to perform the action. | ||
var/required_distance = 1 | ||
/// Flags for extra behavior | ||
var/behavior_flags = NONE | ||
/// Cooldown between actions performances, defaults to the value of | ||
/// CLICK_CD_MELEE because that seemed like a nice standard for the speed of | ||
/// AI behavior | ||
var/action_cooldown = CLICK_CD_MELEE | ||
|
||
/// Called by the AI controller when first being added. Additional arguments | ||
/// depend on the behavior type. For example, if the behavior involves attacking | ||
/// a mob, you may require an argument naming the blackboard key which points to | ||
/// the target. Return FALSE to cancel. | ||
/datum/ai_behavior/proc/setup(datum/ai_controller/controller, ...) | ||
return TRUE | ||
|
||
/// Returns the delay to use for this behavior in the moment. The default | ||
/// behavior cooldown is `CLICK_CD_MELEE`, but can be customized; for example, | ||
/// you may want a mob crawling through vents to move slowly and at a random | ||
/// pace between pipes. | ||
/datum/ai_behavior/proc/get_cooldown(datum/ai_controller/cooldown_for) | ||
return action_cooldown | ||
|
||
/// Called by the AI controller when this action is performed. This will | ||
/// typically require consulting the blackboard for information on the specific | ||
/// actions desired from this behavior, by passing the relevant blackboard data | ||
/// keys to this proc. Returns a combination of [AI_BEHAVIOR_DELAY] or | ||
/// [AI_BEHAVIOR_INSTANT], determining whether or not a cooldown occurs, and | ||
/// [AI_BEHAVIOR_SUCCEEDED] or [AI_BEHAVIOR_FAILED]. The behavior's | ||
/// `finish_action` proc is given TRUE or FALSE depending on whether or not the | ||
/// return value of `perform` is marked as successful or unsuccessful. | ||
/datum/ai_behavior/proc/perform(seconds_per_tick, datum/ai_controller/controller, ...) | ||
controller.behavior_cooldowns[src] = world.time + action_cooldown | ||
|
||
/// Called when the action is finished. This needs the same args as `perform` | ||
/// besides the default ones. This should be used to clear up the blackboard of | ||
/// any unnecessary or obsolete data, and update the state of the pawn if | ||
/// necessary once we know whether or not the AI action was successful. | ||
/// `succeeded` is `TRUE` or `FALSE` depending on whether | ||
/// [/datum/ai_behavior/proc/perform] returns [AI_BEHAVIOR_SUCCEEDED] or | ||
/// [AI_BEHAVIOR_FAILED]. | ||
/datum/ai_behavior/proc/finish_action(datum/ai_controller/controller, succeeded, ...) | ||
LAZYREMOVE(controller.current_behaviors, src) | ||
controller.behavior_args -= type | ||
// If this was a movement task, reset our movement target if necessary | ||
if(!(behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT)) | ||
return | ||
if(behavior_flags & AI_BEHAVIOR_KEEP_MOVE_TARGET_ON_FINISH) | ||
return | ||
clear_movement_target(controller) | ||
controller.ai_movement.stop_moving_towards(controller) | ||
|
||
/// Helper proc to ensure consistency in setting the source of the movement target | ||
/datum/ai_behavior/proc/set_movement_target(datum/ai_controller/controller, atom/target, datum/ai_movement/new_movement) | ||
controller.set_movement_target(type, target, new_movement) | ||
|
||
/// Clear the controller's movement target only if it was us who last set it | ||
/datum/ai_behavior/proc/clear_movement_target(datum/ai_controller/controller) | ||
if(controller.movement_target_source != type) | ||
return | ||
controller.set_movement_target(type, null) |
Oops, something went wrong.