-- 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)