Persist fatal encounters and audit resident readiness with party outcome metrics
Core tests / core (push) Successful in 42s

This commit is contained in:
Codex
2026-09-24 08:05:16 +12:00
parent 900b926b8e
commit d57d793a84
16 changed files with 254 additions and 15 deletions
+69
View File
@@ -0,0 +1,69 @@
-- Inspect actual inventory and equipment once per minute; normal NPC interactions only.
local snapshots,registry={},{}
-- WotLK 3.3.5 UpdateFields.h: OBJECT_END (6) + 0x36 / 0x37.
-- Read only; verified against this AzerothCore build's item update fields.
local ITEM_DURABILITY,ITEM_MAX_DURABILITY=0x003C,0x003D
local registryAt=0
local consumables={}
-- Match playerbots' own food/drink categories, without an incomplete item-ID list.
local q=WorldDBQuery('SELECT entry,spellcategory_1 FROM item_template WHERE class=0 AND subclass IN (0,5) AND spellcategory_1 IN (11,59)')
if q then repeat consumables[q:GetUInt32(0)]=q:GetUInt32(1) until not q:NextRow() end
local function managed(p)
local r=p and registry[p:GetGUIDLow()]
return r and p:IsPlayerbot() and r.name==p:GetName() and r.account==p:GetAccountId() and p:GetBotAI() and not p:GetBotAI():HasGameClientMaster()
end
local function safe(p)
if not managed(p) or p:IsDead() or p:IsInCombat() or p:IsCasting() or p:IsTaxi() then return false end
local g=p:GetGroup()
if g then
if g:IsRaidGroup() or #g:GetMembers()~=g:GetMembersCount() then return false end
for _,m in pairs(g:GetMembers()) do if not managed(m) then return false end end
end
return not (AzerothAISurvivalOverride and AzerothAISurvivalOverride(p))
end
function AzerothAIReadinessSnapshot(p)
local r=snapshots[p:GetGUIDLow()]
return r and r.name==p:GetName() and r.account==p:GetAccountId() and r.values or {}
end
local function inspect(p)
local ai=p:GetBotAI();local v={readinessAt=os.time(),brokenItems=0,freeTalents=p:GetFreeTalentPoints(),foodCount=0,drinkCount=0}
for slot=0,18 do
local item=p:GetItemByPos(255,slot)
if item then
if item:IsBroken() then v.brokenItems=v.brokenItems+1 end
local maximum=item:GetUInt32Value(ITEM_MAX_DURABILITY)
if maximum>0 then local pct=100*item:GetUInt32Value(ITEM_DURABILITY)/maximum;v.minDurabilityPct=math.min(v.minDurabilityPct or 100,pct) end
end
end
for _,item in pairs(ai:GetInventoryItems()) do
local category=consumables[item:GetEntry()]
if category and p:CanUseItem(item) then
if category==11 then v.foodCount=v.foodCount+item:GetCount() else v.drinkCount=v.drinkCount+item:GetCount() end
end
end
snapshots[p:GetGUIDLow()]={name=p:GetName(),account=p:GetAccountId(),values=v}
if not safe(p) then return end
if v.freeTalents>0 then ai:DoSpecificAction('auto talents',true) end
local supplies=v.foodCount<5 or p:GetPowerType()==0 and v.drinkCount<5
if (v.minDurabilityPct or 100)<30 or v.brokenItems>0 or supplies then
-- RPG handles finding an appropriate vendor and walking there. No spawned supplies.
ai:ChangeStrategy('+rpg',1)
if not ai:HasCheat(2) then
if (v.minDurabilityPct or 100)<30 or v.brokenItems>0 then ai:DoSpecificAction('repair',true) end
if supplies then ai:DoSpecificAction('rpg buy',true) end
end
end
end
CreateLuaEvent(function()
local now=os.time()
if now-registryAt>=60 then
local rows={};local result=AuthDBQuery("SELECT guid,name,account_id FROM azerothai_lifecycle WHERE state='active'")
if result then repeat rows[result:GetUInt32(0)]={name=result:GetString(1),account=result:GetUInt32(2)} until not result:NextRow() end
registry=rows;registryAt=now
end
for _,p in pairs(GetPlayersInWorld() or {}) do if managed(p) then
local old=snapshots[p:GetGUIDLow()]
if not old or now-old.values.readinessAt>=60 then local ok,err=pcall(inspect,p);if not ok then PrintError('[AzerothAI readiness] '..tostring(err)) end end
end end
for guid,r in pairs(snapshots) do if now-r.values.readinessAt>600 then snapshots[guid]=nil end end
end,10000,0)