Skip to content

Commit

Permalink
tweak: Optimized NPC Handling (#5570)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gottfrei authored Jul 25, 2024
1 parent f33e67c commit 6ad891f
Show file tree
Hide file tree
Showing 48 changed files with 359 additions and 231 deletions.
13 changes: 8 additions & 5 deletions code/__DEFINES/mobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,15 @@
#define SLIME_FRIENDSHIP_STAY 3 //Min friendship to order it to stay
#define SLIME_FRIENDSHIP_ATTACK 8 //Min friendship to order it to attack

//Hostile simple animals
//If you add a new status, be sure to add a list for it to the simple_animals global in _globalvars/lists/mobs.dm
//Hostile Mob AI Status
#define AI_ON 1
#define AI_IDLE 2
#define AI_OFF 3
#define AI_Z_OFF 4
#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

// Intents
#define INTENT_HELP "help"
Expand Down
3 changes: 2 additions & 1 deletion code/_globalvars/lists/mobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ GLOBAL_LIST_EMPTY(alive_mob_list) //List of all alive mobs, including clientle
GLOBAL_LIST_EMPTY(dead_mob_list) //List of all dead mobs, including clientless. Excludes /mob/new_player
GLOBAL_LIST_EMPTY(respawnable_list) //List of all mobs, dead or in mindless creatures that still be respawned.
GLOBAL_LIST_EMPTY(non_respawnable_keys) //List of ckeys that are excluded from respawning for remainder of round.
GLOBAL_LIST_INIT(simple_animals, list(list(), list(), list(), list())) //One for each AI_* status define, List of all simple animals, including clientless
/// One for each AI_* status define, List of all simple animals, including clientless
GLOBAL_LIST_INIT(simple_animals, list(list(), list(), list(), list()))
GLOBAL_LIST_EMPTY(bots_list) //List of all bots(beepsky, medibots,etc)
GLOBAL_LIST_EMPTY(morphs_alive_list)

Expand Down
2 changes: 1 addition & 1 deletion code/_onclick/other_mobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
if(A.grab_attack(src, pulling))
changeNext_move(CLICK_CD_GRABBING)
return
target = A
GiveTarget(A)
AttackingTarget()

/atom/proc/attack_animal(mob/user)
Expand Down
38 changes: 21 additions & 17 deletions code/controllers/subsystem/idlenpcpool.dm
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ GLOBAL_VAR_INIT(idlenpc_suspension, FALSE)
GLOBAL_VAR_INIT(idlenpc_suspension, TRUE)
#endif

#define DEFAULT_CHECKS_DELAY (4 SECONDS)

SUBSYSTEM_DEF(idlenpcpool)
name = "Idling NPC Pool"
flags = SS_POST_FIRE_TIMING|SS_BACKGROUND
flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT
priority = FIRE_PRIORITY_IDLE_NPC
wait = 60
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
wait = 6 SECONDS
runlevels = RUNLEVEL_GAME|RUNLEVEL_POSTGAME
init_order = INIT_ORDER_IDLENPCS // MUST be after SSmapping since it tracks max Zs
offline_implications = "Idle simple animals will no longer process. Shuttle call recommended."
ss_id = "idle_npc_pool"
Expand All @@ -22,44 +24,46 @@ SUBSYSTEM_DEF(idlenpcpool)
return "IdleNPCS:[length(GLOB.simple_animals[AI_IDLE])]|Z:[length(GLOB.simple_animals[AI_Z_OFF])]"


/datum/controller/subsystem/idlenpcpool/Initialize()
idle_mobs_by_zlevel = new /list(world.maxz, 0)
return SS_INIT_SUCCESS


/datum/controller/subsystem/idlenpcpool/proc/MaxZChanged()
if (!islist(idle_mobs_by_zlevel))
if(!islist(idle_mobs_by_zlevel))
idle_mobs_by_zlevel = new /list(world.maxz,0)
while (SSidlenpcpool.idle_mobs_by_zlevel.len < world.maxz)
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
var/suspension = GLOB.idlenpc_suspension
//var/suspension = GLOB.idlenpc_suspension

while(currentrun.len)
var/mob/living/simple_animal/SA = currentrun[currentrun.len]
--currentrun.len
if(!SA)
log_debug("idlenpcpool encountered an invalid entry, resumed: [resumed], SA [SA], type of SA [SA?.type], null [SA == null], qdelled [QDELETED(SA)], SA in AI_IDLE list: [SA in GLOB.simple_animals[AI_IDLE]]")
if(QDELETED(SA))
GLOB.simple_animals[AI_IDLE] -= SA
stack_trace("Found a null in simple_animals deactive list [SA?.type]!")
continue

var/turf/T = get_turf(SA)
if(suspension && T && !length(SSmobs.clients_by_zlevel[T.z]))
continue
//var/turf/T = get_turf(SA)
//if(suspension && T && !length(SSmobs.clients_by_zlevel[T.z]))
// continue

if(!SA.ckey && SA.AI_delay_current <= world.time)
SA.AI_delay_current = world.time + wait + rand(DEFAULT_CHECKS_DELAY)

if(!SA.ckey)
if(SA.stat != DEAD)
SA.handle_automated_movement()
if(SA.stat != DEAD)
SA.consider_wakeup()

if(MC_TICK_CHECK)
return


#undef DEFAULT_CHECKS_DELAY

30 changes: 21 additions & 9 deletions code/controllers/subsystem/npcpool.dm
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,49 @@ GLOBAL_VAR_INIT(npcpool_suspension, FALSE)
GLOBAL_VAR_INIT(npcpool_suspension, TRUE)
#endif

#define DEFAULT_ACTIONS_DELAY (0.5 SECONDS)

SUBSYSTEM_DEF(npcpool)
name = "NPC Pool"
flags = SS_POST_FIRE_TIMING|SS_NO_INIT|SS_BACKGROUND
priority = FIRE_PRIORITY_NPC
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
runlevels = RUNLEVEL_GAME|RUNLEVEL_POSTGAME
wait = 2 SECONDS
offline_implications = "Simple animals will no longer process. Shuttle call recommended."
ss_id = "npc_pool"

var/list/currentrun = list()


/datum/controller/subsystem/npcpool/get_stat_details()
return "SimpleAnimals: [length(GLOB.simple_animals[AI_ON])]"

/datum/controller/subsystem/npcpool/fire(resumed = FALSE)

/datum/controller/subsystem/npcpool/fire(resumed = FALSE)
if(!resumed)
var/list/activelist = GLOB.simple_animals[AI_ON]
src.currentrun = activelist.Copy()

//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun
var/suspension = GLOB.npcpool_suspension
//var/suspension = GLOB.npcpool_suspension

while(currentrun.len)
var/mob/living/simple_animal/SA = currentrun[currentrun.len]
--currentrun.len
if(!SA)
log_debug("npcpool encountered an invalid entry, resumed: [resumed], SA [SA], type of SA [SA?.type], null [SA == null], qdelled [QDELETED(SA)], SA in AI_ON list: [SA in GLOB.simple_animals[AI_ON]]")

if(QDELETED(SA)) // Some issue causes nulls to get into this list some times. This keeps it running, but the bug is still there.
GLOB.simple_animals[AI_ON] -= SA
stack_trace("Found a null in simple_animals active list [SA?.type]!")
continue

var/turf/T = get_turf(SA)
//var/turf/T = get_turf(SA)
//if(suspension && T && !length(SSmobs.clients_by_zlevel[T.z]))
// continue

if(suspension && T && !length(SSmobs.clients_by_zlevel[T.z]))
continue
if(!SA.ckey && SA.AI_delay_current <= world.time && !HAS_TRAIT(SA, TRAIT_NO_TRANSFORM))
SA.AI_delay_current = world.time + wait + rand(DEFAULT_ACTIONS_DELAY, max(DEFAULT_ACTIONS_DELAY, SA.AI_delay_max))

if(!SA.ckey && !HAS_TRAIT(SA, TRAIT_NO_TRANSFORM))
if(SA.stat != DEAD)
SA.handle_automated_movement()
if(SA.stat != DEAD)
Expand All @@ -48,3 +56,7 @@ SUBSYSTEM_DEF(npcpool)

if(MC_TICK_CHECK)
return


#undef DEFAULT_ACTIONS_DELAY

21 changes: 6 additions & 15 deletions code/game/gamemodes/clockwork/clockwork_mob.dm
Original file line number Diff line number Diff line change
Expand Up @@ -58,21 +58,12 @@
else
..()

/mob/living/simple_animal/hostile/clockwork/marauder/FindTarget(list/possible_targets, HasTargetsList)
. = list()
if(!HasTargetsList)
possible_targets = ListTargets()
for(var/pos_targ in possible_targets)
var/atom/A = pos_targ
if(Found(A))
. = list(A)
break
if(CanAttack(A) && !isclocker(A))//Can we attack it? And no biting our friends!!
. += A
continue
var/Target = PickTarget(.)
GiveTarget(Target)
return Target

/mob/living/simple_animal/hostile/clockwork/marauder/CanAttack(atom/the_target)
if(isclocker(the_target))
return FALSE
return ..()


/mob/living/simple_animal/hostile/clockwork/marauder/bullet_act(obj/item/projectile/P)
if(deflect_projectile(P))
Expand Down
18 changes: 9 additions & 9 deletions code/game/gamemodes/miniantags/bot_swarm/swarmer.dm
Original file line number Diff line number Diff line change
Expand Up @@ -276,11 +276,11 @@
var/area/A = get_area(T)
if(isspaceturf(T) || (!isonshuttle && (istype(A, /area/shuttle) || istype(A, /area/space))) || (isonshuttle && !istype(A, /area/shuttle)))
to_chat(S, "<span class='warning'>Destroying this object has the potential to cause a hull breach. Aborting.</span>")
S.target = null
S.GiveTarget(null)
return FALSE
else if(istype(A, /area/engine/supermatter))
to_chat(S, "<span class='warning'>Disrupting the containment of a supermatter crystal would not be to our benefit. Aborting.</span>")
S.target = null
S.GiveTarget(null)
return FALSE
S.DisIntegrate(src)
return TRUE
Expand Down Expand Up @@ -391,11 +391,11 @@
var/area/A = get_area(T)
if(isspaceturf(T) || (!isonshuttle && (istype(A, /area/shuttle) || istype(A, /area/space))) || (isonshuttle && !istype(A, /area/shuttle)))
to_chat(S, "<span class='warning'>Destroying this object has the potential to cause a hull breach. Aborting.</span>")
S.target = null
S.GiveTarget(null)
return TRUE
else if(istype(A, /area/engine/supermatter))
to_chat(S, "<span class='warning'>Disrupting the containment of a supermatter crystal would not be to our benefit. Aborting.</span>")
S.target = null
S.GiveTarget(null)
return TRUE
return ..()

Expand All @@ -405,7 +405,7 @@
var/area/A = get_area(T)
if(isspaceturf(T) || (!isonshuttle && (istype(A, /area/shuttle) || istype(A, /area/space))) || (isonshuttle && !istype(A, /area/shuttle)))
to_chat(S, "<span class='warning'>Destroying this object has the potential to cause a hull breach. Aborting.</span>")
S.target = null
S.GiveTarget(null)
return TRUE
return ..()

Expand All @@ -416,11 +416,11 @@
var/area/A = get_area(T)
if(isspaceturf(T) || (!isonshuttle && (istype(A, /area/shuttle) || istype(A, /area/space))) || (isonshuttle && !istype(A, /area/shuttle)))
to_chat(S, "<span class='warning'>Destroying this object has the potential to cause a hull breach. Aborting.</span>")
S.target = null
S.GiveTarget(null)
return TRUE
else if(istype(A, /area/engine/supermatter))
to_chat(S, "<span class='warning'>Disrupting the containment of a supermatter crystal would not be to our benefit. Aborting.</span>")
S.target = null
S.GiveTarget(null)
return TRUE
return ..()

Expand All @@ -430,11 +430,11 @@
var/area/A = get_area(T)
if(isspaceturf(T) || (!isonshuttle && (istype(A, /area/shuttle) || istype(A, /area/space))) || (isonshuttle && !istype(A, /area/shuttle)))
to_chat(S, "<span class='warning'>Destroying this object has the potential to cause a hull breach. Aborting.</span>")
S.target = null
S.GiveTarget(null)
return TRUE
else if(istype(A, /area/engine/supermatter))
to_chat(S, "<span class='warning'>Disrupting the containment of a supermatter crystal would not be to our benefit. Aborting.</span>")
S.target = null
S.GiveTarget(null)
return TRUE
return ..()

Expand Down
2 changes: 1 addition & 1 deletion code/game/machinery/computer/arcade.dm
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@
atom_say("WEEWOO WEEWOO, Spaceport Security en route!")
for(var/i, i<=3, i++)
var/mob/living/simple_animal/hostile/syndicate/ranged/orion/O = new/mob/living/simple_animal/hostile/syndicate/ranged/orion(get_turf(src))
O.target = usr
O.GiveTarget(usr)


fuel += FU
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1897,6 +1897,7 @@
minbodytemp = 0
maxbodytemp = 600 // better than human vampire but still dangerous
heat_damage_per_tick = 5 // we are a vampire animal and high temperatures are pretty bad
AI_delay_max = 0 SECONDS
var/dead_for_sure = FALSE // we need this to prevent death() proc to invoke nultiple times
var/datum/antagonist/vampire/vampire
var/mob/living/carbon/human/human_vampire
Expand Down Expand Up @@ -2249,7 +2250,7 @@
if(isliving(target))
var/mob/living/l_target = target
if(l_target.stat != CONSCIOUS && (!isvampire(user) && !isvampirethrall(user))) // will change target on attacker instantly if its current target is unconscious or dead
target = user
GiveTarget(user)


/mob/living/simple_animal/hostile/vampire/bats_summoned/Found(atom/A)
Expand Down
2 changes: 1 addition & 1 deletion code/modules/hydroponics/beekeeping/beebox.dm
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@
continue
if(B.loc == src)
B.forceMove(drop_location())
B.target = user
B.GiveTarget(user)
bees = TRUE
if(bees)
visible_message("<span class='danger'>[user] disturbs the bees!</span>")
Expand Down
32 changes: 21 additions & 11 deletions code/modules/mob/dead/dead.dm
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
/mob/dead/Login()
. = ..()
if(!. || !client)
return FALSE
var/turf/T = get_turf(src)
if (isturf(T))
if(isturf(T))
update_z(T.z)


/mob/dead/Logout()
update_z(null)
return ..()


/mob/dead/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents = TRUE)
..()
update_z(new_turf?.z)

/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

27 changes: 19 additions & 8 deletions code/modules/mob/living/living.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1676,16 +1676,27 @@
if(isnull(client))
registered_z = null
return
if(new_z)
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)
SA.toggle_ai(AI_ON) // Guarantees responsiveness for when appearing right next to mobs
else
SSidlenpcpool.idle_mobs_by_zlevel[new_z] -= SA
if(!new_z)
registered_z = new_z
return
//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/index 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/animal = SSidlenpcpool.idle_mobs_by_zlevel[new_z][index]
if(animal)
if(!oldlen)
//Start AI idle if nobody else was on this z level before (mobs will switch off when this is the case)
animal.toggle_ai(AI_IDLE)
//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(animal)) < MAX_SIMPLEMOB_WAKEUP_RANGE)
animal.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] -= animal
registered_z = new_z


/mob/living/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents = TRUE)
..()
update_z(new_turf?.z)
Expand Down
Loading

0 comments on commit 6ad891f

Please sign in to comment.