91 lines
4.7 KiB
Lua
91 lines
4.7 KiB
Lua
-- Conservative recovery for managed residents with fresh quest/explore intent.
|
|
-- Native position/progress is sampled independently of dashboard summaries.
|
|
local intents, observations, pendingResults = {}, {}, {}
|
|
local LIMIT = 600
|
|
local COOLDOWN = 1800
|
|
local function sqltext(s)
|
|
return "CONVERT(UNHEX('"..string.gsub(s,".",function(c)return string.format('%02X',string.byte(c))end).."') USING utf8mb4)"
|
|
end
|
|
AuthDBExecute([[CREATE TABLE IF NOT EXISTS azerothai_recovery_log (
|
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,guid INT UNSIGNED NOT NULL,
|
|
name VARCHAR(64) NOT NULL,at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
detail VARCHAR(255) NOT NULL,INDEX(guid,at))]])
|
|
AuthDBExecute([[CREATE TABLE IF NOT EXISTS azerothai_recovery_results (
|
|
attempt_id BIGINT UNSIGNED PRIMARY KEY,status VARCHAR(24) NOT NULL,
|
|
checked_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,detail VARCHAR(255) NOT NULL)]])
|
|
local function checkResults(now)
|
|
for guid,r in pairs(pendingResults) do
|
|
local p=GetPlayerByName(r.name);local status,detail
|
|
if not p or not p:IsPlayerbot() or p:GetGUIDLow()~=guid or p:GetAccountId()~=r.account or p:IsDead() then
|
|
status='unverified';detail='Resident unavailable before verification'
|
|
elseif p:GetMapId()~=r.map or p:GetLevel()~=r.level or (p:GetX()-r.x)^2+(p:GetY()-r.y)^2+(p:GetZ()-r.z)^2>25 then
|
|
status='resumed';detail='Native movement or level progress observed after recovery'
|
|
elseif now-r.at>=180 then
|
|
status='no_progress';detail='No movement or level progress observed within three minutes'
|
|
end
|
|
if status then
|
|
AuthDBExecute('INSERT IGNORE INTO azerothai_recovery_results(attempt_id,status,detail) SELECT id,'..sqltext(status)..','..sqltext(detail)..' FROM azerothai_recovery_log WHERE guid='..guid..' AND UNIX_TIMESTAMP(at)='..r.at..' ORDER BY id DESC LIMIT 1')
|
|
pendingResults[guid]=nil
|
|
end
|
|
end
|
|
end
|
|
function AzerothAISetRecoveryIntents(rows)
|
|
local nextIntents={}
|
|
for _,r in ipairs(rows) do
|
|
if type(r.guid)=='number' and type(r.account)=='number' and type(r.name)=='string'
|
|
and (r.action=='quest' or r.action=='explore') then nextIntents[r.guid]=r;r.at=os.time() end
|
|
end
|
|
intents=nextIntents
|
|
return 'Recovery intentions refreshed'
|
|
end
|
|
local function safe(p,a)
|
|
if p:IsDead() or p:IsInCombat() or p:IsCasting() or p:IsTaxi() or a:HasGameClientMaster()
|
|
or p:IsRooted() or a:IsMovementImpaired() then return false end
|
|
local g=p:GetGroup()
|
|
if g then
|
|
if #g:GetMembers()~=g:GetMembersCount() then return false end
|
|
for _,m in pairs(g:GetMembers()) do if not m:IsPlayerbot() then return false end end
|
|
end
|
|
return true
|
|
end
|
|
local function sample()
|
|
local now=os.time()
|
|
checkResults(now)
|
|
for guid,r in pairs(intents) do
|
|
local p=GetPlayerByName(r.name)
|
|
if now-r.at>120 or not p or not p:IsPlayerbot() or p:GetGUIDLow()~=guid or p:GetAccountId()~=r.account then
|
|
observations[guid]=nil
|
|
else
|
|
local a=p:GetBotAI()
|
|
if not a or not safe(p,a) then observations[guid]=nil
|
|
else
|
|
local x,y,z,map=p:GetX(),p:GetY(),p:GetZ(),p:GetMapId()
|
|
local level,hp=p:GetLevel(),p:GetHealth()
|
|
local old=observations[guid]
|
|
if not old or old.map~=map or old.level~=level or old.hp~=hp or (x-old.x)^2+(y-old.y)^2+(z-old.z)^2>25 then
|
|
observations[guid]={x=x,y=y,z=z,map=map,level=level,hp=hp,since=now}
|
|
elseif now-old.since>=LIMIT then
|
|
-- Persistent cooldown survives ALE reloads; exact identity must still be active.
|
|
local q=AuthDBQuery('SELECT guid FROM azerothai_lifecycle WHERE guid='..guid..' AND account_id='..r.account..' AND BINARY name='..sqltext(r.name).." AND state='active'")
|
|
local recent=AuthDBQuery('SELECT id FROM azerothai_recovery_log WHERE guid='..guid..' AND at>NOW()-INTERVAL '..COOLDOWN..' SECOND LIMIT 1')
|
|
if q and not recent then
|
|
local staleFlight=p:GetMovementType()==7 and not p:IsTaxi()
|
|
if staleFlight then p:MoveExpire();p:MoveClear() end
|
|
a:Reset(true)
|
|
local moved=a:DoSpecificAction('move random',true)
|
|
local detail=(staleFlight and 'Cleared stale flight; ' or '')..'AI replanned; walking request='..tostring(moved)
|
|
AuthDBExecute('INSERT INTO azerothai_recovery_log(guid,name,detail) VALUES ('..guid..','..sqltext(r.name)..','..sqltext(detail)..')')
|
|
pendingResults[guid]={name=r.name,account=r.account,at=now,x=x,y=y,z=z,map=map,level=level}
|
|
PrintInfo('[AzerothAI recovery] '..r.name..': '..detail)
|
|
end
|
|
old.since=now
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- Forget residents whose intent changed to resting/socializing or who went offline.
|
|
for guid in pairs(observations) do if not intents[guid] then observations[guid]=nil end end
|
|
end
|
|
CreateLuaEvent(function()local ok,err=pcall(sample);if not ok then PrintError('[AzerothAI recovery] '..tostring(err)) end end,30000,0)
|
|
PrintInfo('[AzerothAI] Conservative stuck recovery enabled: 10 minutes idle, 30 minute cooldown')
|