diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm index 37f4bff5f9..fd55905300 100644 --- a/code/__defines/subsystems.dm +++ b/code/__defines/subsystems.dm @@ -74,7 +74,8 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G #define INIT_ORDER_XENOARCH -20 #define INIT_ORDER_CIRCUIT -21 #define INIT_ORDER_AI -22 -#define INIT_ORDER_GAME_MASTER -24 +#define INIT_ORDER_AI_FAST -23 +#define INIT_ORDER_GAME_MASTER -24 #define INIT_ORDER_TICKER -50 #define INIT_ORDER_CHAT -100 //Should be last to ensure chat remains smooth during init. diff --git a/code/controllers/subsystems/ai.dm b/code/controllers/subsystems/ai.dm index a111b83e43..8b88eb4b4d 100644 --- a/code/controllers/subsystems/ai.dm +++ b/code/controllers/subsystems/ai.dm @@ -2,7 +2,7 @@ SUBSYSTEM_DEF(ai) name = "AI" init_order = INIT_ORDER_AI priority = FIRE_PRIORITY_AI - wait = 5 // This gets run twice a second, however this is technically two loops in one, with the second loop being run every four iterations. + wait = 2 SECONDS flags = SS_NO_INIT|SS_TICKER runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME @@ -22,15 +22,11 @@ SUBSYSTEM_DEF(ai) var/list/currentrun = src.currentrun while(currentrun.len) - // var/mob/living/L = currentrun[currentrun.len] var/datum/ai_holder/A = currentrun[currentrun.len] --currentrun.len if(!A || QDELETED(A) || A.busy) // Doesn't exist or won't exist soon or not doing it this tick continue - if(times_fired % 4 == 0 && A.holder.stat != DEAD) - A.handle_strategicals() - if(A.holder.stat != DEAD) // The /TG/ version checks stat twice, presumably in-case processing somehow got the mob killed in that instant. - A.handle_tactics() + A.handle_strategicals() if(MC_TICK_CHECK) return diff --git a/code/controllers/subsystems/aifast.dm b/code/controllers/subsystems/aifast.dm new file mode 100644 index 0000000000..a6fd3d98d4 --- /dev/null +++ b/code/controllers/subsystems/aifast.dm @@ -0,0 +1,32 @@ +SUBSYSTEM_DEF(aifast) + name = "AI (Fast)" + init_order = INIT_ORDER_AI_FAST + priority = FIRE_PRIORITY_AI + wait = 5 // This gets run twice a second, but shouldn't do much unless mobs are in combat + flags = SS_NO_INIT|SS_TICKER + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + + var/list/processing = list() + var/list/currentrun = list() + +/datum/controller/subsystem/aifast/stat_entry(msg_prefix) + var/list/msg = list(msg_prefix) + msg += "P:[processing.len]" + ..(msg.Join()) + +/datum/controller/subsystem/aifast/fire(resumed = 0) + if (!resumed) + src.currentrun = processing.Copy() + + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + + while(currentrun.len) + var/datum/ai_holder/A = currentrun[currentrun.len] + --currentrun.len + if(!A || QDELETED(A) || A.busy) // Doesn't exist or won't exist soon or not doing it this tick + continue + A.handle_tactics() + + if(MC_TICK_CHECK) + return diff --git a/code/modules/ai/ai_holder.dm b/code/modules/ai/ai_holder.dm index b5272ea996..dfd7bd87b0 100644 --- a/code/modules/ai/ai_holder.dm +++ b/code/modules/ai/ai_holder.dm @@ -1,6 +1,14 @@ // This is a datum-based artificial intelligence for simple mobs (and possibly others) to use. // The neat thing with having this here instead of on the mob is that it is independant of Life(), and that different mobs // can use a more or less complex AI by giving it a different datum. +#define AI_NO_PROCESS 0 +#define AI_PROCESSING (1<<0) +#define AI_FASTPROCESSING (1<<1) + +#define START_AIPROCESSING(Datum) if (!(Datum.process_flags & AI_PROCESSING)) {Datum.process_flags |= AI_PROCESSING;SSai.processing += Datum} +#define STOP_AIPROCESSING(Datum) Datum.process_flags &= ~AI_PROCESSING;SSai.processing -= Datum +#define START_AIFASTPROCESSING(Datum) if (!(Datum.process_flags & AI_FASTPROCESSING)) {Datum.process_flags |= AI_FASTPROCESSING;SSaifast.processing += Datum} +#define STOP_AIFASTPROCESSING(Datum) Datum.process_flags &= ~AI_FASTPROCESSING;SSaifast.processing -= Datum /mob/living var/datum/ai_holder/ai_holder = null @@ -27,7 +35,20 @@ var/busy = FALSE // If true, the ticker will skip processing this mob until this is false. Good for if you need the // mob to stay still (e.g. delayed attacking). If you need the mob to be inactive for an extended period of time, // consider sleeping the AI instead. - + var/process_flags = 0 // Where we're processing, see flag defines. + var/list/static/fastprocess_stances = list( + STANCE_ALERT, + STANCE_APPROACH, + STANCE_FIGHT, + STANCE_BLINDFIGHT, + STANCE_REPOSITION, + STANCE_MOVE, + STANCE_FOLLOW, + STANCE_FLEE + ) + var/list/static/noprocess_stances = list( + STANCE_SLEEP + ) /datum/ai_holder/hostile @@ -40,16 +61,34 @@ /datum/ai_holder/New(var/new_holder) ASSERT(new_holder) holder = new_holder - SSai.processing += src home_turf = get_turf(holder) + manage_processing(AI_PROCESSING) + GLOB.stat_set_event.register(holder, src, .proc/holder_stat_change) ..() /datum/ai_holder/Destroy() holder = null - SSai.processing -= src // We might've already been asleep and removed, but byond won't care if we do this again and it saves a conditional. + manage_processing(AI_NO_PROCESS) home_turf = null return ..() +/datum/ai_holder/proc/manage_processing(var/desired) + if(desired & AI_PROCESSING) + START_AIPROCESSING(src) + else + STOP_AIPROCESSING(src) + + if(desired & AI_FASTPROCESSING) + START_AIFASTPROCESSING(src) + else + STOP_AIFASTPROCESSING(src) + +/datum/ai_holder/proc/holder_stat_change(var/mob, old_stat, new_stat) + if(old_stat >= DEAD && new_stat <= DEAD) //Revived + manage_processing(AI_PROCESSING) + else if(old_stat <= DEAD && new_stat >= DEAD) //Killed + manage_processing(AI_NO_PROCESS) + /datum/ai_holder/proc/update_stance_hud() var/image/stanceimage = holder.grab_hud(LIFE_HUD) stanceimage.icon_state = "ais_[stance]" @@ -78,7 +117,6 @@ return forget_everything() // If we ever wake up, its really unlikely that our current memory will be of use. set_stance(STANCE_SLEEP) - SSai.processing -= src update_paused_hud() // Reverses the above proc. @@ -89,7 +127,6 @@ if(!should_wake()) return set_stance(STANCE_IDLE) - SSai.processing += src update_paused_hud() /datum/ai_holder/proc/should_wake() @@ -122,12 +159,23 @@ // For setting the stance WITHOUT processing it /datum/ai_holder/proc/set_stance(var/new_stance) + if(stance == new_stance) + ai_log("set_stance() : Ignoring change stance to same stance request.", AI_LOG_INFO) + return + ai_log("set_stance() : Setting stance from [stance] to [new_stance].", AI_LOG_INFO) stance = new_stance if(stance_coloring) // For debugging or really weird mobs. stance_color() update_stance_hud() + if(new_stance in fastprocess_stances) //Becoming fast + manage_processing(AI_PROCESSING|AI_FASTPROCESSING) + else if(new_stance in noprocess_stances) + manage_processing(AI_NO_PROCESS) //Becoming off + else + manage_processing(AI_PROCESSING) //Becoming slow + // This is called every half a second. /datum/ai_holder/proc/handle_stance_tactical() ai_log("========= Fast Process Beginning ==========", AI_LOG_TRACE) // This is to make it easier visually to disinguish between 'blocks' of what a tick did. @@ -167,19 +215,6 @@ return switch(stance) - if(STANCE_IDLE) - if(should_go_home()) - ai_log("handle_stance_tactical() : STANCE_IDLE, going to go home.", AI_LOG_TRACE) - go_home() - - else if(should_follow_leader()) - ai_log("handle_stance_tactical() : STANCE_IDLE, going to follow leader.", AI_LOG_TRACE) - set_stance(STANCE_FOLLOW) - - else if(should_wander()) - ai_log("handle_stance_tactical() : STANCE_IDLE, going to wander randomly.", AI_LOG_TRACE) - handle_wander_movement() - if(STANCE_ALERT) ai_log("handle_stance_tactical() : STANCE_ALERT, going to threaten_target().", AI_LOG_TRACE) threaten_target() @@ -241,9 +276,23 @@ if(STANCE_IDLE) if(speak_chance) // In the long loop since otherwise it wont shut up. handle_idle_speaking() + if(hostile) ai_log("handle_stance_strategical() : STANCE_IDLE, going to find_target().", AI_LOG_TRACE) find_target() + + if(should_go_home()) + ai_log("handle_stance_tactical() : STANCE_IDLE, going to go home.", AI_LOG_TRACE) + go_home() + + else if(should_follow_leader()) + ai_log("handle_stance_tactical() : STANCE_IDLE, going to follow leader.", AI_LOG_TRACE) + set_stance(STANCE_FOLLOW) + + else if(should_wander()) + ai_log("handle_stance_tactical() : STANCE_IDLE, going to wander randomly.", AI_LOG_TRACE) + handle_wander_movement() + if(STANCE_APPROACH) if(target) ai_log("handle_stance_strategical() : STANCE_APPROACH, going to calculate_path([target]).", AI_LOG_TRACE) @@ -291,4 +340,7 @@ // 'Taunts' the AI into attacking the taunter. /mob/living/proc/taunt(atom/movable/taunter, force_target_switch = FALSE) if(ai_holder) - ai_holder.receive_taunt(taunter, force_target_switch) \ No newline at end of file + ai_holder.receive_taunt(taunter, force_target_switch) + +#undef AI_PROCESSING +#undef AI_FASTPROCESSING \ No newline at end of file diff --git a/code/modules/ai/ai_holder_disabled.dm b/code/modules/ai/ai_holder_disabled.dm index dc3717fe18..d68e2ecf5d 100644 --- a/code/modules/ai/ai_holder_disabled.dm +++ b/code/modules/ai/ai_holder_disabled.dm @@ -6,7 +6,7 @@ // If our holder is able to do anything. /datum/ai_holder/proc/can_act() if(!holder) // Holder missing. - SSai.processing -= src + manage_processing(AI_NO_PROCESS) return FALSE if(holder.stat) // Dead or unconscious. ai_log("can_act() : Stat was non-zero ([holder.stat]).", AI_LOG_TRACE) diff --git a/code/modules/ai/ai_holder_follow.dm b/code/modules/ai/ai_holder_follow.dm index 1e7bb0875d..45d0d1e7a0 100644 --- a/code/modules/ai/ai_holder_follow.dm +++ b/code/modules/ai/ai_holder_follow.dm @@ -57,7 +57,7 @@ ai_log("lose_follow() : Exited.", AI_LOG_DEBUG) /datum/ai_holder/proc/should_follow_leader() - if(!leader) + if(!leader || target) return FALSE if(follow_until_time && world.time > follow_until_time) lose_follow() diff --git a/code/modules/ai/ai_holder_movement.dm b/code/modules/ai/ai_holder_movement.dm index eb465dec5d..3f9eb1b685 100644 --- a/code/modules/ai/ai_holder_movement.dm +++ b/code/modules/ai/ai_holder_movement.dm @@ -43,6 +43,8 @@ ai_log("walk_to_destination() : Exiting.",AI_LOG_TRACE) /datum/ai_holder/proc/should_go_home() + if(stance != STANCE_IDLE) + return FALSE if(!returns_home || !home_turf) return FALSE if(get_dist(holder, home_turf) > max_home_distance) @@ -139,7 +141,7 @@ return MOVEMENT_ON_COOLDOWN /datum/ai_holder/proc/should_wander() - return wander && !leader + return (stance == STANCE_IDLE) && wander && !leader // Wanders randomly in cardinal directions. /datum/ai_holder/proc/handle_wander_movement() diff --git a/code/modules/ai/ai_holder_targeting.dm b/code/modules/ai/ai_holder_targeting.dm index f33906c662..cacfcf56de 100644 --- a/code/modules/ai/ai_holder_targeting.dm +++ b/code/modules/ai/ai_holder_targeting.dm @@ -25,8 +25,8 @@ // Step 1, find out what we can see. /datum/ai_holder/proc/list_targets() - . = hearers(vision_range, holder) - holder // Remove ourselves to prevent suicidal decisions. ~ SRC is the ai_holder. - . -= dview_mob // Not the dview mob either, nerd. + . = ohearers(vision_range, holder) + . -= dview_mob // Not the dview mob! var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha, /obj/structure/blob)) @@ -43,13 +43,8 @@ if(!has_targets_list) possible_targets = list_targets() for(var/possible_target in possible_targets) - var/atom/A = possible_target - if(found(A)) // In case people want to override this. - . = list(A) - break - if(can_attack(A)) // Can we attack it? - . += A - continue + if(can_attack(possible_target)) // Can we attack it? + . += possible_target var/new_target = pick_target(.) give_target(new_target) @@ -57,7 +52,7 @@ // Step 3, pick among the possible, attackable targets. /datum/ai_holder/proc/pick_target(list/targets) - if(target != null) // If we already have a target, but are told to pick again, calculate the lowest distance between all possible, and pick from the lowest distance targets. + if(target) // If we already have a target, but are told to pick again, calculate the lowest distance between all possible, and pick from the lowest distance targets. targets = target_filter_distance(targets) else targets = target_filter_closest(targets) @@ -88,32 +83,31 @@ // Filters return one or more 'preferred' targets. -// This one is for closest targets. +// This one is for targets closer than our current one. /datum/ai_holder/proc/target_filter_distance(list/targets) + var/target_dist = get_dist(holder, target) + var/list/better_targets = list() for(var/possible_target in targets) var/atom/A = possible_target - var/target_dist = get_dist(holder, target) var/possible_target_distance = get_dist(holder, A) - if(target_dist < possible_target_distance) - targets -= A - return targets + if(possible_target_distance < target_dist) + better_targets += A + return better_targets +// Returns the closest target and anything tied with it for distance /datum/ai_holder/proc/target_filter_closest(list/targets) - var/lowest_distance = -1 - var/list/sorted_targets = list() + var/lowest_distance = 1e6 //fakely far + var/list/closest_targets = list() for(var/possible_target in targets) var/atom/A = possible_target var/current_distance = get_dist(holder, A) - if(lowest_distance == -1) + if(current_distance < lowest_distance) + closest_targets.Cut() lowest_distance = current_distance - sorted_targets += A - else if(current_distance < lowest_distance) - targets.Cut() - lowest_distance = current_distance - sorted_targets += A + closest_targets += A else if(current_distance == lowest_distance) - sorted_targets += A - return sorted_targets + closest_targets += A + return closest_targets /datum/ai_holder/proc/can_attack(atom/movable/the_target, var/vision_required = TRUE) if(!can_see_target(the_target) && vision_required) @@ -163,11 +157,6 @@ return TRUE // return FALSE -// Override this for special targeting criteria. -// If it returns true, the mob will always select it as the target. -/datum/ai_holder/proc/found(atom/movable/the_target) - return FALSE - // 'Soft' loss of target. They may still exist, we still have some info about them maybe. /datum/ai_holder/proc/lose_target() ai_log("lose_target() : Entering.", AI_LOG_TRACE) diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/racoon.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/racoon.dm index b2e913bfb2..2019859f3f 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/sif/racoon.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/racoon.dm @@ -256,14 +256,10 @@ for(var/possible_target in possible_targets) var/atom/A = possible_target - if(found(A)) - . = list(A) - break if(istype(A, /mob/living) && !can_pick_mobs) continue if(can_attack(A)) // Can we attack it? . += A - continue for(var/obj/item/I in .) last_search = world.time diff --git a/vorestation.dme b/vorestation.dme index ea0545624f..f9930ace87 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -225,6 +225,7 @@ #include "code\controllers\verbs.dm" #include "code\controllers\observer_listener\atom\observer.dm" #include "code\controllers\subsystems\ai.dm" +#include "code\controllers\subsystems\aifast.dm" #include "code\controllers\subsystems\air.dm" #include "code\controllers\subsystems\airflow.dm" #include "code\controllers\subsystems\alarm.dm"