Fix shared flight trail replacement and safe follower rejoining
Publish replacement landing targets, bound orphaned trail waits, and plan safe connections from the follower position. Support shared-route regroup landings and preserve catch-up flights after the leader lands. Add geometry, lifecycle and group travel regressions.
This commit is contained in:
1 parent
768810a82d
commit
65e4abb0b9
11 files changed
+423
-24
No files matched your search
@@ -1,3 +1,202 @@
|
||||
# Navigationskorrektur zum lokalen Commit freigegeben
|
||||
|
||||
Der Nutzer hat den lokalen Commit der installierten Shared-flight-Korrektur
|
||||
und ihrer Regressionstests beauftragt. Kein Push und kein weiterer Build oder
|
||||
Serverneustart für diesen Commit. Gesicherte Binärdateien und Laufzeitlogs
|
||||
bleiben außerhalb des Commits.
|
||||
|
||||
---
|
||||
|
||||
# 2026-11-03 – Shared-flight-/Wiedereinstiegsfix installiert
|
||||
|
||||
Nutzer beauftragte die vereinbarten Navigationskorrekturen. Implementierung
|
||||
und gezielte Tests sind abgeschlossen, Build -j10 und Installation erfolgreich.
|
||||
SharedTrail veröffentlicht beim Ersatz den Landepunkt und einen Nachfolger;
|
||||
Store invalidiert auch komplett ersetzte Flugrecords. Folger lösen Ersatzketten
|
||||
auf und warten auf fehlende Landepunkte/Punkte höchstens120s. SharedJoin plant
|
||||
von der tatsächlichen Position zu einem nahen verbleibenden Punkt der
|
||||
Anführerroute, statt alte Historie ab Punkt0 nachzufliegen. Luftige Zielpunkte,
|
||||
Steigung, vertikales Ausweichen und Plattformvolumen werden dabei geprüft.
|
||||
LandFollowing unterstützt beide Folgemodi. Shared-Zwischenlandung endet als
|
||||
Recovery/Failed, nicht als vermeintliche Raidankunft. Der Gruppenmanager bricht
|
||||
validierte Shared-/action-owned Nachholflüge nicht mehr ab, wenn der Leader
|
||||
bereits gelandet ist. Makroroutenwahl bleibt beim Anführer.
|
||||
|
||||
Gezielte Regressionen PASS: shared_flight_rejoin (synthetischer Nach-Taxi-
|
||||
Anschluss und Winienwis Originalkoordinaten, blockierter alter Rückweg,
|
||||
Turmplattform), shared_flight_landing (Recordersatz, Publication, Timeout,
|
||||
Abort), group_flight_follow (beide Landemodi), flight_handoff (echter
|
||||
Update-Replan publiziert Ready+Replacement), adventure_group_travel (Leader
|
||||
landet vor Folger), flight_policy, elevated_flight, free_flight und
|
||||
flight_takeoff_handoff. Alter test_flight_departure war schon vor diesem Task
|
||||
mit unordered_map::at defekt; nur sein Store-Testdouble angepasst, kein PASS
|
||||
für diesen alten Test behaupten.
|
||||
|
||||
Builds jeweils mit pausiertem Loader und erneutem SafeLogout eingeleitet.
|
||||
Vor Installation volle5min seit letzter Anforderung19:36:15 eingehalten.
|
||||
12 Restbots samt GUID/Fraktion/Position und verfügbaren Native-Flags gesichert;
|
||||
nicht verfügbare individuelle Combat/Taxi/Teleportwerte explizit null, Status-
|
||||
Aggregate und Transportausgaben zusätzlich im pre-shutdown.log.
|
||||
Shutdown erneut bekannter separater GameObject::RemoveFromWorld651-ZoneScript-
|
||||
Fehler, Core worldserver-20261103-184941.core und passende alte Executable
|
||||
worldserver-before-fix gesichert. Nicht als behoben darstellen.
|
||||
|
||||
Neuer Server PID84679, frische ready-Meldung, Ports8085/8087 und Fortbestand
|
||||
verifiziert. 118 Bots wieder online, Loader1, safeLogout pending0. Restbots
|
||||
nach tatsächlichem Online geprüft (frühere Abfragen während Login waren noch
|
||||
ungültig). Hildda64/Norara91/Volakmar128/Ashaelri176 hatten danach gescheiterte
|
||||
oder fehlende Flugrecords und hingen über Boden: nativ in Fraktionshauptstadt
|
||||
teleportiert, Ergebnispositionen bestätigt. Reguläre Flüge/Taxi beibehalten.
|
||||
|
||||
Winienwi163 wurde für diese Anreise NICHT teleportiert. Nach sicherem Logout
|
||||
am Taxiknoten und normalem Relog flog sie 19:44:43 selbstständig zur Bastion,
|
||||
landete19:46:30 auf validiertem Boden und erreichte19:46:32 den Eingangsknoten
|
||||
(-4899.67,-4222.36,827.764). Route Completed, Flight Arrived. Dieser Live-Flug
|
||||
war eigenständig ohne SharedTrail-Bindung; Live-Erfolg deshalb nicht als
|
||||
vollständigen Live-Nachweis aller Rejoin-/Replacementfälle ausgeben. Diese
|
||||
sind durch die Produktionscode-Regressionen abgedeckt, Spielabnahme bleibt
|
||||
beim Nutzer. Originale Raidpläne beibehalten, keinen neuen Testtermin oder
|
||||
Dauerbeobachter eingerichtet.
|
||||
|
||||
Artefakte checkpoints/2026-11-03-shared-flight-rejoin/ (result.json,
|
||||
winienwi-live-arrival.log, Build/Install/Startup/Recovery und Roster).
|
||||
Neuer Test tests/clientless/test_shared_flight_rejoin.py ist noch untracked;
|
||||
Source-WIP bewusst uncommittet, Nutzer hat in diesem Turn keinen Commit/Push
|
||||
beauftragt. Vorherige Änderungen/Checkpoint-WIP erhalten.
|
||||
|
||||
---
|
||||
|
||||
# 2026-11-03 – Shared-flight-/Wiedereinstiegsfix in Arbeit
|
||||
|
||||
Nutzer beauftragt die vereinbarten Korrekturen. Implementiert:
|
||||
- SharedTrail.Replace veröffentlicht Landepunkt sofort und verlinkt Nachfolger.
|
||||
- Store invalidiert/verlinkt auch beim Ersatz eines kompletten Flugrecords.
|
||||
- Folger lösen Ersatzgenerationen auf, setzen ihren Join-Cursor zurück und
|
||||
warten auf unvollständige/ausbleibende Punkte maximal120s; Abbruch/Ende greift
|
||||
früher. Keine unbegrenzt gehaltenen verwaisten SharedPointers mehr.
|
||||
- SharedJoin sucht unter den verbleibenden Punkten die nächsten räumlichen
|
||||
Kandidaten (max8 geprüfte Kandidaten). Geometrieplaner erstellt sicheren
|
||||
Anschluss an den luftigen Wegpunkt, einschließlich begrenzter Steigung,
|
||||
vertikalem Ausweichen und Plattformprüfung. Keine neue Gruppen-Makroroute.
|
||||
- LandFollowing unterstützt SharedRoute und MovingTarget. Direkte lokale
|
||||
Landung nur nach Volumenprüfung; Shared-Regroup endet FAILED statt fälschlich
|
||||
Raidankunft ARRIVED, damit der Executor neu plant.
|
||||
- Gruppenmanager lässt Shared-/action-owned Nachholflüge weiterlaufen, wenn
|
||||
der Anführer schon gelandet ist; direkte Flugformation bleibt unverändert.
|
||||
|
||||
Tests bestanden: shared_flight_rejoin (inkl. Winienwis Originalkoordinaten,
|
||||
blockiertem altem Rückweg, sicherem neuem Anschluss und Turmplattform),
|
||||
shared_flight_landing (Recordersatz, Publication,120sWait,Abort),
|
||||
group_flight_follow (beide Landemodi), flight_handoff (echter Update-Replan mit
|
||||
LandingReady+Replacement), adventure_group_travel (Leader am Boden),
|
||||
flight_policy/elevated_flight/free_flight/takeoff_handoff.
|
||||
Bekannter alter test_flight_departure hatte bereits vor diesem Task einen
|
||||
unordered_map::at-Abbruch. Store-Testdouble für neue Produktionsmethode ergänzt;
|
||||
nicht als Gameplaynachweis verwenden.
|
||||
|
||||
Loader seit19:30:08 pausiert, safeLogoutall vor JEDEM Build angefordert.
|
||||
Build1 und Build-final PASS, letzterBuild (GroupGuard) läuft/Log beachten.
|
||||
Letzte SafeLogout-Anforderung19:36:15+01, konservativ volle5min bis19:41:15
|
||||
vorShutdown bei Restbots. Noch12Bots; nicht einzeln ausloggen/Transport abbrechen.
|
||||
Checkpoints/2026-11-03-shared-flight-rejoin/ enthältRoster,Operation,Buildlogs.
|
||||
Installation/Restart/Restbotprüfung/LoaderRestore noch OFFEN. Kein Commit/Push
|
||||
beauftragt. Bisheriger Raidtest für diesen Build unterbrochen; nachher vorherige
|
||||
Bots/Pläne wiederherstellen, nicht ungefragt neuenRaidtest/Zeiten anlegen.
|
||||
Separater ZoneScript-Shutdownfehler bleibt offen; GDB-Dumps weiter sichern.
|
||||
|
||||
---
|
||||
|
||||
# Winienwis Flugkorridor: falscher Wiedereinstieg konkret belegt
|
||||
|
||||
Nutzer fragt warum trotzfreiemHimmel. FollowTrail bindetSharedIndex0 an alte
|
||||
650PunkteStreckeTaliaarnora164. ErsterPunkt(19:12:26):(-4863.52,-6715.61,24.22),
|
||||
bei altem Abflugpunkt Hafen. Winienwi nachTaxi nun(-4170.55,-4748.93,126.786).
|
||||
GeprüfterAnschluss ist2087.72yd rückwärts zumaltenPunkt, NICHTdirektzurBastion.
|
||||
Corridor nimmt418Samples. NativeSurfaceprobes anSamples1..5:
|
||||
Ground126.786,126.785,129.250,126.643,126.372. BeimSample3 nachca15yd
|
||||
(-4175.523469,-4763.044928) istSplineZ126.04988 +0.5Toleranz126.54988,
|
||||
aberSurface129.250 => Bodenfreiheitprüfung scheitert zwingend vorRayprüfung.
|
||||
FollowTrail erhöhtIndex nurbeiDistanz<0.7yd,keineSuchegeeigneterEinstiegspunkte.
|
||||
Somit Anschluss-/SharedTrail-Fehler; kein BelegversperrterDirektverbindungzumRaid.
|
||||
ErgänztgemerktenFix: Wiedereinstieg auf passende vorausliegendeTeilroute nach
|
||||
Taxi/Recovery, eigenergeprüfterAnschluss stattimmerIndex0. Artefakt
|
||||
winienwi-stuck/wrong-shared-entry.json. NurRead-onlyProbes,keineBewegung.
|
||||
|
||||
---
|
||||
|
||||
# Winienwi-Status 03.11.2026 19:21
|
||||
|
||||
Nutzer fragt Status. Winienwi163 hat nativ einen Taxiflug bis taxi_663/Gipfel
|
||||
des Siegers im Schattenhochland beendet (19:21:13). Anschließender Freiflug
|
||||
zur Bastion sofort abgelehnt, weil Spieler noch teleport=1 im Taxi-Landeübergang.
|
||||
Recoverybudget5 war bereits verbraucht, dadurch gesamterAnreiseversuch Failed.
|
||||
19:21:18 teleport0, alive1, combat0, Position(-4170.55,-4748.93,126.787),
|
||||
EntfernungzumRaideingang1140.29yd. Behavior wartet mit 'instance entrance travel
|
||||
failed; retrying the same visit', gleicherRaidintent/Deadline21:00 aktiv.
|
||||
Kein alterSharedTrail-Wartezustandmehr. Keine neue Bewegung/Teleport/Änderung
|
||||
vonCodex, nurRead-onlySnapshot. NächsterautomatischerVersuchnochzuprüfen.
|
||||
|
||||
---
|
||||
|
||||
# Winienwi zum Portal gebracht, 03.11.2026 19:18
|
||||
|
||||
Vereinbarten SharedTrail-/LandFollowing-Fix als offenen Folgepunkt gespeichert,
|
||||
noch nicht implementiert. Native FlightAbort163 plus kurzer BehaviorStop und
|
||||
Adminteleport an sichere Position6yd vor StormwinderSchattenhochlandportal
|
||||
(-8186.15,419.729,116.787). Er benutzte das Portal anschließend selbstständig:
|
||||
19:18:26 PORTAL_USE_START spawn200627/entry207694/spell85545 und EDGE_COMPLETE
|
||||
portal_use edge1801, Ankunft Schattenhochland area5140/zone4922. KeinTeleport
|
||||
direkt insSchattenhochland. Raidroute läuft danach weiter; ersterAbflug dort
|
||||
19:18:34 scheitert Korridor, Recovery beginntBodenweg, späterKampfreaktion.
|
||||
Schedule wieder1, ursprünglichesRaidziel/Deadline erhalten. Temporären game_tele-
|
||||
EintragCodexTempSWHighlandsPortal gelöscht und Katalogreload, Löschung geprüft.
|
||||
KeineSourceänderung/Build/Restart. User beobachtet Testweiter.
|
||||
|
||||
---
|
||||
|
||||
# Vereinbarter nächster Navigationsfix, 03.11.2026
|
||||
|
||||
Nutzer stimmt dem Vorschlag zu und verlangt ihn zu merken: Flug-Neuplanungen
|
||||
müssen den geprüften Landepunkt vollständig veröffentlichen, alte SharedTrails
|
||||
explizit ersetzen/invalidieren und Folger kontrolliert auf die Nachfolgestrecke
|
||||
übernehmen. Anschluss von tatsächlicher Position prüfen. Unvollständige oder
|
||||
verwaiste Flugstrecken dürfen kein unbegrenztes Warten auslösen. Sichere Landung
|
||||
muss sowohl moving-target FollowingPlayer als auch shared-route Following
|
||||
unterstützen und ihr Ergebnis bestätigen. Gruppenroutenwahl bleibt beim Leader.
|
||||
Regression: Leader replant während Folger noch am Start wartet und beendet
|
||||
anschließend Flug; Folger muss nachweislich fliegen oder sicher landen.
|
||||
Noch NICHT implementiert. Jetzt ausdrücklich nur Winienwi163 zum Stormwinder
|
||||
Portal ins Schattenhochland bringen; Test ansonsten weiterlaufen lassen.
|
||||
|
||||
---
|
||||
|
||||
# 2026-11-03 19:15 – Winienwi wartet auf verwaiste Flugstrecke
|
||||
|
||||
Nutzer meldet Winienwi163 hängt fest. Native Statusprüfung: Sturmwind
|
||||
(-8779.848,579.868,119.133), support119.152, mounted1/canFly1/flying0/falling0.
|
||||
Seit19:09:39 keine Bewegung. FlugstateFlying,0Segmente,Detailwaiting for leader
|
||||
validated landing. Group3, leader164Taliaarnora. Anführerin fliegt inzwischen
|
||||
weiter RichtungRaid, nativePosition19:14(-4889.496,-4720.076,555.722).
|
||||
|
||||
Rootcause: ClientlessFlightMgr::Update AIRBORNE_REPLAN ersetztPublished durch
|
||||
neuesSharedTrail ohneLanding/LandingReady zu setzen. BeimLeader16419:09:09
|
||||
trittdiesauf. Winienwi bindet sich19:09:39 an diesesTrail, landingReady0.
|
||||
Leaderlandet19:09:45, beginnt19:09:48weiterenFlug. GehaltenerSharedPointerdes
|
||||
Followers bleibt gültig, aber ohneLandung/ohneAborted. Advancekehrtvorjeder
|
||||
Bewegung mit waiting for leader validated landing zurück;keinWarte-Timeout.
|
||||
ZweiterFehler: GroupTravelMgr fordertLandFollowing wegenSeparation, aberdiese
|
||||
Funktion behandelt nur Status.FollowingPlayer (moving-target mode), nicht
|
||||
record.Following (shared route). Daherreturnfalse ohneRecovery. Statusdetail
|
||||
safe landing before ground regroup/leave ist nurAbsicht,keineausgeführteLandung.
|
||||
|
||||
NurDiagnosebeauftragt,keineSourceänderung/Build/Teleport/Reset. Artefakte unter
|
||||
checkpoints/2026-11-03-2000-raid-test/winienwi-stuck/. Korrektur mussLanding bei
|
||||
Replanpublizieren, alteTrails sauber invalidieren/Followers neu binden sowie
|
||||
zeitlichbegrenzte sichereRecovery beiverlorenemLeader unterstützen. Aktueller
|
||||
Raidtestläuftweiter,keineDauerbeobachtung.
|
||||
|
||||
---
|
||||
|
||||
# 2026-11-03 – Änderungen committet, 19/20-Uhr-Raidtest gestartet
|
||||
|
||||
Der Nutzer bestätigt die automatische Clientfreigabe auch mit Allitester:
|
||||
|
||||
@@ -344,9 +344,11 @@ bool AdventureGroupTravelMgr::Follow(Player* player)
|
||||
{
|
||||
auto flight=sClientlessFlightMgr->GetStatus(guid);
|
||||
if(!flight || flight->State==ClientlessFlightState::Arrived || (!player->IsFlying() && flight->State==ClientlessFlightState::Failed))record.FlightOwned=false;
|
||||
// Shared/action-owned corridors have their own validated destination.
|
||||
// A leader landing ahead must not cancel a follower's catch-up flight.
|
||||
else if(!flight->FollowingPlayer)return true;
|
||||
else if(!player->IsInMap(leader) || !leader->IsFlying() || leader->IsInFlight() || leader->GetTransport() || Separated(player))
|
||||
{sClientlessFlightMgr->LandFollowing(player);record.Detail="safe landing before ground regroup/leave";return true;}
|
||||
else if(!flight->FollowingPlayer)return true;
|
||||
}
|
||||
if(player->GetTransport() || player->IsInFlight())return true;
|
||||
if(!record.Pending.empty())
|
||||
|
||||
@@ -291,16 +291,32 @@ bool ClientlessFlightMgr::Follow(Player* player,Position const& target,std::stri
|
||||
bool ClientlessFlightMgr::LandFollowing(Player* player)
|
||||
{
|
||||
auto found=_records.find(player->GetGUID().GetCounter());if(found==_records.end())return false;
|
||||
auto& record=found->second;if(!record.Status.FollowingPlayer)return false;
|
||||
auto& record=found->second;
|
||||
bool const shared=bool(record.Following);
|
||||
if(!record.Status.FollowingPlayer && !shared)return false;
|
||||
record.Following.reset();record.SharedWaitSince=0;
|
||||
record.Published->Aborted=true;
|
||||
record.Status.FollowingPlayer=false;
|
||||
if(record.Status.State==ClientlessFlightState::Mounting)
|
||||
{Fail(player,record,"group flight cancelled before takeoff");return true;}
|
||||
auto surface=Sample(player,record,player->GetPositionX(),player->GetPositionY());
|
||||
float solid=player->GetMap()->GetHeight(player->GetPhaseShift(),player->GetPositionX(),player->GetPositionY(),MAX_HEIGHT,true,MAX_HEIGHT);
|
||||
if(surface.Valid && std::fabs(surface.Ground-solid)<0.7f)
|
||||
auto sample=[&](float x,float y){return Sample(player,record,x,y);};
|
||||
auto ray=[&](Point const& a,Point const& b){return ClearRay(player,a,b);};
|
||||
Point landing{player->GetPositionX(),player->GetPositionY(),solid};
|
||||
if(surface.Valid && std::fabs(surface.Ground-solid)<0.7f &&
|
||||
solid<=player->GetPositionZ()+0.7f &&
|
||||
Corridor(PointOf(*player),landing,landing,record.Settings,sample,ray,true))
|
||||
{
|
||||
record.Status.Landing.Relocate(player->GetPositionX(),player->GetPositionY(),solid,player->GetOrientation());
|
||||
record.Status.Target=record.Status.Landing;record.Memory={};record.Following.reset();record.Status.Travelled=0;
|
||||
player->StopMoving();record.Moving=false;
|
||||
if(shared)
|
||||
{
|
||||
// This is a regroup landing, not arrival at the requested raid.
|
||||
record.Start=record.Status.Landing;record.Returning=true;
|
||||
record.AbortReason="shared flight regroup landing";record.Breadcrumbs.clear();
|
||||
}
|
||||
record.Status.Detail="landing after group flight";return true;
|
||||
}
|
||||
Fail(player,record,"no safe local landing; returning along validated flight trail");return true;
|
||||
@@ -336,6 +352,19 @@ bool ClientlessFlightMgr::AdvanceFollowing(Player* player,Record& record)
|
||||
record.Moving=true;record.SegmentElapsed=0;++record.Status.Segments;record.Status.Detail="following moving group flight target";return true;
|
||||
}
|
||||
|
||||
void ClientlessFlightMgr::Store(uint32 guid, Record&& record)
|
||||
{
|
||||
auto old=_records.find(guid);
|
||||
if(old!=_records.end() && old->second.Published!=record.Published)
|
||||
{
|
||||
old->second.Published->Aborted=true;
|
||||
if(old->second.Status.MapId==record.Status.MapId &&
|
||||
old->second.Status.Target.GetExactDist(record.Status.Target)<1)
|
||||
old->second.Published->Replacement=record.Published;
|
||||
}
|
||||
_records[guid]=std::move(record);
|
||||
}
|
||||
|
||||
bool ClientlessFlightMgr::Begin(Player * player, Position const & target, std::string & error, bool following)
|
||||
{
|
||||
if (! Trinity::IsValidMapCoord(target.GetPositionX(), target.GetPositionY(), target.GetPositionZ()))
|
||||
@@ -529,9 +558,9 @@ bool ClientlessFlightMgr::Begin(Player * player, Position const & target, std::s
|
||||
record.Memory.Departed=player->GetPositionZ()-origin.Ground>record.Settings.Safety;
|
||||
player->AddUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
||||
TC_LOG_INFO("server.adventurebot.navigation","FLIGHT_RESUME_AIRBORNE guid=%u ground=%.3f z=%.3f",player->GetGUID().GetCounter(),origin.Ground,player->GetPositionZ());
|
||||
_records[player->GetGUID().GetCounter()]=std::move(record);return true;
|
||||
Store(player->GetGUID().GetCounter(),std::move(record));return true;
|
||||
}
|
||||
_records[player->GetGUID().GetCounter()] = std::move(record);
|
||||
Store(player->GetGUID().GetCounter(),std::move(record));
|
||||
if (!existingMount && player->CastSpell(player, spell, false) != SPELL_CAST_OK)
|
||||
{
|
||||
Fail(player, _records.at(player->GetGUID().GetCounter()), "native mount spell rejected");
|
||||
@@ -597,7 +626,7 @@ bool ClientlessFlightMgr::ApproachDeparture(Player* player, Record& record, bool
|
||||
record.Status.State=ClientlessFlightState::Mounting;record.Status.FollowingPlayer=following;
|
||||
record.Status.Detail="walking to an open takeoff point";
|
||||
TC_LOG_INFO("server.adventurebot.navigation","FLIGHT_DEPARTURE_WALK guid=%u destination=(%.3f %.3f %.3f) path=%.2f",player->GetGUID().GetCounter(),x,y,surface.Ground,path.PathLength);
|
||||
_records[player->GetGUID().GetCounter()]=std::move(record);return true;
|
||||
Store(player->GetGUID().GetCounter(),std::move(record));return true;
|
||||
}
|
||||
summary();return false;
|
||||
}
|
||||
@@ -635,14 +664,23 @@ bool ClientlessFlightMgr::Advance(Player * player, Record & record, bool handoff
|
||||
{
|
||||
if(record.Following && !record.Returning)
|
||||
{
|
||||
Point landing;
|
||||
if(!record.Following->Aborted && !record.Following->BindLanding(landing))
|
||||
{record.Status.Detail="waiting for leader validated landing";return true;}
|
||||
if(record.Following->BindLanding(landing))
|
||||
while(record.Following->Replacement)
|
||||
{
|
||||
record.Status.Landing.Relocate(landing.X,landing.Y,landing.Z);
|
||||
record.Published->Landing=landing;record.Published->LandingReady=true;
|
||||
record.Following=record.Following->Replacement;
|
||||
record.SharedIndex=0;record.Join={};record.SharedWaitSince=0;
|
||||
}
|
||||
if(record.Following->Aborted)
|
||||
{LandFollowing(player);return true;}
|
||||
Point landing;
|
||||
if(!record.Following->BindLanding(landing))
|
||||
{
|
||||
if(!record.SharedWaitSince)record.SharedWaitSince=record.Elapsed+1;
|
||||
if(record.Following->Complete || record.Elapsed+1-record.SharedWaitSince>=120000)
|
||||
{LandFollowing(player);return true;}
|
||||
record.Status.Detail="waiting for leader validated landing";return true;
|
||||
}
|
||||
record.Status.Landing.Relocate(landing.X,landing.Y,landing.Z);
|
||||
record.Published->Landing=landing;record.Published->LandingReady=true;
|
||||
}
|
||||
Point current = PointOf(* player), goal = PointOf(record.Returning ? record.Start : record.Status.Landing);
|
||||
auto sample =[&](float x, float y)
|
||||
@@ -669,6 +707,7 @@ bool ClientlessFlightMgr::Advance(Player * player, Record & record, bool handoff
|
||||
player->RemoveAurasByType(SPELL_AURA_MOUNTED);
|
||||
record.Status.State = record.Returning ? ClientlessFlightState::Failed : ClientlessFlightState::Arrived;
|
||||
record.Status.Detail = record.Returning ? "safely returned after abort: " + record.AbortReason : "landed on validated destination ground";
|
||||
record.Published->Complete=true;
|
||||
TC_LOG_INFO("server.adventurebot.navigation", "FLIGHT_LANDED guid=%u segments=%u avoidances=%u travelled=%.2f error=%.2f", player->GetGUID().GetCounter(), record.Status.Segments, record.Status.Avoidances, record.Status.Travelled, Distance(current, goal));
|
||||
return true;
|
||||
}
|
||||
@@ -685,12 +724,20 @@ bool ClientlessFlightMgr::Advance(Player * player, Record & record, bool handoff
|
||||
else if(record.Following)
|
||||
{
|
||||
auto shared=FollowTrail(current,goal,record.Settings,*record.Following,record.SharedIndex,sample,ray,
|
||||
handoff?player->GetSpeed(MOVE_FLIGHT)*1.5f:0.f);
|
||||
if(shared.Waiting)return true;
|
||||
handoff?player->GetSpeed(MOVE_FLIGHT)*1.5f:0.f,&record.Join);
|
||||
if(shared.Waiting)
|
||||
{
|
||||
if(!record.SharedWaitSince)record.SharedWaitSince=record.Elapsed+1;
|
||||
if(record.Following->Complete || record.Elapsed+1-record.SharedWaitSince>=120000)
|
||||
LandFollowing(player);
|
||||
return true;
|
||||
}
|
||||
record.SharedWaitSince=0;
|
||||
next=shared.Step;
|
||||
if(!next.Valid)
|
||||
TC_LOG_WARN("server.adventurebot.navigation","FLIGHT_SHARED_REJECT guid=%u index=%u size=%u aborted=%u landing=(%.3f %.3f %.3f)",
|
||||
player->GetGUID().GetCounter(),uint32(record.SharedIndex),uint32(record.Following->Points.size()),record.Following->Aborted?1u:0u,goal.X,goal.Y,goal.Z);
|
||||
if(!next.Valid){LandFollowing(player);return true;}
|
||||
if(handoff && next.Valid && Distance(next.Next,PointOf(record.Segment))<1)return true;
|
||||
}
|
||||
else
|
||||
@@ -814,7 +861,7 @@ void ClientlessFlightMgr::Update(uint32 diff)
|
||||
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.Published=record.Published->Replace(PointOf(record.Status.Landing));
|
||||
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)",
|
||||
|
||||
@@ -44,6 +44,8 @@ class TC_GAME_API ClientlessFlightMgr
|
||||
std::shared_ptr<ClientlessFlightPath::SharedTrail> Published=std::make_shared<ClientlessFlightPath::SharedTrail>();
|
||||
std::shared_ptr<ClientlessFlightPath::SharedTrail> Following;
|
||||
std::size_t SharedIndex=0;
|
||||
ClientlessFlightPath::SharedJoin Join;
|
||||
uint32 SharedWaitSince=0;
|
||||
ClientlessFlightStatus Status;
|
||||
ClientlessFlightPath::Settings Settings;
|
||||
ClientlessFlightPath::Memory Memory;
|
||||
@@ -62,6 +64,7 @@ class TC_GAME_API ClientlessFlightMgr
|
||||
};
|
||||
ClientlessFlightPath::Surface Sample(Player * player, Record & record, float x, float y) const;
|
||||
bool Advance(Player * player, Record & record, bool handoff = false);
|
||||
void Store(uint32 guid, Record&& record);
|
||||
bool Begin(Player* player, Position const& target, std::string& error, bool following);
|
||||
bool ApproachDeparture(Player* player, Record& record, bool following);
|
||||
bool FindReturnLanding(Player* player, Record& record);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
// Geometry-only receding-horizon navigation. Server adapters supply phased
|
||||
// terrain/ceiling and static+dynamic collision queries. No Player shortcuts.
|
||||
@@ -117,11 +118,11 @@ namespace ClientlessFlightPath
|
||||
}
|
||||
return true;
|
||||
}
|
||||
inline Result Next(Point const & from, Point const & goal, Settings const & cfg, Memory & memory, Sample const & sample, Ray const & ray)
|
||||
inline Result Next(Point const & from, Point const & goal, Settings const & cfg, Memory & memory, Sample const & sample, Ray const & ray, bool airborneGoal = false)
|
||||
{
|
||||
float const distance = Horizontal(from, goal);
|
||||
Surface const here = sample(from.X, from.Y), end = sample(goal.X, goal.Y);
|
||||
if (! here.Valid || ! end.Valid || std::fabs(end.Ground - goal.Z) > 2.5f) return
|
||||
if (! here.Valid || ! end.Valid || (!airborneGoal && std::fabs(end.Ground - goal.Z) > 2.5f)) return
|
||||
{
|
||||
};
|
||||
// Issuing a takeoff spline is not proof of actual clearance. Handoffs
|
||||
@@ -263,6 +264,7 @@ namespace ClientlessFlightPath
|
||||
dz = std::min(0.0f, std::max(dz, here.Ground + cfg.Safety + 4 - from.Z));
|
||||
}
|
||||
}
|
||||
if(airborneGoal)dz=std::clamp(dz,-run*cfg.Slope,run*cfg.Slope);
|
||||
Point far
|
||||
{
|
||||
from.X + dx * run, from.Y + dy * run, from.Z + dz
|
||||
@@ -308,19 +310,65 @@ namespace ClientlessFlightPath
|
||||
std::vector<bool> UnderOverhang;
|
||||
Point Landing;
|
||||
bool LandingReady=false;
|
||||
bool Aborted=false;
|
||||
bool Aborted=false, Complete=false;
|
||||
std::shared_ptr<SharedTrail> Replacement;
|
||||
std::shared_ptr<SharedTrail> Replace(Point const& landing)
|
||||
{
|
||||
auto next=std::make_shared<SharedTrail>();
|
||||
next->Landing=landing;next->LandingReady=true;
|
||||
Replacement=next;Aborted=true;
|
||||
return next;
|
||||
}
|
||||
bool BindLanding(Point& goal) const
|
||||
{
|
||||
if(!LandingReady || Aborted)return false;
|
||||
goal=Landing;return true;
|
||||
}
|
||||
};
|
||||
struct SharedJoin
|
||||
{
|
||||
bool Active=true;
|
||||
std::size_t Target=std::numeric_limits<std::size_t>::max();
|
||||
Memory Navigation;
|
||||
};
|
||||
struct SharedResult {Result Step;bool Waiting=false;};
|
||||
inline SharedResult FollowTrail(Point const& current,Point const& goal,Settings const& settings,
|
||||
SharedTrail const& trail,std::size_t& index,Sample const& sample,Ray const& ray,float lookaheadDistance=0)
|
||||
SharedTrail const& trail,std::size_t& index,Sample const& sample,Ray const& ray,float lookaheadDistance=0,SharedJoin* join=nullptr)
|
||||
{
|
||||
if(trail.Aborted)return {};
|
||||
if(!trail.LandingReady)return {{},true};
|
||||
if(join && join->Active)
|
||||
{
|
||||
if(trail.Points.empty())return {{},true};
|
||||
if(join->Target==std::numeric_limits<std::size_t>::max())
|
||||
{
|
||||
// Join a nearby remaining leg instead of replaying the leader's
|
||||
// entire history after a taxi, recovery or delayed departure.
|
||||
std::vector<std::size_t> candidates;
|
||||
for(std::size_t i=index;i<trail.Points.size();++i)candidates.push_back(i);
|
||||
std::sort(candidates.begin(),candidates.end(),[&](auto a,auto b){
|
||||
float da=Distance(current,trail.Points[a]),db=Distance(current,trail.Points[b]);
|
||||
return da==db?a<b:da<db;
|
||||
});
|
||||
for(std::size_t n=0;n<std::min<std::size_t>(8,candidates.size());++n)
|
||||
{
|
||||
auto candidate=candidates[n];Memory memory;
|
||||
auto step=Next(current,trail.Points[candidate],settings,memory,sample,ray,true);
|
||||
if(!step.Valid)continue;
|
||||
join->Target=candidate;join->Navigation=memory;
|
||||
if(Distance(current,trail.Points[candidate])>=0.7f)
|
||||
{step.Landing=false;return {step,false};}
|
||||
break;
|
||||
}
|
||||
if(join->Target==std::numeric_limits<std::size_t>::max())return {};
|
||||
}
|
||||
if(Distance(current,trail.Points[join->Target])>=0.7f)
|
||||
{
|
||||
auto step=Next(current,trail.Points[join->Target],settings,join->Navigation,sample,ray,true);
|
||||
step.Landing=false;return {step,false};
|
||||
}
|
||||
index=join->Target;join->Active=false;
|
||||
}
|
||||
while(index<trail.Points.size() && Distance(current,trail.Points[index])<0.7f)++index;
|
||||
if(index==trail.Points.size())return {{},true};
|
||||
// Extend a running spline before its endpoint. A corner may only be
|
||||
|
||||
@@ -101,6 +101,11 @@ int main(){
|
||||
travel.status.State=ClientlessTravelState::Arrived;leader.mounted=true;mgr.Follow(&a);assert(a.mounted);leader.mounted=false;a=Player(26); // retain a clean origin
|
||||
// A ground-only bot waits safely when the leader gains excessive height.
|
||||
leader.flying=true;leader.m_positionZ=80;starts=travel.starts;mgr.Follow(&a);assert(travel.starts==starts);leader.flying=false;leader.m_positionZ=0;
|
||||
// A shared/action-owned catch-up corridor survives a leader landing ahead.
|
||||
flight.status.State=ClientlessFlightState::Flying;flight.status.FollowingPlayer=false;
|
||||
mgr.Observe(&a,1);leader.flying=false;auto previousLands=flight.lands;
|
||||
assert(mgr.Follow(&a)&&flight.lands==previousLands);
|
||||
flight.status.State=ClientlessFlightState::Arrived;mgr.Follow(&a);
|
||||
// A native-capable follower uses flight, and safely lands after a kick.
|
||||
flight.enabled=true;leader.flying=true;mgr.Follow(&a);assert(flight.follows==1 && mgr.Airborne(&a));a.g=nullptr;mgr.Follow(&a);assert(flight.lands==1);a.g=&group;flight.status.State=ClientlessFlightState::Arrived;leader.flying=false;flight.enabled=false;mgr.Follow(&a);
|
||||
// A stale owned ground catch-up route cannot suppress a visible-phase
|
||||
|
||||
@@ -61,6 +61,7 @@ bool ClearRay(Player*,Point const&a,Point const&b){
|
||||
'''+column+r'''
|
||||
struct ClientlessFlightMgr{
|
||||
std::unordered_map<unsigned,Record>_records;Surface surface{0,300,true};int approached=0;bool seek=false;
|
||||
void Store(unsigned guid,Record&& record){_records[guid]=std::move(record);}
|
||||
Surface Sample(Player*,Record&,float,float){return surface;}
|
||||
bool ApproachDeparture(Player*,Record&,bool);bool FindReturnLanding(Player*,Record&);
|
||||
bool Prepare(Player*player,Record&record,std::string&error){auto origin=surface;bool following=false;
|
||||
|
||||
@@ -46,7 +46,7 @@ struct Record {
|
||||
struct {bool FollowingPlayer=false;ClientlessFlightState State=ClientlessFlightState::Flying;uint32 MapId=0;Position Landing{800,0,0},Target{800,0,0};float Travelled=0;uint32 Segments=0,Avoidances=0;std::string Detail;} Status;
|
||||
Position LastPosition,Start,Segment{25,0,60};Settings Settings;
|
||||
std::shared_ptr<int> Following;uint32 FormationDelay=0,TargetUpdated=0,Elapsed=0,SegmentElapsed=0,CheckTimer=0;
|
||||
bool Moving=true,Returning=false,ApproachingDeparture=false,ActionOwned=false,SegmentUnderOverhang=false;uint32 DetachedRecoveryCount=0;Position Departure;Memory Memory;std::shared_ptr<SharedTrail> Published;std::vector<Position> Breadcrumbs;
|
||||
bool Moving=true,Returning=false,ApproachingDeparture=false,ActionOwned=false,SegmentUnderOverhang=false;uint32 DetachedRecoveryCount=0;Position Departure;Memory Memory;std::shared_ptr<SharedTrail> Published=std::make_shared<SharedTrail>();std::vector<Position> Breadcrumbs;
|
||||
};
|
||||
enum class ClientlessTravelState {Moving,Arrived,Stuck};
|
||||
struct Travel {struct{ClientlessTravelState State=ClientlessTravelState::Moving;}status;auto GetStatus(uint32){return &status;}}travel;
|
||||
@@ -96,6 +96,14 @@ int main(){
|
||||
r.Elapsed=0;r.Departure=player;travel.status.State=ClientlessTravelState::Moving;
|
||||
mgr.Update(50);assert(mgr.begins==0&&mgr.failures==1);
|
||||
travel.status.State=ClientlessTravelState::Arrived;mgr.Update(50);assert(mgr.begins==1);
|
||||
// A new airborne generation must publish its verified landing immediately
|
||||
// and leave a replacement link for followers already holding the old trail.
|
||||
r.ApproachingDeparture=false;r.Status.State=ClientlessFlightState::Flying;
|
||||
r.Returning=false;r.Status.Segments=12;r.Status.Avoidances=12;r.DetachedRecoveryCount=0;
|
||||
player.fly=true;auto old=r.Published;r.Status.Landing={800,0,0};
|
||||
mgr.Update(50);assert(old->Aborted&&old->Replacement==r.Published);
|
||||
assert(r.Published->LandingReady&&r.Published->Landing.X==800);
|
||||
r.ApproachingDeparture=true;r.Status.State=ClientlessFlightState::Mounting;
|
||||
// A stale Arrived flag cannot permit mounting away from the selected spot.
|
||||
r.Departure.x=player.x+10;mgr.Update(50);assert(mgr.failures==2&&mgr.begins==1);
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@ namespace Movement{struct MoveSplineInit{Player*p;Point target;MoveSplineInit(Pl
|
||||
enum class ClientlessFlightState{Mounting,Flying,Arrived,Failed};
|
||||
struct Record{
|
||||
struct{ClientlessFlightState State=ClientlessFlightState::Flying;bool FollowingPlayer=true;Position Target,Landing;unsigned Segments=0;float Travelled=5000;std::string Detail;}Status;
|
||||
bool Returning=false,Moving=false;unsigned Elapsed=1000,TargetUpdated=0,SegmentElapsed=0;Position Segment;
|
||||
bool Returning=false,Moving=false;unsigned Elapsed=1000,TargetUpdated=0,SegmentElapsed=0;Position Segment,Start;unsigned SharedWaitSince=0;std::string AbortReason;
|
||||
ClientlessFlightPath::Settings Settings;ClientlessFlightPath::Memory Memory;
|
||||
std::shared_ptr<int>Following;std::vector<Position>Breadcrumbs;
|
||||
std::shared_ptr<SharedTrail>Following;std::shared_ptr<SharedTrail>Published=std::make_shared<SharedTrail>();std::vector<Position>Breadcrumbs;
|
||||
};
|
||||
bool clear=true,valid=true;float ceiling=300;
|
||||
bool ClearRay(Player*,Point const&,Point const&){return clear;}
|
||||
@@ -54,6 +54,9 @@ int main(){
|
||||
p.Relocate(110,20,30);r.Moving=true;assert(mgr.AdvanceFollowing(&p,r));assert(!r.Moving && p.stops==2); // holds in air, never dismounts at moving goal
|
||||
assert(mgr.LandFollowing(&p));assert(!r.Status.FollowingPlayer && r.Status.Landing.z==0 && r.Status.Travelled==0);
|
||||
assert(!mgr.Follow(&p,target,error)); // landing cannot be replaced by a new pursuit
|
||||
r.Status.FollowingPlayer=false;r.Following=std::make_shared<SharedTrail>();
|
||||
assert(mgr.LandFollowing(&p));assert(r.Returning&&!r.Following&&r.Start.z==0&&r.Breadcrumbs.empty());
|
||||
r.Returning=false;
|
||||
r.Status.FollowingPlayer=true;r.Status.State=ClientlessFlightState::Mounting;assert(mgr.LandFollowing(&p) && mgr.failures==1);
|
||||
r.Status.State=ClientlessFlightState::Flying;r.Status.FollowingPlayer=true;valid=false;assert(mgr.LandFollowing(&p) && mgr.failures==2);
|
||||
}
|
||||
|
||||
@@ -6,26 +6,48 @@ source=(root/'src/server/game/World/ClientlessFlightMgr.cpp').read_text()
|
||||
a=source.index(' if(record.Following && !record.Returning)',source.index('bool ClientlessFlightMgr::Advance('))
|
||||
b=source.index(' Point current =',a)
|
||||
sync=source[a:b]
|
||||
a=source.index('void ClientlessFlightMgr::Store(')
|
||||
b=source.index('bool ClientlessFlightMgr::Begin(',a)
|
||||
store=source[a:b]
|
||||
code=r'''
|
||||
#include "ClientlessFlightPath.h"
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
using uint32=unsigned;
|
||||
using namespace ClientlessFlightPath;
|
||||
struct Position{float x=0,y=0,z=0;void Relocate(float a,float b,float c){x=a;y=b;z=c;}};
|
||||
struct Record{std::shared_ptr<SharedTrail> Following;std::shared_ptr<SharedTrail> Published=std::make_shared<SharedTrail>();bool Returning=false;struct{Position Landing;std::string Detail;}Status;};
|
||||
struct Position{float x=0,y=0,z=0;void Relocate(float a,float b,float c){x=a;y=b;z=c;}float GetExactDist(Position const&p)const{return std::hypot(std::hypot(x-p.x,y-p.y),z-p.z);}};
|
||||
struct Record{std::shared_ptr<SharedTrail> Following;std::shared_ptr<SharedTrail> Published=std::make_shared<SharedTrail>();bool Returning=false;unsigned SharedWaitSince=0,Elapsed=0;std::size_t SharedIndex=0;SharedJoin Join;struct{unsigned MapId=0;Position Landing,Target;std::string Detail;}Status;};
|
||||
int landings=0;void* player=nullptr;void LandFollowing(void*){++landings;}
|
||||
bool Sync(Record& record){
|
||||
''' + sync + r'''
|
||||
return false;
|
||||
}
|
||||
struct ClientlessFlightMgr{std::unordered_map<unsigned,Record> _records;void Store(unsigned,Record&&);};
|
||||
''' + store + r'''
|
||||
int main(){
|
||||
ClientlessFlightMgr manager;Record original;original.Status.Target.x=100;
|
||||
auto previous=original.Published;manager.Store(1,std::move(original));
|
||||
Record updated;updated.Status.Target.x=100;updated.Published->LandingReady=true;
|
||||
auto successor=updated.Published;manager.Store(1,std::move(updated));
|
||||
assert(previous->Aborted&&previous->Replacement==successor);
|
||||
Record other;other.Status.Target.x=200;manager.Store(1,std::move(other));
|
||||
assert(successor->Aborted&&!successor->Replacement);
|
||||
|
||||
Record r;r.Following=std::make_shared<SharedTrail>();r.Status.Landing.z=777;
|
||||
assert(Sync(r)&&r.Status.Landing.z==777); // still preparing; no zero landing copied
|
||||
r.Following->Landing={12,34,827.765625f};r.Following->LandingReady=true;
|
||||
assert(!Sync(r)&&r.Status.Landing.x==12&&r.Status.Landing.y==34&&r.Status.Landing.z==827.765625f);
|
||||
assert(r.Published->LandingReady&&r.Published->Landing.Z==827.765625f);
|
||||
|
||||
auto replacement=r.Following->Replace({56,78,100});
|
||||
assert(!Sync(r)&&r.Following==replacement&&r.Status.Landing.z==100);
|
||||
replacement->Complete=true;assert(!Sync(r)); // completed valid trail remains usable
|
||||
Record waiting;waiting.Following=std::make_shared<SharedTrail>();
|
||||
assert(Sync(waiting));waiting.Elapsed=120000;assert(Sync(waiting)&&landings==1);
|
||||
waiting.Following->Aborted=true;assert(Sync(waiting)&&landings==2);
|
||||
Settings cfg;auto sample=[](float,float){return Surface{827.765625f,1000,true,0};};
|
||||
auto clear=[](Point const&,Point const&){return true;};
|
||||
// The same shared object first exists while its leader is walking to takeoff.
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
"""Join a leader trail from a later taxi stop instead of replaying its origin."""
|
||||
from pathlib import Path
|
||||
import subprocess,tempfile
|
||||
root=Path(__file__).resolve().parents[2]
|
||||
code=r'''
|
||||
#include "ClientlessFlightPath.h"
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
using namespace ClientlessFlightPath;
|
||||
int main(){Settings cfg;auto flat=[](float,float){return Surface{0,1000,true};};auto clear=[](Point const&,Point const&){return true;};
|
||||
SharedTrail trail;trail.LandingReady=true;trail.Complete=true;trail.Landing={1200,0,0};
|
||||
trail.Points={{0,0,20},{1025,0,60},{1050,0,60},{1100,0,60},{1200,0,0}};
|
||||
SharedJoin join;std::size_t index=0;Point current{1000,0,0};bool connected=false;
|
||||
for(unsigned i=0;i<100&&Distance(current,trail.Landing)>.1f;++i){
|
||||
auto result=FollowTrail(current,trail.Landing,cfg,trail,index,flat,clear,0,&join);
|
||||
assert(!result.Waiting&&result.Step.Valid);assert(result.Step.Next.X>=1000);
|
||||
assert(Corridor(current,result.Step.Next,trail.Landing,cfg,flat,clear,true,result.Step.UnderOverhang));
|
||||
current=result.Step.Next;if(!join.Active)connected=true;
|
||||
}
|
||||
assert(connected&&Distance(current,trail.Landing)<.1f&&index>=1);
|
||||
// Original Winienwi geometry: the old first point is 2,088 yards behind
|
||||
// the taxi stop and its direct segment intersects a raised surface.
|
||||
Point taxi{-4170.55f,-4748.93f,126.786f};SharedTrail actual;
|
||||
actual.LandingReady=true;actual.Landing={-4905.67f,-4232.75f,827.766f};
|
||||
actual.Points={{-4863.52f,-6715.61f,24.22f},{-4850,-4750,540},actual.Landing};
|
||||
auto hills=[](float x,float y){float ground=y< -6500?9.f:126.786f;
|
||||
if(std::fabs(x+4175.523f)<2.5f&&std::fabs(y+4763.045f)<2.5f)ground=129.25f;
|
||||
return Surface{ground,1000,true};};
|
||||
assert(!Corridor(taxi,actual.Points[0],actual.Landing,cfg,hills,clear,true));
|
||||
join={};index=0;auto connectedTaxi=FollowTrail(taxi,actual.Landing,cfg,actual,index,hills,clear,0,&join);
|
||||
assert(connectedTaxi.Step.Valid&&join.Target==1&&connectedTaxi.Step.Next.Z>taxi.Z);
|
||||
// Joining an airborne leg above a tower must still go around its platform.
|
||||
SharedTrail tower;tower.LandingReady=true;tower.Landing={12,0,200};
|
||||
tower.Points={{60,0,210},{12,0,210},tower.Landing};
|
||||
auto platform=[](float x,float y){return Surface{std::hypot(x,y)<20?200.f:0.f,500,true,0.f};};
|
||||
auto solid=[](Point const&a,Point const&b){for(unsigned i=0;i<=200;++i){auto p=Mix(a,b,i/200.f);if(std::hypot(p.X,p.Y)<20&&p.Z>=199.8f&&p.Z<=200.2f)return false;}return true;};
|
||||
current={90,0,80};join={};index=0;
|
||||
for(unsigned i=0;i<100&&Distance(current,tower.Landing)>.1f;++i){
|
||||
auto step=FollowTrail(current,tower.Landing,cfg,tower,index,platform,solid,0,&join);
|
||||
assert(step.Step.Valid&&!step.Waiting);current=step.Step.Next;
|
||||
}
|
||||
assert(Distance(current,tower.Landing)<.1f);
|
||||
// No unvalidated shortcut through a tower or missing terrain.
|
||||
join={};index=0;auto blocked=[](Point const&,Point const&){return false;};
|
||||
assert(!FollowTrail({1000,0,0},trail.Landing,cfg,trail,index,flat,blocked,0,&join).Step.Valid);
|
||||
auto missing=[](float,float){return Surface{};};join={};index=0;
|
||||
assert(!FollowTrail({1000,0,0},trail.Landing,cfg,trail,index,missing,clear,0,&join).Step.Valid);
|
||||
// A replacement publishes its verified destination immediately, even before
|
||||
// any points are flown. Old readers can resolve to this generation.
|
||||
auto old=std::make_shared<SharedTrail>();auto next=old->Replace({100,0,0});
|
||||
assert(old->Aborted&&old->Replacement==next&&next->LandingReady&&next->Landing.X==100);
|
||||
assert(next->Points.empty());next->Points={{25,0,20},{100,0,0}};next->Complete=true;
|
||||
join={};index=0;auto step=FollowTrail({0,0,0},next->Landing,cfg,*next,index,flat,clear,0,&join);
|
||||
assert(step.Step.Valid&&!step.Waiting);
|
||||
std::cout<<"Shared route rejoin after taxi, bounded climb, clear corridor, blocked/missing terrain, replacement publication PASS\n";
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory(prefix="vcore-shared-rejoin-") as directory:
|
||||
p=Path(directory);(p/"test.cpp").write_text(code)
|
||||
subprocess.run(["c++","-std=c++17","-I"+str(root/"src/server/game/World"),str(p/"test.cpp"),"-o",str(p/"test")],check=True)
|
||||
subprocess.run([str(p/"test")],check=True)
|
||||
Reference in new issue
Block a user