From bc8f171055e00060640e85101497f90899f805e5 Mon Sep 17 00:00:00 2001 From: nico Date: Thu, 29 Oct 2026 23:07:10 +0100 Subject: [PATCH] Fix restored bot passengers and add capital Cataclysm portals --- doc/clientless/SESSION-RESUME.md | 213 +++++++++++++++++ doc/clientless/capital-cataclysm-portals.md | 64 +++++ .../graph-after.tsv | 13 ++ .../native-clicks-and-denials.log | 20 ++ .../probes-before-tol-fix.json | 218 +++++++++++++++++ .../probes.json | 219 ++++++++++++++++++ .../spells-after.tsv | 19 ++ .../spells-before.tsv | 0 .../templates-before.tsv | 13 ++ .../test-area-node.log | 1 + .../test-portal-transfer.log | 1 + .../test-portal-use.log | 3 + .../vashjir-policy-before.tsv | 5 + doc/clientless/deck-return-fallback.md | 41 ++++ .../resumed-transport-passengers.md | 62 +++++ ...026_10_29_07_capital_cataclysm_portals.sql | 54 +++++ src/server/game/World/ClientlessLuaEngine.cpp | 4 + .../game/World/ClientlessTransportDeckMgr.cpp | 72 +++++- .../game/World/ClientlessTransportMgr.cpp | 55 ++++- .../game/World/ClientlessTransportMgr.h | 4 + tests/clientless/test_transport.py | 43 +++- .../clientless/test_transport_deck_control.py | 30 ++- .../clientless/test_transport_deck_return.py | 54 +++++ 23 files changed, 1184 insertions(+), 24 deletions(-) create mode 100644 doc/clientless/capital-cataclysm-portals.md create mode 100644 doc/clientless/checkpoints/2026-10-29-capital-cata-portals/graph-after.tsv create mode 100644 doc/clientless/checkpoints/2026-10-29-capital-cata-portals/native-clicks-and-denials.log create mode 100644 doc/clientless/checkpoints/2026-10-29-capital-cata-portals/probes-before-tol-fix.json create mode 100644 doc/clientless/checkpoints/2026-10-29-capital-cata-portals/probes.json create mode 100644 doc/clientless/checkpoints/2026-10-29-capital-cata-portals/spells-after.tsv create mode 100644 doc/clientless/checkpoints/2026-10-29-capital-cata-portals/spells-before.tsv create mode 100644 doc/clientless/checkpoints/2026-10-29-capital-cata-portals/templates-before.tsv create mode 100644 doc/clientless/checkpoints/2026-10-29-capital-cata-portals/test-area-node.log create mode 100644 doc/clientless/checkpoints/2026-10-29-capital-cata-portals/test-portal-transfer.log create mode 100644 doc/clientless/checkpoints/2026-10-29-capital-cata-portals/test-portal-use.log create mode 100644 doc/clientless/checkpoints/2026-10-29-capital-cata-portals/vashjir-policy-before.tsv create mode 100644 doc/clientless/deck-return-fallback.md create mode 100644 doc/clientless/resumed-transport-passengers.md create mode 100644 sql/custom/world/2026_10_29_07_capital_cataclysm_portals.sql create mode 100644 tests/clientless/test_transport_deck_return.py diff --git a/doc/clientless/SESSION-RESUME.md b/doc/clientless/SESSION-RESUME.md index 65e35d7dbf..8c99eaa94b 100644 --- a/doc/clientless/SESSION-RESUME.md +++ b/doc/clientless/SESSION-RESUME.md @@ -1,3 +1,216 @@ +# Commit checkpoint: transport resumption, deck return and capital portals + +User requested a local commit of all completed changes since19075b6c75. Includes +stopped-only recorded deck stand fallback, adoption of restored native ship +passengers and safe recorded exit, capital Cataclysm/Tol Barad/Vashjir graph +bindings and scoped spell repairs (migration07), plus tests/docs/evidence. +Transport final build and six tests passed; portal native tests verified all12 +transfers and both-faction Vashjir graph denial. Temporary profiles/tele fixtures +removed, generated profiles/schedules restored. Final server362937 is operational; +no lifecycle operation or push as part of committing. Earlier uncommitted/pending +notes below are historical; consult git log for the resulting commit. + +# Latest: capital Cataclysm/Tol portals COMPLETE, all12 native transfers verified + +Applied SQL07 graphbindings forSW/Org Hyjal,Deepholm,Uldum,TwilightHighlands, +TolBarad,Vashjir (6/capital). Sources factionACL1/2, duplicateOrgTol219035 skipped. +Nine existing templates keptdata6=0 and worked. OrgTol206595 failednativeclick +atdata6=0 twice, nowvisitorcast1 and verifiednative88341arrival732. Vash90244/ +90245 previouslyempty; native DBteleporteffects160117/118 + existingSmugglersScar +fixedtarget(-4570.29,3478.06,-101.809), visitorcast207690/691. Explicitfixedcave +landing, NOT retailquestprogress destinations. Other targets/conditions unchanged. + +All12 nativeclicks/expectedarrivalcoordinates verified using35Karggan/36Edwin85. +Finalresults/evidence doc/clientless/checkpoints/2026-10-29-capital-cata-portals. +Area5146/children policies byteunchanged. Nativebothbotquery target5057 mask0 +allowed0, both hardgraphtravelattempts rejected destinationdenied. Nodes/edges +remainpresent/enabled. Migrationreplay preservedbindings/effects/targets. Three +existing portal/ACK/area-node regressions passed; no newC++ changes/buildneeded. + +Temp profileskarggan.lua/edwin.lua removed after verifyingexacttestcontents; +generatedprofiles+daily schedulesrestored. Bothtestbotsreturnedowncapitals; +12vcore_cata_probe_*gametelefixturesdeleted,cache reloaded,count0. No human edits. +EarlierHyjalbaselinefalsefailurewasreadingtooearly; finalsaveproveditworked, +userinformed andblanketcasterchange undone. AfterfixfirstTolretestattempt before +Edwinonline was rejected; real afteronline nativeclickARRIVED andfinalJSONcorrect. + +Final server362937 undergdb/tmuxvcore-worldserver, console/tmp/vcore-cata-portals- +final-start-20261029.log. Freshready11s/process+8085/8087 verified. Known old +OutdoorPvPshutdowntracespreserved. No commit/push. Prior uncommitteddeckreturn/ +resumedpassengerWIP preserved. SQL07intent-to-add becausecustomSQLignored. +See capital-cataclysm-portals.md. Taskcomplete; report andwaituserfeedback. + +# Active task: capital Cataclysm/Tol Barad portals incl blocked Vashjir + +User asks graph entries + real functionality checks for bothcapitals inclVashjir, +keeping global area ban. Native imports done12 unique spawns (OrgTol duplicate +219035 intentionally skipped). SQL07 prepared/applied, scoped data6visitorcast +for206595 (OrgTol FAILED live twice atdata6=0),207690/207691 (Vash emptyserver +spells). Nine other templates keep originaldata6=0 and native targets/conditions. +Vash90244/90245 hadNOeffects/targets; added DBnative teleport effects160117/118 +and fixed existing gameteleSmugglersScar landing(-4570.29,3478.06,-101.809), +NOT claimed retailquest-dependent destination logic. GraphSQL seedonlypreserves +manual positions/enabledflags. SourceACL Alliance/Horde; area catalog unchanged. + +Temp tests use35KargganAlliance85 and36EdwinHorde85, nohumans. Temporary specific +profiles karggan.lua/edwin.lua under sourceadventure/profiles copied fromgenerated +DB thenbehavior.enabled/daily_schedule=false. ORIGINAL backups /tmp/vcore-cata- +portals/*-profile-original.lua; files DID NOT EXIST before task. MUST REMOVEONLY +these2 profiles aftertests; luareload+scheduleon torestoregeneratedbehavior. +12 temporary game_tele namesvcore_cata_probe_ created; deleteonlythatprefix +and reloadgame_tele aftertests. No testfixtureleftinrepo/DB onfinish. + +Probe script/tmp/vcore-cata-portals/probe.py completed12tests;11successful. +Failure OrgTolspawn211264 remainedsource after30s/retry; nowchangeddata6=1 and +issued shutdown10 to361432 (console/tmp/vcore-cata-portals-start-20261029.log) +for cache reload. Needwaitfullexit+gdbtrace thenstartsameinstalledbinaryfreshgdb +console/tmp/vcore-cata-portals-final-start-20261029.log,verifyready/ports. Retest +211264 withEdwin36 viaGMtelefixture+nativeclientlessportalobjectuse; pollsaveall/ +DBuntiltargetmap732(-603.724,1387.62,22.0498). Original probes JSONsaved beforefix +/tmp/vcore-cata-portals/probes-before-tol-fix.json. Update finalprobes.json entry. +Other11 includingBOTHVashlandings passednativeclick->expectedposition. Native +35 areaqueriesbothVashtargets returnedfactionMask0/allowed0 area5057; real hard +travel_to_node35 targetVash rejected detail destinationdenied. Need repeat36 +areaqueries whennotintransfer. Verifypolicy before.tsv byteunchanged. + +Corrected earlier misleadingHyjalbaseline: firstDBread tooearly, finalsave proved +native0 worked; user informed, blanketcasterchange undone before tests. Do NOT +claim all portals broken. Otherexistinguncommitteddeckreturn/resumedpassengerWIP +preserved. No newC++ source changes inthisportal task; DBreloadrequiredrestarts. +Next finishOrgTolretest,cleanupfixtures/profiles, tests/docs/evidence,report. + +# Latest: resumed-passenger build INSTALLED, server READY; Shanron on normal plan + +User explicitly released recording hold and requested install/restart. Old349322 +fully exited after known OutdoorPvP shutdown destructor crash (oldconsole trace +retained). Installed successful prepared build using existing CMake: +/tmp/vcore-transport-resume-install.log. Fresh gdb/tmux vcore-worldserver +PID356681, console/tmp/vcore-transport-resume-start-20261029.log. NewServer.log +freshready12s, both8085/8087 listening, process persists. No source/config/DB +changes for deployment; prior uncommitted deck-return+passenger-resume WIP remains. + +Requested native clientless login140 to check Shanron without manual position/ +profile/dayplan override. Core LoadFromDB reported transportGUID2 unavailable, +teleported him to homebind before controller startup; same warning for26. +Thus no restored attachment existed to exercise new ResumePassenger for140. +Confirmed140 online/profileloaded, normal restintent poi_guard_111 arrived and +rest_sleep/rest_look micros. No TRANSPORT_RESUME live event observed yet; do not +claim restored-passenger/exit behavior live-proven from this deployment. Six +focused tests and complete build previously passed. Core inability to restore +certain ship bindings remains a separate observed limitation. User was told +Shanron resumed ordinary schedule via core homebind fallback, not new deck exit. +No push/commit requested. Server operational. Recording hold is now released. + +# Latest: orphan-passenger fix READY; USER HOLD NO INSTALL/RESTART + +Final complete build passed/tmp/vcore-transport-resume-final-build.log (source +TransportMgr object rebuilt after final edits, link completed). Six targeted +tests pass/tmp/vcore-resumed-passenger-tests.json. Latest additional detached +land-tail ownership assertion passes in test_transport log. Native attachment +adoption + next permitted recorded halt + ordinary exit implemented; current +installed server349322/executable21:00:04 unchanged. No runtime console commands, +DB/profile/dayplan changes, installation or restart in this task. Existing +uncommitted stopped-deck-return fallback work retained. New fix is source/build +only and NOT live-verified. See resumed-transport-passengers.md. +USER IS RECORDING ROUTES. Notify ready and WAIT for explicit hold release before +install/restart. No pending builds/test processes. No commit/push requested. + +# Latest: resumed-passenger fix prepared; USER RECORDING, NO RESTART/INSTALL + +User explicitly requests fix for Shanron's orphaned native ship attachment but +NO restart while recording more routes. Added TransportMgr::ResumePassenger +and Resumed flag; Lua Adventure controller invokes before actions, after +observation/maintenance. Preserve normal active rides; require actual known +recorded vehicle+managed alive noncombat passenger, no taxi/pending teleport, +allowed stop. Lazy ensure both stop and graph data load. Reconstruct only Riding +state, never reboard/teleport/change schedule. At each Riding update choose actual +currently stopped permitted recorded dock; no guessed prior destination. Reuse +normal deck return/budget/recorded exit incl native map-transfer ACK; keep control +through detached land tail until arrived. No exiting at newly forbidden stops. +Unrecorded/forbidden services untouched. No static tram DB persistence repair. +Six tests passed/tmp/vcore-resumed-passenger-tests.json; extended production +transport harness includes restart adoption, preserved normal rides, complete +native-modeled exits, denied stops, unknown/unmanaged, landtail and cross-map. +Final build running/tmp/vcore-transport-resume-final-build.log. Source latest +changes after initialbuild must be included; verify final completion. +Current server349322 remains unchanged, NO console/lifecycle/DB/profile changes. +Doc resumed-transport-passengers.md. Previous uncommitted deck-return fallback +work preserved. Next finish final build/checks, tell user ready; WAIT for user +recording hold release before any install/restart. Live behavior unverified. + +# Latest diagnosis: Shanron140 attached to ship but no resumed ride controller + +User observes Shanron aboard ship toSW. Native console transport status140: +`No transport ride state for this character.` actioninfo140 Idle/empty. Character +DB online map0(-8644.53,1335.31,6.12629),transguid2(The Bravery). Currentserver +349322. Both previous and current boot logged generated140 startup but no native +boarding/travel progression; normal core restores MO_TRANSPORT attachment from +saved character, CPC `_rides` exists only in memory and Start refuses already +attached players. Thus restored ship passenger has no ride driver to initiate +recorded exit/deck fallback. Distinct from120Brynna's repeated return issue. +No code/DB/lifecycle/relocation changes for Shanron investigation; existing +uncommitted stopped-deck fallback changes preserved. Needed follow-up: adopt +already attached managed passengers after login/restart using matching recorded +service; disembark only at an actual safe recorded dock, then resume normal plan. +Do not simply detach/teleport a passenger in transit. Existing120fallback still +requires a tracked ride to call DeckMgr::Update and cannot solve orphaned ride. + +# Latest: stopped deck-return fallback INSTALLED, server READY + +User permits transfer to recorded onboard stand after return failure only while +vehicle stopped. Implemented checked direct native spline launch + persistent +return failure state and safe native passenger relocation fallback. No detach, +no transportGUID changes, exact recorded local anchor, same transport/map, +alive/no combat/no pending teleport, valid coordinates, stopped+exitBudget1s +reserve independently rechecked. Ordinary recorded exit still follows. +Five tests PASS: deck control (including failed start/return, motion and short +halt rejection), actual fallback helper with doubles, real meshes, generic +transport, grid loading. Build/tmp/vcore-deck-return-build.log PASS. Installed +/tmp/vcore-deck-return-install.log after341932 fully exited (old known OutdoorPvP +shutdown fault trace retained). Fresh gdb/tmux PID349322, console +/tmp/vcore-deck-return-start-20261029.log, freshready13s,8085/8087 listeners, +process verified. Installed binary contains DECK_RETURN_FALLBACK string. +No native fallback event seen yet; no live/visual acceptance claim. Natural bot +loader active; no manual forced action, DB relocation or profile/plan override. +Doc deck-return-fallback.md. All these changes UNCOMMITTED after19075b6c75. +Previous SESSION diagnostic edit is intentional. Report completion; await user. + +# Latest: stopped deck-return fallback implemented, build passed + +User explicitly permits transfer back to recorded onboard stand after return +navigation fails, only while vehicle stopped. Added Passenger.Returning/ +ReturnFailed, checked immediate native spline launch, failed/timeout/interrupted +return handling; helper rechecks actual same passenger/map, stopped and exit +reserve+1s, life/combat/teleport, valid local/world coordinates. Native local +position+TransportBase passenger relocation+broadcast; attachment never removed. +Ordinary recorded exit remains unchanged. Tests controller/native helper/real +mesh passed, generic transport/grid tests running. Build +/tmp/vcore-deck-return-build.log complete PASS. Not yet installed. Currentserver +341932 remains running. Prior SESSION diagnostic change is expected WIP after +commit19075b6c75. New doc deck-return-fallback.md. Next: finish test checks, +graceful shutdown/install/restart undergdb, freshready+ports, observe boarding/ +returns/exits; do not claim actual fallback occurred until logged. + +# Latest: Brynna120 stuck aboard tram; diagnosed repeated deck return + +User observes Brynna in wagon. Branch19075b6c75 clean at investigation start. +Native status120 Riding SW->IF spawn18803 point4 elapsed238825, abort0, action +Moving. Prior rescueSW completed20:45:28, normal boarding20:46:42/Riding20:46:43. +Deck outing target(2.419,-3.118,-10.445). At destination20:47:55 and20:47:58 +repeated DECK_WALK return target(-0.112,-0.187,-10.345),length3.87. Next dock +20:50:17..22 dozens of identical return launches per second, NO Leaving state. +This confirms recurring deck-return problem, not lost attachment after restart. +DeckMgr::Launch reports success unconditionally via MotionMaster LaunchMoveSpline +(void), Update uses IsSplineEnabled and retries when actual local target not +reached. Exact cause (launch failure, motion slot priority, competing stop) not +proven yet. Unit UpdateSplinePosition does update transport-local position. +Server.log has _checkPathLengths validation errors for76/110, NOT120; do not +attribute those to Brynna without evidence. No code or DB/teleport/lifecycle +changes in this diagnosis. Server341932 remains running. Ordinary rescue refuses +attached passengers by design; do not detach/teleport moving wagon passenger. +Next fix should validate actual deck spline start/completion, avoid repeated +return issuance, reserve enough dock time, retain safe passenger attachment. + # Commit checkpoint: group follow, grounded portal and explicit tram rescue User requested a local commit of the current completed changes. Includes group diff --git a/doc/clientless/capital-cataclysm-portals.md b/doc/clientless/capital-cataclysm-portals.md new file mode 100644 index 0000000000..db958e1256 --- /dev/null +++ b/doc/clientless/capital-cataclysm-portals.md @@ -0,0 +1,64 @@ +# Capital Cataclysm portals and Tol Barad + +World migration `2026_10_29_07_capital_cataclysm_portals.sql` adds twelve native +GameObject portal graph bindings, six for each capital. Source faction ACL is +Alliance1 or Horde2; destination area rules retain precedence. Imports and SQL +seed only: existing enabled flags and manually adjusted positions survive. + +| Destination | Stormwind spawn | Orgrimmar spawn | +| --- | --- | --- | +| Hyjal | 200624 | 219028 | +| Deepholm | 200625 | 211258 | +| Uldum | 200626 | 211259 | +| Twilight Highlands | 200627 | 211260 | +| Tol Barad Peninsula | 220365 | 211264 | +| Vashj'ir | 220366 | 211257 | + +Orgrimmar Tol Barad duplicate219035 is coincident with211264 and deliberately +has no second graph edge. Its shared template receives the same repair. + +Nine existing portal templates worked in native tests without caster changes. +Orgrimmar Tol Barad206595 accepted clicks but failed to transfer Edwin85 in two +attempts; changing only this template to native visitor-cast mode made its +88341 teleport work. Native target coordinates and conditions remain unchanged. +An early Hyjal failure inference was corrected: first save/read was too early, +final save showed successful arrival. No blanket caster change was retained. + +Vashj'ir server-only spells90244/90245 had neither effects nor target positions. +The migration supplies direct native teleport effects160117/160118 and visitor +casting for207690/207691. The fixed landing comes from the existing project +SmugglersScar game_tele1514 at map0(-4570.29,3478.06,-101.809). This is a deliberate +fixed cave destination, not reconstructed retail quest-progress destination +selection. No quest completion is granted or visibility condition bypassed. + +Vashj'ir and children remain closed by unchanged area5146 faction mask0. Both +source and destination nodes and the portal edges exist/enabled. Native area +queries for Alliance35 and Horde36 resolve the landing to5057 with inherited +factionMask0 and allowed0. Actual hard travel_to_node attempts are rejected with +`destination node denied by area/node catalog` for both factions. Diagnostic +native-click tests intentionally exercise the underlying portal, outside +ordinary autonomous planning; the ban was never disabled. + +## Verification and cleanup + +Twelve native CMSG_GAMEOBJ_USE clicks with level85 Karggan35 and Edwin36 reached +their expected spell_target_position destinations. Full position snapshots are +in checkpoints/2026-10-29-capital-cata-portals/probes.json. Before-Tol-fix output +is retained separately. An initial post-restart retest attempted a click before +Edwin finished login and was rejected; the successful retest waited for online +state and native interaction-range validation. Do not count that login race as +a portal failure or an unverified success. + +Migration replay preserved binding/effect/target results. Existing native portal +use, transfer/ACK and area-node regressions passed. No new C++ changes/build were +required; two restarts loaded server spell/template DB changes. Final server +PID362937 under gdb/tmux vcore-worldserver, console +/tmp/vcore-cata-portals-final-start-20261029.log; freshready11s,8085/8087 verified. +Known OutdoorPvP shutdown fault traces retained in earlier consoles. + +The two temporary specific profiles were removed and generated profiles plus +daily schedules restored. Testbots returned to their own capitals. Twelve +vcore_cata_probe_* game_tele fixtures were deleted and cache reloaded (count0). +No human character modified. Prior uncommitted transport fixes preserved. +Native transfers are verified; final client-side visual acceptance remains with +the user. No commit or push requested for this task. diff --git a/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/graph-after.tsv b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/graph-after.tsv new file mode 100644 index 0000000000..d5d95dc0ce --- /dev/null +++ b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/graph-after.tsv @@ -0,0 +1,13 @@ +gameobject_spawn_id gameobject_entry spell_id id enabled from_node_key to_node_key allowed_factions +200624 207692 94565 1798 1 portal_go_200624_source portal_go_200624_target 1 +200625 207693 84515 1799 1 portal_go_200625_source portal_go_200625_target 1 +200626 207695 94564 1800 1 portal_go_200626_source portal_go_200626_target 1 +200627 207694 85545 1801 1 portal_go_200627_source portal_go_200627_target 1 +211257 207690 90244 1808 1 portal_go_211257_source portal_go_211257_target 2 +211258 207689 84513 1803 1 portal_go_211258_source portal_go_211258_target 2 +211259 207687 94564 1804 1 portal_go_211259_source portal_go_211259_target 2 +211260 207686 82621 1805 1 portal_go_211260_source portal_go_211260_target 2 +211264 206595 88341 1806 1 portal_go_211264_source portal_go_211264_target 2 +219028 207688 94565 1807 1 portal_go_219028_source portal_go_219028_target 2 +220365 206594 88339 1802 1 portal_go_220365_source portal_go_220365_target 1 +220366 207691 90245 1809 1 portal_go_220366_source portal_go_220366_target 1 diff --git a/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/native-clicks-and-denials.log b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/native-clicks-and-denials.log new file mode 100644 index 0000000000..b53d0215c9 --- /dev/null +++ b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/native-clicks-and-denials.log @@ -0,0 +1,20 @@ +Native portal click sent: bot=35 spawn=200624 entry=207692. Check teleport completion and cast diagnostics. +Native portal click sent: bot=35 spawn=200625 entry=207693. Check teleport completion and cast diagnostics. +[?2004hTC>Native portal click sent: bot=35 spawn=200626 entry=207695. Check teleport completion and cast diagnostics. +Native portal click sent: bot=35 spawn=200627 entry=207694. Check teleport completion and cast diagnostics. +[?2004hTC>Native portal click sent: bot=35 spawn=220365 entry=206594. Check teleport completion and cast diagnostics. +Native portal click sent: bot=35 spawn=220366 entry=207691. Check teleport completion and cast diagnostics. +Native portal click sent: bot=36 spawn=219028 entry=207688. Check teleport completion and cast diagnostics. +Native portal click sent: bot=36 spawn=211258 entry=207689. Check teleport completion and cast diagnostics. +Native portal click sent: bot=36 spawn=211259 entry=207687. Check teleport completion and cast diagnostics. +[?2004hTC>Native portal click sent: bot=36 spawn=211260 entry=207686. Check teleport completion and cast diagnostics. +TC> Area access GUID 35: revision=1 map=0 area=5057 factionMask=0 minLevel=80 maxLevel=82 gap=5 knownLevel=1 preferFlight=0 nodeMask=-1 allowed=0 +TC> Area access GUID 35: revision=1 map=0 area=5057 factionMask=0 minLevel=80 maxLevel=82 gap=5 knownLevel=1 preferFlight=0 nodeMask=-1 allowed=0 +Native portal click sent: bot=36 spawn=211264 entry=206595. Check teleport completion and cast diagnostics. +Native portal click sent: bot=36 spawn=211257 entry=207690. Check teleport completion and cast diagnostics. +TC> Action GUID 35: state=Failed action='travel_to_node' target='' targetGuid=0 entry=0 detail='destination node denied by area/node catalog' +[?2004hTC>Native portal click sent: bot=36 spawn=211264 entry=206595. Check teleport completion and cast diagnostics. +[?2004hTC>Native portal click sent: bot=36 spawn=211264 entry=206595. Check teleport completion and cast diagnostics. +Area access GUID 36: revision=1 map=0 area=5057 factionMask=0 minLevel=80 maxLevel=82 gap=5 knownLevel=1 preferFlight=0 nodeMask=3 allowed=0 +TC> Area access GUID 36: revision=1 map=0 area=5057 factionMask=0 minLevel=80 maxLevel=82 gap=5 knownLevel=1 preferFlight=0 nodeMask=3 allowed=0 +Action GUID 36: state=Failed action='travel_to_node' target='' targetGuid=0 entry=0 detail='destination node denied by area/node catalog' diff --git a/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/probes-before-tol-fix.json b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/probes-before-tol-fix.json new file mode 100644 index 0000000000..afba330ae1 --- /dev/null +++ b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/probes-before-tol-fix.json @@ -0,0 +1,218 @@ +[ + { + "spawn": 200624, + "bot": 35, + "at_source": true, + "success": true, + "actual": [ + 1.0, + 5534.08, + -3624.69, + 1567.04 + ], + "expected": [ + 1.0, + 5534.08, + -3624.69, + 1567.04 + ] + }, + { + "spawn": 200625, + "bot": 35, + "at_source": true, + "success": true, + "actual": [ + 646.0, + 974.921, + 566.768, + -47.5163 + ], + "expected": [ + 646.0, + 974.921, + 566.768, + -47.5163 + ] + }, + { + "spawn": 200626, + "bot": 35, + "at_source": true, + "success": true, + "actual": [ + 1.0, + -9443.31, + -958.365, + 111.012 + ], + "expected": [ + 1.0, + -9443.31, + -958.365, + 111.012 + ] + }, + { + "spawn": 200627, + "bot": 35, + "at_source": true, + "success": true, + "actual": [ + 0.0, + -4890.07, + -6626.74, + 10.2846 + ], + "expected": [ + 0.0, + -4890.07, + -6626.74, + 10.2846 + ] + }, + { + "spawn": 220365, + "bot": 35, + "at_source": true, + "success": true, + "actual": [ + 732.0, + -369.208, + 1058.73, + 21.7719 + ], + "expected": [ + 732.0, + -369.208, + 1058.73, + 21.7719 + ] + }, + { + "spawn": 220366, + "bot": 35, + "at_source": true, + "success": true, + "actual": [ + 0.0, + -4570.29, + 3478.06, + -101.809 + ], + "expected": [ + 0.0, + -4570.29, + 3478.06, + -101.809 + ] + }, + { + "spawn": 219028, + "bot": 36, + "at_source": true, + "success": true, + "actual": [ + 1.0, + 5534.08, + -3624.69, + 1567.04 + ], + "expected": [ + 1.0, + 5534.08, + -3624.69, + 1567.04 + ] + }, + { + "spawn": 211258, + "bot": 36, + "at_source": true, + "success": true, + "actual": [ + 646.0, + 995.808, + 471.879, + -48.1172 + ], + "expected": [ + 646.0, + 995.808, + 471.879, + -48.1172 + ] + }, + { + "spawn": 211259, + "bot": 36, + "at_source": true, + "success": true, + "actual": [ + 1.0, + -9443.31, + -958.365, + 111.012 + ], + "expected": [ + 1.0, + -9443.31, + -958.365, + 111.012 + ] + }, + { + "spawn": 211260, + "bot": 36, + "at_source": true, + "success": true, + "actual": [ + 0.0, + -4032.79, + -6318.53, + 38.6975 + ], + "expected": [ + 0.0, + -4032.79, + -6318.53, + 38.6975 + ] + }, + { + "spawn": 211264, + "bot": 36, + "at_source": true, + "success": false, + "actual": [ + 1.0, + 2031.22, + -4330.87, + 95.771 + ], + "expected": [ + 732.0, + -603.724, + 1387.62, + 22.0498 + ] + }, + { + "spawn": 211257, + "bot": 36, + "at_source": true, + "success": true, + "actual": [ + 0.0, + -4570.29, + 3478.06, + -101.809 + ], + "expected": [ + 0.0, + -4570.29, + 3478.06, + -101.809 + ] + } +] \ No newline at end of file diff --git a/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/probes.json b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/probes.json new file mode 100644 index 0000000000..e378a60d3b --- /dev/null +++ b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/probes.json @@ -0,0 +1,219 @@ +[ + { + "spawn": 200624, + "bot": 35, + "at_source": true, + "success": true, + "actual": [ + 1.0, + 5534.08, + -3624.69, + 1567.04 + ], + "expected": [ + 1.0, + 5534.08, + -3624.69, + 1567.04 + ] + }, + { + "spawn": 200625, + "bot": 35, + "at_source": true, + "success": true, + "actual": [ + 646.0, + 974.921, + 566.768, + -47.5163 + ], + "expected": [ + 646.0, + 974.921, + 566.768, + -47.5163 + ] + }, + { + "spawn": 200626, + "bot": 35, + "at_source": true, + "success": true, + "actual": [ + 1.0, + -9443.31, + -958.365, + 111.012 + ], + "expected": [ + 1.0, + -9443.31, + -958.365, + 111.012 + ] + }, + { + "spawn": 200627, + "bot": 35, + "at_source": true, + "success": true, + "actual": [ + 0.0, + -4890.07, + -6626.74, + 10.2846 + ], + "expected": [ + 0.0, + -4890.07, + -6626.74, + 10.2846 + ] + }, + { + "spawn": 220365, + "bot": 35, + "at_source": true, + "success": true, + "actual": [ + 732.0, + -369.208, + 1058.73, + 21.7719 + ], + "expected": [ + 732.0, + -369.208, + 1058.73, + 21.7719 + ] + }, + { + "spawn": 220366, + "bot": 35, + "at_source": true, + "success": true, + "actual": [ + 0.0, + -4570.29, + 3478.06, + -101.809 + ], + "expected": [ + 0.0, + -4570.29, + 3478.06, + -101.809 + ] + }, + { + "spawn": 219028, + "bot": 36, + "at_source": true, + "success": true, + "actual": [ + 1.0, + 5534.08, + -3624.69, + 1567.04 + ], + "expected": [ + 1.0, + 5534.08, + -3624.69, + 1567.04 + ] + }, + { + "spawn": 211258, + "bot": 36, + "at_source": true, + "success": true, + "actual": [ + 646.0, + 995.808, + 471.879, + -48.1172 + ], + "expected": [ + 646.0, + 995.808, + 471.879, + -48.1172 + ] + }, + { + "spawn": 211259, + "bot": 36, + "at_source": true, + "success": true, + "actual": [ + 1.0, + -9443.31, + -958.365, + 111.012 + ], + "expected": [ + 1.0, + -9443.31, + -958.365, + 111.012 + ] + }, + { + "spawn": 211260, + "bot": 36, + "at_source": true, + "success": true, + "actual": [ + 0.0, + -4032.79, + -6318.53, + 38.6975 + ], + "expected": [ + 0.0, + -4032.79, + -6318.53, + 38.6975 + ] + }, + { + "spawn": 211264, + "bot": 36, + "at_source": true, + "success": true, + "actual": [ + 732.0, + -603.724, + 1387.62, + 22.0498 + ], + "expected": [ + 732.0, + -603.724, + 1387.62, + 22.0498 + ], + "retest": "native click after confirmed online state; visitor cast=1" + }, + { + "spawn": 211257, + "bot": 36, + "at_source": true, + "success": true, + "actual": [ + 0.0, + -4570.29, + 3478.06, + -101.809 + ], + "expected": [ + 0.0, + -4570.29, + 3478.06, + -101.809 + ] + } +] \ No newline at end of file diff --git a/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/spells-after.tsv b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/spells-after.tsv new file mode 100644 index 0000000000..624d71e82a --- /dev/null +++ b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/spells-after.tsv @@ -0,0 +1,19 @@ +Id Effect EffectAmplitude EffectAura EffectAuraPeriod EffectBasePoints EffectBonusCoefficient EffectChainAmplitude EffectChainTargets EffectDieSides EffectItemType EffectMechanic EffectMiscValue EffectMiscValueB EffectPointsPerResource EffectRadiusIndex EffectRadiusMaxIndex EffectRealPointsPerLevel EffectSpellClassMaskA EffectSpellClassMaskB EffectSpellClassMaskC EffectTriggerSpell EffectImplicitTargetA EffectImplicitTargetB SpellID EffectIndex Comment +160117 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 25 17 90244 0 Orgrimmar Vashjir portal: fixed SmugglersScar landing +160118 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 25 17 90245 0 Stormwind Vashjir portal: fixed SmugglersScar landing +ID EffectIndex MapID PositionX PositionY PositionZ Orientation VerifiedBuild +90244 0 0 -4570.29 3478.06 -101.809 5.62628 0 +90245 0 0 -4570.29 3478.06 -101.809 5.62628 0 +entry data6 +206594 0 +206595 1 +207686 0 +207687 0 +207688 0 +207689 0 +207690 1 +207691 1 +207692 0 +207693 0 +207694 0 +207695 0 diff --git a/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/spells-before.tsv b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/spells-before.tsv new file mode 100644 index 0000000000..e69de29bb2 diff --git a/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/templates-before.tsv b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/templates-before.tsv new file mode 100644 index 0000000000..a0873b6024 --- /dev/null +++ b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/templates-before.tsv @@ -0,0 +1,13 @@ +entry type data0 data6 +206594 22 88339 0 +206595 22 88341 0 +207686 22 82621 0 +207687 22 94564 0 +207688 22 94565 0 +207689 22 84513 0 +207690 22 90244 0 +207691 22 90245 0 +207692 22 94565 0 +207693 22 84515 0 +207694 22 85545 0 +207695 22 94564 0 diff --git a/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/test-area-node.log b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/test-area-node.log new file mode 100644 index 0000000000..5010c3ecfe --- /dev/null +++ b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/test-area-node.log @@ -0,0 +1 @@ +Production area destination excludes outside and unreachable graph nodes. diff --git a/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/test-portal-transfer.log b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/test-portal-transfer.log new file mode 100644 index 0000000000..c8948ed790 --- /dev/null +++ b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/test-portal-transfer.log @@ -0,0 +1 @@ +Portal near/far ACK and denied same-map transfer regressions passed. diff --git a/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/test-portal-use.log b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/test-portal-use.log new file mode 100644 index 0000000000..63ce324f78 --- /dev/null +++ b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/test-portal-use.log @@ -0,0 +1,3 @@ +Interactive portal native click, near/far/global ACK, denial, timeout and target checks passed. +Native portal destination discovery and unsupported/ambiguous spell rejection passed. +Production clientless ACK completion sequence increments only after completed near/far transfers. diff --git a/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/vashjir-policy-before.tsv b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/vashjir-policy-before.tsv new file mode 100644 index 0000000000..cf719df71b --- /dev/null +++ b/doc/clientless/checkpoints/2026-10-29-capital-cata-portals/vashjir-policy-before.tsv @@ -0,0 +1,5 @@ +area_id map_id parent_area_id display_name allowed_factions min_level max_level max_level_gap prefer_flight notes +4815 0 5146 Tang'tharwald NULL 80 82 NULL NULL +5144 0 5146 Schimmernde Weiten NULL 80 82 NULL NULL +5145 0 5146 Abyssische Tiefen NULL 80 82 NULL NULL +5146 0 0 Vashj'ir 0 NULL NULL NULL NULL Vashjir closed to all AdventureBots, including child areas diff --git a/doc/clientless/deck-return-fallback.md b/doc/clientless/deck-return-fallback.md new file mode 100644 index 0000000000..6519a70079 --- /dev/null +++ b/doc/clientless/deck-return-fallback.md @@ -0,0 +1,41 @@ +# Stopped-vehicle fallback to the recorded deck stand + +User authorized transferring a bot to the recorded onboard stand when its return +navigation fails, exclusively while the vehicle is stopped. Implemented in +ClientlessTransportDeckMgr for its supported Bravery/tram deck meshes. + +Bord movement now starts with checked native MoveSplineInit::Launch rather than +a void MotionMaster wrapper. Failed launch is not reported as a successful walk. +The controller distinguishes return walks from initial deck outings. A failed +path/launch, stopped-short return, timeout or departure-interrupted return sets +a persistent return-failure flag. An insufficient walk budget defers the attempt +and allows the same safe fallback once a sufficient stopped window exists. + +At an arrival dock, a failed return can relocate only to the exact recorded local +stand. The helper independently rechecks stopped state, same map, actual same +transport attachment, alive/no combat/no pending teleport, finite bounded local +coordinates, valid transformed world coordinates and remaining exit budget+1s. +It stops the old spline, updates transport-local coordinates, calls the native +passenger relocation and broadcasts movement state. It never removes/adds a +passenger or changes vehicle/GUID. The normal reverse recorded exit then runs, +subject to the existing AtDock recheck. No relocation during vehicle motion. + +Tests: production controller and actual relocation helper against deterministic +doubles; real WMO/M2 deck mesh tests; generic transport and grid-loading tests. +Compile log /tmp/vcore-deck-return-build.log. Initial gameplay root cause behind +Brynna's repeated return launches was not fully instrumented; checked launch and +bounded fallback address this failure without claiming static passenger +persistence across restart was repaired. Runtime deployment results appended +below after installation. + +## Installed state + +Successful build and five focused tests. Installed after complete process exit; +startup PID349322 undergdb in vcore-worldserver reached ready in13s and listens +on8085/8087. Install log /tmp/vcore-deck-return-install.log; new console +/tmp/vcore-deck-return-start-20261029.log. Prior known OutdoorPvP shutdown fault +trace retained in the previous console. No native fallback occurrence observed +yet and no manually forced trip/profile/position/plan change for this task. +Native helper/control test logs /tmp/vcore-deck-return-helper-test.log and +-control-test.log; real meshes/transport/grid logs share that prefix. Visual +acceptance and actual fallback use remain unverified. diff --git a/doc/clientless/resumed-transport-passengers.md b/doc/clientless/resumed-transport-passengers.md new file mode 100644 index 0000000000..6d09e2dc9d --- /dev/null +++ b/doc/clientless/resumed-transport-passengers.md @@ -0,0 +1,62 @@ +# Resume restored clientless transport passengers + +Shanron140 retained native Bravery spawn2 attachment after restart but had no +CPC ride state. The AdventureBot Lua controller now invokes ResumePassenger +before supply/combat/intent actions while a restored exit is owned. Observation +and population maintenance still run first. Active ordinary executor rides are +never replaced. Dead/combat/pending teleport/taxi and unmanaged players are not +adopted. Actual native attachment is required; no passenger is invented. + +For an orphaned passenger, ensure stop and graph data are loaded; match a recorded +stop to the actual native transport object/pointer, spawn and entry; enforce +area/node/faction/level access. Seed a Resumed Riding record without reboarding, +relocating, clearing schedules or changing attachments. Unrecorded/forbidden +services are untouched. Failed ride records with an attached passenger can also +be recovered this way. + +While Riding, choose the first actual permitted recorded dock the vehicle stops +at (deterministic node tie-break for coincident stops). Do not infer the original +journey destination or force a detour back to the seed dock. Native AtDock checks +map, position, heading and stopped state. Reuse deck return, stop-time budgets +and the ordinary reversed recorded exit. No exit at subsequently denied stops. +Native cross-map worldport ACK and transport copies retain the same service. +The resumed controller keeps ownership through the land portion after passenger +detachment, releasing it on Arrived/Failed so normal day planning can continue. +Static tram passenger persistence itself is unchanged: this requires an actual +restored/attached passenger, not merely a saved position in the tram tunnel. + +Six focused tests passed: real transport methods now cover orphan adoption, +no duplicate boarding, normal ride preservation, unmanaged/unknown/denied cases, +first actual permitted dock, real exit completion, land-tail ownership and +cross-map restored ships. Existing deck controller/native fallback/real mesh, +transport grid and group travel tests also pass. Results +/tmp/vcore-resumed-passenger-tests.json; final build log +/tmp/vcore-transport-resume-final-build.log. Gameplay remains unverified until a +later install/restart. + +USER HOLD: the user is recording more routes. Do not install or restart now. +Current running server349322 and its installed executable remain untouched; +changes are source/build only. Existing uncommitted deck-return fallback work +is preserved. No DB/profile/plan/action changes are required for this fix. + +Final complete build succeeded (/tmp/vcore-transport-resume-final-build.log). +Current server process349322 and installed executable timestamp21:00:04 were +verified unchanged. Six test results remain successful, including the added +land-tail check. Ready for later installation; user recording hold remains in +force. No pending build processes or native gameplay verification. + +## Deployment after recording hold release + +User authorized installation/restart. Installed prepared build successfully, +log /tmp/vcore-transport-resume-install.log. Fresh PID356681 undergdb/tmux, +console /tmp/vcore-transport-resume-start-20261029.log. Freshready12s, process +persistence and8085/8087 verified. Known old OutdoorPvP shutdown destructor +fault captured before full old process exit. Source/config/DB untouched by deploy. + +Native login140 requested to inspect Shanron. Core transport restoration failed +for savedGUID2 and moved him to homebind before controller activation. He then +arrived at restlocation poi_guard_111 and executed rest micros. No manual position, +profile or dayplan override. The new resumed-attachment branch could not be live +validated on him because actual attachment was absent; automated tests remain +the verification of that branch. Core missing-transport restoration is separate. +Earlier no-install notes above record the completed hold, not a current ban. diff --git a/sql/custom/world/2026_10_29_07_capital_cataclysm_portals.sql b/sql/custom/world/2026_10_29_07_capital_cataclysm_portals.sql new file mode 100644 index 0000000000..dc3db43b8e --- /dev/null +++ b/sql/custom/world/2026_10_29_07_capital_cataclysm_portals.sql @@ -0,0 +1,54 @@ +-- Empty Vashjir spells and the live-failing Orgrimmar Tol Barad portal +-- cast as their visitor. Other casting modes/conditions/destinations stay. +UPDATE gameobject_template SET data6=1 +WHERE entry IN(207690,207691,206595) AND type=22 AND data2=0 AND data6=0; + +-- Two pre-existing server-only Vashj'ir spells had NO effects or destinations. +-- Supply a direct native teleport to the project's existing SmugglersScar +-- game_tele landing (existing named cave destination). This is an explicit fixed landing, +-- not a reconstruction of quest-dependent retail destinations. +INSERT IGNORE INTO spelleffect_dbc +(Id,Effect,EffectImplicitTargetA,EffectImplicitTargetB,SpellID,EffectIndex,Comment) +SELECT 160117,5,25,17,90244,0,'Orgrimmar Vashjir portal: fixed SmugglersScar landing' +WHERE NOT EXISTS(SELECT 1 FROM spelleffect_dbc WHERE SpellID=90244 AND EffectIndex=0); +INSERT IGNORE INTO spelleffect_dbc +(Id,Effect,EffectImplicitTargetA,EffectImplicitTargetB,SpellID,EffectIndex,Comment) +SELECT 160118,5,25,17,90245,0,'Stormwind Vashjir portal: fixed SmugglersScar landing' +WHERE NOT EXISTS(SELECT 1 FROM spelleffect_dbc WHERE SpellID=90245 AND EffectIndex=0); +INSERT IGNORE INTO spell_target_position +(ID,EffectIndex,MapID,PositionX,PositionY,PositionZ,Orientation,VerifiedBuild) +SELECT s.spell,0,t.map,t.position_x,t.position_y,t.position_z,t.orientation,0 +FROM (SELECT 90244 AS spell UNION ALL SELECT 90245) s +JOIN game_tele t ON t.name='SmugglersScar'; + +-- Source faction permission is independent of destination region permission. +-- In particular Vashjir stays forbidden by the unchanged area catalog. +INSERT INTO vcore_clientless_node_access(node_key,allowed_factions,notes) +SELECT CONCAT('portal_go_',g.guid,'_source'),IF(g.map=0,1,2),'Capital Cataclysm portal; destination area policy still applies' +FROM gameobject g +WHERE g.guid IN(200624,200625,200626,200627,220365,220366,211257,211258,211259,211260,211264,219028) +ON DUPLICATE KEY UPDATE allowed_factions=VALUES(allowed_factions),notes=VALUES(notes); + +-- Persist the same seed-only native bindings produced by portal object import. +-- Existing enabled flags and manually adjusted graph positions are preserved. +INSERT IGNORE INTO vcore_clientless_world_point +(point_key,category,map,position_x,position_y,position_z,orientation,enabled,comment) +SELECT CONCAT('portal_go_',g.guid,'_source'),'travel_portal',g.map,g.position_x,g.position_y,g.position_z,g.orientation,1,'Capital Cataclysm native portal source' +FROM gameobject g WHERE g.guid IN(200624,200625,200626,200627,220365,220366,211257,211258,211259,211260,211264,219028); +INSERT IGNORE INTO vcore_clientless_world_point +(point_key,category,map,position_x,position_y,position_z,orientation,enabled,comment) +SELECT CONCAT('portal_go_',g.guid,'_target'),'travel_portal',p.MapID,p.PositionX,p.PositionY,p.PositionZ,p.Orientation,1,'Capital Cataclysm native portal target' +FROM gameobject g JOIN gameobject_template t ON t.entry=g.id JOIN spell_target_position p ON p.ID=t.data0 AND p.EffectIndex=0 +WHERE g.guid IN(200624,200625,200626,200627,220365,220366,211257,211258,211259,211260,211264,219028); +INSERT IGNORE INTO vcore_clientless_travel_node(node_key,node_type,world_point_key,enabled,comment) +SELECT p.point_key,'portal',p.point_key,1,'Capital Cataclysm native portal' +FROM vcore_clientless_world_point p +WHERE p.point_key REGEXP '^portal_go_(200624|200625|200626|200627|220365|220366|211257|211258|211259|211260|211264|219028)_(source|target)$'; +INSERT IGNORE INTO vcore_clientless_travel_edge(from_node_key,to_node_key,edge_type,cost,bidirectional,enabled,comment) +SELECT CONCAT('portal_go_',g.guid,'_source'),CONCAT('portal_go_',g.guid,'_target'),'portal_use',1,0,1,'Capital Cataclysm native portal click' +FROM gameobject g WHERE g.guid IN(200624,200625,200626,200627,220365,220366,211257,211258,211259,211260,211264,219028); +INSERT IGNORE INTO vcore_clientless_portal_use(edge_id,gameobject_spawn_id,gameobject_entry,spell_id) +SELECT e.id,g.guid,g.id,t.data0 +FROM gameobject g JOIN gameobject_template t ON t.entry=g.id +JOIN vcore_clientless_travel_edge e ON e.from_node_key=CONCAT('portal_go_',g.guid,'_source') AND e.to_node_key=CONCAT('portal_go_',g.guid,'_target') AND e.edge_type='portal_use' +WHERE g.guid IN(200624,200625,200626,200627,220365,220366,211257,211258,211259,211260,211264,219028); diff --git a/src/server/game/World/ClientlessLuaEngine.cpp b/src/server/game/World/ClientlessLuaEngine.cpp index b138ea0d56..4e06fce66b 100644 --- a/src/server/game/World/ClientlessLuaEngine.cpp +++ b/src/server/game/World/ClientlessLuaEngine.cpp @@ -4,6 +4,7 @@ #include "ClientlessPlayerMgr.h" #include "ClientlessActionExecutorMgr.h" #include "ClientlessTravelMgr.h" +#include "ClientlessTransportMgr.h" #include "AdventureBehaviorMgr.h" #include "AdventureCombatMgr.h" #include "AdventureInteractionMgr.h" @@ -235,6 +236,9 @@ public: sClientlessLuaEngine->UpdateWorldPresence(player,sAdventureBehaviorMgr->InteractionIntent(player)); sAdventureGroupTravelMgr->Observe(player,diff); sAdventurePopulationMgr->Maintain(player,diff); + // Restart restores native ship attachment, but CPC ride execution is + // transient. Own its safe recorded exit before starting another action. + if(sClientlessTransportMgr->ResumePassenger(player))return; if(sAdventureGroupTravelMgr->Airborne(player)){sAdventureGroupTravelMgr->Follow(player);return;} if(sAdventureSupplyMgr->Update(player,diff,sAdventureCombatMgr->Busy(player)))return; if(!sAdventureCombatMgr->Update(player,diff) && !sAdventureInteractionMgr->Update(player,diff)) diff --git a/src/server/game/World/ClientlessTransportDeckMgr.cpp b/src/server/game/World/ClientlessTransportDeckMgr.cpp index 994da87a24..549ba0d8fc 100644 --- a/src/server/game/World/ClientlessTransportDeckMgr.cpp +++ b/src/server/game/World/ClientlessTransportDeckMgr.cpp @@ -10,6 +10,9 @@ #include "MoveSplineInit.h" #include "Chat.h" #include "Log.h" +#include "VehicleDefines.h" +#include "WorldPacket.h" +#include "Opcodes.h" #include #include #include @@ -22,7 +25,7 @@ std::unordered_map Models; struct Passenger { ObjectGuid Transport; - bool Tried=false,Moving=false,Reserved=false; + bool Tried=false,Moving=false,Reserved=false,Returning=false,ReturnFailed=false; Point Target{}; time_t Deadline=0; }; @@ -61,18 +64,46 @@ Model* Get(uint32 entry) TC_LOG_INFO("server.adventurebot.navigation","DECK_MESH entry=%u polygons=%u error='%s'",entry,model.Mesh.Polygons(),model.Error.c_str()); return &model; } -void Launch(Player* player,Passenger& state,std::vector const& path) +bool Launch(Player* player,Passenger& state,std::vector const& path) { Movement::PointsArray points; // Already in transport coordinates; never transform these a second time. for(auto const& p:path)points.emplace_back(p[0],p[1],p[2]); Movement::MoveSplineInit init(player);init.DisableTransportPathTransformations(); init.MovebyPath(points);init.SetWalk(true);init.SetVelocity(2.0f); - player->GetMotionMaster()->LaunchMoveSpline(std::move(init)); + if(init.Launch()<=0) + {state.Moving=false;return false;} state.Moving=true;state.Target=path.back(); state.Deadline=GameTime::GetGameTime()+time_t(std::ceil(Length(path)/2.0f))+5; TC_LOG_INFO("server.adventurebot.navigation","DECK_WALK guid=%u points=%u length=%.2f target=(%.3f %.3f %.3f)", player->GetGUID().GetCounter(),uint32(path.size()),Length(path),state.Target[0],state.Target[1],state.Target[2]); + return true; +} +bool ReturnToRecordedStand(Player* player,GameObject* object,Position const& anchor,uint32 exitBudget) +{ + TransportDockState dock; + auto transport=object->ToTransportBase(); + if(!transport || player->GetTransport()!=transport || !player->IsAlive() || player->IsInCombat() || + player->IsBeingTeleported() || player->GetMap()!=object->GetMap() || + !object->GetTransportDockState(dock) || !dock.Stopped || uint64(dock.RemainingPause)100 || std::fabs(y)>100 || std::fabs(z)>100)return false; + transport->CalculatePassengerPosition(x,y,z,&o); + if(!Trinity::IsValidMapCoord(x,y,z,o))return false; + player->StopMoving(); + // Preserve the real passenger attachment and its transport GUID. Change + // only its local position, then use native passenger relocation/broadcast. + player->m_movementInfo.transport.pos.Relocate(anchor); + transport->UpdatePassengerPosition(object->GetMap(),player,x,y,z,o,false); + player->m_movementInfo.guid=player->GetGUID(); + player->m_movementInfo.pos.Relocate(player->GetPosition()); + player->m_movementInfo.time=GameTime::GetGameTimeMS(); + WorldPacket update(SMSG_MOVE_UPDATE);player->WriteMovementInfo(update); + player->SendMessageToSet(&update,true); + TC_LOG_WARN("server.adventurebot.navigation","DECK_RETURN_FALLBACK guid=%u entry=%u local=(%.3f %.3f %.3f)", + player->GetGUID().GetCounter(),object->GetEntry(),anchor.GetPositionX(),anchor.GetPositionY(),anchor.GetPositionZ()); + return true; } } void ClientlessTransportDeckMgr::Clear(uint32 guid){Passengers.erase(guid);} @@ -87,28 +118,45 @@ bool ClientlessTransportDeckMgr::Update(Player* player,GameObject* object,Positi Point current=Local(player); if(!stopped || !player->IsAlive() || player->IsInCombat()) { - if(state.Moving){player->StopMoving();state.Moving=false;state.Reserved=false;} + if(state.Moving){player->StopMoving();state.ReturnFailed|=state.Returning;state.Moving=false;state.Reserved=false;} return false; } if(state.Moving) { // Every active walk retains a reserve before departure, even after a lag spike. if(dock.RemainingPause<1500 || GameTime::GetGameTime()>state.Deadline) - {player->StopMoving();state.Moving=false;state.Reserved=false;return false;} - if(player->IsSplineEnabled())return false; + { + player->StopMoving();state.Moving=false;state.Reserved=false; + state.ReturnFailed|=state.Returning; + if(!state.Returning)return false; + } + else if(player->IsSplineEnabled())return false; state.Moving=false; - if(Distance(current,state.Target)>0.8f){state.Reserved=false;return false;} + if(Distance(current,state.Target)>0.8f) + {state.Reserved=false;state.ReturnFailed|=state.Returning;if(!state.Returning)return false;} } Point anchor{exitAnchor.GetPositionX(),exitAnchor.GetPositionY(),exitAnchor.GetPositionZ()}; if(arriving) { state.Reserved=false; - if(Distance(current,anchor)<=0.65f)return dock.RemainingPause>=exitBudget; + if(Distance(current,anchor)<=0.65f){state.ReturnFailed=false;return dock.RemainingPause>=exitBudget;} + if(state.ReturnFailed) + { + if(!ReturnToRecordedStand(player,object,exitAnchor,exitBudget))return false; + state.Moving=state.Returning=state.ReturnFailed=false;return true; + } std::vector path; - if(!model->Mesh.Path(current,anchor,path))return false; // remain attached, never cut across the deck + if(!model->Mesh.Path(current,anchor,path) || path.size()<2) + { + state.ReturnFailed=true; + if(!ReturnToRecordedStand(player,object,exitAnchor,exitBudget))return false; + state.ReturnFailed=false;return true; + } uint64 budget=uint64(std::ceil(Length(path)/2.0f)*1000)+exitBudget+2000; - if(path.size()<2 || dock.RemainingPause=1.5f && score>bestScore){bestScore=score;best=std::move(path);} } if(!best.empty()) - {Launch(player,state,best);state.Reserved=true;} + {state.Returning=false;if(Launch(player,state,best))state.Reserved=true;} return false; } diff --git a/src/server/game/World/ClientlessTransportMgr.cpp b/src/server/game/World/ClientlessTransportMgr.cpp index 341b2e2274..f19841ac9e 100644 --- a/src/server/game/World/ClientlessTransportMgr.cpp +++ b/src/server/game/World/ClientlessTransportMgr.cpp @@ -415,6 +415,56 @@ bool ClientlessTransportMgr::Start(Player* player, std::string const& from, std: return true; } +bool ClientlessTransportMgr::ResumePassenger(Player* player) +{ + if(!player || !player->IsInWorld() || !sClientlessPlayerMgr->IsManaged(player->GetGUID().GetCounter()) || + !player->IsAlive() || player->IsInCombat() || player->IsBeingTeleported() || player->IsInFlight())return false; + uint32 guid=player->GetGUID().GetCounter();auto existing=_rides.find(guid); + if(existing!=_rides.end() && existing->second.Status.Phase!=ClientlessTransportPhase::Arrived && + existing->second.Status.Phase!=ClientlessTransportPhase::Failed) + return existing->second.Resumed; // never replace a normal executor-owned ride + if(!player->GetTransport() || !EnsureLoaded() || !sClientlessTravelGraphMgr->EnsureLoaded())return false; + ClientlessTransportStop const* source=nullptr; + for(auto const& entry:_stops) + { + auto const& stop=entry.second; + auto object=FindObject(player,stop); + auto node=sClientlessTravelGraphMgr->FindNode(stop.Node); + if(!object || object->ToTransportBase()!=player->GetTransport() || !node || + !sAdventureAreaMgr->AllowsNode(player,*node))continue; + if(!source || stop.NodeNode)source=&stop; + } + if(!source)return false; // unknown vehicles and forbidden stops retain their owner + Ride ride;ride.From=ride.To=*source;ride.Resumed=true; + ride.Status.Phase=ClientlessTransportPhase::Riding; + ride.Status.Detail="restored passenger; waiting for a permitted recorded dock"; + ClientlessTransportDeckMgr::Clear(guid); + _rides[guid]=std::move(ride); + TC_LOG_INFO("server.adventurebot.navigation","TRANSPORT_RESUME guid=%u spawn=%u entry=%u",guid,source->Spawn,source->Entry); + return true; +} + +bool ClientlessTransportMgr::SelectResumedDock(Player* player,GameObject* object,Ride& ride) +{ + if(!ride.Resumed || !object || player->GetTransport()!=object->ToTransportBase())return false; + ClientlessTransportStop const* chosen=nullptr; + for(auto const& entry:_stops) + { + auto const& stop=entry.second; + auto node=sClientlessTravelGraphMgr->FindNode(stop.Node); + if(!SameService(ride.From,stop) || !node || !sAdventureAreaMgr->AllowsNode(player,*node) || + !AtDock(player,object,stop,0))continue; + if(!chosen || stop.NodeNode)chosen=&stop; + } + if(!chosen)return false; + if(ride.From.Node!=chosen->Node || ride.To.Node!=chosen->Node) + { + TC_LOG_INFO("server.adventurebot.navigation","TRANSPORT_RESUME_DOCK guid=%u node='%s'",player->GetGUID().GetCounter(),chosen->Node.c_str()); + ride.From=ride.To=*chosen; + } + return true; +} + ClientlessTransportStatus const* ClientlessTransportMgr::GetStatus(uint32 guid) const { auto itr = _rides.find(guid); @@ -524,9 +574,10 @@ void ClientlessTransportMgr::UpdateRide(Player* player, Ride& ride, uint32 diff) } fail("transport passenger identity changed; attachment retained");return; } + bool permittedArrival=!ride.Resumed || SelectResumedDock(player,object,ride); ClientlessTransportStop const* arrival = nullptr; - if (AtDock(player, object, ride.To, 0)) arrival = &ride.To; - else if (ride.Abort && AtDock(player, object, ride.From, 0)) arrival = &ride.From; + if (permittedArrival && AtDock(player, object, ride.To, 0)) arrival = &ride.To; + else if (!ride.Resumed && ride.Abort && AtDock(player, object, ride.From, 0)) arrival = &ride.From; auto const& exit = arrival ? *arrival : ride.From; uint32 exitBudget=uint32(PathLength(exit)/std::max(1.0f,player->GetSpeed(MOVE_RUN))*1000)+uint32(exit.Points.size())*100+1500; bool ready=ClientlessTransportDeckMgr::Update(player,object,exit.Points.back().Pos,arrival!=nullptr,exitBudget); diff --git a/src/server/game/World/ClientlessTransportMgr.h b/src/server/game/World/ClientlessTransportMgr.h index 44831dffde..e4fa140e1f 100644 --- a/src/server/game/World/ClientlessTransportMgr.h +++ b/src/server/game/World/ClientlessTransportMgr.h @@ -54,6 +54,8 @@ public: bool ArrivalNode(Player* player, std::string const& from, std::string& to); ClientlessTransportStop const* GetStop(std::string const& node); bool Start(Player* player, std::string const& from, std::string const& to, std::string& error); + // Returns true while a restored passenger is owned by its resumed exit. + bool ResumePassenger(Player* player); ClientlessTransportStatus const* GetStatus(uint32 guid) const; void Cancel(uint32 guid); void Update(uint32 diff); @@ -81,6 +83,7 @@ private: uint32 PointTimeout = 0; bool Moving = false; bool Abort = false; + bool Resumed = false; uint32 RecoveryMs = 0, Recoveries = 0; bool RecoveryMoving = false; uint8 WaitStage=0; // 0 node, 1 walking out, 2 waiting, 3 returning @@ -92,6 +95,7 @@ private: bool Save(Player* player, std::string const& stand, std::string& error); void UpdateRide(Player* player, Ride& ride, uint32 diff); void RecoverPassenger(Player* player, Ride& ride, uint32 diff); + bool SelectResumedDock(Player* player, GameObject* object, Ride& ride); void SetPhase(Player* player, Ride& ride, ClientlessTransportPhase phase, char const* detail); GameObject* FindObject(Player* player, ClientlessTransportStop const& stop) const; bool AtDock(Player* player, GameObject* object, ClientlessTransportStop const& stop, uint32 requiredPause) const; diff --git a/tests/clientless/test_transport.py b/tests/clientless/test_transport.py index 0998ae7835..0d85e6c1e0 100644 --- a/tests/clientless/test_transport.py +++ b/tests/clientless/test_transport.py @@ -97,9 +97,10 @@ public: float GetSpeed(int)const{return 7;}void StopMoving(){}bool IsSplineEnabled()const{return false;} Motion* GetMotionMaster(){return &motion;}Session* GetSession(){return &session;} }; -struct Node{} node; -struct Graph{Node* FindNode(std::string const&){return &node;}}graph;auto sClientlessTravelGraphMgr=&graph; -struct AreaAccess{bool allowed=true;bool Applies(Player*){return true;}bool AllowsNode(Player*,Node const&){return allowed;}}areas;auto sAdventureAreaMgr=&areas; +struct Node{std::string Key;} node; +struct Graph{unsigned loads=0;bool EnsureLoaded(){++loads;return true;}Node* FindNode(std::string const&key){node.Key=key;return &node;}}graph;auto sClientlessTravelGraphMgr=&graph; +struct Core{bool managed=true;bool IsManaged(unsigned){return managed;}}clientless;auto sClientlessPlayerMgr=&clientless; +struct AreaAccess{std::string denied;bool allowed=true;bool Applies(Player*){return true;}bool AllowsNode(Player*,Node const&n){return allowed&&n.Key!=denied;}}areas;auto sAdventureAreaMgr=&areas; namespace ClientlessTransportDeckMgr {bool ready=true;int calls=0;void Clear(uint32){} bool Update(Player*,GameObject*,Position const&,bool,uint32){++calls;return ready;}} namespace ClientlessPositioning{bool available=false;bool Nearby(Player*p,Position const&,float,Position&spot,std::vector&path){if(!available)return false;spot=*p;spot.x+=2;path={*p,spot};return true;}} void TransportBase::CalculatePassengerPosition(float&x,float&y,float&z,float*o)const{CalculatePassengerPosition(x,y,z,o,go->x,go->y,go->z,go->o);} @@ -130,6 +131,7 @@ helpers = '\n'.join(function(source, marker) for marker in [ ' float Distance(', ' bool SameService(', ' bool LoadNode(', ' Position WorldPoint(', ' float PathLength(' ]) methods = '\n'.join(function(source, marker) for marker in [ + 'bool ClientlessTransportMgr::ResumePassenger(', 'bool ClientlessTransportMgr::SelectResumedDock(', 'bool ClientlessTransportMgr::HasConnection(', 'bool ClientlessTransportMgr::AtDock(', 'bool ClientlessTransportMgr::Sample(', 'bool ClientlessTransportMgr::Start(', 'void ClientlessTransportMgr::SetPhase(', 'void ClientlessTransportMgr::Cancel(', @@ -243,6 +245,41 @@ int main(){ ClientlessPositioning::available=false; Player denied;denied.object=&dock;areas.allowed=false; assert(!mgr.Start(&denied,"A","B",error));areas.allowed=true; + // Restored attachment without transient CPC state is adopted, never reboarded. + ClientlessTransportMgr resumed;resumed._stops["A"]=stop("A",5,0);resumed._stops["B"]=stop("B",100,104); + Player orphan;GameObject vessel;vessel.x=50;vessel.state.Stopped=false;orphan.object=&vessel;orphan.x=51; + vessel.base.AddPassenger(&orphan);orphan.m_movementInfo.transport.pos.x=1; + assert(resumed.ResumePassenger(&orphan));auto& adopted=resumed._rides.at(26); + assert(adopted.Resumed&&adopted.Status.Phase==Phase::Riding&&orphan.teleports==0&&graph.loads>0); + run(resumed,orphan,adopted);assert(orphan.transport==&vessel.base&&adopted.Status.Phase==Phase::Riding); + // B is the first actual permitted stop, even though seed A was selected. + vessel.x=100;vessel.state.Stopped=true;orphan.x=101; + resumed.UpdateRide(&orphan,adopted,100);assert(adopted.To.Node=="B"); + assert(resumed.ResumePassenger(&orphan));run(resumed,orphan,adopted); + assert(adopted.Status.Phase==Phase::Arrived&&!orphan.transport&&orphan.x==104); + assert(!resumed.ResumePassenger(&orphan)); + // After detachment the remaining land transition still owns the bot until done. + adopted.Status.Phase=Phase::Leaving;assert(resumed.ResumePassenger(&orphan)); + adopted.Status.Phase=Phase::Arrived;assert(!resumed.ResumePassenger(&orphan)); + // Existing normal rides are never replaced by passenger adoption. + vessel.x=5;orphan.x=0;assert(resumed.Start(&orphan,"A","B",error));run(resumed,orphan,resumed._rides.at(26)); + assert(!resumed.ResumePassenger(&orphan)&&!resumed._rides.at(26).Resumed); + // Unknown vehicles, unmanaged players and entirely forbidden services are untouched. + resumed._rides.clear();clientless.managed=false;assert(!resumed.ResumePassenger(&orphan));clientless.managed=true; + vessel.spawn=99;assert(!resumed.ResumePassenger(&orphan));vessel.spawn=1; + areas.allowed=false;assert(!resumed.ResumePassenger(&orphan));areas.allowed=true; + // An adopted passenger must not leave at a subsequently forbidden dock. + vessel.x=50;orphan.x=51;assert(resumed.ResumePassenger(&orphan));areas.denied="B"; + vessel.x=100;orphan.x=101;run(resumed,orphan,resumed._rides.at(26));assert(orphan.transport); + areas.denied.clear();run(resumed,orphan,resumed._rides.at(26));assert(!orphan.transport&&orphan.x==104); + // Native cross-map ship restoration keeps the same service after worldport ACK. + resumed._rides.clear();resumed._stops["A"].Map=1; + GameObject copy1;copy1.map=1;copy1.x=50;copy1.state.Stopped=false; + orphan.map=1;orphan.object=©1;copy1.base.AddPassenger(&orphan);orphan.x=51; + assert(resumed.ResumePassenger(&orphan));run(resumed,orphan,resumed._rides.at(26));assert(orphan.transport); + copy1.base.RemovePassenger(&orphan);orphan.map=0;orphan.object=&vessel;vessel.x=100; + vessel.base.AddPassenger(&orphan);orphan.x=101;orphan.m_movementInfo.transport.pos.x=1; + run(resumed,orphan,resumed._rides.at(26));assert(!orphan.transport&&orphan.map==0&&orphan.x==104); std::cout<<"Transport dock, frame, recorder, cross-map copy and ride regressions passed\n"; } ''' diff --git a/tests/clientless/test_transport_deck_control.py b/tests/clientless/test_transport_deck_control.py index 1dc1060b80..f6e17a3636 100644 --- a/tests/clientless/test_transport_deck_control.py +++ b/tests/clientless/test_transport_deck_control.py @@ -25,7 +25,7 @@ struct Player{Point current{0,0,0};unsigned id=1;bool spline=false,alive=true,co Guid GetGUID(){return {id};}bool IsAlive(){return alive;}bool IsInCombat(){return combat;}bool IsSplineEnabled(){return spline;} void StopMoving(){++stops;spline=false;}void* GetTransport(){return transport;}void GetPlayerListInGrid(std::list&,int){} void GetCreatureListWithEntryInGrid(std::list&,int,int){}}; struct Creature:Player{}; -struct Passenger{unsigned Transport=0;bool Tried=false,Moving=false,Reserved=false;Point Target{};time_t Deadline=0;}; +struct Passenger{unsigned Transport=0;bool Tried=false,Moving=false,Reserved=false,Returning=false,ReturnFailed=false;Point Target{};time_t Deadline=0;}; std::unordered_map Passengers; float Distance(Point const&a,Point const&b){return std::sqrt(std::pow(a[0]-b[0],2)+std::pow(a[1]-b[1],2)+std::pow(a[2]-b[2],2));} Point Local(Player*p){return p->current;}float Length(std::vectorconst&path){float n=0;for(unsigned i=1;iconst&path){++launches;p->spline=true;s.Moving=true;s.Target=path.back();s.Deadline=GameTime::now+20;} +int launches=0;bool launchOK=true; +bool Launch(Player*p,Passenger&s,std::vectorconst&path){++launches;if(!launchOK)return false;p->spline=true;s.Moving=true;s.Target=path.back();s.Deadline=GameTime::now+20;return true;} +unsigned fallback=0; +bool ReturnToRecordedStand(Player*p,GameObject*go,Position const&a,unsigned budget){ + if(!go->dock.Stopped||go->dock.RemainingPausealive||p->combat||p->transport!=go)return false; + ++fallback;p->current={a.x,a.y,a.z};return true;} namespace ClientlessTransportDeckMgr{bool Update(Player*,GameObject*,Position const&,bool,uint32);} '''+s[a:b]+r''' int main(){Player p;Position anchor; @@ -50,12 +54,20 @@ int main(){Player p;Position anchor; // Arriving at the next dock starts a real return, provided it fits the halt. p.current={4,0,0};object.dock.Stopped=true;object.dock.RemainingPause=6000; assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));assert(launches==1); - object.dock.RemainingPause=60000;assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));assert(launches==2); + Passengers[1].ReturnFailed=false;object.dock.RemainingPause=60000;assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));assert(launches==2); assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000)); // still moving p.spline=false;p.current={0,0,0};assert(ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000)); - // Interrupted arrival never claims the exit anchor was reached. - p.current={4,0,0};model.Mesh.blocked=true;assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));assert(launches==2); - model.Mesh.blocked=false;p.combat=true;assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));p.combat=false; + // A failed arrival path may transfer to the exact recorded local anchor, preserving attachment. + p.current={4,0,0};model.Mesh.blocked=true;assert(ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));assert(launches==2&&fallback==1&&p.transport==&object); + // Repeated interrupted return is bounded and uses the fallback at a stopped dock. + model.Mesh.blocked=false;p.current={4,0,0};Passengers[1].Moving=Passengers[1].Returning=true; + Passengers[1].Target={0,0,0};Passengers[1].Deadline=200;p.spline=false; + assert(ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000)&&fallback==2); + object.dock.Stopped=false;p.current={4,0,0};Passengers[1].ReturnFailed=true; + assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000)&&fallback==2); + object.dock.Stopped=true;object.dock.RemainingPause=5500; + assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000)&&fallback==2); + object.dock.RemainingPause=60000;p.combat=true;assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));p.combat=false; Passengers.clear();p.current={0,0,0};p.spline=false;launches=0; ClientlessTransportDeckMgr::Update(&p,&object,anchor,false,5000);Point first=Passengers[1].Target; Player other;other.id=2;ClientlessTransportDeckMgr::Update(&other,&object,anchor,false,5000); @@ -63,6 +75,10 @@ int main(){Player p;Position anchor; // Near departure, a running walk is stopped on the deck. object.dock.RemainingPause=1000;assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,false,5000));assert(!p.spline); object.entry=999;assert(ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000)); + Passengers.clear();object.entry=176310;object.dock.RemainingPause=60000; + p.current={4,0,0};p.spline=false;launchOK=false;auto before=fallback; + assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000)&&Passengers[1].ReturnFailed); + assert(ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000)&&fallback==before+1); std::cout<<"Production deck controller: halt budgets, departure interruption, return-before-exit, blocked paths and reservations passed\n"; } ''' diff --git a/tests/clientless/test_transport_deck_return.py b/tests/clientless/test_transport_deck_return.py new file mode 100644 index 0000000000..b16a7924b9 --- /dev/null +++ b/tests/clientless/test_transport_deck_return.py @@ -0,0 +1,54 @@ +"""Run the native recorded-stand fallback with deterministic passenger relocation.""" +from pathlib import Path +import subprocess,tempfile +root=Path(__file__).resolve().parents[2] +s=(root/'src/server/game/World/ClientlessTransportDeckMgr.cpp').read_text();a=s.index('bool ReturnToRecordedStand(');b=s.index('\n}\nvoid ClientlessTransportDeckMgr::Clear',a) +code=r''' +#include +#include +using uint32=unsigned;using uint64=unsigned long long; +#define TC_LOG_WARN(...) ((void)0) +constexpr int SMSG_MOVE_UPDATE=1; +struct Position{float x=0,y=0,z=0,o=0;float GetPositionX()const{return x;}float GetPositionY()const{return y;}float GetPositionZ()const{return z;}float GetOrientation()const{return o;}void Relocate(Position const&p){*this=p;}}; +struct Guid{unsigned GetCounter(){return 120;}}; +struct WorldPacket{explicit WorldPacket(int){}}; +namespace GameTime{unsigned GetGameTimeMS(){return 123;}} +namespace Trinity{bool IsValidMapCoord(float x,float y,float z,float o){return std::isfinite(x)&&std::isfinite(y)&&std::isfinite(z)&&std::isfinite(o)&&std::fabs(x)<17000;}} +struct Player;struct Transport{ + unsigned relocations=0;void CalculatePassengerPosition(float&x,float&y,float&z,float*){x+=100;y+=200;z+=300;} + void UpdatePassengerPosition(int*,Player*,float,float,float,float,bool); +}transport; +int map; +struct Player:Position{bool alive=true,combat=false,teleport=false;int*world=↦Transport*attached=&transport;unsigned stops=0,updates=0; + struct{Guid guid;Position pos;unsigned time=0;struct{Position pos;unsigned guid=42;}transport;}m_movementInfo; + bool IsAlive(){return alive;}bool IsInCombat(){return combat;}bool IsBeingTeleported(){return teleport;} + Transport*GetTransport(){return attached;}int*GetMap(){return world;}Guid GetGUID(){return {};} + void StopMoving(){++stops;}Position GetPosition(){return *this;} + void WriteMovementInfo(WorldPacket&){assert(m_movementInfo.transport.guid==42);} + void SendMessageToSet(WorldPacket*,bool){++updates;} +}; +void Transport::UpdatePassengerPosition(int*,Player*p,float x,float y,float z,float o,bool){++relocations;p->x=x;p->y=y;p->z=z;p->o=o;} +struct TransportDockState{bool Stopped=true;unsigned RemainingPause=10000;}; +struct GameObject{TransportDockState dock;bool valid=true;Transport*ToTransportBase(){return valid?&transport:nullptr;}int*GetMap(){return ↦}bool GetTransportDockState(TransportDockState&d){d=dock;return true;}unsigned GetEntry(){return 176081;}}object; +'''+s[a:b]+r''' +int main(){ + Player p;Position anchor{2,-3,-10,0}; + assert(ReturnToRecordedStand(&p,&object,anchor,5000)); + assert(p.attached==&transport&&transport.relocations==1&&p.updates==1&&p.stops==1); + assert(p.m_movementInfo.transport.pos.x==2&&p.m_movementInfo.transport.pos.z==-10); + assert(p.x==102&&p.y==197&&p.z==290&&p.m_movementInfo.pos.x==102); + object.dock.Stopped=false;assert(!ReturnToRecordedStand(&p,&object,anchor,5000));object.dock.Stopped=true; + object.dock.RemainingPause=5999;assert(!ReturnToRecordedStand(&p,&object,anchor,5000));object.dock.RemainingPause=6000; + p.combat=true;assert(!ReturnToRecordedStand(&p,&object,anchor,5000));p.combat=false; + p.alive=false;assert(!ReturnToRecordedStand(&p,&object,anchor,5000));p.alive=true; + p.teleport=true;assert(!ReturnToRecordedStand(&p,&object,anchor,5000));p.teleport=false; + p.attached=nullptr;assert(!ReturnToRecordedStand(&p,&object,anchor,5000));p.attached=&transport; + anchor.x=101;assert(!ReturnToRecordedStand(&p,&object,anchor,5000));anchor.x=NAN;assert(!ReturnToRecordedStand(&p,&object,anchor,5000)); + assert(transport.relocations==1&&p.updates==1); // rejected operations never move or detach +} +''' +with tempfile.TemporaryDirectory()as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['c++','-std=c++17',str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) +print('Native deck fallback: stopped-only, sufficient exit reserve, exact local anchor, preserved attachment, world transform and movement broadcast passed')