diff --git a/code/controllers/subsystem/ai_controllers.dm b/code/controllers/subsystem/ai_controllers.dm index e7aa6b53fdc..49c571a9a07 100644 --- a/code/controllers/subsystem/ai_controllers.dm +++ b/code/controllers/subsystem/ai_controllers.dm @@ -23,14 +23,12 @@ SUBSYSTEM_DEF(ai_controllers) /datum/controller/subsystem/ai_controllers/fire(resumed) var/timer = TICK_USAGE_REAL for(var/datum/ai_controller/ai_controller as anything in GLOB.ai_controllers_by_status[planning_status]) - if(!COOLDOWN_FINISHED(ai_controller, failed_planning_cooldown)) - continue - if(!ai_controller.able_to_plan) continue ai_controller.SelectBehaviors(wait * 0.1) + if(!length(ai_controller.current_behaviors)) //Still no plan - COOLDOWN_START(ai_controller, failed_planning_cooldown, AI_FAILED_PLANNING_COOLDOWN) + ai_controller.planning_failed() our_cost = MC_AVERAGE(our_cost, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer)) diff --git a/code/controllers/subsystem/processing/ai_idle_behaviors.dm b/code/controllers/subsystem/processing/ai_idle_behaviors.dm index cda3d354882..8875d971ad8 100644 --- a/code/controllers/subsystem/processing/ai_idle_behaviors.dm +++ b/code/controllers/subsystem/processing/ai_idle_behaviors.dm @@ -1,6 +1,17 @@ PROCESSING_SUBSYSTEM_DEF(idle_ai_behaviors) - name = "idle_ai_behaviors" - flags = SS_NO_INIT | SS_BACKGROUND + name = "AI Idle Behaviors" + flags = SS_BACKGROUND wait = 1.5 SECONDS priority = FIRE_PRIORITY_IDLE_NPC init_order = INIT_ORDER_AI_IDLE_CONTROLLERS //must execute only after ai behaviors are initialized + ///List of all the idle ai behaviors + var/list/idle_behaviors = list() + +/datum/controller/subsystem/processing/idle_ai_behaviors/Initialize() + setup_idle_behaviors() + return SS_INIT_SUCCESS + +/datum/controller/subsystem/processing/idle_ai_behaviors/proc/setup_idle_behaviors() + for(var/behavior_type in subtypesof(/datum/idle_behavior)) + var/datum/idle_behavior/behavior = new behavior_type + idle_behaviors[behavior_type] = behavior diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm index 499da54b1b1..4d5b6600443 100644 --- a/code/datums/ai/_ai_controller.dm +++ b/code/datums/ai/_ai_controller.dm @@ -39,8 +39,6 @@ multiple modular subtrees with behaviors 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 @@ -69,14 +67,15 @@ multiple modular subtrees with behaviors var/able_to_run = FALSE /// are we even able to plan? var/able_to_plan = TRUE + /// are we currently on failed planning timeout? + var/on_failed_planning_timeout = FALSE /datum/ai_controller/New(atom/new_pawn) change_ai_movement_type(ai_movement) init_subtrees() - if(idle_behavior) - idle_behavior = new idle_behavior() + idle_behavior = SSidle_ai_behaviors.idle_behaviors[idle_behavior] if(!isnull(new_pawn)) // unit tests need the ai_controller to exist in isolation due to list schenanigans i hate it here PossessPawn(new_pawn) @@ -240,7 +239,7 @@ multiple modular subtrees with behaviors if(!pawn_turf) CRASH("AI controller [src] controlling pawn ([pawn]) is not on a turf.") #endif - if(!length(SSmobs.clients_by_zlevel[pawn_turf.z])) + if(!length(SSmobs.clients_by_zlevel[pawn_turf.z]) || on_failed_planning_timeout || !able_to_run) return AI_STATUS_OFF if(should_idle()) return AI_STATUS_IDLE @@ -300,6 +299,9 @@ multiple modular subtrees with behaviors /datum/ai_controller/proc/update_able_to_run() SIGNAL_HANDLER able_to_run = get_able_to_run() + if(!able_to_run) + GLOB.move_manager.stop_looping(pawn) //stop moving + set_ai_status(get_expected_ai_status()) ///Returns TRUE if the ai controller can actually run at the moment, FALSE otherwise /datum/ai_controller/proc/get_able_to_run() @@ -312,10 +314,6 @@ multiple modular subtrees with behaviors ///Runs any actions that are currently running /datum/ai_controller/process(seconds_per_tick) - if(!able_to_run) - GLOB.move_manager.stop_looping(pawn) //stop moving - return //this should remove them from processing in the future through event-based stuff. - if(!length(current_behaviors) && idle_behavior) idle_behavior.perform_idle_behavior(seconds_per_tick, src) //Do some stupid shit while we have nothing to do return @@ -491,6 +489,7 @@ multiple modular subtrees with behaviors /datum/ai_controller/proc/on_stat_changed(mob/living/source, new_stat) SIGNAL_HANDLER reset_ai_status() + update_able_to_run() /datum/ai_controller/proc/on_sentience_gained() SIGNAL_HANDLER @@ -531,6 +530,15 @@ multiple modular subtrees with behaviors minimum_distance = iter_behavior.required_distance return minimum_distance +/datum/ai_controller/proc/planning_failed() + on_failed_planning_timeout = TRUE + set_ai_status(get_expected_ai_status()) + addtimer(CALLBACK(src, PROC_REF(resume_planning)), AI_FAILED_PLANNING_COOLDOWN) + +/datum/ai_controller/proc/resume_planning() + on_failed_planning_timeout = FALSE + set_ai_status(get_expected_ai_status()) + /// Returns true if we have a blackboard key with the provided key and it is not qdeleting /datum/ai_controller/proc/blackboard_key_exists(key) var/datum/key_value = blackboard[key]