diff --git a/code/__DEFINES/ai/ai.dm b/code/__DEFINES/ai/ai.dm index 65cc4214908..e894b32b1f6 100644 --- a/code/__DEFINES/ai/ai.dm +++ b/code/__DEFINES/ai/ai.dm @@ -2,8 +2,12 @@ #define GET_TARGETING_STRATEGY(targeting_type) SSai_behaviors.targeting_strategies[targeting_type] #define HAS_AI_CONTROLLER_TYPE(thing, type) istype(thing?.ai_controller, type) -#define AI_STATUS_ON 1 -#define AI_STATUS_OFF 2 +//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" ///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 diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 9bfe6df5a5a..066a37b6d48 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -341,7 +341,6 @@ #define AI_ON 1 #define AI_IDLE 2 #define AI_OFF 3 -#define AI_Z_OFF 4 //The range at which a mob should wake up if you spawn into the z level near it #define MAX_SIMPLEMOB_WAKEUP_RANGE 5 diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 6d04fca876b..c3778351882 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -193,7 +193,6 @@ // If the subsystem isn't listed here it's either DEFAULT or PROCESS (if it's a processing subsystem child) #define FIRE_PRIORITY_PING 10 -#define FIRE_PRIORITY_IDLE_NPC 10 #define FIRE_PRIORITY_SERVER_MAINT 10 #define FIRE_PRIORITY_RESEARCH 10 #define FIRE_PRIORITY_VIS 10 diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index 2de6a1c691f..4e33aa43708 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -59,7 +59,7 @@ GLOBAL_LIST_EMPTY(human_list) //all instances of /mob/living/carbon/human and su GLOBAL_LIST_EMPTY(ai_list) GLOBAL_LIST_EMPTY(pai_list) GLOBAL_LIST_EMPTY(available_ai_shells) -GLOBAL_LIST_INIT(simple_animals, list(list(),list(),list(),list())) // One for each AI_* status define +GLOBAL_LIST_INIT(simple_animals, list(list(),list(),list())) // One for each AI_* status define GLOBAL_LIST_EMPTY(spidermobs) //all sentient spider mobs GLOBAL_LIST_EMPTY(bots_list) GLOBAL_LIST_EMPTY(aiEyes) diff --git a/code/controllers/subsystem/ai_controllers.dm b/code/controllers/subsystem/ai_controllers.dm index 3d8d2653149..44e1948e386 100644 --- a/code/controllers/subsystem/ai_controllers.dm +++ b/code/controllers/subsystem/ai_controllers.dm @@ -3,27 +3,32 @@ 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. + 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/ai_subtrees = list() - ///List of all ai controllers currently running - var/list/active_ai_controllers = list() + 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(), + ) + ///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() /datum/controller/subsystem/ai_controllers/Initialize() setup_subtrees() return SS_INIT_SUCCESS -/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/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] + msg = "Active AIs:[length(active_list)]|Inactive:[length(inactive_list)]" + return ..() /datum/controller/subsystem/ai_controllers/fire(resumed) - for(var/datum/ai_controller/ai_controller as anything in active_ai_controllers) + 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 @@ -32,3 +37,17 @@ SUBSYSTEM_DEF(ai_controllers) 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) + +///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() diff --git a/code/controllers/subsystem/idlenpcpool.dm b/code/controllers/subsystem/idlenpcpool.dm deleted file mode 100644 index ee98e8c4e67..00000000000 --- a/code/controllers/subsystem/idlenpcpool.dm +++ /dev/null @@ -1,47 +0,0 @@ -SUBSYSTEM_DEF(idlenpcpool) - name = "Idling NPC Pool" - flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT - priority = FIRE_PRIORITY_IDLE_NPC - wait = 60 - runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME - - var/list/currentrun = list() - var/static/list/idle_mobs_by_zlevel[][] - -/datum/controller/subsystem/idlenpcpool/stat_entry(msg) - var/list/idlelist = GLOB.simple_animals[AI_IDLE] - var/list/zlist = GLOB.simple_animals[AI_Z_OFF] - msg = "IdleNPCS:[length(idlelist)]|Z:[length(zlist)]" - return ..() - -/datum/controller/subsystem/idlenpcpool/proc/MaxZChanged() - if (!islist(idle_mobs_by_zlevel)) - idle_mobs_by_zlevel = new /list(world.maxz,0) - while (SSidlenpcpool.idle_mobs_by_zlevel.len < world.maxz) - SSidlenpcpool.idle_mobs_by_zlevel.len++ - SSidlenpcpool.idle_mobs_by_zlevel[idle_mobs_by_zlevel.len] = list() - -/datum/controller/subsystem/idlenpcpool/fire(resumed = FALSE) - - if (!resumed) - var/list/idlelist = GLOB.simple_animals[AI_IDLE] - src.currentrun = idlelist.Copy() - - //cache for sanic speed (lists are references anyways) - var/list/currentrun = src.currentrun - - while(currentrun.len) - var/mob/living/simple_animal/SA = currentrun[currentrun.len] - --currentrun.len - if (QDELETED(SA)) - GLOB.simple_animals[AI_IDLE] -= SA - stack_trace("Found a null in simple_animals deactive list [SA.type]!") - continue - - if(!SA.ckey) - if(SA.stat != DEAD) - SA.handle_automated_movement() - if(SA.stat != DEAD) - SA.consider_wakeup() - if (MC_TICK_CHECK) - return diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm index 91f624972ad..664e89eab09 100644 --- a/code/datums/ai/_ai_controller.dm +++ b/code/datums/ai/_ai_controller.dm @@ -68,7 +68,6 @@ multiple modular subtrees with behaviors PossessPawn(new_pawn) /datum/ai_controller/Destroy(force) - set_ai_status(AI_STATUS_OFF) UnpossessPawn(FALSE) set_movement_target(type, null) if(ai_movement.moving_controllers[src]) @@ -116,9 +115,14 @@ multiple modular subtrees with behaviors pawn = new_pawn pawn.ai_controller = src + var/turf/pawn_turf = get_turf(pawn) + if(pawn_turf) + SSai_controllers.ai_controllers_by_zlevel[pawn_turf.z] += src + SEND_SIGNAL(src, COMSIG_AI_CONTROLLER_POSSESSED_PAWN) reset_ai_status() + RegisterSignal(pawn, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_changed_z_level)) RegisterSignal(pawn, COMSIG_MOB_STATCHANGE, PROC_REF(on_stat_changed)) RegisterSignal(pawn, COMSIG_MOB_LOGIN, PROC_REF(on_sentience_gained)) RegisterSignal(pawn, COMSIG_QDELETING, PROC_REF(on_pawn_qdeleted)) @@ -127,25 +131,53 @@ multiple modular subtrees with behaviors /datum/ai_controller/proc/reset_ai_status() set_ai_status(get_expected_ai_status()) -/// Returns what the AI status should be based on current conditions. +/** + * Gets the AI status we expect the AI controller to be on at this current moment. + * Returns AI_STATUS_OFF if it's inhabited by a Client and shouldn't be, if it's dead and cannot act while dead, or is on a z level without clients. + * Returns AI_STATUS_ON otherwise. + */ /datum/ai_controller/proc/get_expected_ai_status() - var/final_status = AI_STATUS_ON - if (!ismob(pawn)) - return final_status + return AI_STATUS_ON var/mob/living/mob_pawn = pawn - if(!continue_processing_when_client && mob_pawn.client) - final_status = AI_STATUS_OFF - - if(ai_traits & CAN_ACT_WHILE_DEAD) - return final_status + return AI_STATUS_OFF if(mob_pawn.stat == DEAD) - final_status = AI_STATUS_OFF + if(ai_traits & CAN_ACT_WHILE_DEAD) + return AI_STATUS_ON + return AI_STATUS_OFF + + var/turf/pawn_turf = get_turf(mob_pawn) +#ifdef TESTING + 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])) + return AI_STATUS_OFF + return AI_STATUS_ON - return final_status +/datum/ai_controller/proc/get_current_turf() + var/mob/living/mob_pawn = pawn + var/turf/pawn_turf = get_turf(mob_pawn) + to_chat(world, "[pawn_turf]") + +///Called when the AI controller pawn changes z levels, we check if there's any clients on the new one and wake up the AI if there is. +/datum/ai_controller/proc/on_changed_z_level(atom/source, turf/old_turf, turf/new_turf, same_z_layer, notify_contents) + SIGNAL_HANDLER + var/mob/mob_pawn = pawn + if((mob_pawn?.client && !continue_processing_when_client)) + return + if(old_turf) + SSai_controllers.ai_controllers_by_zlevel[old_turf.z] -= src + if(new_turf) + SSai_controllers.ai_controllers_by_zlevel[new_turf.z] += src + var/new_level_clients = SSmobs.clients_by_zlevel[new_turf.z].len + if(new_level_clients) + set_ai_status(AI_STATUS_ON) + else + set_ai_status(AI_STATUS_OFF) ///Abstract proc for initializing the pawn to the new controller /datum/ai_controller/proc/TryPossessPawn(atom/new_pawn) @@ -156,9 +188,15 @@ multiple modular subtrees with behaviors if(isnull(pawn)) return // instantiated without an applicable pawn, fine - UnregisterSignal(pawn, list(COMSIG_MOB_LOGIN, COMSIG_MOB_LOGOUT, COMSIG_MOB_STATCHANGE, COMSIG_QDELETING)) + set_ai_status(AI_STATUS_OFF) + UnregisterSignal(pawn, list(COMSIG_MOVABLE_Z_CHANGED, COMSIG_MOB_LOGIN, COMSIG_MOB_LOGOUT, COMSIG_MOB_STATCHANGE, COMSIG_QDELETING)) if(ai_movement.moving_controllers[src]) ai_movement.stop_moving_towards(src) + var/turf/pawn_turf = get_turf(pawn) + if(pawn_turf) + SSai_controllers.ai_controllers_by_zlevel[pawn_turf.z] -= src + if(ai_status) + SSai_controllers.ai_controllers_by_status[ai_status] -= src pawn.ai_controller = null pawn = null if(destroy) @@ -269,15 +307,17 @@ multiple modular subtrees with behaviors /datum/ai_controller/proc/set_ai_status(new_ai_status) if(ai_status == new_ai_status) return FALSE //no change - + + //remove old status, if we've got one + if(ai_status) + SSai_controllers.ai_controllers_by_status[ai_status] -= src ai_status = new_ai_status + SSai_controllers.ai_controllers_by_status[new_ai_status] += src switch(ai_status) if(AI_STATUS_ON) - SSai_controllers.active_ai_controllers += src START_PROCESSING(SSai_behaviors, src) if(AI_STATUS_OFF) STOP_PROCESSING(SSai_behaviors, src) - SSai_controllers.active_ai_controllers -= src CancelActions() /datum/ai_controller/proc/PauseAi(time) diff --git a/code/game/world.dm b/code/game/world.dm index cda503bd3b5..9e57dbba343 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -448,7 +448,7 @@ GLOBAL_VAR(restart_counter) /world/proc/incrementMaxZ() maxz++ SSmobs.MaxZChanged() - SSidlenpcpool.MaxZChanged() + SSai_controllers.on_max_z_changed() /world/proc/change_fps(new_value = 20) if(new_value <= 0) diff --git a/code/modules/clothing/shoes/cowboy.dm b/code/modules/clothing/shoes/cowboy.dm index b7382584e57..73c5a9d0d95 100644 --- a/code/modules/clothing/shoes/cowboy.dm +++ b/code/modules/clothing/shoes/cowboy.dm @@ -4,8 +4,8 @@ icon_state = "cowboy_brown" armor_type = /datum/armor/shoes_cowboy custom_price = PAYCHECK_CREW - var/max_occupants = 4 can_be_tied = FALSE + var/max_occupants = 4 /// Do these boots have spur sounds? var/has_spurs = FALSE /// The jingle jangle jingle of our spurs diff --git a/code/modules/mob/dead/dead.dm b/code/modules/mob/dead/dead.dm index 615eb55898c..30d273db7da 100644 --- a/code/modules/mob/dead/dead.dm +++ b/code/modules/mob/dead/dead.dm @@ -90,16 +90,20 @@ INITIALIZE_IMMEDIATE(/mob/dead) #undef SERVER_HOPPER_TRAIT -/mob/dead/proc/update_z(new_z) // 1+ to register, null to unregister - if (registered_z != new_z) - if (registered_z) - SSmobs.dead_players_by_zlevel[registered_z] -= src - if (client) - if (new_z) - SSmobs.dead_players_by_zlevel[new_z] += src - registered_z = new_z - else - registered_z = null +/** + * updates the Z level for dead players + * If they don't have a new z, we'll keep the old one, preventing bugs from ghosting and re-entering, among others + */ +/mob/dead/proc/update_z(new_z) + if(registered_z == new_z) + return + if(registered_z) + SSmobs.dead_players_by_zlevel[registered_z] -= src + if(isnull(client)) + registered_z = null + return + registered_z = new_z + SSmobs.dead_players_by_zlevel[new_z] += src /mob/dead/Login() . = ..() diff --git a/code/modules/mob/dead/observer/login.dm b/code/modules/mob/dead/observer/login.dm index 4135f8d22fa..c239817a30e 100644 --- a/code/modules/mob/dead/observer/login.dm +++ b/code/modules/mob/dead/observer/login.dm @@ -14,10 +14,6 @@ preferred_form = client.prefs.read_preference(/datum/preference/choiced/ghost_form) ghost_orbit = client.prefs.read_preference(/datum/preference/choiced/ghost_orbit) - var/turf/T = get_turf(src) - if (isturf(T)) - update_z(T.z) - update_icon(ALL, preferred_form) updateghostimages() client.set_right_click_menu_mode(FALSE) diff --git a/code/modules/mob/dead/observer/logout.dm b/code/modules/mob/dead/observer/logout.dm index 4ba701c0ae0..53db92d91e3 100644 --- a/code/modules/mob/dead/observer/logout.dm +++ b/code/modules/mob/dead/observer/logout.dm @@ -1,5 +1,4 @@ /mob/dead/observer/Logout() - update_z(null) if (client) client.images -= (GLOB.ghost_images_default+GLOB.ghost_images_simple) diff --git a/code/modules/mob/living/basic/ruin_defender/flesh.dm b/code/modules/mob/living/basic/ruin_defender/flesh.dm index e33cdcad1a1..17d625cb591 100644 --- a/code/modules/mob/living/basic/ruin_defender/flesh.dm +++ b/code/modules/mob/living/basic/ruin_defender/flesh.dm @@ -40,7 +40,11 @@ /mob/living/basic/living_limb_flesh/Destroy(force) . = ..() - QDEL_NULL(current_bodypart) + if(current_bodypart) + var/obj/item/bodypart/bodypart = current_bodypart + unregister_from_limb(current_bodypart.owner) + if(!QDELETED(bodypart)) + qdel(bodypart) /mob/living/basic/living_limb_flesh/Life(seconds_per_tick = SSMOBS_DT, times_fired) . = ..() @@ -119,16 +123,10 @@ part_type = /obj/item/bodypart/leg/right/flesh target.visible_message(span_danger("[src] [target_part ? "tears off and attaches itself" : "attaches itself"] to where [target][target.p_s()] limb used to be!")) - current_bodypart = new part_type(TRUE) //dont_spawn_flesh, we cant use named arguments here - current_bodypart.replace_limb(target, TRUE) - forceMove(current_bodypart) - register_to_limb(current_bodypart) - -/mob/living/basic/living_limb_flesh/proc/register_to_limb(obj/item/bodypart/part) - ai_controller.set_ai_status(AI_STATUS_OFF) - RegisterSignal(part, COMSIG_BODYPART_REMOVED, PROC_REF(on_limb_lost)) - RegisterSignal(part.owner, COMSIG_LIVING_DEATH, PROC_REF(owner_died)) - RegisterSignal(part.owner, COMSIG_LIVING_ELECTROCUTE_ACT, PROC_REF(owner_shocked)) //detach if we are shocked, not beneficial for the host but hey its a sideeffect + var/obj/item/bodypart/new_bodypart = new part_type(TRUE) //dont_spawn_flesh, we cant use named arguments here + new_bodypart.replace_limb(target, TRUE) + forceMove(new_bodypart) + register_to_limb(new_bodypart) /mob/living/basic/living_limb_flesh/proc/owner_shocked(datum/source, shock_damage, shock_source, siemens_coeff, flags) SIGNAL_HANDLER @@ -154,15 +152,28 @@ /mob/living/basic/living_limb_flesh/proc/on_limb_lost(atom/movable/source, mob/living/carbon/old_owner, special, dismembered) SIGNAL_HANDLER - UnregisterSignal(source, COMSIG_BODYPART_REMOVED) - UnregisterSignal(old_owner, COMSIG_LIVING_ELECTROCUTE_ACT) - UnregisterSignal(old_owner, COMSIG_LIVING_DEATH) + unregister_from_limb(old_owner) addtimer(CALLBACK(src, PROC_REF(wake_up), source), 2 SECONDS) -/mob/living/basic/living_limb_flesh/proc/wake_up(atom/limb) - ai_controller.set_ai_status(AI_STATUS_ON) - forceMove(limb.drop_location()) +/mob/living/basic/living_limb_flesh/proc/register_to_limb(obj/item/bodypart/part) + current_bodypart = part + ai_controller.set_ai_status(AI_STATUS_OFF) + RegisterSignal(current_bodypart, COMSIG_BODYPART_REMOVED, PROC_REF(on_limb_lost)) + if(current_bodypart.owner) + RegisterSignal(current_bodypart.owner, COMSIG_LIVING_DEATH, PROC_REF(owner_died)) + RegisterSignal(current_bodypart.owner, COMSIG_LIVING_ELECTROCUTE_ACT, PROC_REF(owner_shocked)) //detach if we are shocked, not beneficial for the host but hey its a sideeffect + +/mob/living/basic/living_limb_flesh/proc/unregister_from_limb(mob/living/carbon/removing_owner) + UnregisterSignal(current_bodypart, COMSIG_BODYPART_REMOVED) + if(removing_owner) + UnregisterSignal(removing_owner, COMSIG_LIVING_ELECTROCUTE_ACT) + UnregisterSignal(removing_owner, COMSIG_LIVING_DEATH) current_bodypart = null - qdel(limb) + +/mob/living/basic/living_limb_flesh/proc/wake_up(atom/limb) visible_message(span_warning("[src] begins flailing around!")) Shake(6, 6, 0.5 SECONDS) + ai_controller.set_ai_status(AI_STATUS_ON) + forceMove(limb.drop_location()) + qdel(limb) + diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 0af9c504165..70849c936cb 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1748,32 +1748,34 @@ GLOBAL_LIST_EMPTY(fire_appearances) /mob/living/proc/update_z(new_z) // 1+ to register, null to unregister - if (registered_z != new_z) - if (registered_z) - SSmobs.clients_by_zlevel[registered_z] -= src - if (client) - if (new_z) - //Figure out how many clients were here before - var/oldlen = SSmobs.clients_by_zlevel[new_z].len - SSmobs.clients_by_zlevel[new_z] += src - for (var/I in length(SSidlenpcpool.idle_mobs_by_zlevel[new_z]) to 1 step -1) //Backwards loop because we're removing (guarantees optimal rather than worst-case performance), it's fine to use .len here but doesn't compile on 511 - var/mob/living/simple_animal/SA = SSidlenpcpool.idle_mobs_by_zlevel[new_z][I] - if (SA) - if(oldlen == 0) - //Start AI idle if nobody else was on this z level before (mobs will switch off when this is the case) - SA.toggle_ai(AI_IDLE) + if(registered_z == new_z) + return + if(registered_z) + SSmobs.clients_by_zlevel[registered_z] -= src + if(isnull(client)) + registered_z = null + return - //If they are also within a close distance ask the AI if it wants to wake up - if(get_dist(get_turf(src), get_turf(SA)) < MAX_SIMPLEMOB_WAKEUP_RANGE) - SA.consider_wakeup() // Ask the mob if it wants to turn on it's AI - //They should clean up in destroy, but often don't so we get them here - else - SSidlenpcpool.idle_mobs_by_zlevel[new_z] -= SA + //Check the amount of clients exists on the Z level we're leaving from, + //this excludes us as we haven't added ourselves to the new z level yet. + var/old_level_new_clients = (registered_z ? SSmobs.clients_by_zlevel[registered_z].len : null) + //No one is left after we're gone, shut off inactive ones + if(registered_z && old_level_new_clients == 0) + for(var/datum/ai_controller/controller as anything in SSai_controllers.ai_controllers_by_zlevel[registered_z]) + controller.set_ai_status(AI_STATUS_OFF) + + //Check the amount of clients exists on the Z level we're moving towards, excluding ourselves. + var/new_level_old_clients = SSmobs.clients_by_zlevel[new_z].len + registered_z = new_z + //We'll add ourselves to the list now so get_expected_ai_status() will know we're on the z level. + SSmobs.clients_by_zlevel[registered_z] += src - registered_z = new_z - else - registered_z = null + if(new_level_old_clients == 0) //No one was here before, wake up all the AIs. + for (var/datum/ai_controller/controller as anything in SSai_controllers.ai_controllers_by_zlevel[new_z]) + //We don't set them directly on, for instances like AIs acting while dead and other cases that may exist in the future. + //This isn't a problem for AIs with a client since the client will prevent this from being called anyway. + controller.set_ai_status(controller.get_expected_ai_status()) /mob/living/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents) ..() diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index 1cde4d4a740..1c9a67fafbe 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -615,29 +615,6 @@ value = initial(search_objects) search_objects = value -/mob/living/simple_animal/hostile/consider_wakeup() - ..() - var/list/tlist - var/turf/T = get_turf(src) - - if (!T) - return - - if (!length(SSmobs.clients_by_zlevel[T.z])) // It's fine to use .len here but doesn't compile on 511 - toggle_ai(AI_Z_OFF) - return - - var/cheap_search = isturf(T) && !is_station_level(T.z) - if (cheap_search) - tlist = ListTargetsLazy(T.z) - else - tlist = ListTargets() - - if(AIStatus == AI_IDLE && FindTarget(tlist)) - if(cheap_search) //Try again with full effort - FindTarget() - toggle_ai(AI_ON) - /mob/living/simple_animal/hostile/proc/ListTargetsLazy(_Z)//Step 1, find out what we can see var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/vehicle/sealed/mecha)) . = list() diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 1c1ea138d23..47d3ddcf20f 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -131,7 +131,7 @@ ///Played when someone punches the creature. var/attacked_sound = SFX_PUNCH - ///The Status of our AI, can be set to AI_ON (On, usual processing), AI_IDLE (Will not process, but will return to AI_ON if an enemy comes near), AI_OFF (Off, Not processing ever), AI_Z_OFF (Temporarily off due to nonpresence of players). + ///The Status of our AI, can be set to AI_ON (On, usual processing), AI_IDLE (Will not process, but will return to AI_ON if an enemy comes near), AI_OFF (Off, Not processing ever). var/AIStatus = AI_ON ///once we have become sentient, we can never go back. var/can_have_ai = TRUE @@ -221,10 +221,6 @@ GLOB.simple_animals[AIStatus] -= src SSnpcpool.currentrun -= src - var/turf/T = get_turf(src) - if (T && AIStatus == AI_Z_OFF) - SSidlenpcpool.idle_mobs_by_zlevel[T.z] -= src - return ..() /mob/living/simple_animal/examine(mob/user) @@ -514,29 +510,12 @@ return if (AIStatus != togglestatus) if (togglestatus > 0 && togglestatus < 5) - if (togglestatus == AI_Z_OFF || AIStatus == AI_Z_OFF) - var/turf/T = get_turf(src) - if (T) - if (AIStatus == AI_Z_OFF) - SSidlenpcpool.idle_mobs_by_zlevel[T.z] -= src - else - SSidlenpcpool.idle_mobs_by_zlevel[T.z] += src GLOB.simple_animals[AIStatus] -= src GLOB.simple_animals[togglestatus] += src AIStatus = togglestatus else stack_trace("Something attempted to set simple animals AI to an invalid state: [togglestatus]") -/mob/living/simple_animal/proc/consider_wakeup() - if (pulledby || shouldwakeup) - toggle_ai(AI_ON) - -/mob/living/simple_animal/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents) - ..() - if (old_turf && AIStatus == AI_Z_OFF) - SSidlenpcpool.idle_mobs_by_zlevel[old_turf.z] -= src - toggle_ai(initial(AIStatus)) - ///This proc is used for adding the swabbale element to mobs so that they are able to be biopsied and making sure holograpic and butter-based creatures don't yield viable cells samples. /mob/living/simple_animal/proc/add_cell_sample() return diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 7da01f047ef..44d0db90355 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -164,7 +164,7 @@ ///Allows a datum to intercept all click calls this mob is the source of var/datum/click_intercept - ///THe z level this mob is currently registered in + ///The z level this mob is currently registered in var/registered_z = null var/memory_throttle_time = 0 diff --git a/code/modules/unit_tests/limbsanity.dm b/code/modules/unit_tests/limbsanity.dm index 9988c7471e2..a92d481f556 100644 --- a/code/modules/unit_tests/limbsanity.dm +++ b/code/modules/unit_tests/limbsanity.dm @@ -2,13 +2,13 @@ /datum/unit_test/limbsanity/Run() for(var/path in subtypesof(/obj/item/bodypart) - list(/obj/item/bodypart/arm, /obj/item/bodypart/leg)) /// removes the abstract items. - var/obj/item/bodypart/part = new path(null) - if(part.is_dimorphic) - if(!icon_exists(UNLINT(part.should_draw_greyscale ? part.icon_greyscale : part.icon_static), "[part.limb_id]_[part.body_zone]_m")) + var/obj/item/bodypart/part = path + if(part::is_dimorphic) + if(!icon_exists(UNLINT(part::should_draw_greyscale ? part::icon_greyscale : part::icon_static), "[part::limb_id]_[part::body_zone]_m")) TEST_FAIL("[path] does not have a valid icon for male variants") - if(!icon_exists(UNLINT(part.should_draw_greyscale ? part.icon_greyscale : part.icon_static), "[part.limb_id]_[part.body_zone]_f")) + if(!icon_exists(UNLINT(part::should_draw_greyscale ? part::icon_greyscale : part::icon_static), "[part::limb_id]_[part::body_zone]_f")) TEST_FAIL("[path] does not have a valid icon for female variants") - else if(!icon_exists(UNLINT(part.should_draw_greyscale ? part.icon_greyscale : part.icon_static), "[part.limb_id]_[part.body_zone]")) + else if(!icon_exists(UNLINT(part::should_draw_greyscale ? part::icon_greyscale : part::icon_static), "[part::limb_id]_[part::body_zone]")) TEST_FAIL("[path] does not have a valid icon") /// Tests the height adjustment system which dynamically changes how much the chest, head, and arms of a carbon are adjusted upwards or downwards based on the length of their legs and chest. diff --git a/code/modules/unit_tests/mouse_bite_cable.dm b/code/modules/unit_tests/mouse_bite_cable.dm index 6d3150d279d..c2277fe4fd7 100644 --- a/code/modules/unit_tests/mouse_bite_cable.dm +++ b/code/modules/unit_tests/mouse_bite_cable.dm @@ -19,6 +19,8 @@ // Ai controlling processes expect a seconds_per_tick, supply a real-fake dt var/fake_dt = SSai_controllers.wait * 0.1 + // Set AI - AIs by default are off in z-levels with no client, we have to force it on. + biter.ai_controller.set_ai_status(AI_STATUS_ON) // Select behavior - this will queue finding the cable biter.ai_controller.SelectBehaviors(fake_dt) // Process behavior - this will execute the "locate the cable" behavior diff --git a/code/modules/unit_tests/suit_storage_icons.dm b/code/modules/unit_tests/suit_storage_icons.dm index 8c77b052411..12305e7abfc 100644 --- a/code/modules/unit_tests/suit_storage_icons.dm +++ b/code/modules/unit_tests/suit_storage_icons.dm @@ -10,18 +10,13 @@ continue wearable_item_paths |= item_path - for(var/clothing_path in (subtypesof(/obj/item/clothing) - typesof(/obj/item/clothing/head/mob_holder) - typesof(/obj/item/clothing/suit/space/santa))) //mob_holder is a psuedo abstract item. santa suit is a VERY SNOWFLAKE admin spawn suit that can hold /every/ possible item. - var/obj/item/clothing/spawned_item = new clothing_path - for(var/path in spawned_item.allowed) //find all usable suit storage stuff. + for(var/obj/item/clothing/clothing_path in (subtypesof(/obj/item/clothing) - typesof(/obj/item/clothing/head/mob_holder) - typesof(/obj/item/clothing/suit/space/santa))) //mob_holder is a psuedo abstract item. santa suit is a VERY SNOWFLAKE admin spawn suit that can hold /every/ possible item. + for(var/path in clothing_path::allowed) //find all usable suit storage stuff. wearable_item_paths |= path - qdel(spawned_item) - for(var/mod_path in subtypesof(/obj/item/mod/control)) - var/obj/item/mod/control/control_mod = new - for(var/path in control_mod.chestplate.allowed) + for(var/obj/item/mod/control/mod_path in subtypesof(/obj/item/mod/control)) + for(var/path in mod_path::chestplate::allowed) wearable_item_paths |= path - qdel(control_mod) - var/list/already_warned_icons = list() var/count = 1 //to be removed once the test goes live / into CI failure mode. diff --git a/tgstation.dme b/tgstation.dme index 9b3f3bcff0c..1b1eb499512 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -640,7 +640,6 @@ #include "code\controllers\subsystem\garbage.dm" #include "code\controllers\subsystem\icon_smooth.dm" #include "code\controllers\subsystem\id_access.dm" -#include "code\controllers\subsystem\idlenpcpool.dm" #include "code\controllers\subsystem\init_profiler.dm" #include "code\controllers\subsystem\input.dm" #include "code\controllers\subsystem\ipintel.dm"