From a15a1b88ed7d80523f73d928cf658bf50bbdf744 Mon Sep 17 00:00:00 2001 From: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Date: Sat, 6 Apr 2024 21:17:53 +0000 Subject: [PATCH] Basic mobs now use z-level turnoff instead of simple (#82469) ## About The Pull Request On one compile of MetaStation, I saw that there's 45 basic mobs on the station, 256 on lavaland (the number growing from tendrils), and 59 in all other z levels combined. While we do expect Lavaland to be visited every round, at least it won't be running during the times when no one is there, but even more importantly, space exploration is something not done every round, so we don't have any reason to waste our resources on AIs that will never be interacted with. Simple animals had an easy solution to this: If no one is on the Z level, their AI turns off If someone is on the Z level, they are idle unless needed. The last simple animals that exists right now are bots, megafauna, geese, gondolas, and some minor ones like mimic, zombie, dark wizard, soulscythe, etc. Point is, we're very much nearly done going through all simple animals, so this code is being wasted just to ensure things like cleanbots won't work if no one is on the z level, something I doubt happens often, so I took their code and made it work for basic mobs instead. I could've done both but I thought it would look very bad, and maybe this is a good incentivize to get more basic mob conversions. There's one major change here and it's that we're missing the "Idle" mode, some basic mobs like the Lavaland village seems to be made with intent that they'll be running even if players aren't around, so this sets up a future PR that makes idle AI easier to add, and I want to make sure those cases are taken into account. ## Why It's Good For The Game We don't need to always be processing these basic mobs, and sets us in the future to hopefully also implement idle AIs. ## Changelog :cl: balance: Basic mob AIs with no mobs on the Z level now stop. /:cl: --------- Co-authored-by: san7890 --- code/__DEFINES/ai/ai.dm | 8 ++- code/__DEFINES/mobs.dm | 1 - code/__DEFINES/subsystems.dm | 1 - code/_globalvars/lists/mobs.dm | 2 +- code/controllers/subsystem/ai_controllers.dm | 39 +++++++--- code/controllers/subsystem/idlenpcpool.dm | 47 ------------ code/datums/ai/_ai_controller.dm | 72 ++++++++++++++----- code/game/world.dm | 2 +- code/modules/clothing/shoes/cowboy.dm | 2 +- code/modules/mob/dead/dead.dm | 24 ++++--- code/modules/mob/dead/observer/login.dm | 4 -- code/modules/mob/dead/observer/logout.dm | 1 - .../mob/living/basic/ruin_defender/flesh.dm | 47 +++++++----- code/modules/mob/living/living.dm | 48 +++++++------ .../living/simple_animal/hostile/hostile.dm | 23 ------ .../mob/living/simple_animal/simple_animal.dm | 23 +----- code/modules/mob/mob_defines.dm | 2 +- code/modules/unit_tests/limbsanity.dm | 10 +-- code/modules/unit_tests/mouse_bite_cable.dm | 2 + code/modules/unit_tests/suit_storage_icons.dm | 13 ++-- tgstation.dme | 1 - 21 files changed, 175 insertions(+), 197 deletions(-) delete mode 100644 code/controllers/subsystem/idlenpcpool.dm 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"