From 20ccd0fced72c43a4889c9972c603bb38e4c823e Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 25 Sep 2026 09:25:33 +0800 Subject: [PATCH] Use verified pathfinding retreat when bot flee is rejected --- ale/azerothai_survival.lua | 21 ++++++++++----------- tests/survival-lua.test.py | 8 ++++---- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/ale/azerothai_survival.lua b/ale/azerothai_survival.lua index a41d7e4..6077096 100644 --- a/ale/azerothai_survival.lua +++ b/ale/azerothai_survival.lua @@ -102,13 +102,7 @@ local function sample() local now=os.time();refresh(now);restoreExpiredHolds(now) for _,p in pairs(GetPlayersInWorld() or {}) do local previous=observed[p:GetGUIDLow()] - if previous then - if previous.nativeFleeUntil and now>=previous.nativeFleeUntil then - if p:GetMovementType()==9 or p:GetMovementType()==13 then p:MoveExpire() end - previous.nativeFleeUntil=nil - end - outcome(p,previous,now) - end + 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 previousAction,previousAt=r.action,r.actionAt or 0 @@ -182,13 +176,18 @@ local function sample() r.accepted=ai:DoSpecificAction('flee',true) -- The bot action may reject fleeing when its target/context is missing. -- Bound a native pathfinding attempt; never teleport or clear combat/threat. - if not r.accepted and not p:IsMoving() and now-(r.nativeFleeAt or 0)>=30 then + if not r.accepted and not p:IsMoving() and now-(r.nativeFleeAt or 0)>=15 then local threat=enemy if not threat then for _,attacker in pairs(p:GetAttackers()) do threat=attacker;break end end if threat then - p:AttackStop();p:MoveFleeing(threat,8000) - r.nativeFleeAt=now;r.nativeFleeUntil=now+8 - audit(p,'retreat-path-request','Playerbot flee rejected; bounded native flee path requested') + local dx,dy=p:GetX()-threat:GetX(),p:GetY()-threat:GetY() + local distance=math.sqrt(dx*dx+dy*dy) + if distance>0.1 then + p:AttackStop() + p:MoveTo(7822,p:GetX()+20*dx/distance,p:GetY()+20*dy/distance,p:GetZ(),true) + r.nativeFleeAt=now + audit(p,'retreat-path-request','Playerbot flee rejected; 20-yard path away from attacker requested') + end end end audit(p,'retreat-request', 'accepted='..tostring(r.accepted)..'; hp='..math.floor(hp)..'; dangerous='..tostring(overmatched)..'; attackers='..attackers..'; nearbySupport='..support) diff --git a/tests/survival-lua.test.py b/tests/survival-lua.test.py index 37ec853..a326f9d 100644 --- a/tests/survival-lua.test.py +++ b/tests/survival-lua.test.py @@ -16,7 +16,7 @@ function make(id,name) function p:IsPlayerbot()return not self.human end;function p:IsDead()return self.dead end;function p:GetBotAI()return self.ai end function p:GetPowerType()return self.powerType or 1 end;function p:GetPowerPct()return self.mana or 100 end;function p:GetAttackers()return self.attackers or {} end;function p:GetHealthPct()return self.hp end;function p:IsInCombat()return self.combat end function p:HasUnitState(state)assert(state==0x00000100);return self.taxi end;function p:IsMoving()return self.moving end;function p:GetMovementType()return self.motion or 0 end;function p:MoveExpire()self.expired=true;self.motion=0 end;function p:MoveFleeing(t,ms)self.fleeTarget=t;self.motion=9;self.fleeDuration=ms end;function p:IsCasting()return self.cast end;function p:IsRooted()return false end - function p:GetX()return self.x or 0 end;function p:GetY()return 0 end + function p:GetX()return self.x or 0 end;function p:GetY()return 0 end;function p:GetZ()return 0 end;function p:MoveTo(id,x,y,z,path)self.escape={id=id,x=x,y=y,z=z,path=path} end function p:GetUnfriendlyUnitsInRange()return self.friendly and {} or self.selected and {self.selected} or {} end;function p:GetSelection()return self.selected end;function p:StopSpellCast()self.cast=false;self.interrupted=true end function p:AttackStop()self.attackStopped=true end;function p:MoveStop()self.waited=true end function p:GetGroup()return self.group end;function p:GetVictim()return self.enemy end;function p:GetLevel()return 5 end @@ -70,6 +70,6 @@ l=new();l.execute("A.strategies={stay=true};step(0);assert(not A.strategies.stay l=new();l.execute("group();B.distance=50;step(0);step(40);assert(B.master==nil,'No-progress regroup must release its owned master');assert(not AzerothAISurvivalOverride(B));step(30);assert(B.master==nil,'Backoff must prevent immediate retry')") print('Recovered stay cleared and non-progressing regroup bounded') -l=new();l.execute("A.combat=true;A.hp=20;A.attackers={B};function A.ai:DoSpecificAction(s)table.insert(calls,s);return false end;step(0);assert(A.fleeTarget==B and A.fleeDuration==8000);step(8);assert(A.expired);A.fleeTarget=nil;step(8);assert(A.fleeTarget==nil,'Native fallback must honor cooldown')") -l=new();l.execute("A.combat=true;A.hp=20;A.controlled=true;A.attackers={B};function A.ai:DoSpecificAction()return false end;step(0);assert(A.fleeTarget==nil)") -print('Rejected flee fallback is bounded, cooled down, and preserves human control') +l=new();l.execute("A.combat=true;A.hp=20;B.x=5;A.attackers={B};function A.ai:DoSpecificAction(s)table.insert(calls,s);return false end;step(0);assert(A.escape.x==-20 and A.escape.path==true);A.escape=nil;step(8);assert(A.escape==nil,'Native fallback must honor cooldown');step(8);assert(A.escape.x==-20)") +l=new();l.execute("A.combat=true;A.hp=20;A.controlled=true;B.x=5;A.attackers={B};function A.ai:DoSpecificAction()return false end;step(0);assert(A.escape==nil)") +print('Rejected flee uses bounded path away from threat, with cooldown and human protection')