Improve free-flight recovery and fallback handling
This commit is contained in:
1 parent
7b4994400b
commit
856832e7bd
7 files changed
+109
-7
No files matched your search
@@ -206,6 +206,12 @@ ClientlessActionExecutionStatus const* ClientlessActionExecutorMgr::GetStatus(ui
|
||||
return itr != _records.end() ? &itr->second.Status : nullptr;
|
||||
}
|
||||
|
||||
bool ClientlessActionExecutorMgr::OwnsActiveFlight(uint32 characterGuid) const
|
||||
{
|
||||
auto itr = _records.find(characterGuid);
|
||||
return itr != _records.end() && itr->second.Active && itr->second.FlightActive;
|
||||
}
|
||||
|
||||
bool ClientlessActionExecutorMgr::StartAction(Player* player, ClientlessAction const& action, Record& record)
|
||||
{
|
||||
record.StopAtNode = false;record.FormationWaiting=false;record.GroundChunk=false;
|
||||
@@ -1025,7 +1031,7 @@ bool ClientlessActionExecutorMgr::StartRouteStep(
|
||||
}
|
||||
Position target;target.Relocate(step.X,step.Y,step.Z,step.O);
|
||||
if (!validSource || step.MapId!=player->GetMapId() ||
|
||||
!sClientlessFlightMgr->Start(player,target,error))
|
||||
!sClientlessFlightMgr->Start(player,target,error,true))
|
||||
{
|
||||
ClientlessTravelTelemetry::Note(player,"flight-start-rejected",error+" "+ClientlessTravelTelemetry::State(player));
|
||||
return RecoverFlightLeg(player,characterGuid,record,error.empty()?"free flight source not physically reached":error);
|
||||
@@ -1916,7 +1922,7 @@ bool ClientlessActionExecutorMgr::TryFallbackFlight(Player* player, uint32 chara
|
||||
if (!std::isfinite(sClientlessFlightMgr->Estimate(player, goal.MapId, from, target, true)))
|
||||
return false;
|
||||
std::string error;
|
||||
if (!sClientlessFlightMgr->Start(player, target, error))
|
||||
if (!sClientlessFlightMgr->Start(player, target, error, true))
|
||||
{
|
||||
ClientlessTravelTelemetry::Note(player,"flight-fallback-rejected",error+" "+ClientlessTravelTelemetry::State(player));
|
||||
TC_LOG_INFO("server.adventurebot.navigation", "FLIGHT_FALLBACK_UNAVAILABLE guid=%u reason='%s'",
|
||||
@@ -1998,7 +2004,7 @@ void ClientlessActionExecutorMgr::CheckGroundExit(Player* player, uint32 charact
|
||||
if (std::isfinite(sClientlessFlightMgr->Estimate(player, goal.MapId, from, target)))
|
||||
{
|
||||
std::string error;
|
||||
if (sClientlessFlightMgr->Start(player, target, error))
|
||||
if (sClientlessFlightMgr->Start(player, target, error, true))
|
||||
{
|
||||
record.GroundExitActive = true;
|
||||
record.FlightActive = true;
|
||||
|
||||
@@ -50,6 +50,7 @@ public:
|
||||
void SetGroundPathAdapter(uint32 guid,GroundPathAdapter adapter);
|
||||
bool RequestStopAtNode(uint32 characterGuid);
|
||||
ClientlessActionExecutionStatus const* GetStatus(uint32 characterGuid) const;
|
||||
bool OwnsActiveFlight(uint32 characterGuid) const;
|
||||
|
||||
static char const* StateName(ClientlessActionExecutionState state);
|
||||
|
||||
|
||||
@@ -202,8 +202,13 @@ Surface ClientlessFlightMgr::Sample(Player * player, Record & record, float x, f
|
||||
};
|
||||
}
|
||||
|
||||
bool ClientlessFlightMgr::Start(Player* player,Position const& target,std::string& error)
|
||||
{return Begin(player,target,error,false);}
|
||||
bool ClientlessFlightMgr::Start(Player* player,Position const& target,std::string& error,bool actionOwned)
|
||||
{
|
||||
bool started=Begin(player,target,error,false);
|
||||
if(started)
|
||||
if(auto found=_records.find(player->GetGUID().GetCounter());found!=_records.end())found->second.ActionOwned=actionOwned;
|
||||
return started;
|
||||
}
|
||||
bool ClientlessFlightMgr::PrepareFollowingMount(Player* player)
|
||||
{
|
||||
if(!Enabled(player) || !player->IsAlive() || player->IsInCombat() || player->IsBeingTeleported() ||
|
||||
@@ -647,6 +652,18 @@ bool ClientlessFlightMgr::Advance(Player * player, Record & record, bool handoff
|
||||
}
|
||||
if (! next.Valid ||(! record.Returning && record.Status.Travelled > Distance(PointOf(record.Start), goal) * 2 + 500))
|
||||
{
|
||||
// A detached/pathological flight gets one fresh airborne plan. If the
|
||||
// new corridor is still impossible, finish with a checked local
|
||||
// touchdown so the ordinary behavior controller can plan from ground.
|
||||
if(!record.Returning && record.DetachedRecoveryCount && FindReturnLanding(player,record))
|
||||
{
|
||||
record.Returning=true;
|
||||
record.AbortReason="airborne recovery could not reach the original target";
|
||||
record.Breadcrumbs.clear();
|
||||
record.Memory={};record.Memory.Departed=true;
|
||||
record.Status.Detail="airborne recovery landing";
|
||||
return Advance(player,record);
|
||||
}
|
||||
Fail(player, record, "no safe bounded flight corridor; holding position mounted");
|
||||
return false;
|
||||
}
|
||||
@@ -672,7 +689,11 @@ bool ClientlessFlightMgr::Advance(Player * player, Record & record, bool handoff
|
||||
++ record.Status.Segments;
|
||||
record.Status.Avoidances += next.Avoiding ? 1 : 0;
|
||||
if (handoff) ++record.Status.Handoffs;
|
||||
record.Status.Detail = next.Avoiding ? "obstacle avoidance" : next.Landing ? "landing approach" : next.Next.Z < current.Z - 0.25f ? "diagonal descent" : next.Next.Z > current.Z + 0.25f ? "diagonal climb" : "terrain-smoothed cruise";
|
||||
bool const vertical=Horizontal(current,next.Next)<1.0f && std::fabs(next.Next.Z-current.Z)>1.0f;
|
||||
record.Status.Detail = next.Avoiding ? "obstacle avoidance" : next.Landing && vertical ? "vertical landing fallback" :
|
||||
next.Landing ? "landing approach" : vertical && next.Next.Z>current.Z ? "vertical takeoff fallback" :
|
||||
vertical ? "vertical descent fallback" : next.Next.Z < current.Z - 0.25f ? "diagonal descent" :
|
||||
next.Next.Z > current.Z + 0.25f ? "diagonal climb" : "terrain-smoothed cruise";
|
||||
TC_LOG_INFO("server.adventurebot.navigation", "FLIGHT_SEGMENT guid=%u n=%u avoid=%u handoff=%u target=(%.2f %.2f %.2f) detail='%s'", player->GetGUID().GetCounter(), record.Status.Segments, next.Avoiding ? 1 : 0, handoff ? 1 : 0, next.Next.X, next.Next.Y, next.Next.Z, record.Status.Detail.c_str());
|
||||
return true;
|
||||
}
|
||||
@@ -724,6 +745,23 @@ void ClientlessFlightMgr::Update(uint32 diff)
|
||||
Fail(player, record, "flight permission lost or total timeout");
|
||||
continue;
|
||||
}
|
||||
bool const ownerLost=record.ActionOwned && !sClientlessActionExecutorMgr->OwnsActiveFlight(guid);
|
||||
bool const pathological=record.Status.Segments>=12 && record.Status.Avoidances*2>=record.Status.Segments;
|
||||
if(!record.Returning && !record.Following && record.DetachedRecoveryCount==0 && (ownerLost || pathological))
|
||||
{
|
||||
++record.DetachedRecoveryCount;
|
||||
if(ownerLost)record.ActionOwned=false;
|
||||
player->StopMoving();record.Moving=false;record.SegmentElapsed=0;
|
||||
record.Start.Relocate(player);record.LastPosition.Relocate(player);
|
||||
record.Memory={};record.Breadcrumbs.clear();
|
||||
record.Published=std::make_shared<ClientlessFlightPath::SharedTrail>();
|
||||
record.Status.Detail=ownerLost?"replanning detached free flight":"replanning excessive obstacle avoidance";
|
||||
ClientlessTravelTelemetry::Note(player,"flight-airborne-replan",record.Status.Detail);
|
||||
TC_LOG_WARN("server.adventurebot.navigation","FLIGHT_AIRBORNE_REPLAN guid=%u ownerLost=%u segments=%u avoidances=%u target=(%.2f %.2f %.2f)",
|
||||
guid,ownerLost?1:0,record.Status.Segments,record.Status.Avoidances,record.Status.Landing.GetPositionX(),record.Status.Landing.GetPositionY(),record.Status.Landing.GetPositionZ());
|
||||
Advance(player,record);
|
||||
continue;
|
||||
}
|
||||
if (record.CheckTimer > diff)
|
||||
{
|
||||
record.CheckTimer -= diff;
|
||||
|
||||
@@ -31,7 +31,7 @@ class TC_GAME_API ClientlessFlightMgr
|
||||
bool Preferred(Player const * player, uint32 mapId, Position const & position) const;
|
||||
bool Enabled(Player const * player) const;
|
||||
void BeginPlanning(Player const* player = nullptr);
|
||||
bool Start(Player * player, Position const & target, std::string & error);
|
||||
bool Start(Player * player, Position const & target, std::string & error, bool actionOwned = false);
|
||||
bool Follow(Player* player, Position const& target, std::string& error);
|
||||
bool LandFollowing(Player* player);
|
||||
bool PrepareFollowingMount(Player* player);
|
||||
@@ -53,6 +53,8 @@ class TC_GAME_API ClientlessFlightMgr
|
||||
bool ApproachingDeparture = false;
|
||||
Position Departure;
|
||||
uint32 TargetUpdated = 0, FormationDelay = 0;
|
||||
uint32 DetachedRecoveryCount = 0;
|
||||
bool ActionOwned = false;
|
||||
std::string AbortReason;
|
||||
std::vector < Position > Breadcrumbs;
|
||||
std::unordered_map < std::string, bool > Tiles;
|
||||
|
||||
@@ -118,6 +118,13 @@ namespace ClientlessFlightPath
|
||||
if (memory.Departed && from.Z>=goal.Z && from.Z-goal.Z<=12 &&
|
||||
Distance(from,goal)<=cfg.Step && Corridor(from,goal,goal,cfg,sample,ray,false))
|
||||
return {goal,true,false,true};
|
||||
// A normal diagonal touchdown remains the first choice. When the bot
|
||||
// is already above the landing column and there is not enough lateral
|
||||
// room for that slope, descend in the verified column instead of
|
||||
// opening another wide approach.
|
||||
if (memory.Departed && distance < 2.0f && from.Z > goal.Z + 1.5f &&
|
||||
Corridor(from,goal,goal,cfg,sample,ray,false))
|
||||
return {goal,true,false,true};
|
||||
if (distance < 2 && std::fabs(from.Z - goal.Z) < 1.5f) return
|
||||
{
|
||||
goal, Corridor(from, goal, goal, cfg, sample, ray, true), false, true
|
||||
@@ -212,6 +219,20 @@ namespace ClientlessFlightPath
|
||||
return best;
|
||||
}
|
||||
}
|
||||
// Diagonal departure candidates were exhausted. Tight settlements
|
||||
// can still have a completely open vertical column, so use it before
|
||||
// asking the caller to walk to a different takeoff point.
|
||||
if (!best.Valid && departure)
|
||||
{
|
||||
float const top = std::min(here.Ceiling - 6.0f,
|
||||
std::max(from.Z + 20.0f, here.Ground + std::min(cfg.Clearance, 35.0f)));
|
||||
Point vertical{from.X,from.Y,top};
|
||||
if (top > from.Z + 2.0f && Corridor(from,vertical,goal,cfg,sample,ray,true))
|
||||
{
|
||||
memory.Departed=true;
|
||||
return {vertical,true,false,false};
|
||||
}
|
||||
}
|
||||
if (best.Valid) memory.Departed = true;
|
||||
return best;
|
||||
}
|
||||
|
||||
@@ -1273,6 +1273,24 @@ DeletedCharacterTicketTrace = 0
|
||||
|
||||
DungeonFinder.OptionsMask = 1
|
||||
|
||||
#
|
||||
# DungeonFinder.LowPopulation.Enable
|
||||
# DungeonFinder.LowPopulation.FallbackDelay
|
||||
# DungeonFinder.LowPopulation.MinPlayers
|
||||
# DungeonFinder.LowPopulation.SoloGroupOutsideTimeout
|
||||
# Description: Allow the dungeon finder to create a dungeon group below
|
||||
# the normal group size after the fallback delay. The group
|
||||
# remains marked as low-population while inside the dungeon.
|
||||
# Default: Enable = 0
|
||||
# FallbackDelay = 60 seconds
|
||||
# MinPlayers = 1
|
||||
# SoloGroupOutsideTimeout = 300 seconds
|
||||
|
||||
DungeonFinder.LowPopulation.Enable = 0
|
||||
DungeonFinder.LowPopulation.FallbackDelay = 60
|
||||
DungeonFinder.LowPopulation.MinPlayers = 1
|
||||
DungeonFinder.LowPopulation.SoloGroupOutsideTimeout = 300
|
||||
|
||||
#
|
||||
# DungeonFinder.CallToArmsEnable
|
||||
# Description: Enable Call to Arms bonus mechanism for random Cataclysm dungeons
|
||||
|
||||
@@ -28,6 +28,15 @@ int main(){
|
||||
assert(touchdown.Valid&&touchdown.Landing&&Distance(touchdown.Next,{0,0,0})<0.01f);
|
||||
auto solidWall=[](Point const&,Point const&){return false;};
|
||||
assert(!Next({6,0,10},{0,0,0},Settings{},finalApproach,flat,solidWall).Valid);
|
||||
// Diagonal movement is blocked but the local air column is clear: take off
|
||||
// and touch down vertically instead of rejecting an otherwise safe flight.
|
||||
auto verticalOnly=[](Point const&a,Point const&b){return Horizontal(a,b)<0.1f;};
|
||||
Memory verticalTakeoff;
|
||||
auto ascent=Next({0,0,0},{100,0,0},Settings{},verticalTakeoff,flat,verticalOnly);
|
||||
assert(ascent.Valid&&Horizontal({0,0,0},ascent.Next)<0.1f&&ascent.Next.Z>10);
|
||||
Memory verticalLanding;verticalLanding.Departed=true;
|
||||
auto descent=Next({0,0,40},{0,0,0},Settings{},verticalLanding,flat,verticalOnly);
|
||||
assert(descent.Valid&&descent.Landing&&Distance(descent.Next,{0,0,0})<0.01f);
|
||||
auto normal=simulate({0,0,0},{800,0,0},flat,clear);
|
||||
assert(normal.Arrived&&normal.Peak>30&&normal.Peak<=60.1f);
|
||||
auto low=[](float,float){return Surface{0,42,true};};
|
||||
@@ -84,3 +93,10 @@ with tempfile.TemporaryDirectory(prefix='vcore-free-flight-') as folder:
|
||||
cpp=Path(folder)/'test.cpp';binary=Path(folder)/'test';cpp.write_text(harness)
|
||||
subprocess.run(['c++','-std=c++17','-I',str(root/'src/server/game/World'),str(cpp),'-o',str(binary)],check=True)
|
||||
subprocess.run([str(binary)],check=True)
|
||||
|
||||
flight=(root/'src/server/game/World/ClientlessFlightMgr.cpp').read_text()
|
||||
executor=(root/'src/server/game/World/ClientlessActionExecutorMgr.cpp').read_text()
|
||||
assert 'FLIGHT_AIRBORNE_REPLAN' in flight
|
||||
assert 'OwnsActiveFlight(guid)' in flight
|
||||
assert 'found->second.ActionOwned=actionOwned' in flight
|
||||
assert 'itr->second.Active && itr->second.FlightActive' in executor
|
||||
Reference in new issue
Block a user