Prioritize survival readiness over bot work and model goals
Core tests / core (push) Successful in 36s
Core tests / core (push) Successful in 36s
This commit is contained in:
+68
-11
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user