Prioritize survival readiness over bot work and model goals
Core tests / core (push) Successful in 36s

This commit is contained in:
Codex
2026-09-24 04:05:48 +12:00
parent 1552e4c165
commit 0731774457
6 changed files with 135 additions and 16 deletions
+1
View File
@@ -60,6 +60,7 @@ function AzerothAISetRecoveryIntents(rows)
return 'Recovery intentions refreshed'
end
local function safe(p,a)
if AzerothAISurvivalOverride and AzerothAISurvivalOverride(p) then return false end
if p:IsDead() or p:IsInCombat() or p:IsTaxi() or a:HasGameClientMaster()
or p:IsRooted() or a:IsMovementImpaired() then return false end
local g=p:GetGroup()
+68 -11
View File
@@ -8,11 +8,48 @@ local function quoted(s)return "UNHEX('"..string.gsub(tostring(s),'.',function(c
local function audit(p,kind,detail)
AuthDBExecute('INSERT INTO azerothai_survival_audit(guid,name,kind,detail) VALUES ('..p:GetGUIDLow()..','..quoted(p:GetName())..','..quoted(kind)..','..quoted(detail)..')')
end
-- Persist only strategy flags this guard suppresses; reloads cannot strand a pause.
local holdStrategies={'rpg','grind','quest','gather','loot'}
AuthDBExecute([[CREATE TABLE IF NOT EXISTS azerothai_survival_holds (
guid INT UNSIGNED PRIMARY KEY,name VARCHAR(64),account_id INT UNSIGNED,expires BIGINT UNSIGNED,
rpg TINYINT,grind TINYINT,quest TINYINT,gather TINYINT,loot TINYINT)]])
local holdScan=0
local function restoreHoldRow(q)
local guid,name,account=q:GetUInt32(0),q:GetString(1),q:GetUInt32(2)
local p=GetPlayerByName(name)
if not p or not p:IsPlayerbot() or p:GetGUIDLow()~=guid or p:GetAccountId()~=account or not p:GetBotAI() or p:GetBotAI():HasGameClientMaster() then return end
for i,key in ipairs(holdStrategies) do if q:GetUInt32(i+2)==1 then p:GetBotAI():ChangeStrategy('+'..key,1) end end
AuthDBExecute('DELETE FROM azerothai_survival_holds WHERE guid='..guid)
if observed[guid] then observed[guid].holding=false end
end
local function restoreExpiredHolds(now)
if now-holdScan<10 then return end;holdScan=now
local q=AuthDBQuery('SELECT guid,name,account_id,rpg,grind,quest,gather,loot FROM azerothai_survival_holds WHERE expires<='..now)
if q then repeat restoreHoldRow(q) until not q:NextRow() end
end
local function holdPlans(p,ai,r,now)
local flags,changed={},false
for _,key in ipairs(holdStrategies) do
local on=ai:HasStrategy(key,1);flags[#flags+1]=on and 1 or 0
if on then changed=true;ai:ChangeStrategy('-'..key,1) end
end
if not r.holding or changed or now-(r.holdSaved or 0)>=30 then
AuthDBExecute('INSERT INTO azerothai_survival_holds VALUES ('..p:GetGUIDLow()..','..quoted(p:GetName())..','..p:GetAccountId()..','..(now+90)..','..table.concat(flags,',')..') ON DUPLICATE KEY UPDATE expires=VALUES(expires),rpg=GREATEST(rpg,VALUES(rpg)),grind=GREATEST(grind,VALUES(grind)),quest=GREATEST(quest,VALUES(quest)),gather=GREATEST(gather,VALUES(gather)),loot=GREATEST(loot,VALUES(loot))')
r.holdSaved=now
end
r.holding=true
end
local function releasePlans(p,r)
if not r.holding then return end
local q=AuthDBQuery('SELECT guid,name,account_id,rpg,grind,quest,gather,loot FROM azerothai_survival_holds WHERE guid='..p:GetGUIDLow())
if q then restoreHoldRow(q) end
r.holding=false
end
local function dangerous(p,enemy)
if not enemy then return false end
local creature=enemy:ToCreature()
local rank=creature and creature:GetRank() or 0
return enemy:GetLevel()>=p:GetLevel()+3 or rank==1 or rank==2 or rank==3
return enemy:GetLevel()>=p:GetLevel()+2 or rank==1 or rank==2 or rank==3
end
local function dangerousSelection(p)
local target=p:GetSelection()
@@ -57,16 +94,19 @@ end
-- Prevent slower model intentions from undoing an immediate safety action.
function AzerothAISurvivalOverride(p)
local r=observed[p:GetGUIDLow()]
return r and os.time()-(r.actionAt or 0)<20 and (r.action=='retreat requested' or r.action=='recovering health' or r.action=='regroup requested' or r.action=='waiting for companion' or r.action=='avoiding dangerous target') or false
return r and (r.holding or os.time()-(r.actionAt or 0)<20 and (r.action=='retreat requested' or r.action=='recovering health' or r.action=='regroup requested' or r.action=='waiting for companion' or r.action=='avoiding dangerous target' or r.action=='recovering readiness')) or false
end
local function sample()
local now=os.time();refresh(now)
local now=os.time();refresh(now);restoreExpiredHolds(now)
for _,p in pairs(GetPlayersInWorld() or {}) do
local previous=observed[p:GetGUIDLow()]
if previous then outcome(p,previous,now) end
if managed(p) then
local id=p:GetGUIDLow();local ai=p:GetBotAI();local r=observed[id] or {};observed[id]=r
local hp=p:GetHealthPct()
local falling=r.hp and r.hp-hp>=20
local mana=p:GetPowerType()==0 and p:GetPowerPct(0) or 100
if mana~=mana then mana=100 end
if p:IsInCombat() and (hp<55 or r.hp and r.hp-hp>=15) then r.dangerAt=now end
r.hp=hp;r.seen=now
local master=ai and ai:GetMaster()
@@ -78,27 +118,44 @@ local function sample()
local g=safeGroup(p)
-- Never intervene in a party containing humans, unregistered bots, or offline members.
if not p:GetGroup() or g then
local peers=g and g:GetMembers() or {p};local weak=0
local peers=g and g:GetMembers() or {p};local weak,support=0,0
for _,m in pairs(peers) do if m:GetMapId()==p:GetMapId() and p:GetDistance(m)<40 and m:GetHealthPct()<40 then weak=weak+1 end end
for _,m in pairs(peers) do if m~=p and m:GetMapId()==p:GetMapId() and m:GetPhaseMask()==p:GetPhaseMask() and p:GetDistance(m)<=35 and m:GetHealthPct()>=70 then support=support+1 end end
local attackers=0;for _ in pairs(p:GetAttackers()) do attackers=attackers+1 end
local enemy=p:GetVictim()
local overmatched=dangerous(p,enemy)
if p:IsInCombat() and (hp<50 or overmatched or #peers>1 and weak>=math.max(2,math.ceil(#peers/2))) and now-(r.retreatAt or 0)>=8 then
local outnumbered=attackers>support+1 and hp<80
local unsafe=p:IsInCombat() and (hp<(support==0 and 55 or 50) or overmatched or outnumbered or falling and hp<80 or #peers>1 and weak>=math.max(2,math.ceil(#peers/2)))
local avoid=not p:IsInCombat() and dangerousSelection(p)
if unsafe then r.recoverUntil=now+15 end
if hp<85 or mana<35 then r.resting=true end
if hp>=95 and mana>=65 then r.resting=false end
local resting=not p:IsInCombat() and (r.resting or now<(r.recoverUntil or 0))
if unsafe or avoid or resting then holdPlans(p,ai,r,now) else releasePlans(p,r) end
if unsafe and now-(r.retreatAt or 0)>=8 then
r.dangerAt=now;r.retreatAt=now;r.action='retreat requested';r.actionAt=now
if p:IsCasting() then p:StopSpellCast() end
r.accepted=ai:DoSpecificAction('flee',true)
audit(p,'retreat-request', 'accepted='..tostring(r.accepted)..'; hp='..math.floor(hp)..'; dangerous='..tostring(overmatched))
audit(p,'retreat-request', 'accepted='..tostring(r.accepted)..'; hp='..math.floor(hp)..'; dangerous='..tostring(overmatched)..'; attackers='..attackers..'; nearbySupport='..support)
if not r.retreat then r.retreat={at=now,map=p:GetMapId(),x=p:GetX(),y=p:GetY()} end
elseif not p:IsInCombat() and dangerousSelection(p) and now-(r.avoidAt or 0)>=8 then
elseif avoid and now-(r.avoidAt or 0)>=8 then
if p:IsCasting() then p:StopSpellCast() end
p:AttackStop();r.avoidAt=now;r.dangerAt=now;r.action='avoiding dangerous target';r.actionAt=now
r.accepted=ai:DoSpecificAction('flee',true)
audit(p,'avoid-request','accepted='..tostring(r.accepted))
elseif not p:IsCasting() and not p:IsInCombat() and hp<80 and now-(r.dangerAt or 0)<600 and now-(r.restAt or 0)>=20 then
r.restAt=now;r.action='recovering health';r.actionAt=now
ai:DoSpecificAction('eat',true);ai:DoSpecificAction('drink',true)
elseif resting then
r.action='recovering readiness';r.actionAt=now
p:AttackStop()
if not p:IsCasting() then
p:MoveStop()
if now-(r.restAt or 0)>=20 then
r.restAt=now
ai:DoSpecificAction('eat',true);ai:DoSpecificAction('drink',true)
end
end
elseif enabled and now-enabledAt<180 and g and not p:IsInCombat() and not p:IsCasting() then
local leader=ai:GetGroupLeader()
if leader and leader~=p and managed(leader) and not leader:IsInCombat() and not leader:IsTaxi() and leader:GetMapId()==p:GetMapId() and leader:GetPhaseMask()==p:GetPhaseMask() and p:GetDistance(leader)>12 and p:GetDistance(leader)<200 and hp>=65 and now-(r.followAt or 0)>=8 then
if leader and leader~=p and managed(leader) and not leader:IsInCombat() and not leader:IsTaxi() and leader:GetMapId()==p:GetMapId() and leader:GetPhaseMask()==p:GetPhaseMask() and p:GetDistance(leader)>12 and p:GetDistance(leader)<200 and hp>=95 and mana>=65 and now-(r.followAt or 0)>=8 then
ai:SetMaster(leader);ownedMasters[id]=leader:GetGUIDLow()
ai:ChangeStrategy('-stay',1)
r.followAt=now;r.action='regroup requested';r.actionAt=now;r.accepted=ai:DoSpecificAction('follow',true)