WIP: checkpoint AdventureBot intents and unfinished profession implementation

This commit is contained in:
Nico committed 2026-10-27 17:02:01 +01:00
1 parent 119d63ed1c
commit 722b513ee0
83 files changed
+11946 -199

No files matched your search

+259
View File
@@ -0,0 +1,259 @@
# Gesicherter Arbeitsstand – hier fortsetzen
Stand: 27.10.2026. Der Nutzer hat die Implementierung ausdrücklich für einen
Codex-Sitzungswechsel unterbrechen lassen. Diese Notiz ist die maßgebliche
Übergabe. Nicht bei null beginnen und keine vorhandenen Änderungen verwerfen.
## Sofort beim Wiederaufnehmen
1. `AGENTS.md` und diese Datei lesen; Branch und Working Tree prüfen.
2. Ausschließlich `feature/clientless-player-core`, niemals `master`, kein Push.
3. Der unten beschriebene Berufs-Ausbau ist **unfertig und NICHT installiert**.
4. Der laufende Build wurde zur Sicherung geordnet mit SIGINT unterbrochen.
Vor erneutem Bauen trotzdem nach verbleibendem CMake/Ninja-Prozess sehen.
Keine zwei Builds gleichzeitig. Immer **`-j10`**, niemals `-j4`.
5. Neueste Compiler-/Teststände prüfen und die untenstehenden offenen Arbeiten
fortsetzen. Kein Installieren eines nur teilweise gebauten Standes.
## Aktueller Auftrag und verbindliche Nutzerentscheidungen
Wir arbeiten am Intent **Beruf (`profession`)**, nach Geselligkeit, Ruhe,
Freizeit, Wirtschaften und dem Abenteuer-Erkundungsausbau. Danach folgen
Instanzbesuche. Kampf kommt GANZ AM ENDE, gemeinsam mit reaktiver Selbstwehr
(z. B. bei Angriff während einer Reise).
- Alle Berufsaktionen bleiben **dauerhaft rein performativ**. Es wird NICHT
tatsächlich abgebaut, gesammelt, gekürschnert, produziert, gefangen oder
geheilt. Keine Beute, Quellenerschöpfung, Inventar-/Materialänderung,
Fertigkeitserhöhung oder tatsächliches Erlernen. Nicht „zunächst“ schreiben.
- Hauptberufe dürfen NUR ausgeführt werden, wenn sie im Lua-Charakterprofil
ausdrücklich eingetragen sind. Nebenberufe brauchen diese Freigabe nicht;
Vorlieben werden später weiter über gewichteten Zufall ausgestaltet.
- Der Nutzer hat die Rückfrage ausdrücklich beantwortet: **Arthoran hat
Bergbau und Schmiedekunst**. In seinem Profil stehen deshalb dauerhaft
`professions = { primary = { "mining", "blacksmithing" } }`.
KEIN Kräutersammeln bei ihm erlauben, auch nicht vor vorhandenen Pflanzen.
Das generische Profil enthält eine leere Hauptberufsliste.
- Ort und Gebiet sind gleichberechtigte Ziele. Ein Bot soll z. B. das Gebiet
Elwynn anreisen und dort lokal selbst nach passenden Ressourcen suchen.
- **Hierarchie:** Tagesplan wählt Intent, dann passender importierter Ort,
dann lokal mögliche Mikroaktion. Der Ortsbestand darf die Intentauswahl
nicht vetoieren. Die Location-/Intent-Katalogzuordnung legt die Eignung fest.
- Eine für `profession` geeignete Location (auch ein ganzes Gebiet) schaltet
NICHT alle Berufsaktionen frei. Schmieden braucht einen echten nahen Amboss,
Schmelzen eine Esse, Kräutersammeln eine tatsächliche Pflanze UND Profilrecht.
- Gebietsanreise ist bereits vorhanden: `AdventureBehaviorMgr::AreaNode`
sucht einen ERREICHBAREN Graphknoten im Zielgebiet, `Begin` reist über
`travel_to_node`; die Ankunft wird gegen das Gebiet geprüft.
- Abenteuer soll regulär draußen in passenden Weltgebieten stattfinden.
Sturmwind ist nur Testgebiet; keine zusätzlichen Stadt-Sonderregeln bauen,
sondern passende gespeicherte Locations/Intent-Gewichte verwenden.
- Ausritte, Freizeit-Rundflüge und Schlafdarstellung sind auf Nutzerwunsch
weiterhin für spätere visuelle Tests zurückgestellt. Jetzt nicht testen.
## Umgebung und Betriebszustand beim Sichern
- Source: `/home/wow_admin/wow_cata_server/source/TrinityCore`.
- Build: `/home/wow_admin/wow_cata_server/build/debug`, Ninja, Debug.
- Install: `/home/wow_admin/wow_cata_server/server/debug`.
- Testserver: tmux `vcore-worldserver`, cwd `server/debug/bin`, `./worldserver`.
- Vor dem Sichern lief **PID 19058**, letzter installierter Abenteuer-Ausbau.
Diesen nicht blind wiederverwenden: Prozesszustand beim Fortsetzen prüfen.
- Arthoran: Charakter GUID **26**, Account 6, aktuell Abenteuer am importierten
Sturmwind-Ort `poi_tele_954`. Allitester GUID 21 kann als Zuschauer online sein.
- Der BERUFS-Ausbau wurde noch nicht installiert; SQL-Migration `_05` wurde
noch NICHT eingespielt. Der laufende Server ist daher der vorherige Stand.
- Arthorans normale Startvorgabe im gespeicherten Lua-Profil ist wieder
`social` / `goldshire_tavern`. Es gibt aktuell KEIN temporäres Login-Profil.
- Der letzte Build wurde mit j10 unterbrochen, bei 749/751 Schritten (libgame.a gelinkt; letzte Linkschritte noch offen). Die nächste
inkrementelle Ausführung kann normal weiterbauen; Objektdateien erhalten.
- Erster Build hatte nur den Fehler `Creature::GetDeathState` statt
`getDeathState`. Das wurde bereits korrigiert. Der daraufhin neu gestartete
Build hatte bis zur Unterbrechung keine weiteren gemeldeten Compilerfehler.
- CMake wurde mit den bestehenden Einstellungen neu ausgeführt, um die neue
`AdventureProfessionTargets.cpp` einzusammeln (CollectSourceFiles ohne
CONFIGURE_DEPENDS). Das löste einen größeren Build aus. Nicht grundlos
erneut konfigurieren, nur mit j10 weiterbauen.
## Bereits geschriebene, noch unfertige Berufs-Implementierung
### Profil und Regeln
- `AdventureProfessionPolicy.h`: reine Regeln/Task-Spezifikationen, elf
Hauptberufe, kanonische englische Bezeichner, Nebenberufe ohne Primärfreigabe.
Hauptberufsrecht stammt aus dem Profil, NICHT aus `Player::HasSkill`.
- `ClientlessAdventureProfile::PrimaryProfessions` und Lua-Parser für
`professions.primary` als Array. Unbekannte Namen warnen und gewähren nichts.
- `arthoran.lua`: mining + blacksmithing. `generic.lua`: leeres Array.
- `Trainer.h`: nur neuer generischer, read-only Zugriff `GetSpells() const`.
Kein Lernen/Trainer-Handler wird aufgerufen.
### Echte Ressourcen und Arbeitsplätze
- Neu: `AdventureProfessionTargets.{h,cpp}`.
- Erz/Pflanzen: echte aktive CHΕST-GOs, native Lock-/Skill-Anforderungen aus
`sLockStore` / `SkillByLockType`, nicht bloß Namen oder Locationkategorie.
Nur sichtbare, phasenrichtige, bereite und erreichbare Ziele.
- Kürschnerei: vorhandener toter BEAST-Kadaver im Zustand CORPSE mit nativem
SKINNABLE-Flag. Kein Erzeugen/Töten eines Tiers. Kampf/Jagd später.
- Arbeitsplätze: neuer editierbarer Katalog `vcore_adventure_workstation`
mit GO-Templateentry, semantischer Rolle und enabled. Keine Bot-/Ort-ID-Liste.
Native Fokuskennungen geprüft: 1 Amboss, 3 Esse, 4 Kochfeuer, 663 Alchemielabor,
1589 Lexikon. 623/543 sind besondere Amboss-/Essenfokusse.
- Native unsichtbare SPELL_FOCUS-Marker repräsentieren reale Kartenarbeitsplätze
und dürfen dafür genutzt werden. Keine Aktivierung/Objektbenutzung.
- Rollen ohne reale erkennbare Arbeitsplätze bleiben nicht verfügbar; keine
beliebige Werkstatt-Location als Amboss behandeln.
- Trainer: native Tradeskill-Trainer, deren native Spell-/SkillLine-Daten zu
einem erlaubten Haupt- oder einem Nebenberuf passen. Reines Gespräch.
### Szenen und bestehende Legacy-Aktionen
- Wiederverwendete `AdventureSocialActions` mit `Cue::Work`, professionellen
Zielprüfungen, nativen Emote-Zuständen, Abschlussbetrachtung und kurzem
Wegtreten. Keine Harvest-/Craft-/Loot-/Teach-/Heal-Aufrufe.
- Profil und Ziel werden auch während der Szene erneut geprüft.
- Alte `mining_show` / `smithing_show` (Kind `work`, Requirement ore/forge)
werden in `ResolvedAction` auf dieselben neuen gesperrten Szenen umgelegt.
Es darf KEIN Legacy-Bypass der Profil- oder Arbeitsplatzprüfung verbleiben.
- Nach vollständiger Sammeldarstellung wird `work:<GUID>` in der bestehenden
Zielhistorie gespeichert. Kürzlich bearbeitete Ressourcen werden gemieden,
damit die unverändert stehen bleibende Quelle nicht endlos wiederholt wird.
Nur Erzbetrachtung (`ore_inspect`) setzt diese Bearbeitungssperre nicht.
### Gebietssuche / gemeinsame Erkundungssteuerung
- Neue Suche `profession_search`, Requirement `work_search`, Kind `outing`.
- Wiederverwendete MMAP-Routensteuerung: progressive Hinwege, an erreichten
Kontrollpunkten erneut mögliche Berufsarbeit prüfen und Suche dann beenden.
- Bei Gebietsaufträgen wird der lokale Anker nach bestätigtem Suchfortschritt
verschoben. Weiteres Durchsuchen bleibt über jeden MMAP-Punkt im Zielgebiet.
- Gebiets-Abenteuerwege sind jetzt ebenfalls progressiv; normale Freizeitwege
bleiben bestätigte Hin-/Rückwege. Keine Stadt-Sonderregel.
- Zusätzliche native Uferprüfung für performatives Berufsangeln im Gebiet,
ohne zwingenden speziellen Fishing-Location-Key. Ausgerüstete Angel bleibt
nötig. Kein wirklicher Angelzauber/Fang. Noch nicht live getestet.
### Neue Migration
`sql/custom/world/2026_10_27_05_adventure_profession.sql` (noch NICHT angewendet):
Arbeitsplatzrollen, neue Arbeits-/Such-/Nebenberufs-/Gesprächsaktionen,
Berufsgewichtung für Gebiet/Wildnis/Siedlung, neue Texte. Native Fokusdaten
haben Vorrang; Namen ergänzen nur erkennbare geeignete Props. Geänderte
Gewichte/disabled-Rollen sollen bei Wiederholung erhalten bleiben.
## Offene Arbeiten – konkret hier weiter
1. Aktuellen C++-Diff kritisch prüfen; dies ist ein WIP, keine fertige Abnahme.
Insbesondere Berufsszenen, Legacy-Auflösung, progressive Ankeraktualisierung,
Ressourcen-Wiederholungsdämpfung und Uferprüfung gegen Nutzerregeln prüfen.
2. `cmake --build /home/wow_admin/wow_cata_server/build/debug -j10` fortsetzen.
Vorher prüfen, dass kein alter Build mehr läuft. Weitere Fehler beheben.
3. Vollständige Tests erneut laufen lassen. Vor dem Berufs-Ausbau waren
**32 Tests grün**. Seitdem gab es 7 Mock-/Harness-Anpassungsfehler, deren
Fixes bereits TEILWEISE geschrieben sind. Alte Fehlerdateien sind historisch!
- Economy/Exploration/Social-fire: neues Policy-Include und Profilfelder,
Targets-Stub für zusätzlich mitkompilierte Zweige.
- Social: Map-/Position-/Origin-Stubs und native Work-Emotes ergänzt;
zuletzt nach INVALID_HEIGHT-Fix wieder grün.
- Exploration und Leisure: nach Anpassungen zuletzt wieder grün.
- Intent-state: neue Scene-Felder Progressive/Route/Step ergänzt;
letzter Fehler fehlende Player::GetExactDist(Position const&). Der
Überladungs-Fix ist bereits geschrieben, aber noch nicht erneut geprüft.
- Rest-fire: PrimaryProfessions in Mock-Profil/-Kontext ergänzt;
aktuellen Zustand durch erneuten Lauf prüfen.
4. Neue aussagekräftige Berufs-Tests fehlen noch! Geplant:
- tatsächlichen neuen Lua-Parser mit realem Lua 5.4 ausführen, inklusive
Arthoran mining/blacksmithing, leeres/ungültiges Profil sperrt Hauptberufe;
- Mining+Smithing darf vorhandene Kräuter NICHT bearbeiten, umgekehrt darf
Herbalism+Alchemy keine Erz-/Schmiedearbeit; sekundäre Aktionen ohne Liste;
- produktive native Zielklassifikation: Lock-Skills statt Namen, Amboss
versus Esse versus bloße Workshop-/Gebietszuordnung, falsche Phase,
despawnte/gelootete Quelle, Kadaverprüfung und passende Trainer;
- native Arbeitsgeste erst NACH tatsächlicher Ankunft, Profilentzug während
laufender Szene, keine Objektbenutzung/Casts/Inventaränderung, Abschluss;
- Migration auf Schemafixtures ausführen, Idempotenz und bearbeitete
Gewichte/Arbeitsplatzfreigaben erhalten;
- Gebiets-Anreise über erreichbaren Graphnode und lokale progressive Suche
innerhalb des Gebiets, Stopp bei gefundenem zulässigem Arbeitsplatz.
5. Erst dann neue Migration auf TEST-DB anwenden. Noch kein profession_apply-
Modus im gesicherten DB-Helfer! Ergänzen oder engen eigenen Helfer schreiben.
6. Erfolgreichen Build nach sauberem Serverstopp installieren, neu starten und
frische Ready-Meldung + Prozess + Port 8085 prüfen. AGENTS-Recovery beachten.
7. Live prüfen: tatsächlicher Amboss in Sturmwind (z. B. importierter
`poi_guard_541`, Schmiedebereich), dann Gebietsziel `poi_tele_338` = Elwynn,
Gebiet 12, echtes travel_to_node und lokale Ressourcen-/Arbeitsplatzsuche.
Kräuter muss Arthoran verweigern. Inventar/Geld und Ressourcen unverändert
nachweisen. Keine gefälschten Ressourcen, Spawns oder Teleports.
8. Berufs-Dokumentation schreiben, Katalog-/Standdokumente aktualisieren und
offen gebliebene Live-/visuelle Prüfungen ehrlich benennen.
## Wichtige vorherige Ergebnisse – nicht neu implementieren
- Reisegraph: optionale virtuelle Exits regelmäßig (6 s) während Weiterbewegung
prüfen, kein Abbruch nach fünf Versuchen, Kante nicht mitten darin umkehren.
Free-flight-Fallback getrennt von künstlichen Normalreise-Auswahlregeln.
- Ruhe/Feuer: gemeinsames natives Kochfeuer 818, neue Feuer nie in Gebäuden/
Städten. Schlafdarstellung weiterhin vertagt.
- Freizeit: Katze (Spell 10673, Creature 7385), Leine (Item 37460, Spell 48722),
echte native Begleiter-/Itemverwendung. Kanal beim Gehen erhalten. Owned-only
Cleanup, keine Veränderung fremder Begleiter. Nutzer hat sichtbare Katze,
Leine, zusammenhängende Routen und flüssige Bewegung ausdrücklich abgenommen.
- Native Follow-Fix: aktive Player-Splinegeschwindigkeit statt Run-Flag wählen;
SetWalk setzt die Gait-Auswahl, nicht MOVEMENTFLAG_WALKING. Das beseitigte
die ständigen Zwei-Meter-Pausen. Nicht wieder entfernen.
- Native Spline-Kurzsegment-Fix: 0,11 Yards Mindestabstand beim Splitten.
- Wirtschaften rein performativ; Händler/Bank/AH und Vergleich mit zwei GUIDs
technisch live geprüft, Geld/Inventar danach bytegleich.
- Importfehler behoben: SMALLINT maps über GetUInt16 statt GetUInt32, das in
Debug Null ergab. Gezielte, idempotente `clientless adventure repairimport`
reparierte 2.159 unveränderte abgeleitete Orte, ergänzte 217. Jetzt **2.864**
aktive semantische Locations / 3.857 Worldpoints. 16 manuelle Orte unverändert.
Vier Horde-Hauptstädte korrekt auf Karte/Fraktion zugeordnet. Nicht die
originalen world-DB-Quellen ändern. Snapshots liegen bei dieser Übergabe.
- Location-Zuordnung: kein versteckter Wirtschaften-Fallback mehr im Code;
`_04` materialisiert ausdrückliche Kategorie-/Intent-Gewichte, inklusive
begrenzter Freizeitgewichtung für Tavernen. Tagesplan-Intent unabhängig davon.
- Abenteuer `_04`: native Objektuntersuchung ohne Use, Scout/Equipment,
zusammenhängende Bodenwege. Zehn neue Aktionen, 32 Tests bestanden, technisch
live in Sturmwind: Pestle's Apothecary 197117, Weller's Arsenal 197121,
80-Yards-Erkundungsweg mit zehn bestätigten Hin-/Rückwegpunkten. Visuelle
Abnahme und gefährliche offene Welt weiterhin separat.
## Werkzeuge, Logs und reproduzierbarer Betrieb
Dauerhafte Kopien für diese Übergabe:
`doc/clientless/checkpoints/2026-10-27-profession/`.
Darin Build-/Configurelogs, historische fehlgeschlagene Testausgaben,
Ortskatalog-Snapshots und die bisherigen schmalen DB-Helfer. Diese Helfer lesen
Verbindungsdaten aus der lokalen Testserverkonfiguration; keine Passwörter
im Chat/Logs ausgeben. Sie enthalten keine fest codierten Zugangsdaten.
Die Helfer bei Bedarf wieder nach `/tmp` kopieren; Pfade gelten für diesen Host.
Wichtige Live-Logs (normal und ohne vorsorgliche Elevation lesen):
`/home/wow_admin/wow_cata_server/server/debug/bin/Server.log`,
`adventurebot-navigation.log`, `DBErrors.log`. Server.log wird beim Start neu
begonnen, Navigations-/DBErrorslogs können ältere Fehler enthalten. Bekannte
alte Script-/Spell-Warnungen nicht mit neuem Startup-Versagen verwechseln.
Buildbefehle: `cmake --build .../build/debug -j10`, bestehende Installation
mit `cmake --install .../build/debug`. Tool-Sandbox für Build/Install/tmux/DB
beachten, aber normale Logs nicht vorsorglich eskalieren. Keine laufende
Executable überschreiben. Kein Push.
Für gezielte Tests nach Restart wurde bisher nur beim Login die Startvorgabe
in arthoran.lua temporär geändert, vorher byteweise gesichert, nach
`[AdventureBot][Profile] loaded guid=26` exakt zurückgesetzt. Die jetzt dauerhaft
vereinbarten Hauptberufe dabei unbedingt erhalten. Keine temporären Profile
liegenlassen. Alternativ normalen Login abwarten und per Console Intent setzen;
auf unerwünschte automatische Goldshire-Anreise achten.
Console-Beispiele ohne führenden Punkt:
`clientless login 26`, `clientless adventure status 26`,
`clientless adventure intent 26 profession poi_guard_541 3600`,
`clientless adventure intent 26 profession poi_tele_338 3600`,
`clientless adventure micro 26 <action_key>`, `saveall`.
Der Nutzer möchte vor allem konkrete, überprüfte Ergebnisse und wenig
Bestätigungsfragen. Die Hauptberufs-Rückfrage ist bereits BEANTWORTET; nicht
noch einmal stellen. Testservermanagement und lokale Commits sind autorisiert.
+109
View File
@@ -0,0 +1,109 @@
# Abenteuer: Erkundung und Untersuchung
Technischer Intent: `adventure`. Diese Ausbaustufe ergänzt den bisherigen
Aufenthalt durch zusammenhängende Erkundungswege und beobachtende Szenen.
Echte Monsterjagd, Flucht und Todesbewältigung bleiben spätere Ausbaustufen.
Vorhaben, Ortsauswahl und tatsächliche Mikroaktion bleiben getrennt: Die
Location-/Intent-Zuordnung entscheidet, ob ein Ort zum Abenteuer passt;
Umgebung und Erreichbarkeit bestimmen die dort möglichen Aktionen.
Migration: `sql/custom/world/2026_10_27_04_adventure_exploration.sql`.
## Aktionen
| Schlüssel | Darstellung |
|---|---|
| `adventure_explore` | Einen zusammenhängenden nativen MMAP-Weg erkunden und zurückkehren. |
| `adventure_patrol` | Einen kürzeren Wegabschnitt abschreiten; dieselbe geprüfte Routenfähigkeit. |
| `adventure_view` | Im Außenbereich die Umgebung von der aktuellen Position aus betrachten. |
| `adventure_inspect` | Ein tatsächlich vorhandenes erreichbares Objekt aufsuchen und betrachten. |
| `adventure_inspect_detail` | Nach der Ankunft genauer betrachten, eine Fragegeste zeigen und auf das Objekt weisen. |
| `adventure_point` | Zu einem passenden Objekt gehen und darauf zeigen. |
| `adventure_scout` | Innehalten, nach links und rechts Ausschau halten. |
| `adventure_equipment` | Eine kurze Ausrüstungsprüfung mit nativer Benutzungs-/Vorbereitungsgeste darstellen. Keine Inventaränderung. |
| `adventure_entrance` | Am gewählten Ruinen-, Höhlen-/Minen- oder Dungeon-Ort umsehen und Vorbereitung darstellen. Kein Instanzeintritt. |
| `adventure_comment` | Selten einem tatsächlich anwesenden freundlichen Gegenüber eine Entdeckung kommentieren. |
Bestehende Fähigkeiten liefern Gespräche mit freundlichen NPCs, native
Sitzplätze, Essen, Trinken und Feuerrast. Nach einem Erkundungsweg erhalten
verfügbare Rastaktionen bei der folgenden Auswahl mehr Gewicht. Die
bestehenden Verbote für neue Feuer in Gebäuden und Städten gelten weiter.
## Ausführung und Grenzen
Die Fußwege verwenden den bereits für Spaziergänge geprüften lokalen
Routenplaner und Bewegungsexecutor: genau einen vollständigen MMAP-Korridor
wählen, Kontrollpunkte folgen, am Wendepunkt pausieren und dieselben Punkte
zurückverfolgen. Es gibt keine zufälligen einzelnen Nahziele und keine neuen
Reit-/Flugtests. Der Standard der alten winzigen Zufallsbewegung `explore`
wird nur für Abenteuer deaktiviert; angepasste Gewichte und ihre Verwendungen
bei anderen Intents bleiben erhalten.
Wegpunkte bleiben innerhalb des Aufenthaltsgebiets beziehungsweise der Zone.
Es gelten native vollständige Pfade, plausible Bodenhöhen und der begrenzte
Aufenthaltsradius. Zeitende veranlasst einen Rückweg entlang bereits erreichter
Punkte. Fehlgeschlagene Ankünfte werden niemals als Erfolg ausgegeben.
Objekte werden im tatsächlich geladenen Umfeld gesucht (60 Yards), nicht aus
festen Objekt-IDs oder durch erzeugte Test-Spawns. Sichtbare generische Props,
Schilder, Bücher und Goober-Objekte sind zulässig. Kisten/Ressourcen, Fallen,
Türen, Knöpfe, Transportmittel und Möbel werden hierfür nicht gewählt.
Spawnzustand, Phase, Sichtbarkeit, Sichtlinie und erreichbarer Gesprächs-/
Betrachtungspunkt werden geprüft. Ein nicht erreichbares Objekt wird durch
einen weiteren echten Kandidaten ersetzt; gibt es keinen, startet die Szene
nicht. Verschwundene oder nicht mehr sichtbare Ziele beenden den Ablauf.
Diese Fähigkeit ruft **keine Objektbenutzung, keine Beuteerzeugung und keine
Quest-/Gegenstandsinteraktion** auf. Die verwendeten Gesten erzeugen weder
Objekte noch Zauberwirkungen. Ausrüstungsprüfung ist eine Darstellung.
Umsehen verwendet zwei feste Blickrichtungen relativ zur Startausrichtung,
statt den Bot bei jedem Planungsschritt weiterzudrehen. Vorbereitung wird
über die vorhandene Kategorie des Aufenthaltsorts freigegeben; eine genaue
geometrische Erkennung einzelner Höhleneingänge ist nicht enthalten.
Aussichtsszenen garantieren keine besondere Landschaftsqualität. Objekte,
die nur Teil von WMO-/Karten-Geometrie sind, haben keine separate GO-GUID;
sie werden durch ortsbezogenes Umsehen statt Objektinteraktion dargestellt.
Kampf unterbricht die Tätigkeit über die bestehende Sicherheitsbehandlung.
## Location-Zuordnung
Die editierbaren `vcore_adventure_location_intent`-Einträge sind maßgeblich.
Der bisherige Wirtschaften-Fallback im Code wird in ausdrückliche bestehende
Kategorie-Zuordnungen überführt. Bearbeitete Gewichte einschließlich Null
werden erhalten. Neue Kategorien benötigen ihre eigene Zuordnung. Tavernen
erhalten eine geringe Freizeitgewichtung; Sitzaktionen sind weiterhin nur
bei einer tatsächlich geeigneten erreichbaren Sitzgelegenheit verfügbar.
Der Tagesplan wählt Intents weiterhin unabhängig vom vorhandenen Ortskatalog.
## Prüfung
Automatisierte Tests führen produktive Objektfilter, Verfügbarkeitsprüfungen
und Gestensequenzen aus: Ankunft vor Gesten, verschwundene/unsichtbare Ziele,
keine Objektbenutzung, zwei feste Blickrichtungen sowie korrekte Wiederverwendung
der Bodenrouten. Der SQL-Test prüft Referenzen, ausdrückliche Kategoriezuordnung,
Default-Upgrade und Erhalt bearbeiteter Gewichte bei Wiederholung.
Eine erfolgreiche Kompilierung ersetzt die visuelle Ingame-Abnahme nicht.
## Technische Live-Prüfung am 27.10.2026
Build mit `-j10`, Installation und frischer Testserver-Neustart mit Ready-
Meldung und Port 8085 erfolgreich. Alle 32 Clientless-Testskripte bestanden.
Die temporäre Login-Vorgabe wurde nach dem Laden exakt zurückgesetzt.
Am gewöhnlichen importierten Sturmwind-Ort `poi_tele_954` wurden geprüft:
- Automatisch ausgewählte Untersuchung des tatsächlichen Apothekenschilds
`Pestle's Apothecary` (GO 197117), native Ankunft und alle Betrachtungsgesten.
- Detaillierte Untersuchung des Schilds `Weller's Arsenal` (GO 197121),
native Ankunft sowie Blick-, Frage-, Zeige- und Wartephase. Ein anfangs
manuell unterbrochener Durchlauf zählt nicht als Abschluss; der danach
neu gestartete Durchlauf endete nach sämtlichen Gesten regulär.
- Zusammenhängender Erkundungsweg: Ursprung `(-8802.90, 628.57, 94.46)`,
Wendepunkt `(-8843.31, 559.53, 95.62)`, Abstand 80,01 Yards. Alle zehn
Hin-/Rückwegankünfte und die tatsächliche Rückkehr wurden bestätigt.
- Danach automatisch ausgewählte Trinkpause und Ausschauhalten.
Das ist eine technische Live-Prüfung in Sturmwind. Eine visuelle Abnahme
durch den Nutzer, Erkundung in gefährlichen offenen Gebieten und die
Vorbereitung an einem tatsächlichen Höhlen-/Dungeonort stehen noch aus.
+9
View File
@@ -1,5 +1,14 @@
# Autonomes Verhalten: Implementierungsplan und Vertrag
Konzeptfortschreibung vom 27.10.2026: Der vereinbarte Umfang umfasst
[sieben Grundintents](intent-katalog.md). Die folgende Implementierungsbeschreibung
beschreibt vorwiegend die ursprünglichen drei technischen Intents; der neue
[Ruhe-Intent `rest`](rest-intent.md), der [Freizeit-Intent `leisure`](leisure-intent.md)
der [Wirtschaften-Intent `economy`](economy-intent.md) und der
[Abenteuer-Ausbau](adventure-intent.md) sind separat dokumentiert. Die zuvor
ausstehende visuelle Prüfung der Stuhlsitzposition hat der Nutzer inzwischen
erfolgreich abgeschlossen.
Dieser Stand ersetzt die Lua-Tagesroutinen-Annahme aus `mikroaction.md`.
Lua beschreibt Profile; der C++-Planer erzeugt daraus einen Tagesplan.
@@ -0,0 +1,339 @@
# Vcore Development Environment
## Project
Vcore is a custom World of Warcraft Cataclysm 4.3.4 server based on TrinityCore.
This environment is a dedicated development and test server.
The current development focus is the Clientless Player Core and AdventureBot infrastructure.
## Repository and Git Rules
Repository:
`/home/wow_admin/wow_cata_server/source/TrinityCore`
Authorized development branch:
`feature/clientless-player-core`
Rules:
- Work exclusively on the authorized development branch.
- Never switch to, modify, reset, rebase, or merge `master`.
- Never push to `master`.
- Never push any branch without explicit user approval.
- Local commits are permitted.
- Never discard existing changes without approval.
- Never force-push.
- Check the current branch and working tree before making changes.
- Stop and request clarification if the working tree contains unexpected changes.
## Development Permissions
Codex is authorized to:
- Read and modify Vcore source code.
- Implement new features and fix bugs.
- Run CMake and compile Vcore.
- Investigate and resolve compilation errors.
- Install compiled binaries into the testserver installation.
- Start, stop, and restart the test worldserver.
- Inspect logs and server output.
- Modify test databases when required for an authorized task.
- Execute local tests and create local commits.
Codex must never:
- Modify the production server.
- Deploy changes to production.
- Push changes without explicit approval.
- Modify or merge the protected `master` branch.
- Discard user changes without approval.
## Read-only Diagnostics
- Reading the development/test server logs and project files is already authorized.
- Use direct read-only commands such as `rg`, `tail`, `sed`, `cat`, and `stat` inside the sandbox for ordinary file inspection. Do not request elevated execution preemptively for these reads.
- Keep file inspection separate from tmux access, database queries, writes, and lifecycle actions so those actions do not unnecessarily escalate log reading.
- Do not request blanket approval for Python, other interpreters, or shell execution to read logs.
- If a read actually fails because of a technical sandbox restriction, follow the execution environment's escalation rules and request only the narrow access needed. Project authorization does not override the sandbox.
## Directory Structure
Source:
`/home/wow_admin/wow_cata_server/source/TrinityCore`
Build directory:
`/home/wow_admin/wow_cata_server/build/debug`
Server installation:
`/home/wow_admin/wow_cata_server/server/debug`
Worldserver executable:
`/home/wow_admin/wow_cata_server/server/debug/bin/worldserver`
## Build and Installation
Use the existing CMake build configuration.
Inspect the build configuration before changing it.
Build directory:
`/home/wow_admin/wow_cata_server/build/debug`
Install directory:
`/home/wow_admin/wow_cata_server/server/debug`
After successful compilation, install the resulting binaries using the existing CMake installation configuration.
Do not overwrite running executables.
Stop the test worldserver cleanly before installing updated binaries.
Do not delete existing server configuration, logs, databases, or data files.
## Worldserver Management
The test worldserver runs inside a dedicated tmux session.
Session name:
`vcore-worldserver`
Working directory:
`/home/wow_admin/wow_cata_server/server/debug/bin`
Executable:
`./worldserver`
Codex is authorized to start, stop, restart, and recover the test worldserver autonomously.
### Starting the Worldserver
Before starting:
1. Check whether a worldserver instance is already running.
2. Check whether the `vcore-worldserver` tmux session already exists.
3. Never start a second worldserver instance while another instance is active.
4. Remove stale tmux sessions only after verifying their state.
Start command:
```bash
tmux new-session -d -s vcore-worldserver \
-c /home/wow_admin/wow_cata_server/server/debug/bin \
'./worldserver'
```
### Startup Verification
A successfully created tmux session does not indicate successful worldserver startup.
The Vcore worldserver reports successful initialization with a final console message similar to:
```text
World initialized in 0 minutes 10 seconds
TrinityCore rev. <revision> (...) (worldserver-daemon) ready...
```
The authoritative startup indicator is:
`(worldserver-daemon) ready...`
The revision, branch, build configuration, and initialization time may vary.
After starting the worldserver:
1. Monitor the worldserver process and console output.
2. Wait for the `(worldserver-daemon) ready...` message.
3. Verify that the worldserver process remains running.
4. Check for critical startup errors.
5. Verify that the configured worldserver port is listening.
Only report successful startup when these checks pass.
Capture console output from the beginning of the current startup attempt to avoid mistaking messages from an earlier run for current startup success.
If the worldserver exits, hangs, or fails to reach the ready state within a reasonable timeout, follow the startup failure recovery procedure.
### Startup Failure Recovery
The test worldserver may occasionally fail to start because the remote authentication database on the VPS is temporarily unreachable.
When startup fails:
1. Capture relevant console output and logs.
2. Determine whether the worldserver process is still running.
3. Check whether the tmux session is still active.
4. If the TrinityCore console is responsive, attempt a graceful shutdown.
5. If the console is unavailable or unresponsive, terminate the failed worldserver process and its dedicated tmux session.
6. Verify that the previous worldserver process has terminated.
7. Ensure that no stale process remains before restarting.
8. Wait briefly before retrying.
9. Create a fresh tmux session and start the worldserver again.
10. Repeat startup verification.
For startup recovery, Codex may terminate the dedicated test worldserver process and tmux session when graceful shutdown is impossible.
Do not terminate unrelated processes or tmux sessions.
### Retry Policy
Use a maximum of three startup attempts per restart operation.
Wait approximately 10 seconds between attempts.
If startup fails repeatedly:
- Stop retrying.
- Preserve the relevant logs.
- Report the observed errors.
- Explain whether the failure appears related to database connectivity, configuration, compilation, or another cause.
- Do not claim that the testserver is operational.
Do not attempt to repair the remote VPS authentication database or production infrastructure.
### TrinityCore Console Commands
Codex may execute TrinityCore console commands through tmux.
Example:
```bash
tmux send-keys -t vcore-worldserver '.server shutdown 10' Enter
```
Read console output:
```bash
tmux capture-pane -p -t vcore-worldserver -S -100
```
### Graceful Shutdown
For a normally running worldserver, use:
`.server shutdown 10`
Send the command through tmux:
```bash
tmux send-keys -t vcore-worldserver '.server shutdown 10' Enter
```
After issuing the command:
1. Wait for the worldserver to terminate.
2. Verify that the process has stopped.
3. Verify that the tmux session has ended.
4. Only then install new binaries or start another worldserver instance.
If the console is unresponsive, Codex may terminate the dedicated test worldserver process.
Prefer SIGTERM before SIGKILL.
Use SIGKILL only when the process cannot be stopped normally.
### Restart Procedure
1. Verify the current worldserver state.
2. If running normally, initiate graceful shutdown.
3. If the console is unavailable, use the recovery shutdown procedure.
4. Wait for complete process termination.
5. Install successfully compiled binaries if required.
6. Start a fresh tmux session.
7. Verify successful worldserver initialization.
8. If startup fails, follow the startup recovery procedure.
9. Report the final server state.
### Safety
This is a dedicated development and test environment.
Codex is authorized to manage the test worldserver autonomously, including recovery from failed startup attempts.
Never terminate unrelated services or processes.
Never execute worldserver management commands on production infrastructure.
## AdventureBot Architecture
Preserve the existing Clientless Player Core architecture.
Important components include:
- ClientlessTravelMgr
- ClientlessTravelGraphMgr
- ClientlessActionExecutorMgr
- Lua-based bot control
- MMAP navigation
- TravelGraph routing
Architecture principles:
- Clientless Player Core provides generic clientless player functionality.
- AdventureBots are built on top of this infrastructure.
- TravelGraph handles long-distance travel.
- MMAP navigation handles local movement.
- DungeonBots remain independent from AdventureBots.
- AdventureBots must not become infrastructure dependencies for unrelated game systems.
Prefer extending the existing implementation over replacing it.
## Development Workflow
For each development task:
1. Verify the current Git branch and working tree.
2. Inspect the relevant existing implementation.
3. Plan nontrivial changes before implementation.
4. Implement the requested functionality.
5. Compile the affected components.
6. Resolve compilation errors.
7. Install the new build when appropriate.
8. Restart the test worldserver when necessary.
9. Inspect logs and available test results.
10. Report what changed, what was tested, and what remains unverified.
11. Wait for user feedback before pushing changes.
Do not claim that gameplay functionality is verified solely because compilation succeeded.
The user performs final in-game acceptance testing.
## Rollback
When requested, restore the previous implementation without modifying `master`.
Prefer targeted reversions of the relevant development changes.
Never use destructive Git commands without checking whether unrelated work would be lost.
Database rollbacks must be limited to the changes associated with the development task.
## General Principles
- Preserve existing Vcore functionality.
- Avoid unnecessary architectural rewrites.
- Prefer targeted, maintainable changes.
- Investigate root causes instead of masking symptoms.
- Keep the test environment reproducible.
- Clearly distinguish successful builds, automated tests, and verified gameplay behavior.
## Saved session / Wiederaufnahme
Before starting new work, read `doc/clientless/SESSION-RESUME.md`. It records the
user-requested checkpoint, unfinished profession intent, agreed Lua professions,
build/test state and exact next steps. Preserve the saved WIP; do not restart or
revert it. All development/server/branch rules above remain in force.
@@ -0,0 +1,44 @@
from pathlib import Path
import os,re,subprocess,sys
config=Path('/home/wow_admin/wow_cata_server/server/debug/etc/worldserver.conf').read_text()
h,port,u,password,db=re.search(r'^WorldDatabaseInfo\s*=\s*"([^"]+)"',config,re.M).group(1).split(';')
args=['mysql','--protocol=TCP','--host='+h,'--port='+port,'--user='+u,'--database='+db,'--batch','--default-character-set=utf8mb4','--connect-timeout=10']
env=os.environ.copy();env['MYSQL_PWD']=password
op=sys.argv[1] if len(sys.argv)>1 else ''
if op=='state':
h,port,u,password,db=re.search(r'^CharacterDatabaseInfo\s*=\s*"([^"]+)"',config,re.M).group(1).split(';')
args=['mysql','--protocol=TCP','--host='+h,'--port='+port,'--user='+u,'--database='+db,'--batch','--default-character-set=utf8mb4','--connect-timeout=10']
env['MYSQL_PWD']=password
if op=='state':
query="SELECT guid,money FROM characters WHERE guid=26; SELECT item,bag,slot FROM character_inventory WHERE guid=26 ORDER BY bag,slot,item; SELECT guid,itemEntry,count FROM item_instance WHERE owner_guid=26 ORDER BY guid;"
elif op=='workstation_inspect':
query="""SELECT data0 AS focus_id,COUNT(*) AS n,LEFT(GROUP_CONCAT(DISTINCT name ORDER BY name SEPARATOR ', '),600) AS examples FROM gameobject_template WHERE type=8 GROUP BY data0 ORDER BY data0; SELECT entry,name,type,data0 FROM gameobject_template WHERE name REGEXP 'Alchemy|Alchemist|Workbench|Loom|Tanning|Sewing|Enchanter|Runeforge|Cooking Fire|Campfire' AND type IN (5,8,10) ORDER BY entry LIMIT 65; SHOW CREATE TABLE npc_trainer; SHOW CREATE TABLE trainer; SHOW CREATE TABLE creature_trainer;"""
elif op=='profession_inspect':
query="""SELECT t.entry,t.name,t.type,t.data0,t.data1 FROM gameobject_template t WHERE (t.type=8 AND (t.name REGEXP 'Anvil|Forge|Alchemy|Loom|Workbench|Enchant|Tanning|Cooking')) OR (t.type=3 AND t.name IN ('Copper Vein','Silverleaf','Peacebloom')) ORDER BY t.type,t.data0,t.entry LIMIT 90; SELECT w.point_key,a.display_name,w.category,w.map,w.position_x,w.position_y,w.position_z,a.is_area,a.area_id,a.zone_id FROM vcore_clientless_world_point w JOIN vcore_adventure_location a USING(point_key) WHERE w.enabled=1 AND (a.is_area=1 AND a.zone_id=12 OR w.category='workshop' AND a.zone_id=1519) ORDER BY w.point_key LIMIT 25;"""
elif op=='exploration_inspect':
query="""SELECT g.guid,g.id,t.name,t.type,g.position_x,g.position_y,g.position_z FROM gameobject g JOIN gameobject_template t ON t.entry=g.id WHERE g.map=0 AND g.position_x BETWEEN -9000 AND -8750 AND g.position_y BETWEEN 500 AND 750 AND t.type IN (5,9,10) ORDER BY g.id; SELECT w.point_key,a.display_name,w.category,w.map,w.position_x,w.position_y,w.position_z FROM vcore_clientless_world_point w JOIN vcore_adventure_location a USING(point_key) WHERE w.enabled=1 AND w.map=0 AND (a.display_name LIKE '%Northshire%' OR a.display_name LIKE '%Goldshire%' OR a.display_name LIKE '%Stonecairn%') ORDER BY a.display_name;"""
elif op=='exploration_apply':
query='START TRANSACTION;\n'+Path('/home/wow_admin/wow_cata_server/source/TrinityCore/sql/custom/world/2026_10_27_04_adventure_exploration.sql').read_text()+"\nSELECT a.action_key,a.kind,a.requirement,i.weight FROM vcore_adventure_action a JOIN vcore_adventure_action_intent i USING(action_key) WHERE a.action_key LIKE 'adventure_%' OR (a.action_key='explore' AND i.intent='adventure'); COMMIT;"
elif op=='catalog':
query="""SELECT w.point_key,w.category,w.map,w.position_x,w.position_y,w.position_z,w.orientation,w.enabled,COALESCE(w.comment,''),a.display_name,a.source_type,a.source_id,a.area_id,a.zone_id,a.region_key,a.is_area,a.faction FROM vcore_clientless_world_point w JOIN vcore_adventure_location a USING(point_key) ORDER BY w.point_key;"""
elif op=='source_audit':
query="""SELECT id,name,map,position_x,position_y,position_z FROM game_tele WHERE name IN ('Orgrimmar','ThunderBluff','Thunder Bluff','Undercity','SilvermoonCity','Darnassus','TheExodar','Ironforge','Stormwind'); SHOW COLUMNS FROM game_tele; SHOW COLUMNS FROM creature; SELECT c.id,t.name,c.map,c.position_x,c.position_y,c.position_z,COALESCE(NULLIF(c.npcflag,0),t.npcflag) AS flags,t.faction FROM creature c JOIN creature_template t ON t.entry=c.id WHERE t.name IN ('Innkeeper Gryshka','Innkeeper Pala','Innkeeper Allison','Auctioneer Thathung','Auctioneer Chilton') ORDER BY c.map,c.id;"""
elif op=='audit':
query="""SELECT COUNT(*) AS active_locations FROM vcore_clientless_world_point w JOIN vcore_adventure_location a USING(point_key) WHERE w.enabled=1 AND w.category NOT LIKE 'travel%'; SELECT a.source_type,w.category,a.faction,COUNT(*) AS n FROM vcore_adventure_location a JOIN vcore_clientless_world_point w USING(point_key) WHERE w.enabled=1 GROUP BY a.source_type,w.category,a.faction ORDER BY n DESC; SELECT w.point_key,a.display_name,w.category,a.region_key,a.area_id,a.zone_id,a.is_area,a.faction,w.map,w.position_x,w.position_y,w.position_z FROM vcore_adventure_location a JOIN vcore_clientless_world_point w USING(point_key) WHERE w.enabled=1 AND (a.display_name REGEXP 'Orgrimmar|Thunder Bluff|Undercity|Silvermoon|Darnassus|Exodar|Ironforge|Stormwind' OR (a.is_area=1 AND a.zone_id IN (1519,1637,1638,1497,1537,1657,3430,3487,3557))) ORDER BY a.is_area DESC,a.zone_id,w.point_key;"""
elif op=='inspect':
query="""SELECT c.guid,c.id,t.name,t.npcflag,c.position_x,c.position_y,c.position_z,c.orientation FROM creature c JOIN creature_template t ON t.entry=c.id WHERE c.map=0 AND c.position_x BETWEEN -9000 AND -8750 AND c.position_y BETWEEN 450 AND 850 AND (t.npcflag & (128|131072|2097152))<>0 ORDER BY c.id,c.guid; SHOW CREATE TABLE vcore_adventure_location; SHOW CREATE TABLE vcore_clientless_world_point; SELECT category,intent,weight FROM vcore_adventure_location_intent ORDER BY category,intent;"""
elif op=='apply':
query='START TRANSACTION;\n'+Path('/home/wow_admin/wow_cata_server/source/TrinityCore/sql/custom/world/2026_10_27_03_adventure_economy.sql').read_text()+"\nSELECT w.point_key,w.category,w.position_x,w.position_y,w.position_z FROM vcore_clientless_world_point w WHERE w.point_key LIKE 'stormwind_economy_%'; SELECT a.action_key,a.requirement,i.weight FROM vcore_adventure_action a JOIN vcore_adventure_action_intent i USING(action_key) WHERE i.intent='economy'; COMMIT;"
else:raise SystemExit('inspect or apply only')
r=subprocess.run(args,input=query,text=True,capture_output=True,env=env)
if op=='catalog' and not r.returncode:
import hashlib
Path('/tmp/vcore-import-catalog-current.tsv').write_text(r.stdout)
print('Catalog snapshot:',len(r.stdout.splitlines())-1,'records; SHA256',hashlib.sha256(r.stdout.encode()).hexdigest())
elif op=='state' and not r.returncode:
import hashlib
snapshot=Path('/tmp/vcore-economy-state-current.txt');snapshot.write_text(r.stdout)
print('Arthoran persisted money/inventory SHA256:',hashlib.sha256(r.stdout.encode()).hexdigest())
else:print(r.stdout,end='')
if r.stderr:print(r.stderr.replace(password,'[redacted]'),file=sys.stderr,end='')
raise SystemExit(r.returncode)
@@ -0,0 +1,97 @@
2026-10-27_14:10:11 INFO [server.adventurebot.navigation] NAV_PLAN seq=4 guid=26 name='Arthoran' map=0 segment=0 start=(-8848.844 607.931 92.673) target=(-8864.850 595.939 92.645) type=0x1 calculated=1 complete=1 suspicious=0 points=6 direct=20.00 path=20.00 maxSegment=4.00 maxZDelta=0.08 finalDistance=0.00 repaths=0 reason='ok'
2026-10-27_14:10:11 INFO [server.adventurebot.navigation] NAV_EXEC seq=4 guid=26 name='Arthoran' segment=1 splinePoints=6 splineLength=20.00 segmentEnd=(-8864.850 595.939 92.645) target=(-8864.850 595.939 92.645) walk=1
2026-10-27_14:10:11 INFO [server.adventurebot.navigation] NAV_START guid=26 name='Arthoran' map=0 target=(-8864.850 595.939 92.645) remaining=18.50 state=Moving
2026-10-27_14:10:12 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=4 guid=26 segment=1 pos=(-8850.817 606.453 92.631) remaining=16.03 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:13 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=4 guid=26 segment=1 pos=(-8852.901 604.891 92.583) remaining=13.43 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:14 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=4 guid=26 segment=1 pos=(-8854.906 603.389 92.532) remaining=10.93 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:15 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=4 guid=26 segment=1 pos=(-8856.899 601.895 92.520) remaining=8.43 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:16 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=4 guid=26 segment=1 pos=(-8858.903 600.394 92.525) remaining=5.93 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:17 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=4 guid=26 segment=1 pos=(-8860.908 598.892 92.562) remaining=3.43 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:18 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=4 guid=26 segment=1 pos=(-8862.903 597.398 92.603) remaining=0.93 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:19 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=4 guid=26 segment=1 pos=(-8864.850 595.939 92.645) remaining=0.00 deviation=0.00 splineActive=0 repaths=0
2026-10-27_14:10:19 INFO [server.adventurebot.navigation] NAV_COMPLETE seq=4 guid=26 name='Arthoran' remaining=0.00 repaths=0 segments=1 maxDeviation=0.00
2026-10-27_14:10:19 INFO [server.adventurebot.navigation] NAV_PLAN seq=5 guid=26 name='Arthoran' map=0 segment=0 start=(-8864.850 595.939 92.645) target=(-8880.855 583.947 93.233) type=0x1 calculated=1 complete=1 suspicious=0 points=6 direct=20.01 path=20.01 maxSegment=4.00 maxZDelta=0.16 finalDistance=0.00 repaths=0 reason='ok'
2026-10-27_14:10:19 INFO [server.adventurebot.navigation] NAV_EXEC seq=5 guid=26 name='Arthoran' segment=1 splinePoints=6 splineLength=20.01 segmentEnd=(-8880.855 583.947 93.233) target=(-8880.855 583.947 93.233) walk=1
2026-10-27_14:10:19 INFO [server.adventurebot.navigation] NAV_START guid=26 name='Arthoran' map=0 target=(-8880.855 583.947 93.233) remaining=18.51 state=Moving
2026-10-27_14:10:20 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=5 guid=26 segment=1 pos=(-8866.855 594.436 92.748) remaining=16.00 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:21 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=5 guid=26 segment=1 pos=(-8868.860 592.934 92.850) remaining=13.49 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:22 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=5 guid=26 segment=1 pos=(-8870.868 591.430 92.949) remaining=10.98 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:23 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=5 guid=26 segment=1 pos=(-8872.858 589.939 93.033) remaining=8.49 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:24 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=5 guid=26 segment=1 pos=(-8874.869 588.432 93.109) remaining=5.98 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:25 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=5 guid=26 segment=1 pos=(-8876.986 586.846 93.171) remaining=3.34 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:26 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=5 guid=26 segment=1 pos=(-8879.001 585.337 93.208) remaining=0.82 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:27 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=5 guid=26 segment=1 pos=(-8880.855 583.947 93.233) remaining=0.00 deviation=0.00 splineActive=0 repaths=0
2026-10-27_14:10:27 INFO [server.adventurebot.navigation] NAV_COMPLETE seq=5 guid=26 name='Arthoran' remaining=0.00 repaths=0 segments=1 maxDeviation=0.00
2026-10-27_14:10:27 INFO [server.adventurebot.navigation] NAV_PLAN seq=6 guid=26 name='Arthoran' map=0 segment=0 start=(-8880.855 583.947 93.233) target=(-8896.861 571.956 93.007) type=0x1 calculated=1 complete=1 suspicious=0 points=6 direct=20.00 path=20.01 maxSegment=4.01 maxZDelta=0.24 finalDistance=0.00 repaths=0 reason='ok'
2026-10-27_14:10:27 INFO [server.adventurebot.navigation] NAV_EXEC seq=6 guid=26 name='Arthoran' segment=1 splinePoints=6 splineLength=20.01 segmentEnd=(-8896.861 571.956 93.007) target=(-8896.861 571.956 93.007) walk=1
2026-10-27_14:10:27 INFO [server.adventurebot.navigation] NAV_START guid=26 name='Arthoran' map=0 target=(-8896.861 571.956 93.007) remaining=18.50 state=Moving
2026-10-27_14:10:28 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=6 guid=26 segment=1 pos=(-8882.869 582.439 93.283) remaining=15.99 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:29 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=6 guid=26 segment=1 pos=(-8884.861 580.946 93.305) remaining=13.50 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:30 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=6 guid=26 segment=1 pos=(-8886.878 579.435 93.286) remaining=10.98 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:31 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=6 guid=26 segment=1 pos=(-8888.862 577.948 93.161) remaining=8.50 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:32 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=6 guid=26 segment=1 pos=(-8890.875 576.440 93.029) remaining=5.98 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:33 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=6 guid=26 segment=1 pos=(-8892.880 574.939 92.978) remaining=3.47 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:34 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=6 guid=26 segment=1 pos=(-8894.883 573.438 92.976) remaining=0.97 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:35 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=6 guid=26 segment=1 pos=(-8896.861 571.956 93.007) remaining=0.00 deviation=0.00 splineActive=0 repaths=0
2026-10-27_14:10:35 INFO [server.adventurebot.navigation] NAV_COMPLETE seq=6 guid=26 name='Arthoran' remaining=0.00 repaths=0 segments=1 maxDeviation=0.00
2026-10-27_14:10:35 INFO [server.adventurebot.navigation] NAV_PLAN seq=7 guid=26 name='Arthoran' map=0 segment=0 start=(-8896.861 571.956 93.007) target=(-8912.868 559.964 94.237) type=0x1 calculated=1 complete=1 suspicious=0 points=6 direct=20.04 path=20.04 maxSegment=4.02 maxZDelta=0.35 finalDistance=0.00 repaths=0 reason='ok'
2026-10-27_14:10:35 INFO [server.adventurebot.navigation] NAV_EXEC seq=7 guid=26 name='Arthoran' segment=1 splinePoints=6 splineLength=20.04 segmentEnd=(-8912.868 559.964 94.237) target=(-8912.868 559.964 94.237) walk=1
2026-10-27_14:10:35 INFO [server.adventurebot.navigation] NAV_START guid=26 name='Arthoran' map=0 target=(-8912.868 559.964 94.237) remaining=18.54 state=Moving
2026-10-27_14:10:36 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=7 guid=26 segment=1 pos=(-8898.871 570.450 93.132) remaining=16.02 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:37 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=7 guid=26 segment=1 pos=(-8900.871 568.951 93.277) remaining=13.52 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:38 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=7 guid=26 segment=1 pos=(-8902.853 567.467 93.451) remaining=11.04 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:39 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=7 guid=26 segment=1 pos=(-8904.854 565.968 93.614) remaining=8.53 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:40 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=7 guid=26 segment=1 pos=(-8906.861 564.464 93.785) remaining=6.02 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:41 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=7 guid=26 segment=1 pos=(-8908.854 562.970 94.006) remaining=3.52 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:42 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=7 guid=26 segment=1 pos=(-8910.836 561.486 94.147) remaining=1.04 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:43 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=7 guid=26 segment=1 pos=(-8912.854 559.974 94.236) remaining=0.00 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:44 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=7 guid=26 segment=1 pos=(-8912.868 559.964 94.237) remaining=0.00 deviation=0.00 splineActive=0 repaths=0
2026-10-27_14:10:44 INFO [server.adventurebot.navigation] NAV_COMPLETE seq=7 guid=26 name='Arthoran' remaining=0.00 repaths=0 segments=1 maxDeviation=0.00
2026-10-27_14:10:48 INFO [server.adventurebot.navigation] NAV_PLAN seq=8 guid=26 name='Arthoran' map=0 segment=0 start=(-8912.868 559.964 94.237) target=(-8896.861 571.956 93.007) type=0x1 calculated=1 complete=1 suspicious=0 points=6 direct=20.04 path=20.04 maxSegment=4.02 maxZDelta=0.35 finalDistance=0.00 repaths=0 reason='ok'
2026-10-27_14:10:48 INFO [server.adventurebot.navigation] NAV_EXEC seq=8 guid=26 name='Arthoran' segment=1 splinePoints=6 splineLength=20.04 segmentEnd=(-8896.861 571.956 93.007) target=(-8896.861 571.956 93.007) walk=1
2026-10-27_14:10:48 INFO [server.adventurebot.navigation] NAV_START guid=26 name='Arthoran' map=0 target=(-8896.861 571.956 93.007) remaining=18.54 state=Moving
2026-10-27_14:10:49 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=8 guid=26 segment=1 pos=(-8910.871 561.460 94.149) remaining=16.04 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:50 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=8 guid=26 segment=1 pos=(-8908.852 562.973 94.006) remaining=13.52 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:51 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=8 guid=26 segment=1 pos=(-8906.845 564.476 93.783) remaining=11.00 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:52 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=8 guid=26 segment=1 pos=(-8904.964 565.885 93.622) remaining=8.64 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:53 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=8 guid=26 segment=1 pos=(-8902.808 567.501 93.447) remaining=5.94 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:54 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=8 guid=26 segment=1 pos=(-8900.805 569.002 93.271) remaining=3.43 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:55 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=8 guid=26 segment=1 pos=(-8898.807 570.499 93.128) remaining=0.93 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:56 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=8 guid=26 segment=1 pos=(-8896.861 571.956 93.007) remaining=0.00 deviation=0.00 splineActive=0 repaths=0
2026-10-27_14:10:56 INFO [server.adventurebot.navigation] NAV_COMPLETE seq=8 guid=26 name='Arthoran' remaining=0.00 repaths=0 segments=1 maxDeviation=0.00
2026-10-27_14:10:56 INFO [server.adventurebot.navigation] NAV_PLAN seq=9 guid=26 name='Arthoran' map=0 segment=0 start=(-8896.861 571.956 93.007) target=(-8880.855 583.947 93.233) type=0x1 calculated=1 complete=1 suspicious=0 points=6 direct=20.00 path=20.01 maxSegment=4.01 maxZDelta=0.24 finalDistance=0.00 repaths=0 reason='ok'
2026-10-27_14:10:56 INFO [server.adventurebot.navigation] NAV_EXEC seq=9 guid=26 name='Arthoran' segment=1 splinePoints=6 splineLength=20.01 segmentEnd=(-8880.855 583.947 93.233) target=(-8880.855 583.947 93.233) walk=1
2026-10-27_14:10:56 INFO [server.adventurebot.navigation] NAV_START guid=26 name='Arthoran' map=0 target=(-8880.855 583.947 93.233) remaining=18.50 state=Moving
2026-10-27_14:10:57 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=9 guid=26 segment=1 pos=(-8894.864 573.452 92.976) remaining=16.01 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:58 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=9 guid=26 segment=1 pos=(-8892.857 574.955 92.978) remaining=13.50 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:10:59 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=9 guid=26 segment=1 pos=(-8890.867 576.447 93.030) remaining=11.01 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:00 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=9 guid=26 segment=1 pos=(-8888.864 577.947 93.161) remaining=8.51 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:01 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=9 guid=26 segment=1 pos=(-8886.867 579.443 93.286) remaining=6.01 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:02 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=9 guid=26 segment=1 pos=(-8884.851 580.954 93.305) remaining=3.49 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:03 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=9 guid=26 segment=1 pos=(-8882.849 582.454 93.283) remaining=0.99 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:04 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=9 guid=26 segment=1 pos=(-8880.855 583.947 93.233) remaining=0.00 deviation=0.00 splineActive=0 repaths=0
2026-10-27_14:11:04 INFO [server.adventurebot.navigation] NAV_COMPLETE seq=9 guid=26 name='Arthoran' remaining=0.00 repaths=0 segments=1 maxDeviation=0.00
2026-10-27_14:11:04 INFO [server.adventurebot.navigation] NAV_PLAN seq=10 guid=26 name='Arthoran' map=0 segment=0 start=(-8880.855 583.947 93.233) target=(-8864.850 595.939 92.645) type=0x1 calculated=1 complete=1 suspicious=0 points=6 direct=20.01 path=20.01 maxSegment=4.00 maxZDelta=0.16 finalDistance=0.00 repaths=0 reason='ok'
2026-10-27_14:11:04 INFO [server.adventurebot.navigation] NAV_EXEC seq=10 guid=26 name='Arthoran' segment=1 splinePoints=6 splineLength=20.01 segmentEnd=(-8864.850 595.939 92.645) target=(-8864.850 595.939 92.645) walk=1
2026-10-27_14:11:04 INFO [server.adventurebot.navigation] NAV_START guid=26 name='Arthoran' map=0 target=(-8864.850 595.939 92.645) remaining=18.51 state=Moving
2026-10-27_14:11:05 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=10 guid=26 segment=1 pos=(-8878.854 585.447 93.206) remaining=16.01 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:06 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=10 guid=26 segment=1 pos=(-8876.860 586.941 93.167) remaining=13.52 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:07 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=10 guid=26 segment=1 pos=(-8874.861 588.438 93.109) remaining=11.02 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:08 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=10 guid=26 segment=1 pos=(-8872.847 589.948 93.033) remaining=8.50 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:09 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=10 guid=26 segment=1 pos=(-8870.844 591.448 92.948) remaining=6.00 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:10 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=10 guid=26 segment=1 pos=(-8868.862 592.933 92.850) remaining=3.52 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:11 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=10 guid=26 segment=1 pos=(-8866.839 594.449 92.748) remaining=0.99 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:12 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=10 guid=26 segment=1 pos=(-8864.850 595.939 92.645) remaining=0.00 deviation=0.00 splineActive=0 repaths=0
2026-10-27_14:11:12 INFO [server.adventurebot.navigation] NAV_COMPLETE seq=10 guid=26 name='Arthoran' remaining=0.00 repaths=0 segments=1 maxDeviation=0.00
2026-10-27_14:11:12 INFO [server.adventurebot.navigation] NAV_PLAN seq=11 guid=26 name='Arthoran' map=0 segment=0 start=(-8864.850 595.939 92.645) target=(-8848.844 607.931 92.673) type=0x1 calculated=1 complete=1 suspicious=0 points=6 direct=20.00 path=20.00 maxSegment=4.00 maxZDelta=0.08 finalDistance=0.03 repaths=0 reason='ok'
2026-10-27_14:11:12 INFO [server.adventurebot.navigation] NAV_EXEC seq=11 guid=26 name='Arthoran' segment=1 splinePoints=6 splineLength=20.00 segmentEnd=(-8848.844 607.931 92.642) target=(-8848.844 607.931 92.673) walk=1
2026-10-27_14:11:12 INFO [server.adventurebot.navigation] NAV_START guid=26 name='Arthoran' map=0 target=(-8848.844 607.931 92.673) remaining=18.50 state=Moving
2026-10-27_14:11:13 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=11 guid=26 segment=1 pos=(-8862.856 597.433 92.602) remaining=16.01 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:14 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=11 guid=26 segment=1 pos=(-8860.846 598.939 92.561) remaining=13.50 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:15 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=11 guid=26 segment=1 pos=(-8858.829 600.450 92.524) remaining=10.98 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:16 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=11 guid=26 segment=1 pos=(-8856.846 601.936 92.520) remaining=8.50 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:17 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=11 guid=26 segment=1 pos=(-8854.824 603.451 92.534) remaining=5.97 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:18 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=11 guid=26 segment=1 pos=(-8852.815 604.956 92.585) remaining=3.46 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:19 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=11 guid=26 segment=1 pos=(-8850.809 606.459 92.619) remaining=0.96 deviation=0.00 splineActive=1 repaths=0
2026-10-27_14:11:20 DEBUG [server.adventurebot.navigation] NAV_SAMPLE seq=11 guid=26 segment=1 pos=(-8848.844 607.931 92.642) remaining=0.00 deviation=0.00 splineActive=0 repaths=0
2026-10-27_14:11:20 INFO [server.adventurebot.navigation] NAV_COMPLETE seq=11 guid=26 name='Arthoran' remaining=0.00 repaths=0 segments=1 maxDeviation=0.00
@@ -0,0 +1,34 @@
LEISURE_WALK_PLAN guid=26 kind='pet_walk' span=80.02 outbound=4 origin=(-8848.84 607.93 92.67) goal=(-8912.87 559.96 94.24)
COMPANION_START guid=26 spell=10673 summoned=1
LEISURE_START guid=26 kind='pet_walk' waypoints=8
COMPANION_CONFIRMED guid=26 pet='GUID Full: 0xf1301cd900000961 Type: Creature Entry: 7385 Low: 2401' entry=7385
COMPANION_LEASH_REQUEST guid=26 item=37460 spell=48722
CreatureAI::EngagementOver called even though creature is not currently engaged.
COMPANION_LEASH_CONFIRMED guid=26 spell=48722
COMPANION_WALK_SAMPLE guid=26 owner=(-8864.85 595.94 92.65) pet=(-8862.91 597.40 92.59) distance=2.43 leashed=1
LEISURE_WAYPOINT guid=26 kind='pet_walk' reached=1/8
CreatureAI::EngagementOver called even though creature is not currently engaged.
COMPANION_WALK_SAMPLE guid=26 owner=(-8880.86 583.95 93.23) pet=(-8879.00 585.34 93.21) distance=2.32 leashed=1
LEISURE_WAYPOINT guid=26 kind='pet_walk' reached=2/8
CreatureAI::EngagementOver called even though creature is not currently engaged.
COMPANION_WALK_SAMPLE guid=26 owner=(-8896.86 571.96 93.01) pet=(-8894.87 573.45 92.98) distance=2.48 leashed=1
LEISURE_WAYPOINT guid=26 kind='pet_walk' reached=3/8
CreatureAI::EngagementOver called even though creature is not currently engaged.
COMPANION_WALK_SAMPLE guid=26 owner=(-8912.87 559.96 94.24) pet=(-8910.87 561.46 94.17) distance=2.50 leashed=1
LEISURE_WAYPOINT guid=26 kind='pet_walk' reached=4/8
CreatureAI::EngagementOver called even though creature is not currently engaged.
COMPANION_WALK_SAMPLE guid=26 owner=(-8896.86 571.96 93.01) pet=(-8898.80 570.51 93.12) distance=2.42 leashed=1
LEISURE_WAYPOINT guid=26 kind='pet_walk' reached=5/8
CreatureAI::EngagementOver called even though creature is not currently engaged.
COMPANION_WALK_SAMPLE guid=26 owner=(-8880.86 583.95 93.23) pet=(-8882.85 582.46 93.26) distance=2.49 leashed=1
LEISURE_WAYPOINT guid=26 kind='pet_walk' reached=6/8
Player::_LoadSkills: Player 'Allitester' (GUID Full: 0x0000000000000015 Type: Player Low: 21, Race: 1, Class: 1) has forbidden skill 118 for his race/class combination
CreatureAI::EngagementOver called even though creature is not currently engaged.
COMPANION_WALK_SAMPLE guid=26 owner=(-8864.85 595.94 92.65) pet=(-8866.87 594.43 92.75) distance=2.52 leashed=1
LEISURE_WAYPOINT guid=26 kind='pet_walk' reached=7/8
COMPANION_WALK_SAMPLE guid=26 owner=(-8848.84 607.93 92.64) pet=(-8850.84 606.44 92.59) distance=2.49 leashed=1
LEISURE_WAYPOINT guid=26 kind='pet_walk' reached=8/8
LEISURE_COMPLETE guid=26 kind='pet_walk' legs=8
COMPANION_END guid=26
MICRO_START guid=26 intent='leisure' location='stormwind_leisure_trade' action='leisure_view' durationMs=36000
Received not handled opcode [CMSG_USED_FOLLOW 0x7912 (30994)] from [Player: Allitester GUID Full: 0x0000000000000015 Type: Player Low: 21, Account: 1]
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,10 @@
from pathlib import Path
import os,re,subprocess,sys
c=Path('/home/wow_admin/wow_cata_server/server/debug/etc/worldserver.conf').read_text();v=re.search(r'^WorldDatabaseInfo\s*=\s*"([^"]+)"',c,re.M).group(1).split(';');h,port,u,p,d=v
a=['mysql','--protocol=TCP','--host='+h,'--port='+port,'--user='+u,'--database='+d,'--batch','--default-character-set=utf8mb4','--connect-timeout=10'];env=os.environ.copy();env['MYSQL_PWD']=p
q="SELECT entry,name FROM creature_template WHERE entry IN (7385,7386); SELECT name,map,position_x,position_y,position_z,orientation FROM game_tele WHERE name IN ('TradeDistrict','CathedralSquare'); SELECT action_key,kind,requirement FROM vcore_adventure_action WHERE action_key LIKE 'social%';"
if sys.argv[1:]==['apply']:
q='START TRANSACTION;\n'+Path('/home/wow_admin/wow_cata_server/source/TrinityCore/sql/custom/world/2026_10_27_02_adventure_leisure.sql').read_text()+"\nSELECT a.action_key,a.kind,a.requirement,i.weight FROM vcore_adventure_action a JOIN vcore_adventure_action_intent i USING(action_key) WHERE i.intent='leisure'; SELECT w.point_key,w.position_x,w.position_y,w.position_z FROM vcore_clientless_world_point w WHERE w.point_key='stormwind_leisure_trade'; COMMIT;"
elif sys.argv[1:]!=['inspect']:raise SystemExit('inspect or apply only')
r=subprocess.run(a,input=q,text=True,capture_output=True,env=env);print(r.stdout,end='');print(r.stderr.replace(p,'[redacted]'),file=sys.stderr,end='') if r.returncode else None
raise SystemExit(r.returncode)
@@ -0,0 +1,777 @@
[1/751] cd /home/wow_admin/wow_cata_server/source/TrinityCore && /usr/bin/cmake -DBUILDDIR="/home/wow_admin/wow_cata_server/build/debug" -P /home/wow_admin/wow_cata_server/source/TrinityCore/cmake/genrev.cmake /home/wow_admin/wow_cata_server/build/debug
[2/751] Building CXX object src/server/game/CMakeFiles/game.dir/World/ClientlessPerceptionMgr.cpp.o
[3/751] Building CXX object src/server/game/CMakeFiles/game.dir/World/AdventureProfessionTargets.cpp.o
[4/751] Building CXX object src/server/game/CMakeFiles/game.dir/World/ClientlessRouteRecorderMgr.cpp.o
[5/751] Building CXX object src/server/game/CMakeFiles/game.dir/World/ClientlessPlayerMgr.cpp.o
[6/751] Building CXX object src/server/game/CMakeFiles/game.dir/World/ClientlessCompanionMgr.cpp.o
[7/751] Building CXX object src/server/game/CMakeFiles/game.dir/World/ClientlessLuaEngine.cpp.o
[8/751] Building CXX object src/server/game/CMakeFiles/game.dir/World/ClientlessFlightMgr.cpp.o
[9/751] Building CXX object src/server/game/CMakeFiles/game.dir/World/ClientlessTransportMgr.cpp.o
[10/751] Building CXX object src/server/game/CMakeFiles/game.dir/World/ClientlessActionExecutorMgr.cpp.o
[11/751] Building CXX object src/server/game/CMakeFiles/game.dir/World/ClientlessWorldPointMgr.cpp.o
[12/751] Building CXX object src/server/game/CMakeFiles/game.dir/World/ClientlessTravelMgr.cpp.o
[13/751] Building CXX object src/server/game/CMakeFiles/game.dir/World/ClientlessTravelGraphMgr.cpp.o
[14/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/gen_scriptloader/static/ScriptLoader.cpp.o
[15/751] Building CXX object src/server/game/CMakeFiles/game.dir/World/WorldStates/WorldStateMgr.cpp.o
[16/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Battlefield/battlefield_script_loader.cpp.o
[17/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Battlefield/BattlefieldTB.cpp.o
[18/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_achievement.cpp.o
[19/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_ahbot.cpp.o
[20/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Battlefield/BattlefieldWG.cpp.o
[21/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_arena.cpp.o
[22/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_bf.cpp.o
[23/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_account.cpp.o
[24/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_ban.cpp.o
[25/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_cast.cpp.o
[26/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_cheat.cpp.o
[27/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_deserter.cpp.o
[28/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_character.cpp.o
[29/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_event.cpp.o
[30/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_disable.cpp.o
[31/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_gm.cpp.o
[32/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_honor.cpp.o
[33/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_group.cpp.o
[34/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_debug.cpp.o
[35/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_guild.cpp.o
[36/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_gobject.cpp.o
[37/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_go.cpp.o
[38/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_instance.cpp.o
[39/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_learn.cpp.o
[40/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_lfg.cpp.o
[41/751] Building CXX object src/server/game/CMakeFiles/game.dir/World/World.cpp.o
[42/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_message.cpp.o
[43/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_list.cpp.o
[44/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_mmaps.cpp.o
[45/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_lookup.cpp.o
[46/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_modify.cpp.o
[47/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_pet.cpp.o
[48/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_script_loader.cpp.o
[49/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_misc.cpp.o
[50/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_quest.cpp.o
[51/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_rbac.cpp.o
[52/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_npc.cpp.o
[53/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_reset.cpp.o
[54/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_send.cpp.o
[55/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Custom/custom_script_loader.cpp.o
[56/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_server.cpp.o
[57/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_titles.cpp.o
[58/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_reload.cpp.o
[59/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_ticket.cpp.o
[60/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_tele.cpp.o
[61/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Commands/cs_wp.cpp.o
[62/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/arathi_highlands.cpp.o
[63/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Custom/vcore_diagnostics.cpp.o
/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/scripts/Custom/vcore_diagnostics.cpp:236:39: warning: ‘std::optional<std::__cxx11::basic_string<char> > VCoreDiagnostics::DisplayTextColumn(const std::string&, const std::string&)’ defined but not used [-Wunused-function]
236 | static std::optional<std::string> DisplayTextColumn(
| ^~~~~~~~~~~~~~~~~
[64/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/eastern_kingdoms_script_loader.cpp.o
[65/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_dun_morogh.cpp.o
[66/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_blasted_lands.cpp.o
[67/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_dun_morogh_area_coldridge_valley.cpp.o
[68/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Custom/viano_adventurers.cpp.o
/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/scripts/Custom/viano_adventurers.cpp: In member function ‘void VianoAdventurers::npc_horde_adventurer_melee::npc_horde_adventurer_meleeAI::HandleOutOfCombatRecovery(uint32)’:
/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/scripts/Custom/viano_adventurers.cpp:310:49: warning: comparison of integer expressions of different signedness: ‘int32’ {aka ‘int’} and ‘uint32’ {aka ‘unsigned int’} [-Wsign-compare]
310 | if (maxMana > 0 && me->GetPower(POWER_MANA) < maxMana)
| ~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/scripts/Custom/viano_adventurers.cpp: In member function ‘void VianoAdventurers::npc_horde_adventurer_tank::npc_horde_adventurer_tankAI::HandleOutOfCombatRecovery(uint32)’:
/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/scripts/Custom/viano_adventurers.cpp:861:57: warning: comparison of integer expressions of different signedness: ‘int32’ {aka ‘int’} and ‘uint32’ {aka ‘unsigned int’} [-Wsign-compare]
861 | if (maxMana && me->GetPower(POWER_MANA) < maxMana)
| ~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/scripts/Custom/viano_adventurers.cpp: In member function ‘void VianoAdventurers::npc_horde_adventurer_healer::npc_horde_adventurer_healerAI::HandleOutOfCombatRecovery(uint32, Player*)’:
/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/scripts/Custom/viano_adventurers.cpp:1518:57: warning: comparison of integer expressions of different signedness: ‘int32’ {aka ‘int’} and ‘uint32’ {aka ‘unsigned int’} [-Wsign-compare]
1518 | if (maxMana && me->GetPower(POWER_MANA) < maxMana)
| ~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
[69/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_duskwood.cpp.o
[70/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_ghostlands.cpp.o
[71/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_elwynn_forest.cpp.o
[72/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_hinterlands.cpp.o
[73/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_ironforge.cpp.o
[74/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Custom/viano_bot_commands.cpp.o
/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/scripts/Custom/viano_bot_commands.cpp: In function ‘bool VianoBotCommands::HandleClientlessLocationAdd(ChatHandler*, const char*)’:
/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/scripts/Custom/viano_bot_commands.cpp:2062:20: warning: unused variable ‘loaded’ [-Wunused-variable]
2062 | bool const loaded = sClientlessWorldPointMgr->Reload();
| ^~~~~~
/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/scripts/Custom/viano_bot_commands.cpp: In function ‘bool VianoBotCommands::HandleClientlessLocationMove(ChatHandler*, const char*)’:
/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/scripts/Custom/viano_bot_commands.cpp:2099:20: warning: unused variable ‘loaded’ [-Wunused-variable]
2099 | bool const loaded = sClientlessWorldPointMgr->Reload();
| ^~~~~~
/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/scripts/Custom/viano_bot_commands.cpp: In function ‘bool VianoBotCommands::HandleClientlessLocationRemove(ChatHandler*, const char*)’:
/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/scripts/Custom/viano_bot_commands.cpp:2123:20: warning: unused variable ‘loaded’ [-Wunused-variable]
2123 | bool const loaded = sClientlessWorldPointMgr->Reload();
| ^~~~~~
[75/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_redridge_mountains.cpp.o
[76/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_silverpine_forest.cpp.o
[77/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_swamp_of_sorrows.cpp.o
[78/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_stormwind_city.cpp.o
[79/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_hillsbrad_foothills.cpp.o
[80/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_tirisfal_glades.cpp.o
[81/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_tol_barad.cpp.o
[82/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_undercity.cpp.o
[83/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_western_plaguelands.cpp.o
[84/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/AlteracValley/alterac_valley.cpp.o
[85/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/AlteracValley/boss_balinda.cpp.o
[86/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_wetlands.cpp.o
[87/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/AlteracValley/boss_drekthar.cpp.o
[88/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_twilight_highlands.cpp.o
[89/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/AlteracValley/boss_galvangar.cpp.o
[90/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/zone_westfall.cpp.o
[91/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/AlteracValley/boss_vanndar.cpp.o
[92/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BaradinHold/instance_baradin_hold.cpp.o
[93/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BaradinHold/boss_alizabal.cpp.o
[94/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BaradinHold/boss_occuthar.cpp.o
[95/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BaradinHold/boss_pit_lord_argaloth.cpp.o
[96/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BastionOfTwilight/bastion_of_twilight.cpp.o
[97/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BastionOfTwilight/instance_bastion_of_twilight.cpp.o
[98/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BastionOfTwilight/boss_halfus_wyrmbreaker.cpp.o
[99/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BastionOfTwilight/boss_chogall.cpp.o
[100/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockCaverns/boss_beauty.cpp.o
[101/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockCaverns/blackrock_caverns.cpp.o
[102/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockCaverns/boss_ascendant_lord_obsidius.cpp.o
[103/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BastionOfTwilight/boss_ascendant_council.cpp.o
[104/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockCaverns/boss_corla_herald_of_twilight.cpp.o
[105/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BastionOfTwilight/boss_theralion_and_valiona.cpp.o
[106/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockCaverns/boss_karsh_steelbender.cpp.o
[107/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockCaverns/instance_blackrock_caverns.cpp.o
[108/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockCaverns/boss_romogg_bonecrusher.cpp.o
[109/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.cpp.o
[110/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_ambassador_flamelash.cpp.o
[111/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_general_angerforge.cpp.o
[112/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_emperor_dagran_thaurissan.cpp.o
[113/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_magmus.cpp.o
[114/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_moira_bronzebeard.cpp.o
[115/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_high_interrogator_gerstahn.cpp.o
[116/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_tomb_of_seven.cpp.o
[117/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockDepths/boss_coren_direbrew.cpp.o
[118/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_drakkisath.cpp.o
[119/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_gizrul_the_slavener.cpp.o
[120/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockDepths/instance_blackrock_depths.cpp.o
[121/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_halycon.cpp.o
[122/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_gyth.cpp.o
[123/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_highlord_omokk.cpp.o
[124/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_lord_valthalak.cpp.o
[125/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_mother_smolderweb.cpp.o
[126/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_overlord_wyrmthalak.cpp.o
[127/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_pyroguard_emberseer.cpp.o
[128/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_quartermaster_zigris.cpp.o
[129/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_rend_blackhand.cpp.o
[130/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_shadow_hunter_voshgajin.cpp.o
[131/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_the_beast.cpp.o
[132/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_urok_doomhowl.cpp.o
[133/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_warmaster_voone.cpp.o
[134/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackrockSpire/instance_blackrock_spire.cpp.o
[135/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackwingDescent/blackwing_descent.cpp.o
[136/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackwingDescent/instance_blackwing_descent.cpp.o
[137/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_broodlord_lashlayer.cpp.o
[138/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackwingDescent/boss_atramedes.cpp.o
[139/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_chromaggus.cpp.o
[140/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackwingDescent/boss_chimaeron.cpp.o
[141/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackwingDescent/boss_magmaw.cpp.o
[142/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackwingDescent/boss_maloriak.cpp.o
[143/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_ebonroc.cpp.o
[144/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackwingDescent/boss_omnotron_defense_system.cpp.o
[145/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_firemaw.cpp.o
[146/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_flamegor.cpp.o
[147/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackwingDescent/boss_nefarians_end.cpp.o
[148/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_nefarian.cpp.o
[149/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_razorgore.cpp.o
[150/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_vaelastrasz.cpp.o
[151/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/BlackwingLair/instance_blackwing_lair.cpp.o
[152/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/MoltenCore/boss_garr.cpp.o
[153/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/MoltenCore/boss_baron_geddon.cpp.o
[154/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/MoltenCore/boss_gehennas.cpp.o
[155/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/MoltenCore/boss_lucifron.cpp.o
[156/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/MoltenCore/boss_golemagg.cpp.o
[157/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/MoltenCore/boss_magmadar.cpp.o
[158/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/MoltenCore/boss_majordomo_executus.cpp.o
[159/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/MoltenCore/boss_ragnaros.cpp.o
[160/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/MoltenCore/boss_sulfuron_harbinger.cpp.o
[161/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/MoltenCore/instance_molten_core.cpp.o
[162/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/BlackrockMountain/MoltenCore/boss_shazzrah.cpp.o
[163/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Deadmines/boss_admiral_ripsnarl.cpp.o
[164/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Deadmines/boss_captain_cookie.cpp.o
[165/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Deadmines/boss_glubtok.cpp.o
[166/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Deadmines/boss_foe_reaper_5000.cpp.o
[167/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Deadmines/boss_vanessa_van_cleef.cpp.o
[168/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Deadmines/deadmines.cpp.o
[169/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Gilneas/gilneas_chapter_1.cpp.o
[170/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Deadmines/instance_deadmines.cpp.o
[171/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Deadmines/boss_helix_gearbreaker.cpp.o
[172/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Gilneas/gilneas_chapter_2.cpp.o
[173/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Gnomeregan/gnomeregan.cpp.o
[174/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Gnomeregan/instance_gnomeregan.cpp.o
[175/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/GrimBatol/boss_drahga_shadowburner.cpp.o
[176/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/GrimBatol/boss_erudax.cpp.o
[177/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/GrimBatol/grim_batol.cpp.o
[178/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/GrimBatol/instance_grim_batol.cpp.o
[179/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Karazhan/boss_curator.cpp.o
[180/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/GrimBatol/boss_general_umbriss.cpp.o
[181/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Karazhan/boss_maiden_of_virtue.cpp.o
[182/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/GrimBatol/boss_forgemaster_throngus.cpp.o
[183/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Karazhan/boss_midnight.cpp.o
[184/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Karazhan/boss_moroes.cpp.o
[185/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Karazhan/boss_netherspite.cpp.o
[186/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Karazhan/boss_shade_of_aran.cpp.o
[187/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Karazhan/boss_prince_malchezaar.cpp.o
[188/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Karazhan/boss_nightbane.cpp.o
[189/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Karazhan/instance_karazhan.cpp.o
[190/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Karazhan/boss_terestian_illhoof.cpp.o
[191/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Karazhan/bosses_opera.cpp.o
[192/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Karazhan/karazhan.cpp.o
[193/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/MagistersTerrace/boss_priestess_delrissa.cpp.o
[194/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/MagistersTerrace/boss_selin_fireheart.cpp.o
[195/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp.o
[196/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/MagistersTerrace/boss_vexallus.cpp.o
[197/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/MagistersTerrace/instance_magisters_terrace.cpp.o
[198/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ScarletEnclave/the_scarlet_enclave_chapter_2.cpp.o
[199/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/MagistersTerrace/magisters_terrace.cpp.o
[200/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ScarletEnclave/zone_the_scarlet_enclave.cpp.o
[201/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ScarletEnclave/the_scarlet_enclave_chapter_5.cpp.o
[202/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ScarletMonastery/boss_arcanist_doan.cpp.o
[203/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ScarletMonastery/boss_azshir_the_sleepless.cpp.o
[204/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ScarletEnclave/the_scarlet_enclave_chapter_1.cpp.o
[205/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ScarletMonastery/boss_bloodmage_thalnos.cpp.o
[206/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ScarletMonastery/boss_herod.cpp.o
[207/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ScarletMonastery/boss_high_inquisitor_fairbanks.cpp.o
[208/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ScarletMonastery/boss_houndmaster_loksey.cpp.o
[209/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ScarletMonastery/boss_headless_horseman.cpp.o
[210/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ScarletMonastery/boss_interrogator_vishas.cpp.o
[211/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ScarletMonastery/boss_scorn.cpp.o
[212/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ScarletMonastery/boss_mograine_and_whitemane.cpp.o
[213/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ScarletMonastery/instance_scarlet_monastery.cpp.o
[214/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Scholomance/boss_death_knight_darkreaver.cpp.o
[215/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Scholomance/boss_darkmaster_gandling.cpp.o
[216/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Scholomance/boss_doctor_theolen_krastinov.cpp.o
[217/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Scholomance/boss_illucia_barov.cpp.o
[218/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Scholomance/boss_instructor_malicia.cpp.o
[219/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Scholomance/boss_jandice_barov.cpp.o
[220/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Scholomance/boss_kirtonos_the_herald.cpp.o
[221/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Scholomance/boss_lord_alexei_barov.cpp.o
[222/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Scholomance/boss_kormok.cpp.o
[223/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Scholomance/boss_ras_frostwhisper.cpp.o
[224/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Scholomance/boss_lorekeeper_polkelt.cpp.o
[225/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Scholomance/boss_the_ravenian.cpp.o
[226/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Scholomance/boss_vectus.cpp.o
[227/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Scholomance/instance_scholomance.cpp.o
[228/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ShadowfangKeep/boss_baron_ashbury.cpp.o
[229/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ShadowfangKeep/boss_baron_silverlaine.cpp.o
[230/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ShadowfangKeep/instance_shadowfang_keep.cpp.o
[231/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ShadowfangKeep/boss_apothecary_hummel.cpp.o
[232/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ShadowfangKeep/boss_commander_springvale.cpp.o
[233/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ShadowfangKeep/shadowfang_keep.cpp.o
[234/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ShadowfangKeep/boss_lord_godfrey.cpp.o
[235/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ShadowfangKeep/boss_lord_walden.cpp.o
[236/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Stratholme/boss_balnazzar.cpp.o
[237/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Stratholme/boss_baron_rivendare.cpp.o
[238/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Stratholme/boss_baroness_anastari.cpp.o
[239/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Stratholme/boss_cannon_master_willey.cpp.o
[240/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Stratholme/boss_magistrate_barthilas.cpp.o
[241/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Stratholme/boss_nerubenkan.cpp.o
[242/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Stratholme/boss_maleki_the_pallid.cpp.o
[243/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Stratholme/boss_order_of_silver_hand.cpp.o
[244/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Stratholme/boss_ramstein_the_gorger.cpp.o
[245/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Stratholme/boss_postmaster_malown.cpp.o
[246/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Stratholme/boss_timmy_the_cruel.cpp.o
[247/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Stratholme/instance_stratholme.cpp.o
[248/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/SunkenTemple/instance_sunken_temple.cpp.o
[249/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/SunkenTemple/sunken_temple.cpp.o
[250/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Stratholme/stratholme.cpp.o
[251/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/SunwellPlateau/boss_eredar_twins.cpp.o
[252/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/SunwellPlateau/boss_felmyst.cpp.o
[253/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/SunwellPlateau/boss_brutallus.cpp.o
[254/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/SunwellPlateau/boss_kiljaeden.cpp.o
[255/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/SunwellPlateau/boss_kalecgos.cpp.o
[256/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/SunwellPlateau/sunwell_plateau.cpp.o
[257/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/SunwellPlateau/boss_muru.cpp.o
[258/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/TheStockade/boss_hogger.cpp.o
[259/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/TheStockade/boss_lord_overheat.cpp.o
[260/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/TheStockade/boss_randolph_moloch.cpp.o
[261/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/SunwellPlateau/instance_sunwell_plateau.cpp.o
[262/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/TheStockade/instance_the_stockade.cpp.o
[263/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ThroneOfTheTides/boss_commander_ulthok.cpp.o
[264/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ThroneOfTheTides/boss_mindbender_gursha.cpp.o
[265/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ThroneOfTheTides/boss_lady_nazjar.cpp.o
[266/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ThroneOfTheTides/instance_throne_of_the_tides.cpp.o
[267/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Uldaman/boss_ironaya.cpp.o
[268/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Uldaman/boss_archaedas.cpp.o
[269/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ThroneOfTheTides/throne_of_the_tides.cpp.o
[270/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Uldaman/uldaman.cpp.o
[271/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ThroneOfTheTides/boss_ozumat.cpp.o
[272/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/Uldaman/instance_uldaman.cpp.o
[273/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulAman/boss_daakara.cpp.o
[274/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulAman/boss_halazzi.cpp.o
[275/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulAman/boss_hexlord.cpp.o
[276/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulAman/boss_nalorakk.cpp.o
[277/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulAman/boss_akilzon.cpp.o
[278/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulAman/instance_zulaman.cpp.o
[279/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulAman/zulaman.cpp.o
[280/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulAman/boss_janalai.cpp.o
[281/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulGurub/boss_grilek.cpp.o
[282/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulGurub/boss_hazzarah.cpp.o
[283/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulGurub/boss_bloodlord_mandokir.cpp.o
[284/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulGurub/boss_renataki.cpp.o
[285/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulGurub/boss_jindo_the_godbreaker.cpp.o
[286/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulGurub/boss_wushoolay.cpp.o
[287/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulGurub/boss_high_priestess_kilnara.cpp.o
[288/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulGurub/boss_high_priest_venoxis.cpp.o
[289/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulGurub/instance_zulgurub.cpp.o
[290/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Events/childrens_week.cpp.o
[291/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Events/events_script_loader.cpp.o
[292/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulGurub/boss_zanzil.cpp.o
[293/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/EasternKingdoms/ZulGurub/zulgurub.cpp.o
[294/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Events/fireworks_show.cpp.o
[295/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/kalimdor_script_loader.cpp.o
[296/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/zone_ashenvale.cpp.o
[297/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/zone_azuremyst_isle.cpp.o
[298/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/zone_desolace.cpp.o
[299/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/zone_bloodmyst_isle.cpp.o
[300/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/zone_durotar.cpp.o
[301/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/zone_moonglade.cpp.o
[302/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/zone_felwood.cpp.o
[303/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/zone_mulgore.cpp.o
[304/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/zone_northern_barrens.cpp.o
[305/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/zone_southern_barrens.cpp.o
[306/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/zone_mount_hyjal.cpp.o
[307/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/zone_silithus.cpp.o
[308/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/zone_tanaris.cpp.o
[309/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/zone_thunder_bluff.cpp.o
[310/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/zone_ungoro_crater.cpp.o
[311/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/zone_uldum.cpp.o
[312/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/zone_winterspring.cpp.o
[313/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/BlackfathomDeeps/blackfathom_deeps.cpp.o
[314/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/BlackfathomDeeps/boss_aku_mai.cpp.o
[315/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/BlackfathomDeeps/boss_gelihast.cpp.o
[316/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/BlackfathomDeeps/boss_kelris.cpp.o
[317/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/BlackfathomDeeps/instance_blackfathom_deeps.cpp.o
[318/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_archimonde.cpp.o
[319/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_anetheron.cpp.o
[320/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_azgalor.cpp.o
[321/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_rage_winterchill.cpp.o
[322/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_kazrogal.cpp.o
[323/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjal_trash.cpp.o
[324/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjal.cpp.o
[325/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjalAI.cpp.o
[326/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/CullingOfStratholme/boss_chrono_lord_epoch.cpp.o
[327/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/CullingOfStratholme/boss_infinite_corruptor.cpp.o
[328/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/CullingOfStratholme/boss_meathook.cpp.o
[329/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/BattleForMountHyjal/instance_hyjal.cpp.o
[330/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/CullingOfStratholme/boss_mal_ganis.cpp.o
[331/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/CullingOfStratholme/boss_salramm_the_fleshcrafter.cpp.o
[332/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/CullingOfStratholme/culling_of_stratholme.cpp.o
[333/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/DragonSoul/instance_dragon_soul.cpp.o
[334/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/EndTime/end_time.cpp.o
[335/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/EndTime/instance_end_time.cpp.o
[336/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/CullingOfStratholme/instance_culling_of_stratholme.cpp.o
[337/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/EndTime/boss_murozond.cpp.o
[338/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/CullingOfStratholme/npc_arthas.cpp.o
[339/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/EscapeFromDurnholdeKeep/boss_captain_skarloc.cpp.o
[340/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/EndTime/boss_echo_of_jaina.cpp.o
[341/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/EscapeFromDurnholdeKeep/boss_epoch_hunter.cpp.o
[342/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/EscapeFromDurnholdeKeep/boss_leutenant_drake.cpp.o
[343/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/EscapeFromDurnholdeKeep/old_hillsbrad.cpp.o
[344/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/DragonSoul/boss_madness_of_deathwing.cpp.o
[345/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/TheBlackMorass/boss_aeonus.cpp.o
[346/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/HourOfTwilight/instance_hour_of_twilight.cpp.o
[347/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/TheBlackMorass/boss_chrono_lord_deja.cpp.o
[348/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/TheBlackMorass/boss_temporus.cpp.o
[349/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/EscapeFromDurnholdeKeep/instance_old_hillsbrad.cpp.o
[350/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/WellOfEternity/instance_well_of_eternity.cpp.o
[351/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/TheBlackMorass/instance_the_black_morass.cpp.o
[352/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/CavernsOfTime/TheBlackMorass/the_black_morass.cpp.o
[353/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/DireMaul/instance_dire_maul.cpp.o
[354/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/Firelands/firelands.cpp.o
[355/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/Firelands/boss_alysrazor.cpp.o
[356/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/Firelands/boss_baleroc.cpp.o
[357/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/Firelands/instance_firelands.cpp.o
[358/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/HallsOfOrigination/boss_ammunae.cpp.o
[359/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/Firelands/boss_majordomo_staghelm.cpp.o
[360/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/HallsOfOrigination/boss_anraphet.cpp.o
[361/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/Firelands/boss_lord_rhyolith.cpp.o
[362/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/HallsOfOrigination/boss_earthrager_ptah.cpp.o
[363/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/HallsOfOrigination/boss_isiset.cpp.o
[364/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/HallsOfOrigination/boss_rajh.cpp.o
[365/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/HallsOfOrigination/boss_setesh.cpp.o
[366/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/HallsOfOrigination/instance_halls_of_origination.cpp.o
[367/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/Firelands/boss_ragnaros_firelands.cpp.o
[368/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/HallsOfOrigination/boss_temple_guardian_anhuur.cpp.o
[369/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/HallsOfOrigination/halls_of_origination.cpp.o
[370/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/LostCityOfTheTolvir/boss_lockmaw_and_augh.cpp.o
[371/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/LostCityOfTheTolvir/boss_general_husam.cpp.o
[372/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/LostCityOfTheTolvir/boss_high_prophet_barim.cpp.o
[373/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/LostCityOfTheTolvir/instance_lost_city_of_the_tolvir.cpp.o
[374/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/LostCityOfTheTolvir/lost_city_of_the_tolvir.cpp.o
[375/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/Maraudon/boss_celebras_the_cursed.cpp.o
[376/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/Maraudon/boss_landslide.cpp.o
[377/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/LostCityOfTheTolvir/boss_siamat.cpp.o
[378/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/Maraudon/boss_noxxion.cpp.o
[379/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/Maraudon/boss_princess_theradras.cpp.o
[380/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/Maraudon/instance_maraudon.cpp.o
[381/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/OnyxiasLair/boss_onyxia.cpp.o
[382/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/RagefireChasm/instance_ragefire_chasm.cpp.o
[383/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/RazorfenDowns/boss_amnennar_the_coldbringer.cpp.o
[384/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/OnyxiasLair/instance_onyxias_lair.cpp.o
[385/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/RazorfenDowns/boss_glutton.cpp.o
[386/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/RazorfenDowns/boss_mordresh_fire_eye.cpp.o
[387/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/RazorfenDowns/boss_tuten_kash.cpp.o
[388/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/RazorfenDowns/instance_razorfen_downs.cpp.o
[389/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/RazorfenDowns/razorfen_downs.cpp.o
[390/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/RazorfenKraul/instance_razorfen_kraul.cpp.o
[391/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/RazorfenKraul/razorfen_kraul.cpp.o
[392/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/RuinsOfAhnQiraj/boss_ayamiss.cpp.o
[393/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/RuinsOfAhnQiraj/boss_kurinnaxx.cpp.o
[394/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/RuinsOfAhnQiraj/boss_moam.cpp.o
[395/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/RuinsOfAhnQiraj/boss_buru.cpp.o
[396/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/RuinsOfAhnQiraj/boss_ossirian.cpp.o
[397/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/RuinsOfAhnQiraj/boss_rajaxx.cpp.o
[398/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/RuinsOfAhnQiraj/instance_ruins_of_ahnqiraj.cpp.o
[399/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/TempleOfAhnQiraj/boss_bug_trio.cpp.o
[400/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/TempleOfAhnQiraj/boss_fankriss.cpp.o
[401/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/TempleOfAhnQiraj/boss_cthun.cpp.o
[402/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/TempleOfAhnQiraj/boss_huhuran.cpp.o
[403/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/TempleOfAhnQiraj/boss_ouro.cpp.o
[404/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/TempleOfAhnQiraj/boss_sartura.cpp.o
[405/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/TempleOfAhnQiraj/boss_twinemperors.cpp.o
[406/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/TempleOfAhnQiraj/boss_skeram.cpp.o
[407/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/TempleOfAhnQiraj/boss_viscidus.cpp.o
[408/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/TempleOfAhnQiraj/instance_temple_of_ahnqiraj.cpp.o
[409/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/TempleOfAhnQiraj/mob_anubisath_sentinel.cpp.o
[410/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/ThroneOfTheFourWinds/instance_throne_of_the_four_winds.cpp.o
[411/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/ThroneOfTheFourWinds/throne_of_the_four_winds.cpp.o
[412/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/VortexPinnacle/boss_grand_vizier_ertan.cpp.o
[413/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/VortexPinnacle/instance_vortex_pinnacle.cpp.o
[414/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/VortexPinnacle/boss_altairus.cpp.o
[415/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/ThroneOfTheFourWinds/boss_conclave_of_wind.cpp.o
[416/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/ThroneOfTheFourWinds/boss_alakir.cpp.o
[417/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/WailingCaverns/boss_kresh.cpp.o
[418/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/VortexPinnacle/boss_asaad.cpp.o
[419/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/WailingCaverns/boss_lady_anacondra.cpp.o
[420/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/WailingCaverns/boss_lord_cobrahn.cpp.o
[421/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/VortexPinnacle/vortex_pinnacle.cpp.o
[422/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/WailingCaverns/boss_lord_pythas.cpp.o
[423/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/WailingCaverns/boss_lord_serpentis.cpp.o
[424/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/WailingCaverns/boss_skum.cpp.o
[425/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/WailingCaverns/boss_verdan_the_everliving.cpp.o
[426/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/WailingCaverns/instance_wailing_caverns.cpp.o
[427/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/WailingCaverns/wailing_caverns.cpp.o
[428/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/ZulFarrak/boss_zum_rah.cpp.o
[429/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Maelstrom/kezan.cpp.o
[430/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/ZulFarrak/instance_zulfarrak.cpp.o
[431/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Kalimdor/ZulFarrak/zulfarrak.cpp.o
[432/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Maelstrom/maelstrom_script_loader.cpp.o
[433/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Maelstrom/zone_deepholm.cpp.o
[434/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Maelstrom/Stonecore/instance_stonecore.cpp.o
[435/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Maelstrom/Stonecore/boss_corborus.cpp.o
[436/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Maelstrom/Stonecore/boss_slabhide.cpp.o
[437/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Maelstrom/Stonecore/boss_ozruk.cpp.o
[438/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/northrend_script_loader.cpp.o
[439/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Maelstrom/Stonecore/boss_high_priestess_azil.cpp.o
[440/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/zone_crystalsong_forest.cpp.o
[441/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Maelstrom/Stonecore/stonecore.cpp.o
[442/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/zone_dalaran.cpp.o
[443/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/zone_borean_tundra.cpp.o
[444/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/zone_icecrown.cpp.o
[445/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/zone_howling_fjord.cpp.o
[446/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/zone_dragonblight.cpp.o
[447/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/zone_grizzly_hills.cpp.o
[448/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/zone_sholazar_basin.cpp.o
[449/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/zone_wintergrasp.cpp.o
[450/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/AzjolNerub/Ahnkahet/boss_amanitar.cpp.o
[451/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/zone_storm_peaks.cpp.o
[452/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/zone_zuldrak.cpp.o
[453/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/AzjolNerub/Ahnkahet/boss_herald_volazj.cpp.o
[454/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/AzjolNerub/Ahnkahet/boss_jedoga_shadowseeker.cpp.o
[455/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/AzjolNerub/Ahnkahet/boss_elder_nadox.cpp.o
[456/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/AzjolNerub/Ahnkahet/boss_prince_taldaram.cpp.o
[457/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/AzjolNerub/Ahnkahet/instance_ahnkahet.cpp.o
[458/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/AzjolNerub/AzjolNerub/instance_azjol_nerub.cpp.o
[459/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp.o
[460/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/ChamberOfAspects/ObsidianSanctum/boss_sartharion.cpp.o
[461/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp.o
[462/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp.o
[463/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/ChamberOfAspects/ObsidianSanctum/instance_obsidian_sanctum.cpp.o
[464/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/ChamberOfAspects/ObsidianSanctum/obsidian_sanctum.cpp.o
[465/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/ChamberOfAspects/RubySanctum/boss_baltharus_the_warborn.cpp.o
[466/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/ChamberOfAspects/RubySanctum/boss_general_zarithrian.cpp.o
[467/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/ChamberOfAspects/RubySanctum/instance_ruby_sanctum.cpp.o
[468/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/ChamberOfAspects/RubySanctum/boss_saviana_ragefire.cpp.o
[469/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/CrusadersColiseum/TrialOfTheChampion/boss_black_knight.cpp.o
[470/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/CrusadersColiseum/TrialOfTheChampion/boss_grand_champions.cpp.o
[471/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/ChamberOfAspects/RubySanctum/ruby_sanctum.cpp.o
[472/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/CrusadersColiseum/TrialOfTheChampion/boss_argent_challenge.cpp.o
[473/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/ChamberOfAspects/RubySanctum/boss_halion.cpp.o
[474/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/CrusadersColiseum/TrialOfTheChampion/trial_of_the_champion.cpp.o
[475/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/CrusadersColiseum/TrialOfTheChampion/instance_trial_of_the_champion.cpp.o
[476/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_anubarak_trial.cpp.o
[477/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/CrusadersColiseum/TrialOfTheCrusader/trial_of_the_crusader.cpp.o
[478/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/DraktharonKeep/boss_king_dred.cpp.o
[479/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/DraktharonKeep/boss_novos.cpp.o
[480/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp.o
[481/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_lord_jaraxxus.cpp.o
[482/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_twin_valkyr.cpp.o
[483/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp.o
[484/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_northrend_beasts.cpp.o
[485/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/DraktharonKeep/boss_tharon_ja.cpp.o
[486/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/DraktharonKeep/boss_trollgore.cpp.o
[487/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/DraktharonKeep/instance_drak_tharon_keep.cpp.o
[488/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/FrozenHalls/ForgeOfSouls/forge_of_souls.cpp.o
[489/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/FrozenHalls/HallsOfReflection/boss_horAI.cpp.o
[490/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/FrozenHalls/ForgeOfSouls/instance_forge_of_souls.cpp.o
[491/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/FrozenHalls/HallsOfReflection/boss_falric.cpp.o
[492/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/FrozenHalls/ForgeOfSouls/boss_bronjahm.cpp.o
[493/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/FrozenHalls/HallsOfReflection/boss_marwyn.cpp.o
[494/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/FrozenHalls/ForgeOfSouls/boss_devourer_of_souls.cpp.o
[495/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp.o
[496/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/FrozenHalls/HallsOfReflection/instance_halls_of_reflection.cpp.o
[497/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/FrozenHalls/PitOfSaron/boss_forgemaster_garfrost.cpp.o
[498/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/FrozenHalls/PitOfSaron/instance_pit_of_saron.cpp.o
[499/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Gundrak/boss_drakkari_colossus.cpp.o
[500/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Gundrak/boss_eck.cpp.o
[501/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/FrozenHalls/PitOfSaron/boss_scourgelord_tyrannus.cpp.o
[502/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/FrozenHalls/PitOfSaron/boss_krickandick.cpp.o
[503/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/FrozenHalls/PitOfSaron/pit_of_saron.cpp.o
[504/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Gundrak/boss_gal_darah.cpp.o
[505/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Gundrak/boss_moorabi.cpp.o
[506/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Gundrak/boss_slad_ran.cpp.o
[507/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Gundrak/instance_gundrak.cpp.o
[508/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/IcecrownCitadel/boss_festergut.cpp.o
[509/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp.o
[510/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/IcecrownCitadel/boss_blood_queen_lana_thel.cpp.o
[511/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/IcecrownCitadel/boss_lord_marrowgar.cpp.o
[512/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/IcecrownCitadel/boss_deathbringer_saurfang.cpp.o
[513/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/IcecrownCitadel/boss_lady_deathwhisper.cpp.o
[514/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/IcecrownCitadel/boss_rotface.cpp.o
[515/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/IcecrownCitadel/boss_icecrown_gunship_battle.cpp.o
[516/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/IcecrownCitadel/boss_professor_putricide.cpp.o
[517/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/IcecrownCitadel/icecrown_citadel_teleport.cpp.o
[518/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/IsleOfConquest/boss_ioc_horde_alliance.cpp.o
[519/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/IcecrownCitadel/boss_sindragosa.cpp.o
[520/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp.o
[521/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/IcecrownCitadel/icecrown_citadel.cpp.o
[522/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Naxxramas/boss_anubrekhan.cpp.o
[523/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp.o
[524/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/IsleOfConquest/isle_of_conquest.cpp.o
[525/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Naxxramas/boss_faerlina.cpp.o
[526/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/IcecrownCitadel/boss_the_lich_king.cpp.o
[527/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Naxxramas/boss_gluth.cpp.o
[528/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Naxxramas/boss_grobbulus.cpp.o
[529/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Naxxramas/boss_four_horsemen.cpp.o
[530/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Naxxramas/boss_heigan.cpp.o
[531/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Naxxramas/boss_gothik.cpp.o
[532/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Naxxramas/boss_loatheb.cpp.o
[533/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Naxxramas/boss_maexxna.cpp.o
[534/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Naxxramas/boss_noth.cpp.o
[535/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Naxxramas/boss_kelthuzad.cpp.o
[536/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Naxxramas/boss_patchwerk.cpp.o
[537/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Naxxramas/boss_razuvious.cpp.o
[538/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Naxxramas/instance_naxxramas.cpp.o
[539/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Naxxramas/naxxramas.cpp.o
[540/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Naxxramas/boss_thaddius.cpp.o
[541/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Nexus/Nexus/boss_anomalus.cpp.o
[542/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Nexus/Nexus/boss_commander_stoutbeard.cpp.o
[543/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Nexus/EyeOfEternity/instance_eye_of_eternity.cpp.o
[544/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Naxxramas/boss_sapphiron.cpp.o
[545/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Nexus/Nexus/boss_keristrasza.cpp.o
[546/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Nexus/Nexus/boss_magus_telestra.cpp.o
[547/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Nexus/EyeOfEternity/boss_malygos.cpp.o
[548/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Nexus/Nexus/boss_nexus_commanders.cpp.o
[549/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Nexus/Nexus/instance_nexus.cpp.o
[550/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Nexus/Nexus/boss_ormorok.cpp.o
[551/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Nexus/Oculus/boss_drakos.cpp.o
[552/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Nexus/Oculus/boss_urom.cpp.o
[553/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Nexus/Oculus/boss_eregos.cpp.o
[554/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Nexus/Oculus/boss_varos.cpp.o
[555/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Nexus/Oculus/instance_oculus.cpp.o
[556/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/HallsOfLightning/boss_ionar.cpp.o
[557/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Nexus/Oculus/oculus.cpp.o
[558/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/HallsOfLightning/instance_halls_of_lightning.cpp.o
[559/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/HallsOfLightning/boss_loken.cpp.o
[560/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/HallsOfLightning/boss_general_bjarngrim.cpp.o
[561/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/HallsOfStone/boss_krystallus.cpp.o
[562/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/HallsOfStone/boss_maiden_of_grief.cpp.o
[563/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/HallsOfLightning/boss_volkhan.cpp.o
[564/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/HallsOfStone/boss_sjonnir.cpp.o
[565/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/HallsOfStone/halls_of_stone.cpp.o
[566/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/HallsOfStone/instance_halls_of_stone.cpp.o
[567/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/Ulduar/boss_auriaya.cpp.o
[568/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/Ulduar/boss_freya.cpp.o
[569/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/Ulduar/boss_algalon_the_observer.cpp.o
[570/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/Ulduar/boss_general_vezax.cpp.o
[571/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/Ulduar/boss_ignis.cpp.o
[572/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/Ulduar/boss_assembly_of_iron.cpp.o
[573/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/Ulduar/boss_hodir.cpp.o
[574/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp.o
[575/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/Ulduar/boss_kologarn.cpp.o
[576/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/Ulduar/boss_razorscale.cpp.o
[577/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/UtgardeKeep/UtgardeKeep/boss_skarvald_dalronn.cpp.o
[578/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/Ulduar/instance_ulduar.cpp.o
[579/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/UtgardeKeep/UtgardeKeep/boss_keleseth.cpp.o
[580/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp.o
[581/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/UtgardeKeep/UtgardeKeep/instance_utgarde_keep.cpp.o
[582/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/Ulduar/boss_mimiron.cpp.o
[583/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/Ulduar/boss_xt002.cpp.o
[584/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/Ulduar/boss_thorim.cpp.o
[585/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.cpp.o
[586/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/UtgardeKeep/UtgardePinnacle/boss_ymiron.cpp.o
[587/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/UtgardeKeep/UtgardePinnacle/instance_utgarde_pinnacle.cpp.o
[588/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/UtgardeKeep/UtgardePinnacle/boss_svala.cpp.o
[589/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/Ulduar/Ulduar/boss_yogg_saron.cpp.o
[590/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/UtgardeKeep/UtgardePinnacle/boss_palehoof.cpp.o
[591/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/VaultOfArchavon/boss_emalon.cpp.o
[592/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/VaultOfArchavon/boss_archavon.cpp.o
[593/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp.o
[594/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/VaultOfArchavon/boss_koralon.cpp.o
[595/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/VaultOfArchavon/boss_toravon.cpp.o
[596/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/VaultOfArchavon/instance_vault_of_archavon.cpp.o
[597/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/VioletHold/boss_lavanthor.cpp.o
[598/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/VioletHold/boss_erekem.cpp.o
[599/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/VioletHold/boss_cyanigosa.cpp.o
[600/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/VioletHold/boss_ichoron.cpp.o
[601/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/VioletHold/boss_moragg.cpp.o
[602/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/VioletHold/boss_xevozz.cpp.o
[603/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/VioletHold/boss_zuramat.cpp.o
[604/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/VioletHold/instance_violet_hold.cpp.o
[605/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/OutdoorPvP/OutdoorPvPHP.cpp.o
[606/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/OutdoorPvP/OutdoorPvPScriptLoader.cpp.o
[607/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/OutdoorPvP/OutdoorPvPTF.cpp.o
[608/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Northrend/VioletHold/violet_hold.cpp.o
[609/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/OutdoorPvP/OutdoorPvPNA.cpp.o
[610/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/boss_doomwalker.cpp.o
[611/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/OutdoorPvP/OutdoorPvPSI.cpp.o
[612/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/boss_doomlord_kazzak.cpp.o
[613/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/OutdoorPvP/OutdoorPvPZM.cpp.o
[614/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/outland_script_loader.cpp.o
[615/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/zone_blades_edge_mountains.cpp.o
[616/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/zone_nagrand.cpp.o
[617/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/zone_shattrath_city.cpp.o
[618/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/zone_hellfire_peninsula.cpp.o
[619/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/zone_zangarmarsh.cpp.o
[620/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/Auchindoun/AuchenaiCrypts/boss_exarch_maladaar.cpp.o
[621/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/zone_terokkar_forest.cpp.o
[622/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/Auchindoun/AuchenaiCrypts/boss_shirrak_the_dead_watcher.cpp.o
[623/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/zone_shadowmoon_valley.cpp.o
[624/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/zone_netherstorm.cpp.o
[625/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/Auchindoun/AuchenaiCrypts/instance_auchenai_crypts.cpp.o
[626/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/Auchindoun/ManaTombs/boss_nexusprince_shaffar.cpp.o
[627/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/Auchindoun/ManaTombs/boss_pandemonius.cpp.o
[628/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/Auchindoun/ManaTombs/instance_mana_tombs.cpp.o
[629/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/Auchindoun/SethekkHalls/boss_anzu.cpp.o
[630/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/Auchindoun/SethekkHalls/boss_darkweaver_syth.cpp.o
[631/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/Auchindoun/ShadowLabyrinth/boss_ambassador_hellmaw.cpp.o
[632/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/Auchindoun/SethekkHalls/instance_sethekk_halls.cpp.o
[633/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/Auchindoun/SethekkHalls/boss_talon_king_ikiss.cpp.o
[634/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/Auchindoun/ShadowLabyrinth/boss_blackheart_the_inciter.cpp.o
[635/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/Auchindoun/ShadowLabyrinth/boss_grandmaster_vorpil.cpp.o
[636/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/Auchindoun/ShadowLabyrinth/instance_shadow_labyrinth.cpp.o
[637/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/Auchindoun/ShadowLabyrinth/boss_murmur.cpp.o
[638/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/Auchindoun/ShadowLabyrinth/shadow_labyrinth.cpp.o
[639/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/BlackTemple/boss_gurtogg_bloodboil.cpp.o
[640/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/BlackTemple/black_temple.cpp.o
[641/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/BlackTemple/boss_mother_shahraz.cpp.o
[642/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/BlackTemple/boss_illidari_council.cpp.o
[643/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/BlackTemple/boss_reliquary_of_souls.cpp.o
[644/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/BlackTemple/boss_supremus.cpp.o
[645/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/BlackTemple/boss_shade_of_akama.cpp.o
[646/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/BlackTemple/boss_illidan.cpp.o
[647/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/BlackTemple/boss_warlord_najentus.cpp.o
[648/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/BlackTemple/instance_black_temple.cpp.o
[649/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/SerpentShrine/boss_fathomlord_karathress.cpp.o
[650/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/BlackTemple/boss_teron_gorefiend.cpp.o
[651/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/SerpentShrine/boss_hydross_the_unstable.cpp.o
[652/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/SerpentShrine/boss_lady_vashj.cpp.o
[653/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/SerpentShrine/boss_leotheras_the_blind.cpp.o
[654/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/SerpentShrine/boss_lurker_below.cpp.o
[655/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/SerpentShrine/boss_morogrim_tidewalker.cpp.o
[656/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/SteamVault/boss_mekgineer_steamrigger.cpp.o
[657/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/SteamVault/boss_hydromancer_thespia.cpp.o
[658/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/SteamVault/boss_warlord_kalithresh.cpp.o
[659/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/SerpentShrine/instance_serpent_shrine.cpp.o
[660/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/SteamVault/instance_steam_vault.cpp.o
[661/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/TheSlavePens/boss_mennu_the_betrayer.cpp.o
[662/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/TheSlavePens/boss_quagmirran.cpp.o
[663/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/TheSlavePens/boss_rokmar_the_crackler.cpp.o
[664/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/TheSlavePens/instance_the_slave_pens.cpp.o
[665/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/TheUnderbog/boss_hungarfen.cpp.o
[666/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/TheUnderbog/boss_the_black_stalker.cpp.o
[667/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/TheUnderbog/instance_the_underbog.cpp.o
[668/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/CoilfangReservoir/TheSlavePens/boss_ahune.cpp.o
[669/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/GruulsLair/boss_gruul.cpp.o
[670/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/GruulsLair/boss_high_king_maulgar.cpp.o
[671/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/GruulsLair/instance_gruuls_lair.cpp.o
[672/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/HellfireCitadel/BloodFurnace/boss_the_maker.cpp.o
[673/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/HellfireCitadel/BloodFurnace/boss_broggok.cpp.o
[674/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/HellfireCitadel/BloodFurnace/boss_kelidan_the_breaker.cpp.o
[675/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/HellfireCitadel/HellfireRamparts/boss_omor_the_unscarred.cpp.o
[676/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/HellfireCitadel/BloodFurnace/instance_blood_furnace.cpp.o
[677/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/HellfireCitadel/HellfireRamparts/boss_vazruden_the_herald.cpp.o
[678/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/HellfireCitadel/HellfireRamparts/boss_watchkeeper_gargolmar.cpp.o
[679/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/HellfireCitadel/HellfireRamparts/instance_hellfire_ramparts.cpp.o
[680/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/HellfireCitadel/MagtheridonsLair/instance_magtheridons_lair.cpp.o
[681/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/HellfireCitadel/ShatteredHalls/boss_warbringer_omrogg.cpp.o
[682/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/HellfireCitadel/ShatteredHalls/boss_nethekurse.cpp.o
[683/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/HellfireCitadel/ShatteredHalls/boss_warchief_kargath_bladefist.cpp.o
[684/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/HellfireCitadel/MagtheridonsLair/boss_magtheridon.cpp.o
[685/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/HellfireCitadel/ShatteredHalls/instance_shattered_halls.cpp.o
[686/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/Eye/boss_alar.cpp.o
[687/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/HellfireCitadel/ShatteredHalls/shattered_halls.cpp.o
[688/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/Eye/boss_astromancer.cpp.o
[689/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/Eye/boss_kaelthas.cpp.o
[690/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/Eye/boss_void_reaver.cpp.o
[691/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/Eye/the_eye.cpp.o
[692/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/Eye/instance_the_eye.cpp.o
[693/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/Mechanar/boss_gatewatcher_gyrokill.cpp.o
[694/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/Mechanar/boss_gatewatcher_ironhand.cpp.o
[695/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/Mechanar/boss_nethermancer_sepethrea.cpp.o
[696/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/Mechanar/boss_mechano_lord_capacitus.cpp.o
[697/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/Mechanar/boss_pathaleon_the_calculator.cpp.o
[698/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/Mechanar/instance_mechanar.cpp.o
[699/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/arcatraz/arcatraz.cpp.o
[700/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/arcatraz/boss_dalliah_the_doomsayer.cpp.o
[701/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/arcatraz/boss_harbinger_skyriss.cpp.o
[702/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/arcatraz/boss_wrath_scryer_soccothrates.cpp.o
[703/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/arcatraz/boss_zereketh_the_unbound.cpp.o
[704/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/arcatraz/instance_arcatraz.cpp.o
[705/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/botanica/boss_high_botanist_freywinn.cpp.o
[706/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/botanica/boss_commander_sarannis.cpp.o
[707/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/botanica/boss_laj.cpp.o
[708/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/botanica/boss_thorngrin_the_tender.cpp.o
[709/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/botanica/instance_the_botanica.cpp.o
[710/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Outland/TempestKeep/botanica/boss_warp_splinter.cpp.o
[711/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Pet/pet_generic.cpp.o
[712/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Pet/pet_mage.cpp.o
[713/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Pet/pet_dk.cpp.o
[714/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Pet/pet_hunter.cpp.o
[715/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Pet/pet_paladin.cpp.o
[716/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Pet/pet_script_loader.cpp.o
[717/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Pet/pet_priest.cpp.o
[718/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Pet/pet_shaman.cpp.o
[719/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Spells/spell_pet.cpp.o
[720/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Spells/spell_holiday.cpp.o
[721/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Spells/spell_dk.cpp.o
[722/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Spells/spell_hunter.cpp.o
[723/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Spells/spell_druid.cpp.o
[724/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Spells/spell_mage.cpp.o
[725/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Spells/spell_priest.cpp.o
[726/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Spells/spell_paladin.cpp.o
[727/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Spells/spell_script_loader.cpp.o
[728/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/World/achievement_scripts.cpp.o
[729/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/World/action_ip_logger.cpp.o
[730/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Spells/spell_quest.cpp.o
[731/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Spells/spell_rogue.cpp.o
[732/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/World/areatrigger_scripts.cpp.o
[733/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Spells/spell_item.cpp.o
[734/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Spells/spell_warrior.cpp.o
[735/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Spells/spell_shaman.cpp.o
[736/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/World/duel_reset.cpp.o
[737/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Spells/spell_generic.cpp.o
[738/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/World/guards.cpp.o
[739/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/World/chat_log.cpp.o
[740/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/Spells/spell_warlock.cpp.o
[741/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/World/mob_generic_creature.cpp.o
[742/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/World/go_scripts.cpp.o
[743/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/World/item_scripts.cpp.o
[744/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/World/npc_innkeeper.cpp.o
[745/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/World/world_map_scripts.cpp.o
[746/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/World/world_script_loader.cpp.o
[747/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/World/npc_professions.cpp.o
[748/751] Building CXX object src/server/scripts/CMakeFiles/scripts.dir/World/npcs_special.cpp.o
[749/751] Linking CXX static library src/server/game/libgame.a
ninja: build stopped: interrupted by user.
@@ -0,0 +1,61 @@
-- Detected 64-bit platform
-- UNIX: Using default configuration directory
-- UNIX: Configuring uninstall target
-- UNIX: Created uninstall target
-- UNIX: Detected compiler: /usr/bin/cc
-- GCC: Minimum version required is 10.0.0, found 15.2.0 - ok!
-- GCC: SFMT enabled, SSE2 flags forced
-- GCC: All warnings enabled
-- GCC: Debug-flags set (-g3)
-- Found MySQL:
-- Found MySQL: found components: binary
-- Found MySQL executable: /usr/bin/mysql
* TrinityCore revision : 119d63ed1c37+ 2026-10-26 22:45:38 +0100 (feature/clientless-player-core branch)
* TrinityCore buildtype : Debug
* Install core to : /home/wow_admin/wow_cata_server/server/debug
* Build world/auth : Yes (default)
* Build with scripts : Yes (static)
* Build map/vmap tools : No
* Build unit tests : No (default)
* Unity build enabled : No (default)
* Build core w/PCH : Yes (default)
* Build scripts w/PCH : Yes (default)
* Show all warnings : Yes
*** WITH_COREDEBUG - WARNING!
*** additional core debug logs have been enabled!
*** this setting doesn't help to get better crash logs!
*** in case you are searching for better crash logs use
*** -DCMAKE_BUILD_TYPE=RelWithDebug
*** DO NOT ENABLE IT UNLESS YOU KNOW WHAT YOU'RE DOING!
* Use coreside debug : Yes
* Show source tree : Yes (hierarchical)
* Use GIT revision hash : Yes (default)
-- Found Readline library: /usr/lib/x86_64-linux-gnu/libreadline.so
-- Include dir is: /usr/include
* Script configuration (static):
|
+- worldserver
| +- Battlefield
| +- Commands
| +- Custom
| +- EasternKingdoms
| +- Events
| +- Kalimdor
| +- Maelstrom
| +- Northrend
| +- OutdoorPvP
| +- Outland
| +- Pet
| +- Spells
| +- World
|
-- Configuring done (0.4s)
-- Generating done (0.1s)
-- Build files have been written to: /home/wow_admin/wow_cata_server/build/debug
@@ -0,0 +1,12 @@
/tmp/vcore-economy-t0vuw0w2/test.cpp: In function ‘bool TargetVisible(Player*, WorldObject*, const std::string&)’:
/tmp/vcore-economy-t0vuw0w2/test.cpp:61:57: error: ‘AdventureProfessionPolicy’ has not been declared
61 | if (!AdventureEconomyPolicy::Economic(kind) && !AdventureProfessionPolicy::Recognized(kind)) return player->IsWithinLOSInMap(target);
| ^~~~~~~~~~~~~~~~~~~~~~~~~
Traceback (most recent call last):
File "/home/wow_admin/wow_cata_server/source/TrinityCore/tests/clientless/test_adventure_economy.py", line 101, in <module>
subprocess.run(['c++','-std=c++17','-I',str(root/'src/server/game/World'),str(cpp),'-o',str(binary)],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['c++', '-std=c++17', '-I', '/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/game/World', '/tmp/vcore-economy-t0vuw0w2/test.cpp', '-o', '/tmp/vcore-economy-t0vuw0w2/test']' returned non-zero exit status 1.
@@ -0,0 +1,30 @@
/tmp/vcore-exploration-adul8yl4/test.cpp: In static member function ‘static bool AdventureSocialActions::Available(Player*, const AdventureMicroAction&, AdventureSocialContext&)’:
/tmp/vcore-exploration-adul8yl4/test.cpp:50:9: error: ‘AdventureProfessionPolicy’ has not been declared
50 | if (AdventureProfessionPolicy::Recognized(kind)) return c.Intent=="profession" &&
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-exploration-adul8yl4/test.cpp:51:9: error: ‘AdventureProfessionPolicy’ has not been declared
51 | AdventureProfessionPolicy::Allowed(kind,c.PrimaryProfessions) &&
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-exploration-adul8yl4/test.cpp:51:51: error: ‘struct AdventureSocialContext’ has no member named ‘PrimaryProfessions’
51 | AdventureProfessionPolicy::Allowed(kind,c.PrimaryProfessions) &&
| ^~~~~~~~~~~~~~~~~~
/tmp/vcore-exploration-adul8yl4/test.cpp:52:10: error: ‘AdventureProfessionPolicy’ has not been declared
52 | (AdventureProfessionPolicy::Untargeted(kind)?!p->IsInWater() && !p->IsFalling():!AdventureProfessionTargets::Find(p,kind,c).empty());
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-exploration-adul8yl4/test.cpp:52:58: error: ‘struct Player’ has no member named ‘IsInWater’
52 | (AdventureProfessionPolicy::Untargeted(kind)?!p->IsInWater() && !p->IsFalling():!AdventureProfessionTargets::Find(p,kind,c).empty());
| ^~~~~~~~~
/tmp/vcore-exploration-adul8yl4/test.cpp:52:77: error: ‘struct Player’ has no member named ‘IsFalling’
52 | (AdventureProfessionPolicy::Untargeted(kind)?!p->IsInWater() && !p->IsFalling():!AdventureProfessionTargets::Find(p,kind,c).empty());
| ^~~~~~~~~
/tmp/vcore-exploration-adul8yl4/test.cpp:52:90: error: ‘AdventureProfessionTargets’ has not been declared
52 | (AdventureProfessionPolicy::Untargeted(kind)?!p->IsInWater() && !p->IsFalling():!AdventureProfessionTargets::Find(p,kind,c).empty());
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
Traceback (most recent call last):
File "/home/wow_admin/wow_cata_server/source/TrinityCore/tests/clientless/test_adventure_exploration.py", line 60, in <module>
subprocess.run(['c++','-std=c++17','-I',str(root/'src/server/game/World'),str(cpp),'-o',str(binary)],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['c++', '-std=c++17', '-I', '/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/game/World', '/tmp/vcore-exploration-adul8yl4/test.cpp', '-o', '/tmp/vcore-exploration-adul8yl4/test']' returned non-zero exit status 1.
@@ -0,0 +1,24 @@
/tmp/vcore-adventure-state-4s9vbnjn/test.cpp: In lambda function:
/tmp/vcore-adventure-state-4s9vbnjn/test.cpp:82:22: error: ‘struct AdventureBehaviorMgr::Record::<unnamed>’ has no member named ‘Progressive’
82 | if(r.Leisure.Progressive && !r.Leisure.Route.empty() && r.Leisure.Step>=r.Leisure.Route.size() &&
| ^~~~~~~~~~~
/tmp/vcore-adventure-state-4s9vbnjn/test.cpp:82:48: error: ‘struct AdventureBehaviorMgr::Record::<unnamed>’ has no member named ‘Route’
82 | if(r.Leisure.Progressive && !r.Leisure.Route.empty() && r.Leisure.Step>=r.Leisure.Route.size() &&
| ^~~~~
/tmp/vcore-adventure-state-4s9vbnjn/test.cpp:82:75: error: ‘struct AdventureBehaviorMgr::Record::<unnamed>’ has no member named ‘Step’
82 | if(r.Leisure.Progressive && !r.Leisure.Route.empty() && r.Leisure.Step>=r.Leisure.Route.size() &&
| ^~~~
/tmp/vcore-adventure-state-4s9vbnjn/test.cpp:82:91: error: ‘struct AdventureBehaviorMgr::Record::<unnamed>’ has no member named ‘Route’
82 | if(r.Leisure.Progressive && !r.Leisure.Route.empty() && r.Leisure.Step>=r.Leisure.Route.size() &&
| ^~~~~
/tmp/vcore-adventure-state-4s9vbnjn/test.cpp:83:44: error: ‘struct AdventureBehaviorMgr::Record::<unnamed>’ has no member named ‘Route’
83 | player->GetExactDist(r.Leisure.Route.back())<=2)
| ^~~~~
Traceback (most recent call last):
File "/home/wow_admin/wow_cata_server/source/TrinityCore/tests/clientless/test_adventure_intent_state.py", line 108, in <module>
subprocess.run(['c++','-std=c++17','-I',str(root/'src/server/game/World'),str(cpp),'-o',str(binary)],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['c++', '-std=c++17', '-I', '/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/game/World', '/tmp/vcore-adventure-state-4s9vbnjn/test.cpp', '-o', '/tmp/vcore-adventure-state-4s9vbnjn/test']' returned non-zero exit status 1.
@@ -0,0 +1,32 @@
/tmp/vcore-leisure-l3qqdidj/test.cpp: In member function ‘bool AdventureBehaviorMgr::LeisureAvailable(Player*, const Record&, const AdventureMicroAction&) const’:
/tmp/vcore-leisure-l3qqdidj/test.cpp:166:95: error: ‘FishingDirection’ was not declared in this scope
166 | return rod && rod->GetTemplate()->GetSubClass()==ITEM_SUBCLASS_WEAPON_FISHING_POLE && FishingDirection(p,direction);
| ^~~~~~~~~~~~~~~~
/tmp/vcore-leisure-l3qqdidj/test.cpp: In member function ‘bool AdventureBehaviorMgr::StartLeisure(Player*, Record&, const AdventureMicroAction&)’:
/tmp/vcore-leisure-l3qqdidj/test.cpp:199:17: error: ‘FishingDirection’ was not declared in this scope
199 | if(!FishingDirection(p,direction))return false;
| ^~~~~~~~~~~~~~~~
/tmp/vcore-leisure-l3qqdidj/test.cpp:216:7: error: ‘struct AdventureBehaviorMgr::Record::LeisureScene’ has no member named ‘Progressive’
216 | s.Progressive=s.Kind=="work_search" || (r.Intent=="adventure" && site->IsArea && foot);
| ^~~~~~~~~~~
/tmp/vcore-leisure-l3qqdidj/test.cpp:217:10: error: ‘struct AdventureBehaviorMgr::Record::LeisureScene’ has no member named ‘Progressive’
217 | if(s.Progressive)s.Route.resize(s.OutboundSteps);
| ^~~~~~~~~~~
/tmp/vcore-leisure-l3qqdidj/test.cpp: In member function ‘bool AdventureBehaviorMgr::UpdateLeisure(Player*, Record&, uint32)’:
/tmp/vcore-leisure-l3qqdidj/test.cpp:356:37: error: ‘ProfessionWorkAvailable’ was not declared in this scope
356 | if(s.Kind=="work_search" && ProfessionWorkAvailable(p,r))
| ^~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-leisure-l3qqdidj/test.cpp:367:20: error: ‘struct AdventureBehaviorMgr::Record::LeisureScene’ has no member named ‘Progressive’
367 | r.Detail=s.Progressive?"area search reached next observation point":"leisure outing completed at starting point";
| ^~~~~~~~~~~
/tmp/vcore-leisure-l3qqdidj/test.cpp:373:14: error: ‘struct AdventureBehaviorMgr::Record::LeisureScene’ has no member named ‘Progressive’
373 | if(s.Progressive) {s.Route.resize(s.Step);return false;}
| ^~~~~~~~~~~
Traceback (most recent call last):
File "/home/wow_admin/wow_cata_server/source/TrinityCore/tests/clientless/test_adventure_leisure.py", line 196, in <module>
subprocess.run(['c++','-std=c++17',str(cpp),'-o',str(binary)],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['c++', '-std=c++17', '/tmp/vcore-leisure-l3qqdidj/test.cpp', '-o', '/tmp/vcore-leisure-l3qqdidj/test']' returned non-zero exit status 1.
@@ -0,0 +1,29 @@
/tmp/vcore-rest-fire-policy-he3ktm09/test.cpp: In member function ‘AdventureSocialContext AdventureBehaviorMgr::SocialContext(Player*, Record&)’:
/tmp/vcore-rest-fire-policy-he3ktm09/test.cpp:38:63: error: ‘struct Profile’ has no member named ‘PrimaryProfessions’
38 | location?location->Point.Category:"",profile?profile->PrimaryProfessions:AdventureProfessionPolicy::Professions{}};
| ^~~~~~~~~~~~~~~~~~
/tmp/vcore-rest-fire-policy-he3ktm09/test.cpp:38:82: error: ‘AdventureProfessionPolicy’ has not been declared
38 | location?location->Point.Category:"",profile?profile->PrimaryProfessions:AdventureProfessionPolicy::Professions{}};
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-rest-fire-policy-he3ktm09/test.cpp:38:120: error: expected ‘}’ before ‘{’ token
38 | location?location->Point.Category:"",profile?profile->PrimaryProfessions:AdventureProfessionPolicy::Professions{}};
| ^
/tmp/vcore-rest-fire-policy-he3ktm09/test.cpp:33:12: note: to match this ‘{’
33 | return {r.Anchor,r.AnchorMap,location&&location->Point.Category=="festival",
| ^
/tmp/vcore-rest-fire-policy-he3ktm09/test.cpp:38:120: note: probably missing a comma or an operator before
38 | location?location->Point.Category:"",profile?profile->PrimaryProfessions:AdventureProfessionPolicy::Professions{}};
| ^
/tmp/vcore-rest-fire-policy-he3ktm09/test.cpp:38:122: error: could not convert ‘{r.AdventureBehaviorMgr::Record::Anchor, r.AdventureBehaviorMgr::Record::AnchorMap, ((location != 0) && std::operator==<char, char_traits<char>, allocator<char> >(location->Location::Point.Location::<unnamed struct>::Category, ((const char*)"festival"))), ((profile != 0) ? std::__cxx11::basic_string<char>(profile->Profile::Locale) : std::__cxx11::basic_string<char>(((const char*)"enUS"), std::allocator<char>())), r.AdventureBehaviorMgr::Record::Intent, r.AdventureBehaviorMgr::Record::Random, r.AdventureBehaviorMgr::Record::TargetHistory, r.AdventureBehaviorMgr::Record::VariantHistory, <lambda closure object>AdventureBehaviorMgr::SocialContext(Player*, Record&)::<lambda(const Position&, float)>{((AdventureBehaviorMgr*)this), player, (* & r)}, (std::operator!=<char, char_traits<char>, allocator<char> >(r.AdventureBehaviorMgr::Record::Intent, ((const char*)"rest")) || ((location != 0) && std::operator==<char, char_traits<char>, allocator<char> >(location->Location::Point.Location::<unnamed struct>::Category, ((const char*)"rest_ground")))), (((location != 0) && std::operator==<char, char_traits<char>, allocator<char> >(r.AdventureBehaviorMgr::Record::Intent, ((const char*)"economy"))) ? AdventureEconomyPolicy::LocationRequirement(location->Location::Point.Location::<unnamed struct>::Category) : std::__cxx11::basic_string<char>(((const char*)""), std::allocator<char>())), ((location != 0) ? std::__cxx11::basic_string<char>(location->Location::Point.Location::<unnamed struct>::Category) : std::__cxx11::basic_string<char>(((const char*)""), std::allocator<char>())), <expression error>}’ from ‘<brace-enclosed initializer list>’ to ‘AdventureSocialContext’
38 | location?location->Point.Category:"",profile?profile->PrimaryProfessions:AdventureProfessionPolicy::Professions{}};
| ^
| |
| <brace-enclosed initializer list>
Traceback (most recent call last):
File "/home/wow_admin/wow_cata_server/source/TrinityCore/tests/clientless/test_adventure_rest_fire.py", line 74, in <module>
subprocess.run(['c++','-std=c++17','-I',str(root/'src/server/game/World'),str(cpp),'-o',str(binary)],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['c++', '-std=c++17', '-I', '/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/game/World', '/tmp/vcore-rest-fire-policy-he3ktm09/test.cpp', '-o', '/tmp/vcore-rest-fire-policy-he3ktm09/test']' returned non-zero exit status 1.
@@ -0,0 +1,107 @@
/tmp/vcore-social-test-cy2ndyrb/test.cpp: In function ‘std::vector<AdventureSocialFlow::Step> Steps(const std::string&, uint32)’:
/tmp/vcore-social-test-cy2ndyrb/test.cpp:100:13: error: ‘AdventureProfessionPolicy’ has not been declared
100 | if (AdventureProfessionPolicy::Recognized(kind)) return AdventureProfessionPolicy::Steps(kind,duration);
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:100:65: error: ‘AdventureProfessionPolicy’ has not been declared
100 | if (AdventureProfessionPolicy::Recognized(kind)) return AdventureProfessionPolicy::Steps(kind,duration);
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp: In static member function ‘static bool AdventureSocialActions::Update(Player*, AdventureSocialScene&, uint32, AdventureSocialContext&)’:
/tmp/vcore-social-test-cy2ndyrb/test.cpp:256:8: error: ‘AdventureProfessionPolicy’ has not been declared
256 | if(AdventureProfessionPolicy::Recognized(s.Kind) && (c.Intent!="profession" || !AdventureProfessionPolicy::Allowed(s.Kind,c.PrimaryProfessions)))return false;
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:256:60: error: ‘struct AdventureSocialContext’ has no member named ‘Intent’
256 | if(AdventureProfessionPolicy::Recognized(s.Kind) && (c.Intent!="profession" || !AdventureProfessionPolicy::Allowed(s.Kind,c.PrimaryProfessions)))return false;
| ^~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:256:85: error: ‘AdventureProfessionPolicy’ has not been declared
256 | if(AdventureProfessionPolicy::Recognized(s.Kind) && (c.Intent!="profession" || !AdventureProfessionPolicy::Allowed(s.Kind,c.PrimaryProfessions)))return false;
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:256:129: error: ‘struct AdventureSocialContext’ has no member named ‘PrimaryProfessions’
256 | if(AdventureProfessionPolicy::Recognized(s.Kind) && (c.Intent!="profession" || !AdventureProfessionPolicy::Allowed(s.Kind,c.PrimaryProfessions)))return false;
| ^~~~~~~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:272:12: error: ‘AdventureProfessionPolicy’ has not been declared
272 | if(AdventureProfessionPolicy::Recognized(s.Kind) && !AdventureProfessionTargets::Valid(p,target,s.Kind,c))return false;
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:272:62: error: ‘AdventureProfessionTargets’ has not been declared
272 | if(AdventureProfessionPolicy::Recognized(s.Kind) && !AdventureProfessionTargets::Valid(p,target,s.Kind,c))return false;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:363:27: error: ‘AdventureProfessionPolicy’ has not been declared
363 | auto task=AdventureProfessionPolicy::Find(s.Kind);
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:364:30: error: ‘AdventureProfessionPolicy’ has not been declared
364 | if(!task || !AdventureProfessionPolicy::Allowed(s.Kind,c.PrimaryProfessions))return false;
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:364:74: error: ‘struct AdventureSocialContext’ has no member named ‘PrimaryProfessions’
364 | if(!task || !AdventureProfessionPolicy::Allowed(s.Kind,c.PrimaryProfessions))return false;
| ^~~~~~~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:365:30: error: ‘EMOTE_STATE_USE_STANDING’ was not declared in this scope; did you mean ‘EMOTE_ONESHOT_USE_STANDING’?
365 | uint32 emote=EMOTE_STATE_USE_STANDING;
| ^~~~~~~~~~~~~~~~~~~~~~~~
| EMOTE_ONESHOT_USE_STANDING
/tmp/vcore-social-test-cy2ndyrb/test.cpp:368:26: error: ‘AdventureProfessionPolicy’ has not been declared
368 | case AdventureProfessionPolicy::Motion::Mine:emote=EMOTE_STATE_WORK_MINING;break;
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:368:72: error: ‘EMOTE_STATE_WORK_MINING’ was not declared in this scope
368 | case AdventureProfessionPolicy::Motion::Mine:emote=EMOTE_STATE_WORK_MINING;break;
| ^~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:369:26: error: ‘AdventureProfessionPolicy’ has not been declared
369 | case AdventureProfessionPolicy::Motion::Chop:emote=EMOTE_STATE_WORK_CHOPWOOD;break;
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:369:72: error: ‘EMOTE_STATE_WORK_CHOPWOOD’ was not declared in this scope
369 | case AdventureProfessionPolicy::Motion::Chop:emote=EMOTE_STATE_WORK_CHOPWOOD;break;
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:370:26: error: ‘AdventureProfessionPolicy’ has not been declared
370 | case AdventureProfessionPolicy::Motion::Kneel:emote=EMOTE_STATE_KNEEL;break;
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:370:73: error: ‘EMOTE_STATE_KNEEL’ was not declared in this scope; did you mean ‘EMOTE_STATE_DANCE’?
370 | case AdventureProfessionPolicy::Motion::Kneel:emote=EMOTE_STATE_KNEEL;break;
| ^~~~~~~~~~~~~~~~~
| EMOTE_STATE_DANCE
/tmp/vcore-social-test-cy2ndyrb/test.cpp:433:8: error: ‘AdventureProfessionPolicy’ has not been declared
433 | if(AdventureProfessionPolicy::Recognized(s.Kind))
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:435:22: error: ‘AdventureProfessionPolicy’ has not been declared
435 | if(auto task=AdventureProfessionPolicy::Find(s.Kind))
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:439:38: error: ‘struct Position’ has no member named ‘Relocate’
439 | Position departure;departure.Relocate(p);
| ^~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:440:29: error: ‘struct WorldObject’ has no member named ‘GetAngle’
440 | float angle=target->GetAngle(p);
| ^~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:441:19: error: ‘struct Position’ has no member named ‘m_positionX’; did you mean ‘Position’?
441 | departure.m_positionX+=std::cos(angle)*2;departure.m_positionY+=std::sin(angle)*2;
| ^~~~~~~~~~~
| Position
/tmp/vcore-social-test-cy2ndyrb/test.cpp:441:60: error: ‘struct Position’ has no member named ‘m_positionY’; did you mean ‘Position’?
441 | departure.m_positionX+=std::cos(angle)*2;departure.m_positionY+=std::sin(angle)*2;
| ^~~~~~~~~~~
| Position
/tmp/vcore-social-test-cy2ndyrb/test.cpp:442:25: error: ‘struct Player’ has no member named ‘GetMap’
442 | float height=p->GetMap()->GetHeight(p->GetPhaseShift(),departure.GetPositionX(),departure.GetPositionY(),p->GetPositionZ()+3,true,8);
| ^~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:442:48: error: ‘struct Player’ has no member named ‘GetPhaseShift’
442 | float height=p->GetMap()->GetHeight(p->GetPhaseShift(),departure.GetPositionX(),departure.GetPositionY(),p->GetPositionZ()+3,true,8);
| ^~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:442:74: error: ‘struct Position’ has no member named ‘GetPositionX’; did you mean ‘Position’?
442 | float height=p->GetMap()->GetHeight(p->GetPhaseShift(),departure.GetPositionX(),departure.GetPositionY(),p->GetPositionZ()+3,true,8);
| ^~~~~~~~~~~~
| Position
/tmp/vcore-social-test-cy2ndyrb/test.cpp:442:99: error: ‘struct Position’ has no member named ‘GetPositionY’; did you mean ‘Position’?
442 | float height=p->GetMap()->GetHeight(p->GetPhaseShift(),departure.GetPositionX(),departure.GetPositionY(),p->GetPositionZ()+3,true,8);
| ^~~~~~~~~~~~
| Position
/tmp/vcore-social-test-cy2ndyrb/test.cpp:443:19: error: ‘INVALID_HEIGHT’ was not declared in this scope
443 | if(height>INVALID_HEIGHT+1 && std::fabs(height-p->GetPositionZ())<=2)
| ^~~~~~~~~~~~~~
/tmp/vcore-social-test-cy2ndyrb/test.cpp:445:23: error: ‘struct Position’ has no member named ‘m_positionZ’; did you mean ‘Position’?
445 | departure.m_positionZ=height;
| ^~~~~~~~~~~
| Position
Traceback (most recent call last):
File "/home/wow_admin/wow_cata_server/source/TrinityCore/tests/clientless/test_adventure_social.py", line 190, in <module>
subprocess.run(['c++','-std=c++17','-I',str(root/'src/server/game/World'),str(cpp),'-o',str(binary)],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['c++', '-std=c++17', '-I', '/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/game/World', '/tmp/vcore-social-test-cy2ndyrb/test.cpp', '-o', '/tmp/vcore-social-test-cy2ndyrb/test']' returned non-zero exit status 1.
@@ -0,0 +1,24 @@
/tmp/vcore-social-fire-swu4udr3/test.cpp: In static member function ‘static bool AdventureSocialActions::Available(Player*, const AdventureMicroAction&, AdventureSocialContext&)’:
/tmp/vcore-social-fire-swu4udr3/test.cpp:79:9: error: ‘AdventureProfessionPolicy’ has not been declared
79 | if (AdventureProfessionPolicy::Recognized(kind)) return c.Intent=="profession" &&
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-social-fire-swu4udr3/test.cpp:80:9: error: ‘AdventureProfessionPolicy’ has not been declared
80 | AdventureProfessionPolicy::Allowed(kind,c.PrimaryProfessions) &&
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-social-fire-swu4udr3/test.cpp:80:51: error: ‘struct AdventureSocialContext’ has no member named ‘PrimaryProfessions’
80 | AdventureProfessionPolicy::Allowed(kind,c.PrimaryProfessions) &&
| ^~~~~~~~~~~~~~~~~~
/tmp/vcore-social-fire-swu4udr3/test.cpp:81:10: error: ‘AdventureProfessionPolicy’ has not been declared
81 | (AdventureProfessionPolicy::Untargeted(kind)?!p->IsInWater() && !p->IsFalling():!AdventureProfessionTargets::Find(p,kind,c).empty());
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/vcore-social-fire-swu4udr3/test.cpp:81:90: error: ‘AdventureProfessionTargets’ has not been declared
81 | (AdventureProfessionPolicy::Untargeted(kind)?!p->IsInWater() && !p->IsFalling():!AdventureProfessionTargets::Find(p,kind,c).empty());
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
Traceback (most recent call last):
File "/home/wow_admin/wow_cata_server/source/TrinityCore/tests/clientless/test_adventure_social_fire.py", line 86, in <module>
subprocess.run(['c++','-std=c++17','-I',str(root/'src/server/game/World'),str(cpp),'-o',str(binary)],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['c++', '-std=c++17', '-I', '/home/wow_admin/wow_cata_server/source/TrinityCore/src/server/game/World', '/tmp/vcore-social-fire-swu4udr3/test.cpp', '-o', '/tmp/vcore-social-fire-swu4udr3/test']' returned non-zero exit status 1.
@@ -0,0 +1,18 @@
from pathlib import Path
import os,re,subprocess,sys
config=Path('/home/wow_admin/wow_cata_server/server/debug/etc/worldserver.conf').read_text()
match=re.search(r'^WorldDatabaseInfo\s*=\s*"([^"]+)"',config,re.M)
if not match:raise SystemExit('Test world database configuration not found')
host,port,user,password,database=match.group(1).split(';')
args=['mysql','--protocol=TCP','--host='+host,'--port='+port,'--user='+user,'--database='+database,'--default-character-set=utf8mb4','--batch','--connect-timeout=10']
env=os.environ.copy();env['MYSQL_PWD']=password
query="SELECT a.action_key,a.kind,a.min_seconds,a.max_seconds,a.requirement,i.intent,i.weight FROM vcore_adventure_action a JOIN vcore_adventure_action_intent i USING(action_key) WHERE a.action_key IN ('rest_fire','campfire_rest','social_fire'); SELECT point_key,category,map,position_x,position_y,position_z FROM vcore_clientless_world_point WHERE enabled=1 AND category='rest_ground' LIMIT 10;"
if sys.argv[1:] == ['apply']:
migration=Path('/home/wow_admin/wow_cata_server/source/TrinityCore/sql/custom/world/2026_10_27_01_adventure_rest_fire.sql').read_text()
query='START TRANSACTION;\n'+migration+'\n'+query+'\nCOMMIT;'
elif sys.argv[1:] != ['inspect']:raise SystemExit('Use inspect or apply')
result=subprocess.run(args,input=query,text=True,capture_output=True,env=env)
print(result.stdout,end='')
if result.returncode:
print(result.stderr.replace(password,'[redacted]'),file=sys.stderr,end='')
raise SystemExit(result.returncode)
+162
View File
@@ -0,0 +1,162 @@
# Wirtschaften-Intent
Technischer Name: `economy`. Diese Ausbaustufe stellt wirtschaftliche Besuche
dar: regulär anreisen, einen passenden Dienstleister aufsuchen, ruhig schauen,
reden oder warten, sich verabschieden und den Schalter wieder verlassen.
Es gibt keine tatsächlichen Käufe, Verkäufe, Auktionen, Gebote, Banktransfers
oder Geldbewegungen. Es werden keine entsprechenden Interaktions- oder
Transaktionshandler aufgerufen und keine wirtschaftlichen Fenster simuliert.
## Mikroaktionen
| Aktion | Ablauf |
|---|---|
| `economy_vendor_visit` | Einen freundlichen erreichbaren Händler aufsuchen, kurz umsehen, verabschieden und zurücktreten. |
| `economy_goods` | Beim Händler länger Waren betrachten. Die Darstellung richtet sich zum tatsächlichen Händler; eine passende Auslage ist keine Voraussetzung. |
| `economy_trade` | Begrüßen, Handelsgespräch darstellen, zuhören, nicken und verabschieden. |
| `economy_compare` | Zwei verschiedene erreichbare Händler nacheinander besuchen und Angebote betrachten. Ein einzelner Händler genügt nicht. |
| `economy_auction_visit` | Einen tatsächlichen Auktionator aufsuchen und das Auktionshaus kurz besuchen. |
| `economy_auctions` | Beim Auktionator eine längere ruhige Prüfphase darstellen. |
| `economy_bank_visit` | Einen tatsächlichen Bankier aufsuchen und die Bank kurz besuchen. |
| `economy_banking` | Am Bankschalter Gespräch und Prüfung darstellen. |
| `economy_supplies` | Ruhige Prüfung der Vorräte darstellen, ohne Inventarzugriff oder Änderungen. |
| `economy_wait` | Bei einem passenden Dienstleister auf Bedienung warten. Kein Warteschlangensystem. |
| `economy_finish` | Einen zuvor besuchten, noch anwesenden Dienstleister verabschieden und den Schalter verlassen. |
## Ziele und Ausführung
Migration: `sql/custom/world/2026_10_27_03_adventure_economy.sql` nach den
bisherigen Intent-Migrationen. Sie ergänzt Katalogeinträge mit `INSERT IGNORE`;
bearbeitete Definitionen, Gewichte und Positionen bleiben erhalten.
Der Tagesplan entscheidet zuerst anhand des Profils über das Vorhaben. Erst
anschließend wählt der Ortsplaner ein passendes vorhandenes importiertes Ziel.
Ein fehlender Händler-Eintrag darf den Intent im Tagesplan nicht ausschließen.
Siedlungen, Gebiete und bekannte Serviceorte werden für Wirtschaften höher
gewichtet; auch andere importierte Orte sind grundsätzlich verwendbar.
Ausdrücklich eingestellte Ortsgewichte einschließlich Null behalten Vorrang.
Fraktionszuordnung und normale Reiseprüfungen bleiben bestehen.
`stormwind_economy_market`, `stormwind_economy_bank` und
`stormwind_economy_auction` sind zusätzliche gezielte Testpunkte. Sie begrenzen
weder die Ortswahl noch die Verfügbarkeit des Intents für andere Fraktionen.
An jedem anderen passenden Ort findet der Bot die wirklichen Dienstleister
über `ClientlessPerceptionMgr`: Suchradius 100 Yards, native Fähigkeiten,
Freundlichkeit, Phase und anschließende geprüfte lokale Annäherung.
Dafür müssen keine einzelnen Händler oder deren Aufenthaltsorte vorgegeben
werden. Es wird kein Handels-NPC aus einer Datenbankposition nachgespielt.
Die vorhandenen sozialen Fähigkeiten bleiben gemeinsam genutzt. Zielwahl
prüft die nativen Rollen `VENDOR`, `BANKER` oder `AUCTIONEER`, Freundlichkeit,
Lebendigkeit, Kampfzustand, Phase und den Aufenthaltsbereich. Vor der Annäherung muss der Dienstleister
noch nicht vom Ankunftspunkt aus sichtbar sein. Der mögliche Gesprächsplatz
und die tatsächliche Szene benötigen eine native Sichtlinie auf Augenhöhe
über den Schalter hinweg. Wände bleiben blockierend.
Nur ausdrücklich spezialisierte Testpunkte grenzen die Rollen zusätzlich ein: Händler im Marktbereich,
Bankiers in der Bank, Auktionatoren im Auktionshaus. Das gilt auch für
Warte- und Abschiedsszenen. Andere Bots sind keine Dienstleister. Nicht erreichbare Schalter werden nicht
als Besuch gezählt. Die Prüfung bleibt während der Szene aktiv. Händlervergleiche
wechseln ausdrücklich zu einer anderen GUID. Wiederholungsdämpfung berücksichtigt
Aktionen, Ziele und Textvarianten.
Nach dem Abschied läuft der Bot über MMAP zum Ankunftspunkt zurück. Nur die
bestätigte tatsächliche Ankunft erzeugt `ECONOMY_VISIT_COMPLETE`; ein gescheiterter
Rückweg täuscht keinen Abschluss vor. Fehlende Dienstleister lösen eine reguläre
Suche nach einem anderen Wirtschaftsziel aus. Das gilt auch, wenn keine
der möglichen Serviceannäherungen einen erreichbaren Gesprächsplatz findet. Kampf, Intentwechsel und Offline
beenden die Szene über das bestehende Reset-Verfahren.
Der technische Intentname ist auch für manuelle Aufträge und neue Tagespläne
verfügbar. `activity_affinity.economy` steuert die Tagesplangewichtung
(Standard 0,5; Arthoran 0,45). Bereits gespeicherte vollständige Tagespläne
werden nicht verändert. Lokale Servicewege werden im Gehmodus und innerhalb
der Location-Zone ausgeführt. Die Intentauswahl fragt den Ortskatalog nicht ab.
Beispiel für die Testserverkonsole:
```
clientless adventure intent 26 economy stormwind_economy_market 3600
clientless adventure micro 26 economy_compare
clientless adventure status 26
```
Die gezielte Mikroaktion setzt einen bereits angekommenen sicheren Bot voraus.
## Prüfung
Automatisierte Prüfungen führen die produktive Rollen-/Umgebungswahl und
Gestensequenzen aus, einschließlich Rollenverlust, zweitem Händler, verweigerter
Navigation und bestätigter Abreise. Der Katalogtest führt die Migration aus und
prüft Referenzen, Allianzgrenzen, native Servicevoraussetzungen und Idempotenz.
Eine erfolgreiche Kompilierung allein ist keine Ingame-Abnahme.
## Live-Prüfung am 27.10.2026
Der Testserver wurde mit `-j10` gebaut, installiert und bis zur frischen
Ready-Meldung mit lebendem Prozess und Port 8085 geprüft. Alle 29
Clientless-Testskripte bestanden.
- Händlervergleich vollständig: Edna Mullby (1286) und Thurman Mullby (1285),
unterschiedliche tatsächliche GUIDs, Gesprächspositionen und Gesten,
`ECONOMY_COMPARE_NEXT`, Abreise und `ECONOMY_VISIT_COMPLETE visits=2`.
- Auktionshaus vollständig: Auctioneer Chilton (8670), längere Prüfphase,
Abschied, Abreise und bestätigter Abschluss.
- Bank vollständig: Scott Westmill (43820) sowie ein automatisch ausgewählter
weiterer Bankbesuch; tatsächliche Kundenseite, Abschied und bestätigter
Rückweg. Die anfängliche Sichtprüfung auf den Körperpunkt hinter dem
Schalter wurde durch geprüfte Annäherung und native Sicht auf Augenhöhe
ersetzt. Dabei wird keine Kollisions- oder Sichtlinienprüfung umgangen.
- Im finalen Stand wählte Arthoran nach dem Login selbstständig
`economy_banking` bei Thomas Westmill (43822); lokale Wege liefen mit
`walk=1`. Bank-, Händler- und Auktionshausrollen bleiben nach Location getrennt.
- Gespeichertes Geld und vollständige Inventarzuordnung einschließlich
Gegenstands-IDs und Stapelzahlen waren nach den drei Servicebereichen
bytegleich zum Ausgangsstand. Temporäre Login-Profiländerungen wurden
zurückgesetzt; der normale Initial-Intent bleibt unverändert.
Die technische Live-Prüfung ersetzt nicht die visuelle Ingame-Abnahme.
Weitere Städte und eine Prüfung verschiedener gleichzeitig anwesender Bots
sind noch nicht durchgeführt.
Auch der abschließende automatische Bankablauf im finalen Build endete mit
`ECONOMY_VISIT_COMPLETE action=economy_banking visits=1`; anschließend
begann selbstständig ein weiterer Besuch bei Newton Burnside (2456).
## Korrektur der Planungshierarchie und des Imports
Die anfängliche Einschränkung auf drei Wirtschaftstestpunkte und die davon
abhängige Tagesplanauswahl waren falsch. Sie wurden entfernt. Der Ortsimport
hatte bereits stattgefunden; zum Diagnosezeitpunkt gab es 2.647 aktive Orte.
Ein separater Fehler las `SMALLINT`-Karten-IDs mit `GetUInt32`, was im Debug-Core
Null ergab. Dadurch wurden unter anderem Kalimdor- und Scherbenweltorte
fälschlich Karte 0 zugeordnet. Import und Katalogladen verwenden nun
`GetUInt16`. Die Weltdatenbank-Quelldaten selbst werden nicht geändert.
`clientless adventure repairimport` ergänzt den normalen Import um eine
gezielte Reparatur: Nur Datensätze, deren Quelle, Koordinaten, Kategorie und
Metadaten noch den alten automatisch erzeugten Werten entsprechen, werden
berichtigt. Manuelle Orte und abweichende Bearbeitungen bleiben erhalten.
Karten-, Gebiets-, Regions- und Fraktionsdaten werden neu abgeleitet; bei
Wachen-POIs dient ein naher nativer Höhenanker statt der obersten Dachfläche
als Bezug. Der normale Import bleibt additiv.
Die gezielte Reparatur wurde auf dem Testserver zweimal ausgeführt. Sie
berichtigte 2.159 bestehende automatisch erzeugte Datensätze und ergänzte
217 nun verwertbare Quellorte (insgesamt 2.864 aktive Orte). Alle 16 manuellen
Datensätze blieben vollständig unverändert. Der zweite Lauf war bytegleich
zum ersten Ergebnis. Orgrimmar und Donnerfels liegen auf Karte 1, Unterstadt
auf Karte 0, Silbermond auf Karte 530; alle vier sind als Horde-Orte zugeordnet.
Der abschließende Test nutzte ausschließlich den gewöhnlichen importierten
Sturmwind-Ort `poi_tele_954`: Arthoran nahm über die generische Wahrnehmung
einen etwa 68 Yards entfernten Bankier wahr, erreichte ihn über native
Navigation im Gehmodus und begann selbstständig eine Bankgesprächsszene.
Ein spezieller Bank-Testpunkt wurde für diesen Ablauf nicht verwendet.
Build mit `-j10`, Installation und Neustart waren erfolgreich; alle 30
Clientless-Testskripte bestanden. Eine Reise mit einem Horde-Bot ist dadurch
noch nicht ingame geprüft.
Die anschließende Abenteuer-Migration überführt die allgemeinen Wirtschafts-
gewichte in ausdrückliche Location-/Intent-Katalogeinträge. Ein im Code
versteckter Kategorie-Fallback entfällt damit; vorhandene angepasste Gewichte
einschließlich Null behalten Vorrang.
+65 -1
View File
@@ -9,7 +9,8 @@ Es werden dafür weder Nodes noch Kanten in der Datenbank aufgezeichnet.
Ein Flug kann an einem regulären Graphnode oder einem virtuellen Straßenausstieg
beginnen und an einem regulären Node oder einer Location enden. Virtuelle
Straßeneinstiege sind keine Flugziele. Der Abflugpunkt muss tatsächlich erreicht
sein; ein Bot außerhalb des Graphen erhält keinen beliebigen direkten Luftstart.
sein. Für den Rückfall bei fehlendem Bodenweg kann der tatsächliche sichere
Aufenthaltsort als Abflugpunkt dienen; dabei gelten dieselben nativen Startprüfungen.
Normale Straßenreisen haben einen Kostenvorteil. Flug wird bei genügend großem
Vorteil berücksichtigt; bevorzugte Flugzonen senken Mindestdistanz und Kosten.
@@ -26,6 +27,69 @@ Unbekannte Gebietsmetadaten erlauben lediglich einen Planungsversuch: Beim Start
entscheiden die normalen Mountfähigkeiten und Zauberprüfungen des Servers.
Vashj'irs besondere Unterwasserreisen sind nicht enthalten.
## Optionale Straßenausstiege und Flug als Rückfall
Ein vom Planer vorgeschlagener virtueller Exit ist ein optionaler Anschluss.
Die Ausführung behält die aufgezeichnete Kante bis zum nächsten regulären Node
in ihrer gewählten Richtung bei, auch nach einem virtuellen Einstieg mitten auf
der Kante. Alle sechs Sekunden prüft sie von der aktuellen Position den lokalen
MMAP-Anschluss zum Reiseziel. Während einer negativen Prüfung läuft die bestehende
Straßenspline weiter: kein Halt, kein Umdrehen, keine Sperre der Kante und kein
Verbrauch der fünf Recovery-Versuche. Das gilt auch nach beliebig vielen negativen
Prüfungen. Nur ein vollständiger Anschluss mit höchstens 500 Yards Pfadlänge und
höchstens 1,5 Yards Zielabweichung löst den lokalen Ausstieg aus.
Scheitert ein bereits gestarteter lokaler Ausstieg, versucht die Ausführung den
Wiedereinstieg am nächsten noch nicht passierten Straßenpunkt über MMAP. Den
Fortschritt bestimmt der Index der laufenden Spline, nicht der geometrisch nächste
Punkt, der auf sich kreuzenden Straßen hinter dem Bot liegen könnte. Ein tatsächlich
nicht mehr erreichbarer Wiedereinstieg ist dagegen ein Bewegungsfehler und darf
Recovery auslösen. Ein geplantes Flugziel ab einem virtuellen Exit bleibt eine
optionale normale Flugalternative mit den üblichen Auswahlregeln.
Wird kein Exit nutzbar, erreicht der Bot den regulären Endnode. Erst dort wird die
Fortsetzung neu geplant; das Erreichen dieses Nodes zählt nicht als Fehlversuch.
Nach vollständig durchfahrener Kante wird deren erfolglose geometrische
Ausstiegsannahme für diese Reiserichtung nicht erneut als gesicherte Zieloption
geplant. Die Kante bleibt befahrbar und bei späteren Durchfahrten werden weiterhin
Live-Prüfungen durchgeführt. So führen dieselben unbelegten Ausstiegsannahmen
nicht zu endlosen Rundreisen zwischen den regulären Endnodes.
Ein angeforderter Intentwechsel kann dort wie an anderen sicheren Nodes stattfinden.
Wenn keine reguläre Reise geplant werden kann, eine nutzbare Teilroute endet oder
die tatsächliche Bewegungs-Recovery ausgeschöpft ist, prüft die Ausführung freien
Flug zum Ziel auf derselben Karte. Für diesen Rückfall gelten weder die normale
Mindestdistanz noch Zonenpräferenzen, Straßenkostenvorteile oder die normale
20.000-Yards-Auswahlschranke. Flugberechtigung, gelernte Flugmounts, native
Zauberprüfungen, Geometrie, sichere Landung und der Fußweg vom Landeplatz zum Ziel
bleiben erforderlich. Eine erfolgreich gestartete Rückfalloption wird innerhalb
derselben Reiseaktion nicht endlos erneut gestartet.
Die Diagnoseeinträge heißen `GROUND_EXIT_CHECK`, `GROUND_EXIT_ARRIVED`,
`FLIGHT_FALLBACK_UNAVAILABLE` und `FLIGHT_FALLBACK_STARTED`.
Regressionstests:
```bash
python3 tests/clientless/test_ground_exit_continuation.py
python3 tests/clientless/test_free_flight_planner.py
python3 tests/clientless/test_flight_policy.py
python3 tests/clientless/test_adventure_graph_handover.py
```
Sie führen produktive Methoden mit kontrollierten Bewegungs- und MMAP-Daten aus.
Unter anderem bleiben bei zwanzig negativen Exitprüfungen Route, Straßenfahrt,
Richtung, Sperrlisten und Recovery-Zähler erhalten; ein später gültiger Anschluss
führt zum ursprünglichen Ziel. Das ersetzt keine visuelle Abnahme im Spiel.
Prüfstand vom 27.10.2026 für diese Korrektur: Alle 21 Skripte unter
`tests/clientless/test_*.py` bestanden. Der bestehende Debug-Build wurde mit
`cmake --build /home/wow_admin/wow_cata_server/build/debug -j10` erfolgreich
gebaut und über die vorhandene CMake-Installation übernommen. Der frische
Testserver-Start erreichte `(worldserver-daemon) ready...`; Prozess und Listener
auf Port 8085 wurden danach bestätigt. Arthorans konkrete Hafen–Tor–Tavernenreise
ist mit diesem Stand noch nicht im Spiel abgenommen.
## Profil und Konfiguration
Im `movement`-Abschnitt des Charakterprofils:
+47
View File
@@ -0,0 +1,47 @@
# Sieben Grundintents der AdventureBots
Konzeptstand: 27.10.2026. Vom Nutzer aus dem vorangegangenen Gespräch
rekonstruiert und bestätigt. Diese Festlegung erweitert die bisher nur drei
Intents umfassenden Dokumente. Sie beschreibt das vereinbarte Verhalten,
nicht eine bereits vollständig vorhandene Implementierung.
AdventureBots stellen glaubwürdige Abenteurer dar. Ein Intent beschreibt ein
Vorhaben; Reise und kontextabhängige Mikroaktionen setzen es um. Eine
Mikroaktion kann zu mehreren Intents passen, etwa Sitzen bei Geselligkeit
oder Ruhe. Entscheidend ist der Zweck des Aufenthalts.
## Die sieben Grundintents
| Intent | Zweck und Beispiele |
|---|---|
| Geselligkeit | Tavernenbesuche, Essen und Trinken, Gespräche und gemeinsame soziale Szenen. |
| Abenteuer | Erkundung, Untersuchung von Orten und Objekten, sichtbare Aktivitäten in der offenen Welt. Eine tatsächliche Kampf-KI bleibt eine spätere Ausbaustufe. |
| Beruf | Zum Berufsprofil passende Arbeit darstellen, beispielsweise an Erzquellen, Pflanzen, Schmieden oder Werkbänken. Ressourcen und Inventar werden dabei nicht verändert. |
| Instanzen | Eine Instanz performativ besuchen: zum Eingang reisen, die Instanz betreten und nach einiger Zeit wieder am Ausgang erscheinen. Der Bot muss die Instanz weder durchlaufen noch dort tatsächlich kämpfen. |
| Ruhe | Erholung darstellen: sich an geeigneten Plätzen schlafen legen, sitzen und andere erholsame Tätigkeiten ausführen. |
| Freizeit | Sich ohne konkretes Arbeits- oder Abenteuerziel bewegen, beispielsweise in Sturmwind spazieren gehen, ziellos umherlaufen, reiten oder fliegen. |
| Wirtschaften | Wirtschaftliche Aktivitäten performativ darstellen: Händler, Auktionshaus oder Bank aufsuchen. Der Bot handelt dabei nicht tatsächlich. |
Instanzbesuche bilden einen eigenen Grundintent und werden nicht lediglich
unter Abenteuer geführt. Freizeit ist ebenfalls ein eigenes Vorhaben;
Fortbewegung dient dort dem Aufenthalt selbst und nicht nur der Anreise.
Beruf bezeichnet die dargestellte Arbeit, Wirtschaften die dargestellten
wirtschaftlichen Besuche und Aktivitäten.
## Implementierungsstand und offene Ausgestaltung
Der bestehende Verhaltensmanager kennt aktuell die technischen Intentnamen
`social`, `adventure`, `profession`, `rest`, `leisure` und `economy`. Der [Ruhe-Intent](rest-intent.md)
ist ergänzt, ebenso der [Freizeit-Intent](leisure-intent.md) und der
[Wirtschaften-Intent](economy-intent.md). Der [Abenteuer-Ausbau](adventure-intent.md)
ergänzt Erkundung und Objektuntersuchung. Instanzbesuche bleiben konzeptionell
festgehalten; ihr technischer Name und Detailablauf sind noch nicht umgesetzt. Der ausführliche bisherige
[Mikroaktionskatalog](mikroaktions-katalog.md) bleibt als Grundlage erhalten.
Der Geselligkeitsausbau ist implementiert. Die Sitzposition auf nativen
Stühlen wurde vom Nutzer auch visuell im Spiel geprüft und funktioniert.
Eine Prüfung von Gesprächen zwischen mehreren AdventureBots setzt voraus,
dass mehrere solche Bots vorhanden sind.
Weitere Einzelheiten des früheren Gesprächs sind durch diese Rekonstruktion
nicht belegt und dürfen nicht als bereits vereinbart vorausgesetzt werden.
+55
View File
@@ -0,0 +1,55 @@
› OK stop, er findet ja den weg schon wieder nicht. Es sieht so aus, als würde er den virtuellen entry auf der Kante sturmwind Hafen - sturmwind gate verwenden. Er reitet diese auch lang aber nach einem Stück bricht er ab. Warum? Er mü…
1. Der Planer bewertet den restlichen Weg zur Taverne zunächst nur anhand der Luftlinienentfernung. Ob MMAP ihn tatsächlich laufen kann, prüft er erst am Ausstieg.
2. Dort scheitert der lokale Anschluss mit PathRejected.
3. Die Fehlerbehandlung sperrt nur diesen einzelnen Ausstiegspunkt. Deshalb wählt der Planer beim nächsten Versuch einen benachbarten Punkt derselben Kante – statt zuverlässig bis zum regulären Endknoten weiterzureisen.
4. Nach fünf Fehlversuchen übernimmt der Adventure-Manager und wählt die Stadtbank als Ersatzort. Dadurch wird das ursprüngliche Tavernen-Ziel aufgegeben.
Das Weiterfahren bis zum echten Knoten fehlt hier als wirksame Rückfallstrategie. Sturmwind Gate und der Greifenmeister werden zwar im Suchlauf geprüft, aber nicht als zwingender nächster Schritt über die weiterhin nutzbare
aufgezeichnete Kante angesteuert.
Beim Fliegen kommt eine zweite Einschränkung hinzu: Die aktuelle Konfiguration lässt gewöhnliche Flugstrecken erst ab 600 Yards zu. Die hier geplanten lokalen Anschlüsse liegen bei ungefähr 260–300 Yards und werden dadurch
ausgeschlossen. Flugfähigkeit allein genügt der aktuellen Auswahl also nicht.
Die Korrektur muss deshalb an der Reiseplanung ansetzen: nach gescheitertem virtuellem Ausstieg die nutzbare Kante bis zum regulären Knoten weiterverfolgen und Flug als Alternative bei blockiertem Bodenweg berücksichtigen. Für die
Taverne wäre dabei ein erreichbarer Landeplatz draußen mit anschließendem Fußweg erforderlich.
Worked for 1m 20s • 07:44
› Das Problem besteht also darin, wie unser Graphmanager alternativen wählt und mit fehlversuchen umgeht. Nach 5 gescheiterten virtuellen Exits die komplette Reise ab zu brechen ist natürlich Unsinn. Auch ist es unsinn, den Bot den
virtuellen exit nur 5 Mal probieren zu lassen, eine bessere Alternative wäre, ihn diesen in einem regulärem Zeitabstand regelmäßig erneut prüfen zu lassen, während der prüfung reitet er weiter zum regulärem Node Ziel, er darf auch auf
einer Kante nicht einfach mitten drinn umkehren, sondern muss zumindest zum nächsten NOde, virtuelle sind hier gleichberechtigt mit den anderen. SO verhindern wir, dass er auf der Strecke hin und herläuft weil er keinen virtuellen
exit findet. In unserem konkreten Beispiel heißt das, Arthoran läuft die reguläre route zum Tor von Sturmwind und prüft regelmäig ob er den virtuellen exit nehmen kann, beim scheitern läuft er weiter bis er irgendwann einen gültigen
virtuellen exit findet oder an der nächsten Node angekommen ist. Außerdem müssen wir bei der Auswahl des freien fliegens die Auswahlkriterien für eine normale Reise von einem Fallback für unerereichbare Ziele behandeln. Grundsätzlich
gilt: der Bot versucht erstmal immer so nah wie er kann an sein Zeil heran zu kommen. Wenn er feststellt, das keine reguläre Route zum Ziel führt, oder die Reise abgebrochen ist und er nicht mehr weiter kommt, dann soll er natürlich
auch das freie fliegen prüfen, ohn Regelen, die wir künstlich für normale Reisen gesetzt haben.
• Ich ändere die Reiseausführung so, dass ein fehlgeschlagener virtueller Ausstieg die laufende Kante nicht mehr abbricht: Arthoran folgt ihr weiter zum nächsten Knoten und prüft unterwegs in festen Abständen neue Ausstiegsmöglichkeiten. Für den freien Flug trenne ich die normale Reisewahl vom Rückfall bei blockiertem Bodenweg; die nativen Flug-, Kollisions- und Landeprüfungen bleiben dabei erhalten.
• Ran git status --short --branch; cat src/server/game/World/ClientlessTravelGraphMgr.h | tail -150; cat src/server/game/World/ClientlessFlightMgr.h
└ };
#define sClientlessFlightMgr ClientlessFlightMgr::instance()
#endif
+ 217 lines (^t to expand)
• Explored
└ Read ClientlessActionExecutorMgr.cpp, ClientlessActionExecutorMgr.h
Read ClientlessTravelGraphMgr.cpp
Read ClientlessFlightMgr.cpp
+ Show details
■ Conversation interrupted - use /feedback if something went wrong
› DU hast wohl Probleme mit der Internetverbindung, probier es nochmal
■ Conversation interrupted - use /feedback if something went wrong
› gut das klappt nicht, ich ahne auch warum. Speicher mal bitte den aktuellen Stand unserer Unterhaltungen und Konversationen so, dass du später ohne Probleme fortsetzen kannst.
■ Conversation interrupted - use /feedback if something went wrong
› A
+283
View File
@@ -0,0 +1,283 @@
# Freizeit-Intent
Technischer Name: `leisure`. Freizeit ist ein Aufenthalt mit Spaziergängen,
Pausen und freiwilligen Ausflügen, keine Folge endloser Fernreiseaufträge.
Migration: `sql/custom/world/2026_10_27_02_adventure_leisure.sql` nach den
Geselligkeits- und Ruhe-Migrationen. Definitionen und bearbeitete Gewichte bleiben
erhalten. Neue Tagespläne können Freizeit anhand `activity_affinity.leisure`
auswählen (Standard 0,5); vorhandene gespeicherte Tagespläne bleiben bestehen.
## Mikroaktionen
| Schlüssel | Ablauf |
|---|---|
| `leisure_walk` | Zusammenhängender MMAP-Hinweg zu einem entfernten Außenpunkt, danach dieselben Wegpunkte zurück; Gehmodus und kurze Pause am Wendepunkt. |
| `leisure_roam` | Eine lokale Spazierstrecke auswählen, vollständig verfolgen und auf demselben Weg zurückkehren. |
| `leisure_pet_walk` | Eigenen Begleiter beschwören, gegebenenfalls anleinen, eine zusammenhängende Strecke entspannt hin und zurück gehen, zurückkehren und eigene Begleiterressourcen aufräumen. |
| `leisure_ride` | Kurzer Rundritt mit Profil-Bodenmount; nur Außenwege, danach zurückkehren und absteigen. |
| `leisure_flight` | Kurzer Rundflug mit nativen Start-, Kollisions- und Landeprüfungen; sichere Landung und Rückkehr vor Intentwechsel. |
| `leisure_fish` | An einem markierten `leisure_fishing`-Platz mit ausgerüsteter Angel und geprüftem Wasser native Angelhaltung darstellen. Kein Fischfang, Beuteobjekt oder Inventarzugang. |
| `leisure_view`, `leisure_linger` | Aussicht betrachten beziehungsweise ruhig verweilen. |
Gemeinsame Szenen ergänzen NPC-/Bot-Gespräche, Zuhören, native Sitzplätze,
Essen, Trinken und Feuerrast. Tanz wird bei Freizeit nur an einer als `festival`
klassifizierten Location angeboten. Neue Feuer bleiben in Gebäuden und Städten
verboten. Freizeit verwendet vorhandene Fähigkeiten statt eigener Sitz-,
Gesprächs- oder Feuerimplementierungen.
Geplante Fußspaziergänge bleiben höchstens 250 Yards vom Aufenthaltsanker entfernt; andere lokale Ausflüge höchstens 120 Yards.
Stadtlocations mit `zone_id` und Gebietsaufträge behalten ihre native Gebietsgrenze;
Bodenwege werden einschließlich Zwischenpunkten über MMAP geprüft. Reitwege
müssen entlang der gesamten berechneten Bodenroute im Außenbereich bleiben.
Bodenrunden wählen nahe Bodenhöhen mit höchstens drei Yards Abweichung zum
Ausgangspunkt; erhöhte Dachpunkte werden nicht als Spaziergangziele verwendet.
Nach einer Sitz-/Esspause im Gebäude bleiben Fußspaziergänge, Umherlaufen und
Haustierausführen wählbar: Die normalen lokalen Wege führen wieder zu den
geprüften Außenpunkten. Rückkehrort ist dann der äußere Freizeitanker statt
des Innenraum-Sitzplatzes. Ausritte und Rundflüge benötigen weiterhin einen
Außenstart; die Feuerverbote in Gebäuden und Städten bleiben bestehen.
Kein unerreichter Wegpunkt zählt als Ankunft. Normale Ausflüge kehren zum
Ausgangspunkt zurück; Zeitlimits, verlorene Begleiter oder Navigationsfehler
beenden einen nicht mehr sicher ausführbaren Ablauf, ohne eine Rückkehr vorzutäuschen.
Fußspaziergänge (`walk`, `roam`, `pet_walk`) wählen vor dem Losgehen genau
**eine** vollständige native Bodenroute mit mindestens 65 Yards Abstand zum
Ausgangspunkt. Das Hinweg-Budget beträgt abhängig von der Mikroaktionsdauer
100–220 Yards. Unvollständige Pfade, Shortcuts, übermäßige Umwege, fremde Gebiete
und Innenraumstrecken werden verworfen. Etwa alle 20 Yards werden echte
MMAP-Punkte als Kontrollpunkte gespeichert; lokale Navigation verbindet sie.
Der Wendepunkt kommt nur einmal vor, danach folgen die Kontrollpunkte rückwärts
bis zum Start. Es gibt keine regelmäßigen Pausen zwischen Kontrollpunkten;
am Wendepunkt sind vier Sekunden vorgesehen. Die Begleiter-Abstandsprüfung
bleibt unabhängig davon aktiv. Bei Zeitende oder einem gescheiterten Hinweg
wird entlang tatsächlich erreichter Kontrollpunkte zurückgegangen, statt einen
neuen geraden Rückweg oder weitere Zufallsziele zu wählen. Ein nicht möglicher
Rückweg wird als Fehler beendet und niemals als Rückkehr ausgegeben.
`stormwind_leisure_trade` verwendet die vorhandene Bodenposition von
`game_tele.TradeDistrict` als Freizeitanker. Dorthin wird regulär gereist, nicht
teleportiert. Weitere Orte beziehungsweise Angelplätze müssen passend markiert
und räumlich geprüft werden. Eine fehlende Angel oder ein fehlender geeigneter
Angelplatz verhindert ausschließlich die Angelaktion.
## Katze und Haustierleine
Arthoran verwendet im `movement`-Profil:
```lua
companion_spell = 10673, -- Bombay Cat, Kreatur 7385
companion_leash_item = 37460, -- Rope Pet Leash, Gegenstandszauber 48722
```
Diese IDs wurden in den geladenen nativen Spell-/SpellEffect-/Item-sparse-Daten
und der Test-Weltdatenbank bestätigt. Der Controller stellt die im Profil
gewählte nicht kämpfende Begleiterfähigkeit und die Leine einmalig als
Bot-Ausstattung bereit. Bestehende Ausstattung wird wiederverwendet; ein volles
Inventar verhindert keine unangeleinte Variante.
`ClientlessCompanionMgr` ist eine generische Clientless-Core-Fähigkeit ohne
Abhängigkeit vom AdventureBot-Manager. Die Beschwörung ist ein normaler,
nicht ausgelöster Spielerzauber. Erst ein tatsächlich vorhandener eigener
Begleiter mit passendem Beschwörungszauber bestätigt die Beschwörung. Die
Katze folgt mit dem nativen Folgegenerator. Für die eigens beschworene Katze
wird ein Abstand von 1,5 Yards mit nativem Aufholen gewählt; ein bereits vorhandener
Begleiter behält seine Bewegungskonfiguration. Es gibt keine Spielerteleports,
künstliche Katzen-Spawns oder eine ersetzt implementierte Tier-KI.
Die Leine wird über den normalen Gegenstandszauber-Auftrag des Besitzers
benutzt, mit tatsächlicher Gegenstands-GUID, Inventarposition und eigenem
Minipet als Ziel. Dadurch bleiben native Gegenstands-, Zauber-, Ziel-,
Abklingzeit- und Scriptprüfungen erhalten. Die Wirkung wird am tatsächlichen
Begleiter bestätigt. Die native Leine ist ein beweglicher Kanalzauber. Der Clientless-Reisemanager
erhält solche Kanäle beim Starten lokaler Bewegungen; nur bewegungsblockierende
Zauber werden abgebrochen. Unbewegliche Kanäle werden nicht als Gehleine verwendet; eine nicht anwendbare Leine blockiert den Spaziergang nicht.
Der Besitzer beginnt erst nach bestätigter Beschwörung und abgeschlossener
Leinenprüfung zu gehen. Bei aktiver Leine reagiert die Steuerung in jedem Weltupdate vor Erreichen der
nativen Fünf-Yards-Reichweite: Ab vier Yards wartet Arthoran, bis die Katze wieder
höchstens 3,25 Yards entfernt ist. Ohne Leine gilt eine Grenze von 15 Yards.
Eine Wartephase dauert höchstens 20 Sekunden. Eine verlorene eigene Leinenwirkung
kann höchstens zweimal über dieselbe normale Gegenstandsbenutzung neu angelegt werden. Beim Abschluss oder Abbruch werden nur die durch
diese Szene aufgebrachte Leinenwirkung und eine von ihr beschworene Katze
entfernt. Ein passender bereits vorhandener Begleiter kann benutzt werden und
bleibt danach bestehen. Fremde Begleiter und Kampf-Pets werden nicht ersetzt.
## Diagnose und Tests
```text
clientless adventure intent 26 leisure stormwind_leisure_trade 3600
clientless adventure status 26
clientless adventure micro 26 leisure_pet_walk
clientless adventure micro 26 leisure_walk
clientless adventure micro 26 leisure_ride
clientless adventure micro 26 leisure_flight
```
Gezielte Mikroaktionen benötigen den passenden laufenden Intent am Zielort und
durchlaufen dieselben Voraussetzungen wie die automatische Auswahl.
Logereignisse: `LEISURE_START`, `LEISURE_WAYPOINT`, `LEISURE_COMPLETE`,
`COMPANION_START`, `COMPANION_CONFIRMED`, `COMPANION_LEASH_REQUEST`,
`COMPANION_LEASH_CONFIRMED`, `COMPANION_LEASH_UNAVAILABLE`,
`COMPANION_WALK_SAMPLE` und `COMPANION_END`.
```bash
python3 tests/clientless/test_adventure_leisure.py
python3 tests/clientless/test_adventure_leisure_catalog.py
python3 tests/clientless/test_clientless_companion.py
```
Die Tests führen produktive Methoden mit kontrollierten Core-Adaptern aus:
Rundwege, tatsächliche Ankunft, Gebietsgrenzen, Begleiterbereitschaft,
Folgeabstand/Zeitlimit, Reitmodus, Fluglandung vor Intentwechsel, Angelvoraussetzungen,
Besitzprüfung, native Gegenstandsziele, Leinenbestätigung, Wiederverwendung und
Bereinigung. Die Migration wird auf wiederholbare Ergänzung, vorhandene gemeinsame
Aktionen und Erhalt manueller Gewichte geprüft. Sichtprüfung von Katze, Leine,
Spaziergängen, Ausritten, Flügen und Angelhaltung bleibt eine separate Ingame-Abnahme.
## Testserverstand am 27.10.2026
- Additive Freizeit-Migration angewendet: 2.644 aktive Adventure-Locations,
42 Aktionen und 56 Dialogzeilen. Vorhandene Definitionen wurden erhalten.
- Bestehende Debug-Konfiguration nur für die neue Quelldatei neu eingelesen,
mit `-j10` gebaut und in den gestoppten Testserver installiert. Alle
25 Clientless-Regressionsskripte bestanden am abschließenden Stand.
- Frischer Worldserver-Start mit `ready...`, fortlaufendem Prozess und
Listener auf Port 8085 bestätigt. Keine Produktion und kein Push.
- Arthoran reiste regulär von der Gilded Rose zum Freizeitanker im Handelsdistrikt.
`INTENT_ARRIVED` und automatische Spaziergang-/Umherlauf-/Sitzszenen wurden
beobachtet. Ein erster Ausritt erreichte einen Wegpunkt und wurde für den
Katzentest unterbrochen; ein vollständiger Live-Rundritt ist damit nicht belegt.
- Der erste Katzentest bestätigte die native Bombay-Katze und ihr Folgen.
Er zeigte außerdem, dass die Leine ein beweglicher Kanalzauber ist. Der
generische Reisemanager erhält diesen Kanal nun statt jeden Zauber pauschal
abzubrechen. Nicht passende optionale Wegpunkte werden übersprungen; die
tatsächliche Rückkehr bleibt verpflichtend. Hohe Dachpunkte sind für
Bodenrunden ausgeschlossen.
- Die native Leine hat fünf Yards Reichweite. Für die selbst beschworene Katze
wird deshalb derselbe native Folgegenerator mit engerem Abstand verwendet;
Abstandskontrolle läuft in jedem Weltupdate. Bestehende Begleiter behalten
ihre eigene Bewegungskonfiguration.
- Abschließender Live-Test: native Beschwörung von Kreatur 7385, normale
Gegenstandsbenutzung von 37460 mit Zauber 48722 und
`COMPANION_LEASH_CONFIRMED`. Zwei Bodenrunden mit 15 bestätigten Wegpunkten
einschließlich Rückkehr. Alle 15 `COMPANION_WALK_SAMPLE`-Proben zeigten
`leashed=1`, ohne `COMPANION_LEASH_LOST`; größte protokollierte Entfernung
3,27 Yards. Rückkehr am 27.10.2026 um 12:57:49 mit `NAV_COMPLETE`, danach
`LEISURE_COMPLETE ... legs=15` und `COMPANION_END`.
- Nach dem Rundgang blieb Arthoran im Freizeit-Intent und wechselte zum
ruhigen Verweilen. Sein Startprofil war nur für den Testlogin temporär auf
Freizeit/Sturmwind gesetzt und ist wieder auf den bisherigen Sozialstart
zurückgesetzt; Katze, Leine und Freizeit-Affinität bleiben im Profil konfiguriert.
Das ist eine serverseitige Bestätigung, keine visuelle Abnahme. Offen bleiben
insbesondere das Aussehen der Katze an der Leine, vollständige Ausritte und
Freizeit-Rundflüge sowie markierte Angelplätze und die Angelhaltung. Der zuvor
verschobene visuelle Schlaftest bleibt davon unabhängig offen.
### Allgemeine lokale Spline-Segmentierung
Beim anschließenden freien Umherlaufen trat der zuvor auch bei anderen Reisen
beobachtete Fehler `_checkPathLengths()` für Arthoran auf. Ursache: Der
Clientless-Reisemanager entfernte nur Abschnitte bis 0,05 Yards, während native
Splines mindestens 0,1 Yards zwischen internen Punkten verlangen. Zusätzlich
konnte das Kürzen bei 45 Yards einen zu kurzen letzten Rest erzeugen.
Die Segmentbildung verwendet jetzt 0,11 Yards Mindestlänge als Rundungsmarge
und endet bei zu kurzem Rest am letzten wirklichen Pfadpunkt. Danach folgt die
nächste reguläre lokale Teilstrecke. Die native Prüfung bleibt unverändert.
`test_local_spline_segments.py` führt die produktive Segmentbildung gegen die
produktive native Längenprüfung aus, einschließlich des gefundenen Restfalls
und 1.000 gesampelter Pfade. Der ergänzte Stand besteht alle 26 Clientless-Tests
und wurde mit `-j10` erfolgreich gebaut.
Nach Installation der Spline-Korrektur erreichte der Testserver erneut
`ready...`; Prozess und Port 8085 wurden bestätigt. Der längere Live-Test
`leisure_roam` durchlief acht bestätigte Wegpunkte einschließlich Rückkehr
und endete am 27.10.2026 um 13:08:02 mit `NAV_COMPLETE` und
`LEISURE_COMPLETE ... kind='roam' legs=8`. Dabei wurde auch eine native
45-Yards-Teilstrecke erfolgreich abgeschlossen und regulär fortgesetzt.
Im frischen Serverlog trat für GUID 26 kein `_checkPathLengths()` mehr auf.
Das temporär geänderte Test-Startziel ist zurückgesetzt. Der anschließende
Katzenspaziergang wird für die Sichtprüfung wieder über den normalen
Mikroaktionsbefehl gestartet.
## Korrektur: Freizeit nach einer Innenraumpause
Der Nutzer beobachtete Arthoran wiederholt beim Sitzen statt beim Spazieren.
Der laufende Auftrag war `leisure`, die letzten Szenen bestanden aber aus
Sitzen, Essen und Trinken auf Sitzplätzen bei `(-8827,648,98)` beziehungsweise
`(-8829,647,98)`. Die Auswahl sperrte bisher sämtliche Bewegungsaktionen im
Innenbereich. Damit konnten Fußausflüge nach einer Innenraumpause nicht mehr
angeboten werden.
Die Außenstart-Voraussetzung gilt jetzt nur noch für Ausritte und Rundflüge.
Spazieren, Umherlaufen und Haustierausführen dürfen im Innenbereich beginnen;
sie verwenden weiterhin geprüfte Außenwegpunkte und vollständige MMAP-Wege.
Ihr Rückkehrort ist in diesem Fall der Außenanker statt des Innenraum-Sitzplatzes.
Feuerverbote und übrige Umgebungsprüfungen bleiben bestehen.
Die Produktionsmethoden-Regression prüft ausdrücklich einen Innenraumstart,
freigegebene Fuß-/Haustierausflüge, weiterhin gesperrtes Reiten/Fliegen und die
Rückkehr zum Außenanker. Alle 26 Tests und der Build mit `-j10` bestanden.
Die Korrektur ist installiert; der frische Testserver-Start erreichte `ready...`,
Prozess und Port 8085 wurden bestätigt. Das nur für den Login geänderte
Startprofil wurde zurückgesetzt.
Im laufenden Server wurden danach ein automatisch ausgewählter Katzenspaziergang
mit 15 bestätigten Wegpunkten und durchgehend aktiver Leine sowie ein anschließend
automatisch ausgewählter Spaziergang beobachtet. Gezielte Sitzszenen waren an
den jeweiligen momentanen Rundwegpositionen nicht verfügbar; eine Live-Abnahme
des exakten Innenraum-Sitzplatz-zu-Außenweg-Übergangs ist damit noch nicht belegt.
Zur direkten Sichtprüfung wurde der Katzenspaziergang erneut gezielt gestartet.
## Zusammenhängende Fußrouten (27.10.2026)
Die oben dokumentierten früheren Live-Tests mit kleinen Kreisrouten sind
historische Ergebnisse. Die neue Hin-/Rückwegplanung ersetzt diese Fußrouten.
Der ausführbare C++-Regressionstest prüft Mindestreichweite, spiegelbildliche
Rückwegpunkte, tatsächliche Ankunft, Rückkehr nach Zeitende und verweigerte
MMAP-Verbindungen. Die sichtbare Natürlichkeit bleibt der Ingame-Abnahme
vorbehalten.
Live-Test des neuen Stands: Arthoran ging mit Katze und bestätigter Leine
von `(-8818.91, 614.20, 95.18)` zum 131,60 Yards entfernten Ziel
`(-8891.75, 504.59, 94.09)`. Alle neun Hinweg- und neun Rückwegankünfte
wurden bestätigt; die rückwärts besuchten Kontrollpunkte stimmten innerhalb
eines Yards mit dem Hinweg überein. Die tatsächliche Rückkehr lag unter
0,1 Yard vom Start entfernt. Alle 18 Begleiterproben bestätigten die aktive
Leine. Danach wurde automatisch ein weiterer normaler Spaziergang geplant.
Build mit `-j10`, Installation, Neustart, Ready-Meldung und Port 8085 waren
erfolgreich; alle 26 Clientless-Testskripte bestanden. Das ist eine technische
Live-Prüfung, keine abschließende visuelle Abnahme.
## Gleichmäßiges Tempo beim Haustierausführen
Die häufigen kurzen Stopps nach wenigen Yards wurden durch das native Folgen
ausgelöst: `MoveSplineInit::SetWalk` bestimmt das Splinetempo, setzt aber nicht
das Spielerflag `MOVEMENTFLAG_WALKING`. Der Folgegenerator las für Spieler
bisher nur dieses Flag und sagte deshalb Laufbewegung voraus, obwohl Arthoran
ging. Die Katze eilte voraus und die berechtigte Leinen-Abstandskontrolle
unterbrach daraufhin den Besitzer.
Der generische native Folgegenerator verwendet jetzt bei einer aktiven
Server-Spline deren tatsächliche Geschwindigkeit auch für Spieler. Ohne aktive
Spline bleibt die Auswahl anhand der normalen Spielerbewegungsflags erhalten.
Die Abstandsprüfung und der native Leinenzauber bleiben bestehen. Ein separater
ausführbarer Regressionstest deckt servergesteuertes Gehen, explizite Tempi,
Laufen, Kreaturen und normale Clientbewegung ab.
Live-Prüfung der Tempokorrektur: Die Katze begleitete Arthoran über acht
jeweils etwa 20 Yards lange Wegstücke (80 Yards hinaus und zurück). Für
alle acht gab es genau einen `NAV_START` und einen `NAV_COMPLETE`, keine
Unterbrechungen mit erneutem Start derselben Strecke. Alle acht
Begleiterproben bestätigten die aktive Leine; der Abstand lag bei etwa
2,3–2,5 Yards. Die Pause am Wendepunkt blieb erhalten. Build mit `-j10`,
Installation, Testserver-Neustart und alle 27 Clientless-Testskripte erfolgreich.
Die abschließende visuelle Beurteilung erfolgt im Spiel.
Der Nutzer hat anschließend sowohl die zusammenhängenden Fußspaziergänge als
auch das flüssige Haustierausführen nach der Tempokorrektur visuell bestätigt.
Schlafdarstellung, Ausritte, Freizeitflüge und Angeln sind dadurch nicht abgenommen.
+153 -8
View File
@@ -1,13 +1,19 @@
# Vorschlag: Mikroaktionskatalog der AdventureBots
# Mikroaktionskatalog der sieben AdventureBot-Intents
Stand: 26.10.2026. Dieser Katalog beschreibt die vereinbarten Aktionen.
Die Geselligkeitsaktionen sind implementiert; weitere Abenteuer- und
Berufsaktionen bleiben Ausbaustufen. Den geprüften Implementierungsstand
Stand: 27.10.2026. Grundlage ist der [Intent-Katalog](intent-katalog.md) mit
**sieben Grundintents**. Die bestehenden Detailkataloge für Geselligkeit,
Abenteuer und Beruf werden hier um Instanzen, Ruhe, Freizeit und Wirtschaften
ergänzt. Die neuen Detailvorschläge sind eine Arbeitsgrundlage zur Abstimmung.
Der bisherige Katalog vom 26.10.2026 beschreibt die vereinbarten Aktionen.
Geselligkeitsaktionen sowie die Ausbaustufen für Ruhe, Freizeit, Wirtschaften
und [Abenteuer-Erkundung](adventure-intent.md) sind implementiert. Weitere
Berufsaktionen und echte Kampfhandlungen bleiben Ausbaustufen. Den geprüften Implementierungsstand
beschreibt [autonomous-behavior.md](autonomous-behavior.md).
Die Aktionen werden nach den drei Intents gewichtet und über
Umgebungsbedingungen freigegeben. Einige Aktionen dürfen in allen drei
Katalogen vorkommen, erhalten aber jeweils andere Gewichte und Varianten.
Die Aktionen werden nach den sieben Intents gewichtet und über
Umgebungsbedingungen freigegeben. Einige Aktionen dürfen in mehreren
Intent-Katalogen vorkommen, erhalten aber jeweils andere Gewichte und Varianten.
## Gemeinsame Grundfähigkeit: Lagerfeuer entzünden
@@ -22,7 +28,9 @@ enthält hierfür keinen eigenen Datensatz; der Zauber stammt aus den DBC-Daten.
Ein Rastablauf kann damit lauten: geeigneten Rastplatz aufsuchen → vorhandenes
Feuer nutzen oder selbst ein Lagerfeuer entzünden → hinsetzen → essen, trinken
oder pausieren → weiterziehen. Vorhandene Feuer sind eine Möglichkeit, keine
Voraussetzung. Das eigene Feuer verwendet den normalen Zauber und dessen
Voraussetzung. Neue Feuer dürfen nur außerhalb von Gebäuden und Städten
entzündet werden; diese Grenze gilt für sämtliche Intents. Vorhandene geeignete
Feuer bleiben nutzbar. Das eigene Feuer verwendet den normalen Zauber und dessen
native Lebensdauer. Die Geselligkeitsumsetzung verwendet dafür den nativen Zauber und prüft
das tatsächlich entstandene Feuerobjekt.
@@ -148,3 +156,140 @@ Die Geselligkeits-Ausbaustufe umfasst NPC-/Bot-Gespräche, Sitzplatzsuche,
Tresen/Gastwirt und situationsbezogene Gesprächsreaktionen. Objektuntersuchung
und die weiteren Abenteuer- und Berufsaktionen können darauf aufbauen,
ohne bereits die Kampf-KI vorauszusetzen.
## Instanzen
Der Besuch ist performativ. Die sichtbare Anreise und das Betreten müssen
plausibel stattfinden; ein Durchlaufen oder Kampf innerhalb der Instanz ist
nicht erforderlich. Die verborgene Aufenthaltsphase ist Teil des Intent-Ablaufs,
keine zufällig neben anderen Gesten auswählbare Mikroaktion.
| Aktion | Darstellung und Bedingungen |
|---|---|
| Den Eingang aufsuchen | Einen erreichbaren Wartepunkt vor dem passenden Instanzeingang aufsuchen. |
| Den Eingang betrachten | Zum Eingang schauen, kurz innehalten und sich umsehen. |
| Sich vorbereiten | Ausrüstung prüfen und eine kurze Vorbereitung darstellen. |
| Proviant zu sich nehmen | Vor dem Betreten essen oder trinken, sofern ein geeigneter Platz vorhanden ist. |
| Andere Abenteurer begrüßen | Nur bei tatsächlich anwesendem passendem Gegenüber. |
| Die Instanz betreten | Den tatsächlichen Eingang nutzen; bloßes Stehen davor zählt nicht als Besuch. |
| Den Aufenthalt abstrahieren | Nach bestätigtem Eintritt für eine festgelegte Besuchsdauer außerhalb der sichtbaren Außenwelt bleiben. Keine Dungeon-KI erforderlich. |
| Am Ausgang zurückkehren | Nach dem Aufenthalt am zugehörigen plausiblen Ausgang wieder erscheinen. |
| Nach dem Besuch sammeln | Kurz orientieren, Ausrüstung prüfen oder eine Rast darstellen. |
Vorgeschlagener Ablauf: Anreise → Vorbereitung → bestätigter Eintritt →
abstrahierter Aufenthalt → Rückkehr → kurze Nachbereitung.
Zeitmodell, technische Unterbringung, Neustartverhalten und Rückkehrposition
werden vor der Umsetzung festgelegt. Ein fehlgeschlagener Eintritt darf die
Aufenthaltsphase nicht starten.
## Ruhe
Die erste Umsetzung ist als [Ruhe-Intent `rest`](rest-intent.md) dokumentiert:
native Sitzplätze, markierte Schlaf-/Bodenrastplätze, Essen und Trinken im
Sitzen, ruhiges Umsehen und Feuerrast über die gemeinsame Feuerfähigkeit.
Neue Feuer sind im Ruhe-Intent auf markierte Bodenrast-Locations beschränkt. Weitere Aktionen dieses Katalogs bleiben Ausbau.
Erholung ist das eigentliche Vorhaben. Schlaf und längere Rast brauchen einen
passenden Platz; zufälliges Niederlegen auf Straßen oder in Durchgängen ist
keine geeignete Standarddarstellung.
| Aktion | Darstellung und Bedingungen |
|---|---|
| Einen Ruheplatz suchen | Einen erreichbaren geeigneten Schlaf- oder Rastplatz aufsuchen. |
| Sich zur Ruhe setzen | Einen nativen Sitzplatz benutzen oder an einem geeigneten Rastpunkt am Boden sitzen. |
| Sich schlafen legen | An einem geeigneten Platz eine native Schlafdarstellung verwenden; vorhandene geeignete Betten bevorzugen, ohne deren Benutzbarkeit vorauszusetzen. |
| Schlafen | Eine zusammenhängende Ruhephase ohne dazwischengewürfelte Gespräche oder Arbeitsgesten halten. |
| Aufwachen und aufstehen | Schlafdarstellung beenden und sich vor dem Weitergehen aufrichten. |
| Am Feuer ruhen | Ein vorhandenes Feuer aufsuchen oder an geeignetem Platz ein eigenes entzünden. |
| Eine kleine Mahlzeit einnehmen | Essen oder trinken als Teil einer Rast. |
| Ruhig verweilen | Im Sitzen oder Stehen eine längere stille Pause einlegen. |
| Den Ruheplatz verlassen | Sitz- oder Schlafzustand sauber beenden und den Platz freigeben. |
## Freizeit
Die Umsetzung ist in [Freizeit-Intent `leisure`](leisure-intent.md) dokumentiert.
Die Bewegung oder Beschäftigung ist hier selbst das Ziel. Freizeit braucht
keine ständig neuen Sehenswürdigkeiten oder vorgetäuschten Arbeitsaufträge.
| Aktion | Darstellung und Bedingungen |
|---|---|
| Spazieren gehen | Mehrere erreichbare Punkte innerhalb der gewählten Stadt oder Umgebung im Gehmodus besuchen. |
| Ziellos umherlaufen | Abwechslungsreiche lokale Ziele innerhalb eines begrenzten Aufenthaltsbereichs wählen; keine endlose Fernreise. |
| Ein Haustier ausführen | Eigenen Begleiter beschwören, wenn möglich mit der nativen Haustierleine anleinen, einer zusammenhängenden lokalen MMAP-Strecke folgen und auf demselben Weg zurückkehren. Arthoran verwendet eine Bombay-Katze. |
| Einen Ausritt machen | In einer geeigneten Umgebung beritten umherziehen; vor unzulässigen Bereichen absteigen. |
| Einen Rundflug machen | Bei vorhandener Flugfähigkeit und erlaubtem Gebiet einen Freizeitflug mit geeigneter Landemöglichkeit durchführen. |
| Eine Aussicht genießen | Einen geeigneten Aussichtspunkt aufsuchen und länger betrachten. |
| An einem Platz verweilen | Einen Park, Brunnen, Marktplatz oder ein Ufer besuchen und dort pausieren. |
| Angeln darstellen | Einen erreichbaren geeigneten Angelplatz aufsuchen und den gemeinsamen Angelablauf verwenden. |
| Andere beobachten | Zum tatsächlichen Geschehen schauen und ruhig verweilen. |
| Sich ungezwungen unterhalten | Einen passenden anwesenden NPC oder Bot ansprechen. |
| An einem Fest teilnehmen | Nur bei tatsächlich passender Umgebung gelegentlich tanzen, jubeln oder andere Festgesten zeigen. |
| Eine Pause einlegen | Sitzen, essen oder trinken; anschließend die Freizeitbeschäftigung fortsetzen. |
## Wirtschaften
Alle Tätigkeiten sind performativ. Es gibt keine tatsächlichen Käufe,
Verkäufe, Auktionen, Gebote, Banktransfers oder Geldbewegungen.
| Aktion | Darstellung und Bedingungen |
|---|---|
| Einen Händler aufsuchen | Zu einem erreichbaren passenden Händler gehen. |
| Waren betrachten | Zum Händler oder einer geeigneten Auslage ausrichten und verweilen. |
| Ein Handelsgespräch darstellen | Begrüßen, reden, zuhören und gegebenenfalls eine passende Textzeile verwenden. |
| Angebote vergleichen | Nacheinander unterschiedliche passende Händler oder Stände besuchen; Wiederholungen dämpfen. |
| Das Auktionshaus besuchen | Den zugänglichen Innenbereich beziehungsweise einen geeigneten Auktionator aufsuchen. |
| Auktionen betrachten | Beim Auktionator eine ruhige Prüfphase mit gelegentlichen passenden Gesten darstellen. |
| Die Bank besuchen | Einen geeigneten Bankschalter beziehungsweise Bankier aufsuchen. |
| Bankgeschäfte darstellen | Am Schalter verweilen und ein kurzes Gespräch oder eine Prüfung darstellen. |
| Taschen und Vorräte prüfen | Eine kurze Vorbereitung oder Nachbereitung ohne Inventaränderung darstellen. |
| Auf Bedienung warten | An einem geeigneten Punkt kurz warten; kein vollständiges Warteschlangensystem erforderlich. |
| Den Besuch abschließen | Verabschieden, Interaktion beenden und den Schalter oder Stand verlassen. |
## Gemeinsame Aktionen und Intent-Zuordnung
Eine Aktion wird einmal als Fähigkeit umgesetzt und kann mehreren Intents
zugeordnet werden. Der Intent liefert Gewicht, Dauer, Variante und Zielpräferenz.
Ein passender Intent allein reicht nicht: Umgebung, erreichbares Ziel und
benötigte Darstellung müssen ebenfalls passen. Es werden nicht alle Aktionen
automatisch für jeden Intent freigeschaltet.
| Gemeinsame Aktion | Passende Intents | Unterschied im Ablauf |
|---|---|---|
| Angeln | Freizeit, Beruf | Freizeit betont entspanntes Verweilen; Beruf betont die wiederholte dargestellte Arbeit am Angelplatz. |
| Sitzen | Ruhe, Geselligkeit, Freizeit; als Pause auch Abenteuer und Beruf | Ruhe hält eine längere Erholungsphase; Geselligkeit verbindet Sitzen mit Trinken und Gesprächen. |
| Essen und Trinken | Geselligkeit, Ruhe, Abenteuer, Beruf, Freizeit, Instanzen | Tavernenaufenthalt, Rast, Arbeitspause oder Vorbereitung bestimmen Dauer und Variante. |
| Lagerfeuer und Feuerrast | Abenteuer, Ruhe, Geselligkeit, Freizeit | Je nach Vorhaben unterwegs rasten, länger ruhen oder gesellig verweilen. |
| Gespräch | Geselligkeit, Abenteuer, Beruf, Freizeit, Wirtschaften, Instanzen | Gastwirt, Reisender, Ausbilder, Händler oder Abenteurer sind passende unterschiedliche Ziele. |
| Umsehen und Betrachten | Alle sieben | Blickziel und Anlass müssen zur Szene passen. |
| Ausrüstung oder Vorräte prüfen | Abenteuer, Beruf, Instanzen, Wirtschaften | Vorbereitung, Werkzeugprüfung oder dargestellte Bestandsprüfung. |
| Ortswechsel | Alle sieben | Unterstützt das Vorhaben; nur bei Freizeit kann die Bewegung selbst die Haupttätigkeit sein. |
### Gemeinsamer Angelablauf
Angelplatz wählen → zum erreichbaren Ufer gehen → zum geeigneten Wasser
ausrichten → Angeln darstellen → warten → gegebenenfalls wiederholen →
Darstellung beenden und Platz verlassen.
Für beide Intents bleibt Angeln zunächst performativ: kein Fang und keine
Inventaränderung. Vor der Implementierung sind geeignete native Darstellung,
eventuell benötigte Ausrüstung und die Prüfung eines Angelplatzes zu klären.
Die Wiederverwendung derselben Fähigkeit darf keine tatsächliche Beuteerzeugung
als unbeabsichtigten Nebeneffekt einschalten.
## Schrittweise Umsetzung
Die folgenden Schritte sind ein Vorschlag für die Reihenfolge, keine bereits
implementierten Funktionen:
1. Den Katalog abstimmen und je Ausbaustufe Ziele, Voraussetzungen und Abschluss festlegen.
2. Ruhe mit Sitz-, Rast- und Schlafabläufen ergänzen; bestehende Sitz- und Feuerfähigkeiten wiederverwenden.
3. Freizeit mit Spaziergängen und Aufenthalten ergänzen; Ausritte und Rundflüge gesondert prüfen.
4. Wirtschaften mit Händler-, Bank- und Auktionshausbesuchen ergänzen.
5. Abenteuer um gezielte Erkundung und Objektuntersuchung erweitern.
6. Beruf um passende Arbeitsplätze und Darstellungen erweitern; Angeln gemeinsam für Beruf und Freizeit ergänzen.
7. Instanzbesuche mit bestätigtem Eintritt, abstrahiertem Aufenthalt und zuverlässiger Rückkehr ergänzen.
Jede Stufe erhält passende technische Prüfungen und anschließend eine visuelle
Abnahme im Spiel. Der bestehende Geselligkeitsausbau bleibt Grundlage für
gemeinsame Gesprächs-, Sitz- und Rastfähigkeiten. Echte Kampf-KI bleibt separat.
+190
View File
@@ -0,0 +1,190 @@
# Ruhe-Intent
Stand: 27.10.2026. Technischer Intentname: `rest`.
Ruhe ist ein eigenes Vorhaben mit anschließender Auswahl passender
Mikroaktionen. Tavernen werden bei der Ortswahl stärker gewichtet als
Siedlungen. Native Stühle und Bänke dürfen auch außerhalb von Tavernen
benutzt werden, beispielsweise in Sturmwind. Ein Ort ohne passende
Ruhemöglichkeit löst die Suche nach einer Ersatzlocation aus.
## Mikroaktionen
| Schlüssel | Ausführung |
|---|---|
| `rest_seat` | Erreichbaren nativen Stuhl oder eine Bank aufsuchen, regulär benutzen und 60–100 Sekunden sitzen. |
| `rest_sleep` | Einen ausdrücklich markierten Schlafplatz aufsuchen und 120–300 Sekunden mit nativer Schlafhaltung ruhen. |
| `rest_ground` | Einen ausdrücklich markierten Bodenrastplatz aufsuchen und 60–120 Sekunden sitzen. |
| `rest_drink` | Eine vorhandene native Sitzgelegenheit benutzen und Trinken darstellen. |
| `rest_eat` | Eine vorhandene native Sitzgelegenheit benutzen und Essen darstellen. |
| `rest_look` | Kurzes ruhiges Umsehen zwischen den Ruhephasen. |
| `rest_fire` | Vorhandenes geeignetes Feuer nutzen oder an einem markierten Bodenrastplatz ein natives Kochfeuer entzünden, hingehen, sitzen, trinken und ruhen (45–75 Sekunden Szenenbasis). |
Sitz-, Ess-, Trink- und Feuerabläufe verwenden dieselben Fähigkeiten wie Geselligkeit.
`2026_10_27_01_adventure_rest_fire.sql` ergänzt `rest_fire` mit Auswahlgewicht 3,
ohne vorhandene Szenen oder manuell geänderte Gewichte zu überschreiben.
Für vorhandene Feuer gelten die bisherigen Ortsgrenzen, Phasen-, Sicht- und
Navigationsprüfungen. Ein eigenes Feuer wird bei `rest` nur an einer Location
mit Kategorie `rest_ground` erlaubt, zusätzlich zu den bestehenden Prüfungen
auf Außenbereich, ebenen freien Boden, Wasser, Fallen und Zauberabklingzeit.
Zusätzlich verbietet die gemeinsame Feuerfähigkeit das Entzünden innerhalb von
Städten für sämtliche Intents. Dafür werden Stadt-/Handelskanal-Kennzeichen der
nativen AreaTable am aktuellen Standort, in übergeordneten Gebieten und der
Zone geprüft. Fehlende oder zyklische Gebietsdaten erlauben kein neues Feuer.
Die Prüfung erfolgt auch unmittelbar vor dem Zaubern. Vorhandene geeignete
Feuer bleiben in Städten und Gebäuden nutzbar.
Ein fehlendes Feuer an einer Stadtbank oder in einer Taverne berechtigt nicht
zum Entzünden. Der native Zauber 818 benötigt keine Kochkunst; erst das
bestätigte Feuerobjekt erlaubt die anschließende Feuerrast. Verschwindet es,
endet die Szene. Aufstehen und Aufräumen erfolgen über denselben Szenenabschluss
wie bei den anderen Ruheaktionen.
Gespräche, Bestellungen und Tanz gehören nicht zum anfänglichen Ruhe-Katalog.
Die Zeit der Schlaf- und Bodenrastphase beginnt nach bestätigter Ankunft;
die lokale Anreise verwendet den Gehmodus und kein Bodenmount.
## Geeignete Orte
Die additive Migration ist
`sql/custom/world/2026_10_27_00_adventure_rest.sql` (nach den bisherigen
Verhaltens- und Geselligkeitsmigrationen anwenden).
Die vorhandene Weltpunkt-Tabelle bleibt der Ortskatalog. Zusätzliche Kategorien:
- `rest_seat`: Anlaufpunkt bei einer geeigneten nativen Sitzgelegenheit.
- `rest_sleep`: ausdrücklich festgelegter Schlafplatz mit Position und Ausrichtung.
- `rest_ground`: ausdrücklich festgelegter Bodenrastplatz.
Ein Schlafplatz ist keine automatische Bettinteraktion. Der Bot läuft zur
markierten Position und nimmt dort die native Schlafhaltung ein. Dekorative
Betten werden weder als benutzbar vorausgesetzt noch durch Teleports betreten.
Neue Schlafplätze müssen räumlich und visuell geprüft werden.
Die Migration ergänzt einen Ruheplatz im Obergeschoss der Goldhainer Taverne
(`goldshire_sleep_upstairs`) und einen Anlaufpunkt bei einer vorhandenen
Sturmwinder Steinbank (`stormwind_rest_bench`, Spawn 219847, Entry 203946).
Die Bankdefinition wird nur angelegt, wenn der passende native Spawn vorhanden
ist. Bestehende Definitionen und manuell bearbeitete Gewichte bleiben erhalten.
Beim Schlafen oder Bodenrasten muss der Punkt zur Fraktion und Karte passen
und höchstens 30 Yards vom Aufenthaltsanker entfernt sein; maximal 12 Yards
Höhenunterschied erlauben einen Schlafplatz im Obergeschoss. Der lokale Weg
muss vollständig und unverdächtig sein und darf maximal 120 Yards lang sein.
Anlauf und Ankunft prüfen auf andere Spieler am Platz. Die Ankunft muss
innerhalb von 60 Sekunden und mit höchstens einem Yard Positionsabweichung
bestätigt werden. Wasser, Fallen, Kartenwechsel, entfernte Ortsdefinitionen,
veränderte Ruhehaltung und Kampf beenden beziehungsweise verhindern die Szene.
Beim Abbruch oder Intentwechsel wird die Haltung zurückgesetzt. Fernreisen
behalten die vorhandene sichere Übergabe an regulären Graphnodes.
## Bedienung
Serverkonsole (im Spiel mit führendem Punkt):
```text
clientless adventure intent 26 rest goldshire_tavern 3600
clientless adventure status 26
clientless adventure micro 26 rest_sleep
clientless adventure intent 26 rest stormwind_rest_bench 3600
clientless adventure micro 26 rest_seat
clientless adventure stop 26
```
`micro` ist ein gezielter Test während eines laufenden passenden Intents am
Zielort. Es umgeht weder Ortsbedingungen noch Navigation oder native
Sitzbestätigung. Der Tagesplaner kann `rest` anhand von
`activity_affinity.rest` auswählen (Standardgewicht 0,5); vorhandene gespeicherte
Tagespläne werden nicht nachträglich umgeschrieben. Arthorans Tagesplan bleibt
für die gezielte Sichtprüfung ausgeschaltet.
## Prüfungen
```bash
python3 tests/clientless/test_adventure_rest.py
python3 tests/clientless/test_adventure_rest_fire.py
python3 tests/clientless/test_adventure_social_fire.py
python3 tests/clientless/test_adventure_micro_reset.py
python3 tests/clientless/test_adventure_intent_state.py
python3 tests/clientless/test_adventure_social.py
python3 tests/clientless/test_adventure_walk.py
```
Die neue Regression führt die produktiven Auswahl- und Aktualisierungsfunktionen
mit kontrollierten Navigationsadaptern aus: geeignete Kategorien, Karte,
Fraktion, Abstand und Höhe, bestätigte Ankunft, belegte Plätze, vollständige
Ruhedauer nach Anreise, Navigationsfehler, Zeitlimit, Katalogänderung und
unterbrochene Haltung. Bestehende Prüfungen decken Sitzbestätigung, Gehmodus,
Aufräumen und Intentübergabe ab. Build und technische Tests ersetzen die
visuelle Abnahme der neuen Ruheabläufe nicht.
## Testserverstand am 27.10.2026
- Debug-Build mit `-j10` erfolgreich, installiert. Alle neun ausgeführten
Adventure-/Beobachter-Regressionsskripte erfolgreich.
- Frische Worldserver-Sitzung mit `(worldserver-daemon) ready...`, fortlaufendem
Prozess und geöffnetem Port 8085 bestätigt.
- Goldhain: `INTENT_ARRIVED`, `REST_APPROACH` und `REST_CONFIRMED` für
`goldshire_sleep_upstairs` bestätigt. Der lokale Weg führte über die Treppe
und zwei Splineabschnitte mit `walk=1`; die tatsächliche Ankunft lag bei
`(-9457.200, 32.800, 63.820)`. Angeforderte Schlafdauer: 131 Sekunden.
Der nachfolgende Intentwechsel beendete diese Schlafphase vorzeitig.
- Sturmwind: `INTENT_ARRIVED` und `rest_seat` bestätigt. Native Steinbank
Entry 203936, Slotposition `(-8473.900, 847.299, 105.786)`, Slotfehler
`0.000`, Sitzhaltung `5`, Bewegungs-GUID `26`. Der lokale Anlauf lief mit
`walk=1`. Die Auswahl darf andere passende Bänke in der Nähe des
Anlaufpunkts verwenden.
- Der Banktest begann nach administrativer Positionierung am Kathedralenplatz;
die anschließende Navigation zum Ruheort und die Sitzplatzbenutzung waren
regulär. Die Fernreise von Goldhain zu dieser Bank ist nicht als erfolgreich
geprüft einzustufen: der bestehende Graph plante einen weiten Umweg über
Eisenschmiede (16 Schritte zum Hafenanker); der Versuch wurde beendet.
Bei einer zwischenzeitlichen Rückreise traten außerdem vorhandene
Navigationsfehler mit `_checkPathLengths()` auf. Diese Reiseprobleme wurden
im Rahmen der Ruhe-Erweiterung nicht verändert.
- Für den isolierten Banktest wurde der Startauftrag im Profil kurzzeitig
angepasst und nach bestätigtem Login wieder vollständig zurückgesetzt.
Arthoran bleibt zur Sichtprüfung im laufenden Ruhe-Intent in Sturmwind;
sein Tagesplan ist aus. Das unveränderte Profil startet nach erneutem Login
weiterhin den bisherigen Sozialauftrag in Goldhain.
Offen bleiben die visuelle Abnahme der neuen Schlafdarstellung und des
Ruheablaufs sowie eine reguläre Fernreise bis zum Stadtbank-Ruheort.
Bodenrast ist automatisiert geprüft, hat aber noch keinen eigenen
Live-Testpunkt. Weitere Ruheorte können über den bestehenden Ortskatalog
ergänzt werden.
Fortschreibung: Der Nutzer hat den lokalen Anlauf zur Stadtbank, Absteigen
und Hinsetzen visuell bestätigt. Für die anschließende Schlafvorführung in
Sturmwind wurde `gilded_rose_sleep_upstairs` im Obergeschoss der Taverne
`the_gilded_rose` ergänzt. Die markierte Bodenposition verwendet die native
Schlafhaltung; automatische Bettbenutzung ist weiterhin nicht implementiert.
## Feuerrast-Ergänzung am 27.10.2026
`rest_fire` ist im Testkatalog aktiv (34 Aktionen insgesamt). Die additive
Migration wurde angewendet; vorhandene Feueraktionen für Geselligkeit und
Abenteuer blieben unverändert. Alle 22 Clientless-Regressionsskripte und der
Debug-Build mit `-j10` bestanden. Die Binärdateien sind installiert; der frische
Start erreichte `ready...`, Prozess und Port 8085 wurden bestätigt.
Arthoran wurde mit seinem Ruheauftrag in der Gilded Rose wieder eingeloggt.
Sein Profil wurde nur für diesen Login temporär angepasst und anschließend
bytegenau zurückgesetzt. `INTENT_ARRIVED` und natives Sitzen sind erneut
bestätigt. Der gezielte Aufruf `micro 26 rest_fire` wurde dort mangels eines
geeigneten erkannten Feuers abgelehnt; die laufende Sitzszene blieb erhalten.
Damit ist die Laufzeitsperre an diesem Innenraum geprüft, nicht eine erfolgreiche
Feuerrast. Feuererzeugung und vorhandene Feuer sind mit kontrollierten Adaptern
getestet; eine positive Sichtprüfung der Feuerrast und der verschobene visuelle
Schlaftest bleiben offen. Im aktuellen Testkatalog gibt es noch keine aktivierten
`rest_ground`-Locations; neue Plätze müssen vor Freigabe räumlich geprüft werden.
Die ergänzte Gebäudesperre/Stadtsperre ist ebenfalls gebaut (`-j10`) und
installiert. Die drei betroffenen Feuer-/Szenen-Regressionsskripte bestanden,
einschließlich unmarkierter Stadtviertel mit Stadt-Parent, getrennter Zone,
Handelskanal-Kennzeichen, fehlender Gebietsdaten und weiterhin nutzbarer
vorhandener Feuer. Die geladenen enUS-AreaTable-Daten bestätigen die
Stadtkennzeichen unter anderem für Sturmwind und deren Vererbung an das
selbst unmarkierte Untergebiet 1617. Der Testserver erreichte erneut `ready...`;
Prozess und Port 8085 wurden geprüft. Arthoran ist wieder im Ruhe-Intent in
der Gilded Rose, sein temporär angepasstes Startprofil ist zurückgesetzt.
+7
View File
@@ -5,3 +5,10 @@
!2026_10_26_02_clientless_portal_use.sql
!2026_10_26_03_adventure_behavior.sql
!2026_10_26_04_adventure_social.sql
!2026_10_27_00_adventure_rest.sql
!2026_10_27_01_adventure_rest_fire.sql
!2026_10_27_02_adventure_leisure.sql
!2026_10_27_03_adventure_economy.sql
!2026_10_27_04_adventure_exploration.sql
!2026_10_27_05_adventure_profession.sql
@@ -0,0 +1,55 @@
-- Apply after 2026_10_26_04. Preserve edited weights and existing world points.
INSERT IGNORE INTO vcore_adventure_location_intent VALUES
('tavern','rest',8),('settlement','rest',1),
('rest_seat','rest',4),('rest_sleep','rest',6),('rest_ground','rest',3);
-- Native furniture scenes are shared with social; no generic ground sitting
-- in streets. Sleeping and ground sitting require explicitly authored points.
INSERT IGNORE INTO vcore_adventure_action VALUES
('rest_seat','social',60,100,'seat',0),
('rest_sleep','recovery',120,300,'sleep',0),
('rest_ground','recovery',60,120,'ground',0),
('rest_drink','social',35,60,'drink_seated',0),
('rest_eat','social',35,60,'eat_seated',0),
('rest_look','look',6,12,'',0);
INSERT IGNORE INTO vcore_adventure_action_intent VALUES
('rest_seat','rest',6),('rest_sleep','rest',8),('rest_ground','rest',3),
('rest_drink','rest',1),('rest_eat','rest',1),('rest_look','rest',0.25);
-- Sleep/ground points are deliberately not generated from innkeepers or random
-- terrain. Add surveyed resting spots as enabled world points with category
-- rest_sleep/rest_ground. A city bench approach point uses rest_seat.
-- Initial Goldshire upstairs resting corner. Navigation must confirm arrival
-- before the native sleep pose is applied; no teleport onto decorative beds.
INSERT IGNORE INTO vcore_clientless_world_point
(point_key,category,map,position_x,position_y,position_z,orientation,enabled,comment) VALUES
('goldshire_sleep_upstairs','rest_sleep',0,-9457.2,32.8,63.9322,1.45,1,
'Designated upstairs tavern floor resting spot; native sleep pose after MMAP arrival');
INSERT IGNORE INTO vcore_adventure_location
(point_key,display_name,source_type,region_key,zone_id,faction) VALUES
('goldshire_sleep_upstairs','Goldhain: Ruheplatz im Obergeschoss','manual','elwynn',12,1);
-- Keep this dependent on the actual native bench spawn, rather than creating
-- furniture. The approach lies two yards in front of the Stormwind bench.
INSERT IGNORE INTO vcore_clientless_world_point
(point_key,category,map,position_x,position_y,position_z,orientation,enabled,comment)
SELECT 'stormwind_rest_bench','rest_seat',g.map,
g.position_x+2*COS(g.orientation),g.position_y+2*SIN(g.orientation),
g.position_z,g.orientation,1,'Rest approach to native Stormwind stone bench spawn 219847'
FROM gameobject g JOIN gameobject_template t ON t.entry=g.id
WHERE g.guid=219847 AND g.map=0 AND g.id=203946 AND t.type=7;
INSERT IGNORE INTO vcore_adventure_location
(point_key,display_name,source_type,region_key,zone_id,faction)
SELECT point_key,'Sturmwind: Bankpause','manual','stormwind',1519,1
FROM vcore_clientless_world_point WHERE point_key='stormwind_rest_bench';
-- Gilded Rose upstairs resting corner; MMAP arrival is required and visual
-- suitability is checked in game. This is a floor pose, not native bed use.
INSERT IGNORE INTO vcore_clientless_world_point
(point_key,category,map,position_x,position_y,position_z,orientation,enabled,comment) VALUES
('gilded_rose_sleep_upstairs','rest_sleep',0,-8863.8,685.8,104.932,3.74,1,
'Designated upstairs Gilded Rose floor sleeping spot; visual acceptance pending');
INSERT IGNORE INTO vcore_adventure_location
(point_key,display_name,source_type,region_key,zone_id,faction) VALUES
('gilded_rose_sleep_upstairs','Gilded Rose: Ruheplatz im Obergeschoss','manual','stormwind',1519,1);
@@ -0,0 +1,7 @@
-- Apply after 2026_10_27_00. Reuse the shared native fire scene for rest.
-- Preserve existing action definitions and manually edited selection weights.
INSERT IGNORE INTO vcore_adventure_action
(action_key,kind,min_seconds,max_seconds,requirement,emote_id) VALUES
('rest_fire','social',45,75,'fire',0);
INSERT IGNORE INTO vcore_adventure_action_intent
(action_key,intent,weight) VALUES ('rest_fire','rest',3);
@@ -0,0 +1,39 @@
-- Additive leisure catalog after the social/rest stages; preserve edited weights.
INSERT IGNORE INTO vcore_adventure_location_intent (category,intent,weight) VALUES
('leisure','leisure',8),('leisure_fishing','leisure',3),('settlement','leisure',3),
('landmark','leisure',1),('wilderness','leisure',2),('festival','leisure',3);
INSERT IGNORE INTO vcore_adventure_action
(action_key,kind,min_seconds,max_seconds,requirement,emote_id) VALUES
('leisure_walk','leisure',150,240,'walk',0),
('leisure_roam','leisure',90,180,'roam',0),
('leisure_pet_walk','leisure',180,300,'pet_walk',0),
('leisure_ride','leisure',90,180,'ride',0),
('leisure_flight','leisure',180,300,'flight',0),
('leisure_fish','leisure',45,90,'fish',0),
('leisure_view','look',20,45,'',0),
('leisure_linger','look',30,75,'',0);
INSERT IGNORE INTO vcore_adventure_action_intent (action_key,intent,weight) VALUES
('leisure_walk','leisure',6),('leisure_roam','leisure',3),('leisure_pet_walk','leisure',3),
('leisure_ride','leisure',1),('leisure_flight','leisure',0.4),('leisure_fish','leisure',2),
('leisure_view','leisure',2),('leisure_linger','leisure',2),
('social_npc','leisure',1),('social_bot','leisure',1),('social_listen','leisure',0.8),
('social_seat','leisure',2),('rest_drink','leisure',1),('rest_eat','leisure',1),
('social_fire','leisure',0.5),('social_dance','leisure',0.3);
-- Native teleport catalog supplies surveyed ground coordinates; travel remains MMAP.
INSERT IGNORE INTO vcore_clientless_world_point
(point_key,category,map,position_x,position_y,position_z,orientation,enabled,comment)
SELECT 'stormwind_leisure_trade','leisure',map,position_x,position_y,position_z,orientation,1,
'Leisure anchor at native TradeDistrict ground position; no teleport is performed'
FROM game_tele WHERE name='TradeDistrict' AND map=0;
INSERT IGNORE INTO vcore_adventure_location
(point_key,display_name,source_type,region_key,zone_id,faction)
SELECT point_key,'Sturmwind: Freizeit im Handelsdistrikt','manual','stormwind',1519,1
FROM vcore_clientless_world_point WHERE point_key='stormwind_leisure_trade';
INSERT IGNORE INTO vcore_adventure_dialog (line_key,locale,intent,text,weight) VALUES
('greet_leisure','deDE','leisure','Guten Tag! Ein schöner Tag für einen Spaziergang.',1),
('talk_leisure','deDE','leisure','Heute lasse ich es einmal ruhig angehen.',1),
('farewell_leisure','deDE','leisure','Macht es gut, ich drehe noch eine Runde.',1),
('greet_leisure','enUS','leisure','Hello! A fine day for a stroll.',1),
('talk_leisure','enUS','leisure','I am taking things easy today.',1),
('farewell_leisure','enUS','leisure','Take care. I will walk a little farther.',1);
@@ -0,0 +1,48 @@
-- Performative economic visits. Additive catalog; preserve edited definitions/weights.
INSERT IGNORE INTO vcore_adventure_location_intent (category,intent,weight) VALUES
('economy_vendor','economy',5),('economy_bank','economy',3),('economy_auction','economy',3);
INSERT IGNORE INTO vcore_adventure_action
(action_key,kind,min_seconds,max_seconds,requirement,emote_id) VALUES
('economy_vendor_visit','social',15,25,'vendor_visit',0),
('economy_goods','social',25,45,'goods',0),
('economy_trade','social',25,40,'trade',0),
('economy_compare','social',50,70,'vendor_compare',0),
('economy_auction_visit','social',20,30,'auction_visit',0),
('economy_auctions','social',35,60,'auctions',0),
('economy_bank_visit','social',15,25,'bank_visit',0),
('economy_banking','social',30,50,'banking',0),
('economy_supplies','social',12,20,'supplies',0),
('economy_wait','social',10,20,'service_wait',0),
('economy_finish','social',5,10,'service_finish',0);
INSERT IGNORE INTO vcore_adventure_action_intent (action_key,intent,weight) VALUES
('economy_vendor_visit','economy',2),('economy_goods','economy',5),('economy_trade','economy',4),
('economy_compare','economy',3),('economy_auction_visit','economy',2),('economy_auctions','economy',5),
('economy_bank_visit','economy',2),('economy_banking','economy',5),('economy_supplies','economy',1),
('economy_wait','economy',1),('economy_finish','economy',0.5);
-- Named Stormwind service spawns supply the anchor's facing and position. Bank
-- and auction NPCs stand on a raised counter platform; visitors use the floor
-- in front. These are navigation destinations, never teleport instructions.
INSERT IGNORE INTO vcore_clientless_world_point
(point_key,category,map,position_x,position_y,position_z,orientation,enabled,comment)
SELECT CASE c.id WHEN 1285 THEN 'stormwind_economy_market' WHEN 2456 THEN 'stormwind_economy_bank' ELSE 'stormwind_economy_auction' END,
CASE c.id WHEN 1285 THEN 'economy_vendor' WHEN 2456 THEN 'economy_bank' ELSE 'economy_auction' END,
c.map,c.position_x+COS(c.orientation)*7,c.position_y+SIN(c.orientation)*7,
c.position_z-CASE WHEN c.id=1285 THEN 0 ELSE 1.5 END,c.orientation,1,
'Performative economic visit: arrival/withdrawal point in front of native service NPC; no transactions'
FROM creature c JOIN creature_template t ON t.entry=c.id
WHERE c.map=0 AND c.position_x BETWEEN -9000 AND -8750 AND c.position_y BETWEEN 450 AND 850
AND ((c.id=1285 AND (t.npcflag & 128)<>0) OR (c.id=2456 AND (t.npcflag & 131072)<>0)
OR (c.id=8719 AND (t.npcflag & 2097152)<>0)) ORDER BY c.guid;
INSERT IGNORE INTO vcore_adventure_location
(point_key,display_name,source_type,region_key,zone_id,faction)
SELECT point_key,CASE category WHEN 'economy_vendor' THEN 'Sturmwind: Waren im Handelsdistrikt'
WHEN 'economy_bank' THEN 'Sturmwind: Bankbesuch' ELSE 'Sturmwind: Auktionshausbesuch' END,
'manual','stormwind',1519,1 FROM vcore_clientless_world_point
WHERE point_key IN ('stormwind_economy_market','stormwind_economy_bank','stormwind_economy_auction');
INSERT IGNORE INTO vcore_adventure_dialog (line_key,locale,intent,text,weight) VALUES
('greet_economy','deDE','economy','Guten Tag. Ich möchte mich ein wenig umsehen.',1),
('talk_economy','deDE','economy','Ich prüfe gerade, was ich für meine nächste Reise brauche.',1),
('farewell_economy','deDE','economy','Vielen Dank für Eure Zeit. Einen schönen Tag noch!',1),
('greet_economy','enUS','economy','Good day. I would like to have a look around.',1),
('talk_economy','enUS','economy','I am checking what I need for my next journey.',1),
('farewell_economy','enUS','economy','Thank you for your time. Have a good day!',1);
@@ -0,0 +1,43 @@
-- Exploration builds on existing social/rest/native ground-outing abilities.
INSERT IGNORE INTO vcore_adventure_action
(action_key,kind,min_seconds,max_seconds,requirement,emote_id) VALUES
('adventure_explore','outing',150,240,'explore',0),
('adventure_patrol','outing',90,180,'patrol',0),
('adventure_view','social',25,45,'vista',0),
('adventure_inspect','social',20,35,'inspect',0),
('adventure_inspect_detail','social',25,40,'inspect_detail',0),
('adventure_point','social',8,15,'point_object',0),
('adventure_scout','social',20,35,'scout',0),
('adventure_equipment','social',12,20,'equipment',0),
('adventure_entrance','social',20,35,'entrance',0),
('adventure_comment','social',10,20,'comment',0);
INSERT IGNORE INTO vcore_adventure_action_intent (action_key,intent,weight) VALUES
('adventure_explore','adventure',5),('adventure_patrol','adventure',3),
('adventure_view','adventure',2),('adventure_inspect','adventure',4),
('adventure_inspect_detail','adventure',3),('adventure_point','adventure',1),
('adventure_scout','adventure',3),('adventure_equipment','adventure',1),
('adventure_entrance','adventure',2),('adventure_comment','adventure',0.4),
('social_npc','adventure',1.5),('social_seat','adventure',1),
('social_fire','adventure',1.5),('rest_drink','adventure',0.5),('rest_eat','adventure',1);
-- Retire only the untouched original tiny random wander for this intent;
-- custom weights and its social/profession uses are preserved.
UPDATE vcore_adventure_action_intent SET weight=0
WHERE action_key='explore' AND intent='adventure' AND weight=5;
INSERT IGNORE INTO vcore_adventure_dialog (line_key,locale,intent,text,weight) VALUES
('greet_adventure','deDE','adventure','Seid gegrüßt. Ich sehe mich hier ein wenig um.',1),
('talk_adventure','deDE','adventure','Gibt es hier in der Gegend etwas Interessantes zu entdecken?',1),
('farewell_adventure','deDE','adventure','Danke für Eure Zeit. Ich werde mich noch etwas umsehen.',1),
('comment_adventure','deDE','adventure','Das ist einen genaueren Blick wert.',1),
('greet_adventure','enUS','adventure','Greetings. I am exploring the area.',1),
('talk_adventure','enUS','adventure','Is there anything interesting to discover nearby?',1),
('farewell_adventure','enUS','adventure','Thank you for your time. I will look around some more.',1),
('comment_adventure','enUS','adventure','That deserves a closer look.',1);
-- Locations own their intent eligibility through the editable category mapping.
-- Materialize the previous general economic defaults instead of applying a
-- hidden code fallback. Existing edited weights, including zero, take priority.
INSERT IGNORE INTO vcore_adventure_location_intent (category,intent,weight)
SELECT DISTINCT category,'economy',CASE
WHEN category IN ('settlement','zone') THEN 6
WHEN category IN ('bank','service','tavern') THEN 4 ELSE 1 END
FROM vcore_clientless_world_point WHERE category NOT LIKE 'travel%';
INSERT IGNORE INTO vcore_adventure_location_intent (category,intent,weight) VALUES ('tavern','leisure',1);
@@ -0,0 +1,81 @@
-- All jobs remain permanently performative. No harvesting/loot/crafting handlers.
CREATE TABLE IF NOT EXISTS vcore_adventure_workstation (
gameobject_entry INT UNSIGNED NOT NULL,
role VARCHAR(32) NOT NULL,
enabled TINYINT UNSIGNED NOT NULL DEFAULT 1,
PRIMARY KEY(gameobject_entry,role)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Native spell-focus semantics, verified against the loaded world templates.
INSERT IGNORE INTO vcore_adventure_workstation (gameobject_entry,role)
SELECT entry,'anvil' FROM gameobject_template WHERE type=8 AND data0 IN (1,623);
INSERT IGNORE INTO vcore_adventure_workstation (gameobject_entry,role)
SELECT entry,'forge' FROM gameobject_template WHERE type=8 AND data0 IN (3,543);
INSERT IGNORE INTO vcore_adventure_workstation (gameobject_entry,role)
SELECT entry,'cooking' FROM gameobject_template WHERE type=8 AND data0=4;
INSERT IGNORE INTO vcore_adventure_workstation (gameobject_entry,role)
SELECT entry,'alchemy' FROM gameobject_template WHERE type=8 AND data0=663;
INSERT IGNORE INTO vcore_adventure_workstation (gameobject_entry,role)
SELECT entry,'inscription' FROM gameobject_template WHERE type=8 AND data0=1589;
-- Semantic roles describe object TYPES, never bot-specific locations. Permit
-- native focus-zero anvil/forge templates and explicit workshop props too.
INSERT IGNORE INTO vcore_adventure_workstation (gameobject_entry,role)
SELECT entry,'anvil' FROM gameobject_template WHERE (type=5 OR (type=8 AND data0=0)) AND LOWER(name) LIKE '%anvil%';
INSERT IGNORE INTO vcore_adventure_workstation (gameobject_entry,role)
SELECT entry,'forge' FROM gameobject_template WHERE (type=5 OR (type=8 AND data0=0)) AND LOWER(name) LIKE '%forge%' AND LOWER(name) NOT LIKE '%runeforge%';
INSERT IGNORE INTO vcore_adventure_workstation (gameobject_entry,role)
SELECT entry,'workbench' FROM gameobject_template WHERE (type=8 AND data0 IN (1,623)) OR (type IN (5,8) AND LOWER(name) LIKE '%workbench%') OR ((type=5 OR (type=8 AND data0=0)) AND LOWER(name) LIKE '%anvil%');
INSERT IGNORE INTO vcore_adventure_workstation (gameobject_entry,role)
SELECT entry,'sewing' FROM gameobject_template WHERE type IN (5,8) AND (LOWER(name)='loom' OR LOWER(name) LIKE '% loom%' OR LOWER(name) LIKE 'doodad_loom%' OR LOWER(name) LIKE '%sewing%');
INSERT IGNORE INTO vcore_adventure_workstation (gameobject_entry,role)
SELECT entry,'leather' FROM gameobject_template WHERE type IN (5,8) AND (LOWER(name) LIKE '%tanning%' OR LOWER(name) LIKE '%leatherworking%table%');
INSERT IGNORE INTO vcore_adventure_workstation (gameobject_entry,role)
SELECT entry,'jewelcrafting' FROM gameobject_template WHERE type IN (5,8) AND LOWER(name) LIKE '%jewel%table%';
INSERT IGNORE INTO vcore_adventure_workstation (gameobject_entry,role)
SELECT entry,'enchanting' FROM gameobject_template WHERE type IN (5,8) AND LOWER(name) LIKE '%enchant%table%';
INSERT IGNORE INTO vcore_adventure_workstation (gameobject_entry,role)
SELECT entry,'wood' FROM gameobject_template WHERE type IN (5,8) AND (LOWER(name) LIKE '%woodpile%' OR LOWER(name) LIKE '%wood pile%' OR LOWER(name) LIKE '%lumber pile%');
INSERT IGNORE INTO vcore_adventure_action
(action_key,kind,min_seconds,max_seconds,requirement,emote_id) VALUES
('profession_search','outing',90,150,'work_search',0),
('profession_ore_inspect','social',12,20,'ore_inspect',0),
('profession_herbalism','social',20,35,'work_herbalism',0),
('profession_skinning','social',15,25,'work_skinning',0),
('profession_smelting','social',25,40,'work_smelting',0),
('profession_engineering','social',25,45,'work_engineering',0),
('profession_alchemy','social',25,45,'work_alchemy',0),
('profession_tailoring','social',25,45,'work_tailoring',0),
('profession_leatherworking','social',25,45,'work_leatherworking',0),
('profession_inscription','social',25,45,'work_inscription',0),
('profession_enchanting','social',25,45,'work_enchanting',0),
('profession_jewelcrafting','social',25,45,'work_jewelcrafting',0),
('profession_wood','social',20,40,'work_wood',0),
('profession_cooking','social',25,45,'work_cooking',0),
('profession_first_aid','social',15,25,'work_first_aid',0),
('profession_fishing','leisure',45,90,'fish',0),
('profession_tools','social',12,20,'profession_tools',0),
('profession_trainer','social',25,40,'profession_trainer',0),
('profession_vendor','social',25,40,'profession_vendor',0);
INSERT IGNORE INTO vcore_adventure_action_intent (action_key,intent,weight) VALUES
('profession_search','profession',8),('profession_ore_inspect','profession',2),
('profession_herbalism','profession',5),('profession_skinning','profession',5),
('profession_smelting','profession',3),('profession_engineering','profession',4),
('profession_alchemy','profession',4),('profession_tailoring','profession',4),
('profession_leatherworking','profession',4),('profession_inscription','profession',4),
('profession_enchanting','profession',4),('profession_jewelcrafting','profession',4),
('profession_wood','profession',0.5),('profession_cooking','profession',0.5),
('profession_first_aid','profession',0.3),('profession_fishing','profession',0.5),
('profession_tools','profession',1),('profession_trainer','profession',1),
('profession_vendor','profession',1),('social_seat','profession',0.5);
-- A zone is a legitimate destination. Its actual workplaces/resources are
-- discovered after travel to a reachable graph node in that area.
INSERT IGNORE INTO vcore_adventure_location_intent (category,intent,weight) VALUES
('zone','profession',3),('wilderness','profession',3),('settlement','profession',2);
UPDATE vcore_adventure_action_intent SET weight=0
WHERE action_key='explore' AND intent='profession' AND weight=2;
INSERT IGNORE INTO vcore_adventure_dialog (line_key,locale,intent,text,weight) VALUES
('greet_profession','deDE','profession','Seid gegrüßt. Habt Ihr kurz Zeit für eine Fachfrage?',1),
('talk_profession','deDE','profession','Ich sehe mich nach passenden Materialien und Arbeitsplätzen um.',1),
('farewell_profession','deDE','profession','Vielen Dank. Ich mache mich wieder an die Arbeit.',1),
('greet_profession','enUS','profession','Greetings. Do you have a moment for a trade question?',1),
('talk_profession','enUS','profession','I am looking for suitable materials and places to work.',1),
('farewell_profession','enUS','profession','Thank you. I will get back to work.',1);
@@ -70,6 +70,8 @@ namespace Trainer
void SendSpells(Creature const* npc, Player const* player, LocaleConstant locale) const;
void TeachSpell(Creature const* npc, Player* player, uint32 spellId) const;
std::vector<Spell> const& GetSpells() const { return _spells; }
Type GetTrainerType() const { return _type; }
uint32 GetTrainerRequirement() const { return _requirement; }
bool IsTrainerValidForPlayer(Player const* player) const;
@@ -62,15 +62,15 @@ inline float GetVelocity(Unit* owner, Unit* target, bool catchUp)
{
float targetSpeed = 0.f;
float velocity = 0.f;
if (target->IsPlayer())
// Server-driven player splines can select a gait/velocity without changing
// MOVEMENTFLAG_WALKING (MoveSplineInit::SetWalk). Follow the actual motion,
// just as for creatures, rather than predicting a running player ahead.
if (!target->movespline->Finalized())
targetSpeed = target->movespline->Velocity();
else if (target->IsPlayer())
targetSpeed = target->GetSpeed(SelectSpeedType(target->m_movementInfo.GetMovementFlags()));
else
{
if (!target->movespline->Finalized())
targetSpeed = target->movespline->Velocity();
else
targetSpeed = target->GetSpeed(MOVE_RUN);
}
targetSpeed = target->GetSpeed(MOVE_RUN);
if (catchUp)
velocity = targetSpeed;
+602 -56
View File
@@ -1,5 +1,6 @@
#include "AdventureBehaviorMgr.h"
#include "AdventureLocationMgr.h"
#include "AdventureEconomyPolicy.h"
#include "ClientlessLuaEngine.h"
#include "ClientlessPlayerMgr.h"
#include "ClientlessActionExecutorMgr.h"
@@ -11,6 +12,9 @@
#include "DatabaseEnv.h"
#include "DBCStores.h"
#include "GameObject.h"
#include "Item.h"
#include "ItemTemplate.h"
#include "Creature.h"
#include "GameTime.h"
#include "Log.h"
#include "Player.h"
@@ -26,9 +30,18 @@
namespace
{
using namespace AdventureBehaviorPolicy;
AdventureMicroAction ResolvedAction(AdventureMicroAction action)
{
if(action.Kind=="work")
{
action.Requirement=AdventureProfessionPolicy::Legacy(action.Requirement);
action.Kind=action.Requirement.empty()?"unsupported":"social";
}
return action;
}
bool Known(std::string const & intent)
{
return intent == "social" || intent == "adventure" || intent == "profession";
return intent == "social" || intent == "adventure" || intent == "profession" || intent == "rest" || intent == "leisure" || intent == "economy";
}
bool Busy(uint32 guid)
{
@@ -78,6 +91,8 @@ void AdventureBehaviorMgr::Online(Player * player)
TC_LOG_ERROR("server.adventurebot", "ADVENTURE_DISABLED guid=%u missing catalogs", player->GetGUID().GetCounter());
return;
}
if (profile->CompanionSpell)
ClientlessCompanionMgr::Prepare(player,profile->CompanionSpell,profile->CompanionLeashItem);
Record r;
r.Enabled = true;
r.Schedule = profile->DailySchedule;
@@ -163,14 +178,14 @@ void AdventureBehaviorMgr::Generate(Player * player, Record & record, uint32 dat
{
std::vector < double > weights
{
Affinity(profile->ActivityAffinity, "social", 1) * Affinity(profile->Personality, "sociability", 0.5f) + Affinity(profile->ActivityAffinity, "tavern", 0.5f) * Affinity(profile->Personality, "tavern_affinity", 0.5f), Affinity(profile->ActivityAffinity, "adventure", 1) * Affinity(profile->Personality, "adventurousness", 0.5f), Affinity(profile->ActivityAffinity, "profession", 0.5f)
Affinity(profile->ActivityAffinity, "social", 1) * Affinity(profile->Personality, "sociability", 0.5f) + Affinity(profile->ActivityAffinity, "tavern", 0.5f) * Affinity(profile->Personality, "tavern_affinity", 0.5f), Affinity(profile->ActivityAffinity, "adventure", 1) * Affinity(profile->Personality, "adventurousness", 0.5f), Affinity(profile->ActivityAffinity, "profession", 0.5f), Affinity(profile->ActivityAffinity, "rest", 0.5f), Affinity(profile->ActivityAffinity, "leisure", 0.5f), Affinity(profile->ActivityAffinity, "economy", 0.5f)
};
auto i = Choose(weights, record.Random);
return std::array < std::string, 3 >
return std::array < std::string, 6 >
{
"social", "adventure", "profession"
"social", "adventure", "profession", "rest", "leisure", "economy"
}
[std::min(i, std::size_t(2))];
[std::min(i, std::size_t(5))];
};
record.Plan = AdventureBehaviorPolicy::Generate(record.Random, Affinity(profile->Personality, "wanderlust", 0.3f), pick,[&](std::string const & intent)
{
@@ -194,6 +209,12 @@ void AdventureBehaviorMgr::SetWanted(Player * player, Record & r, std::string co
r.Deadline = deadline;
r.Dirty = true;
r.Tried.clear();
// A sightseeing flight must land and return before a new intent takes over.
if (r.Leisure.Active && r.Leisure.Kind=="flight")
{
r.Leisure.StopRequested=true;
return;
}
ResetMicro(player, r);
uint32 guid = player->GetGUID().GetCounter();
sClientlessPlayerMgr->ClearQueuedActions(guid);
@@ -321,7 +342,9 @@ void AdventureBehaviorMgr::Fallback(Player * player, Record & r)
void AdventureBehaviorMgr::ResetMicro(Player * player, Record & r)
{
ResetLeisure(player,r);
AdventureSocialActions::Reset(player,r.Social);
r.Recovery = {};
if (! r.Micro.empty())
{
player->SetUInt32Value(UNIT_NPC_EMOTESTATE, 0);
@@ -341,20 +364,22 @@ void AdventureBehaviorMgr::ResetMicro(Player * player, Record & r)
r.WorkPending = false;
}
bool AdventureBehaviorMgr::LocalMove(Player * player, Record & r, Position const & target, float maxPath)
bool AdventureBehaviorMgr::LocalMove(Player * player, Record & r, Position const & target, float maxPath, bool riding)
{
auto l = sAdventureLocationMgr->Find(r.Location);
if (! l || player->GetMapId() != r.AnchorMap || r.Anchor.GetExactDist(target) > 60) return false;
if (l->IsArea)
if (! l || player->GetMapId() != r.AnchorMap || r.Anchor.GetExactDist(target) > (r.Leisure.PlannedWalk ? 250.0f : (r.Intent=="economy" || r.Intent=="adventure" || r.Intent=="profession") ? 200.0f : r.Intent=="leisure" ? 120.0f : 60.0f)) return false;
uint32 const boundary=l->IsArea?l->AreaId:((r.Intent=="leisure" || r.Intent=="economy" || r.Intent=="adventure" || r.Intent=="profession")?l->ZoneId:0);
if (boundary)
{
auto terrain = player->GetMap()->GetTerrain();
uint32 zone = 0, area = 0;
terrain->GetZoneAndAreaId(player->GetPhaseShift(), player->GetMapId(), zone, area, target);
if (area != l->AreaId && zone != l->AreaId) return false;
if (area != boundary && zone != boundary) return false;
}
ClientlessPathDiagnostics path;
if (! sClientlessTravelMgr->AnalyzePath(player, target, path) || ! path.Complete || path.Suspicious || path.FinalDistance > 1.5f || path.PathLength > maxPath) return false;
if (l->IsArea)
if (! sClientlessTravelMgr->AnalyzePath(player, target, path) || ! path.Complete || path.Suspicious || path.FinalDistance > 1.5f || path.PathLength > maxPath)
{r.Detail="local path rejected: "+path.Reason;return false;}
if (boundary || riding)
{
PathGenerator navigation(player);
if (! navigation.CalculatePath(target)) return false;
@@ -362,10 +387,472 @@ bool AdventureBehaviorMgr::LocalMove(Player * player, Record & r, Position const
{
uint32 zone = 0, area = 0;
player->GetMap()->GetTerrain()->GetZoneAndAreaId(player->GetPhaseShift(), player->GetMapId(), zone, area, Position(point.x, point.y, point.z));
if (area != l->AreaId && zone != l->AreaId) return false;
if (boundary && area != boundary && zone != boundary) return false;
if (riding)
{
PositionFullTerrainStatus terrain;
player->GetMap()->GetTerrain()->GetFullTerrainStatusForPosition(player->GetPhaseShift(),player->GetMapId(),point.x,point.y,point.z,terrain);
if (!terrain.outdoors) return false;
}
}
}
return sClientlessTravelMgr->StartTravel(player, target, false, false, r.Intent=="social");
if (!sClientlessTravelMgr->StartTravel(player, target, riding, false,
!riding && (r.Intent=="social" || r.Intent=="rest" || r.Intent=="leisure" || r.Intent=="economy" || r.Intent=="adventure" || r.Intent=="profession"))) return false;
auto status=sClientlessTravelMgr->GetStatus(player->GetGUID().GetCounter());
return status && (status->State==ClientlessTravelState::Moving || status->State==ClientlessTravelState::Arrived);
}
bool AdventureBehaviorMgr::ProfessionWorkAvailable(Player* p,Record& r)
{
if(r.Intent!="profession")return false;
auto context=SocialContext(p,r);
for(auto const& catalog:sAdventureLocationMgr->Actions())
{
auto action=ResolvedAction(catalog);
if(action.Intents.count("profession") && AdventureProfessionPolicy::Find(action.Requirement) &&
!AdventureProfessionPolicy::Untargeted(action.Requirement) && AdventureSocialActions::Available(p,action,context))return true;
}
return false;
}
bool AdventureBehaviorMgr::FishingDirection(Player* p,float& orientation) const
{
if(p->IsInWater() || p->IsFalling())return false;
for(float distance:{4.0f,8.0f})for(unsigned i=0;i<8;++i)
{
float angle=float(i)*6.2831853f/8;
float x=p->GetPositionX()+std::cos(angle)*distance,y=p->GetPositionY()+std::sin(angle)*distance;
float water=p->GetMap()->GetWaterLevel(p->GetPhaseShift(),x,y);
float ground=p->GetMap()->GetHeight(p->GetPhaseShift(),x,y,p->GetPositionZ()+3,true,20);
if(std::isfinite(water) && water>INVALID_HEIGHT+1 && ground>INVALID_HEIGHT+1 && water>ground+0.5f &&
std::fabs(water-p->GetPositionZ())<5 && p->IsWithinLOS(x,y,water+0.5f)) {orientation=angle;return true;}
}
return false;
}
bool AdventureBehaviorMgr::LeisureAvailable(Player* p,Record const& r,AdventureMicroAction const& a) const
{
if (Unsafe(p) || p->IsInWater() || p->IsFalling()) return false;
// Adventure exploration shares the proven native ground-outing executor.
if (r.Intent=="adventure") return (a.Requirement=="explore" || a.Requirement=="patrol") && p->IsOutdoors();
if(r.Intent=="profession")
{
if(a.Requirement=="work_search")return p->IsOutdoors();
if(a.Requirement!="fish")return false;
auto rod=p->GetWeaponForAttack(BASE_ATTACK,true);float direction=0;
return rod && rod->GetTemplate()->GetSubClass()==ITEM_SUBCLASS_WEAPON_FISHING_POLE && FishingDirection(p,direction);
}
if (r.Intent!="leisure") return false;
auto profile=sClientlessLuaEngine->GetAdventureProfile(p->GetGUID().GetCounter());
auto location=sAdventureLocationMgr->Find(r.Location);
if (!profile || !location) return false;
if (a.Requirement=="fish")
{
auto rod=p->GetWeaponForAttack(BASE_ATTACK,true);
return location->Point.Category=="leisure_fishing" && rod &&
rod->GetTemplate()->GetSubClass()==ITEM_SUBCLASS_WEAPON_FISHING_POLE;
}
// A seat/meal may leave the bot inside. Foot outings must still be able
// to navigate back outside; only mounted outings require an outdoor start.
if (a.Requirement=="pet_walk") return ClientlessCompanionMgr::Available(p,profile->CompanionSpell);
if (a.Requirement=="walk" || a.Requirement=="roam") return true;
if (!p->IsOutdoors()) return false;
if (a.Requirement=="ride") return profile->CanGroundMount && !profile->GroundMounts.empty();
if (a.Requirement=="flight") return sClientlessFlightMgr->Enabled(p);
return a.Requirement=="walk" || a.Requirement=="roam";
}
bool AdventureBehaviorMgr::PlanLeisureWalk(Player* p, Record& r)
{
auto& s=r.Leisure;
auto location=sAdventureLocationMgr->Find(r.Location);
uint32 const boundary=location->IsArea?location->AreaId:location->ZoneId;
// Choose one substantial destination, then follow its actual ground corridor.
// Randomness selects the outing, never the next individual footstep.
float const offset=std::uniform_real_distribution<float>(0,6.2831853f)(r.Random);
float const budget=std::min(220.0f,std::max(100.0f,float(r.MicroTimer)*0.001f*1.0f));
float best=0;
for (unsigned i=0;i<16;++i)
{
float const angle=offset+float(i%8)*6.2831853f/8;
float const radius=i<8?std::min(150.0f,budget*0.7f):80.0f;
Position goal;
goal.Relocate(s.Origin.GetPositionX()+std::cos(angle)*radius,
s.Origin.GetPositionY()+std::sin(angle)*radius,s.Origin.GetPositionZ());
float const z=p->GetMap()->GetHeight(p->GetPhaseShift(),goal.GetPositionX(),goal.GetPositionY(),
s.Origin.GetPositionZ()+2,true,15);
if (!std::isfinite(z) || z<=INVALID_HEIGHT+1 || std::fabs(z-s.Origin.GetPositionZ())>3) continue;
goal.m_positionZ=z;
PathGenerator navigation(p);
navigation.SetPathLengthLimit(240);
if (!navigation.CalculatePath(s.Origin,goal) || navigation.GetPathType()!=PATHFIND_NORMAL) continue;
auto const& path=navigation.GetPath();
if (path.size()<2) continue;
std::vector<Position> outbound;
Position last=s.Origin;
float length=0,section=0;
bool valid=true;
for (auto const& point:path)
{
Position target;target.Relocate(point.x,point.y,point.z);
float const step=last.GetExactDist(target);
if (!std::isfinite(step) || step>12 || std::fabs(point.z-last.GetPositionZ())>4 || r.Anchor.GetExactDist(target)>250)
{valid=false;break;}
PositionFullTerrainStatus terrain;
p->GetMap()->GetTerrain()->GetFullTerrainStatusForPosition(p->GetPhaseShift(),p->GetMapId(),point.x,point.y,point.z,terrain);
uint32 zone=0,area=0;
p->GetMap()->GetTerrain()->GetZoneAndAreaId(p->GetPhaseShift(),p->GetMapId(),zone,area,target);
if (!terrain.outdoors || (boundary && area!=boundary && zone!=boundary)) {valid=false;break;}
length+=step;section+=step;last=target;
if (section>=20) {outbound.push_back(target);section=0;}
}
float const span=s.Origin.GetExactDist(last);
if (!valid || length>budget || span<65 || length>span*2.5f || last.GetExactDist(goal)>1.5f) continue;
if (outbound.empty() || outbound.back().GetExactDist(last)>2) outbound.push_back(last);
else outbound.back()=last;
if (span>best)
{
best=span;s.Route=std::move(outbound);
}
}
if (s.Route.empty()) return false;
s.PlannedWalk=true;s.OutboundSteps=s.Route.size();s.WalkTrace={s.Origin};
// The endpoint occurs once; every outbound checkpoint is retraced afterwards.
for (std::size_t i=s.OutboundSteps-1;i>0;--i) s.Route.push_back(s.Route[i-1]);
s.Route.push_back(s.Origin);
TC_LOG_INFO("server.adventurebot","LEISURE_WALK_PLAN guid=%u kind='%s' span=%.2f outbound=%u origin=(%.2f %.2f %.2f) goal=(%.2f %.2f %.2f)",
p->GetGUID().GetCounter(),s.Kind.c_str(),best,uint32(s.OutboundSteps),
s.Origin.GetPositionX(),s.Origin.GetPositionY(),s.Origin.GetPositionZ(),
s.Route[s.OutboundSteps-1].GetPositionX(),s.Route[s.OutboundSteps-1].GetPositionY(),s.Route[s.OutboundSteps-1].GetPositionZ());
return true;
}
bool AdventureBehaviorMgr::StartLeisure(Player* p,Record& r,AdventureMicroAction const& a)
{
if (!LeisureAvailable(p,r,a)) return false;
sClientlessLuaEngine->DismountGroundMount(p,"leisure-scene-start");
auto& s=r.Leisure;s={};s.Kind=a.Requirement;s.Origin.Relocate(p);
if (!p->IsOutdoors() && (s.Kind=="walk" || s.Kind=="roam" || s.Kind=="pet_walk"))
s.Origin=r.Anchor; // Return outside after leaving an indoor leisure pause.
if (s.Kind=="fish")
{
float direction=sAdventureLocationMgr->Find(r.Location)->Point.O;
if(r.Intent=="profession")
{
if(!FishingDirection(p,direction))return false;
p->SetFacingTo(direction);p->SetUInt32Value(UNIT_NPC_EMOTESTATE,EMOTE_STATE_FISHING);
s.Active=true;r.Detail="performative fishing without loot";return true;
}
float x=p->GetPositionX()+std::cos(direction)*4;
float y=p->GetPositionY()+std::sin(direction)*4;
float water=p->GetMap()->GetWaterLevel(p->GetPhaseShift(),x,y);
float ground=p->GetMap()->GetHeight(p->GetPhaseShift(),x,y,p->GetPositionZ()+3,true,20);
if (!std::isfinite(water) || water<=INVALID_HEIGHT+1 || water<=ground+0.5f) return false;
p->SetFacingTo(sAdventureLocationMgr->Find(r.Location)->Point.O);
p->SetUInt32Value(UNIT_NPC_EMOTESTATE,EMOTE_STATE_FISHING);
s.Active=true;r.Detail="performative fishing without loot";
return true;
}
bool const foot=s.Kind=="walk" || s.Kind=="roam" || s.Kind=="pet_walk" || s.Kind=="explore" || s.Kind=="patrol" || s.Kind=="work_search";
if (foot && !PlanLeisureWalk(p,r)) {s={};return false;}
auto site=sAdventureLocationMgr->Find(r.Location);
s.Progressive=s.Kind=="work_search" || (r.Intent=="adventure" && site->IsArea && foot);
if(s.Progressive)s.Route.resize(s.OutboundSteps);
if (!foot)
{
bool const riding=s.Kind=="ride",flight=s.Kind=="flight";
unsigned const points=flight?3:riding?8:s.Kind=="roam"?7:16;
float const radius=flight?55.0f:riding?35.0f:s.Kind=="roam"?25.0f:12.0f;
float const offset=std::uniform_real_distribution<float>(0,6.2831853f)(r.Random);
auto location=sAdventureLocationMgr->Find(r.Location);
uint32 const boundary=location->IsArea?location->AreaId:location->ZoneId;
for (unsigned i=0;i<points;++i)
{
float angle=offset+float(i%8)*6.2831853f/8;
if (s.Kind=="roam") angle=std::uniform_real_distribution<float>(0,6.2831853f)(r.Random);
Position target;
target.Relocate(r.Anchor.GetPositionX()+std::cos(angle)*radius,
r.Anchor.GetPositionY()+std::sin(angle)*radius,r.Anchor.GetPositionZ());
PositionFullTerrainStatus terrain;
p->GetMap()->GetTerrain()->GetFullTerrainStatusForPosition(p->GetPhaseShift(),p->GetMapId(),
target.GetPositionX(),target.GetPositionY(),target.GetPositionZ(),terrain);
if (!terrain.outdoors) continue;
float z=p->GetMap()->GetHeight(p->GetPhaseShift(),target.GetPositionX(),target.GetPositionY(),
r.Anchor.GetPositionZ()+(flight?10.0f:2.0f),true,flight?30.0f:15.0f);
// A pleasant ground outing must not select a roof above the street.
if (z<=INVALID_HEIGHT+1 || std::fabs(z-r.Anchor.GetPositionZ())>(flight?15.0f:3.0f)) continue;
target.m_positionZ=z;
if (boundary)
{
uint32 zone=0,area=0;
p->GetMap()->GetTerrain()->GetZoneAndAreaId(p->GetPhaseShift(),p->GetMapId(),zone,area,target);
if (area!=boundary && zone!=boundary) continue;
}
if (!flight)
{
ClientlessPathDiagnostics nav;
if (!sClientlessTravelMgr->AnalyzePath(p,target,nav) || !nav.Complete || nav.Suspicious ||
nav.FinalDistance>1.5f || nav.PathLength>350) continue;
}
s.Route.push_back(target);
}
if (s.Route.size()<3) {s={};return false;}
s.Route.push_back(s.Origin);
}
auto profile=sClientlessLuaEngine->GetAdventureProfile(p->GetGUID().GetCounter());
if (s.Kind=="pet_walk" && !ClientlessCompanionMgr::Start(p,s.Companion,profile->CompanionSpell,profile->CompanionLeashItem))
{s={};return false;}
s.Active=true;
r.Detail="leisure outing: "+s.Kind;
TC_LOG_INFO("server.adventurebot","LEISURE_START guid=%u kind='%s' waypoints=%u",p->GetGUID().GetCounter(),s.Kind.c_str(),uint32(s.Route.size()));
return true;
}
bool AdventureBehaviorMgr::UpdateLeisure(Player* p,Record& r,uint32 diff)
{
auto& s=r.Leisure;
if (!s.Active || p->GetMapId()!=r.AnchorMap || !p->IsAlive()) return false;
auto returnAlongWalk=[&]()
{
s.Route.clear();
for (auto it=s.WalkTrace.rbegin();it!=s.WalkTrace.rend();++it)
if (!s.Route.empty() || p->GetExactDist(*it)>2) s.Route.push_back(*it);
if (s.Route.empty()) s.Route.push_back(s.Origin);
s.Returning=true;s.Step=0;s.PauseMs=0;s.Moving=false;s.LegElapsed=0;
};
s.Elapsed+=diff;s.LegElapsed+=diff;
if (s.Kind=="fish") return r.MicroTimer && !p->IsInCombat() && p->GetExactDist(s.Origin)<=1 &&
p->GetUInt32Value(UNIT_NPC_EMOTESTATE)==EMOTE_STATE_FISHING;
if (s.Elapsed>600000) s.StopRequested=true;
if (s.Elapsed>720000 && !s.Flying && !p->IsFlying())
{r.Detail="leisure outing exceeded safe time limit";return false;}
if (s.Companion.Active)
{
if (!ClientlessCompanionMgr::Update(p,s.Companion,diff)) return false;
if (!s.Companion.Ready)
{
if (s.Moving)
{
sClientlessTravelMgr->CancelTravel(p->GetGUID().GetCounter());p->StopMoving();s.Moving=false;
}
return true;
}
auto pet=ClientlessCompanionMgr::Resolve(p,s.Companion);
if (!pet) return false;
// Native leashes have five yards range. React at world-update cadence
// before the owner walks out of range, rather than checking once/second.
float const followLimit=s.Companion.Leashed?(s.CompanionWait?3.25f:4.0f):15.0f;
if (p->GetExactDist(pet)>followLimit)
{
s.CompanionWait+=diff;
if (s.Moving)
{
sClientlessTravelMgr->CancelTravel(p->GetGUID().GetCounter());p->StopMoving();s.Moving=false;
}
return s.CompanionWait<20000;
}
s.CompanionWait=0;
}
if (s.Flying)
{
auto status=sClientlessFlightMgr->GetStatus(p->GetGUID().GetCounter());
if (status && (status->State==ClientlessFlightState::Mounting || status->State==ClientlessFlightState::Flying)) return true;
s.Flying=false;
if (!status || status->State!=ClientlessFlightState::Arrived)
{
r.Detail="leisure flight failed; native flight recovery retained";return false;
}
s.LegElapsed=0;
// Landing may be beside the waypoint: confirm the short ground approach.
if (!LocalMove(p,r,s.Route[s.Step],120)) return false;
s.Moving=true;
}
if (s.Moving)
{
auto travel=sClientlessTravelMgr->GetStatus(p->GetGUID().GetCounter());
if (travel && travel->State==ClientlessTravelState::Moving)
{
if (s.LegElapsed<90000) return true;
sClientlessTravelMgr->CancelTravel(p->GetGUID().GetCounter());p->StopMoving();
s.Moving=false;
if (s.PlannedWalk && !s.Returning) {returnAlongWalk();return true;}
r.Detail="leisure waypoint timed out";return false;
}
s.Moving=false;
if (!travel || travel->State!=ClientlessTravelState::Arrived || p->GetExactDist(s.Route[s.Step])>2)
{
if (s.PlannedWalk && !s.Returning)
{
sClientlessTravelMgr->CancelTravel(p->GetGUID().GetCounter());p->StopMoving();
returnAlongWalk();return true;
}
r.Detail="leisure waypoint not reached";return false;
}
if (s.Companion.Active)
if (auto pet=ClientlessCompanionMgr::Resolve(p,s.Companion))
TC_LOG_INFO("server.adventurebot","COMPANION_WALK_SAMPLE guid=%u owner=(%.2f %.2f %.2f) pet=(%.2f %.2f %.2f) distance=%.2f leashed=%u",
p->GetGUID().GetCounter(),p->GetPositionX(),p->GetPositionY(),p->GetPositionZ(),
pet->GetPositionX(),pet->GetPositionY(),pet->GetPositionZ(),p->GetExactDist(pet),
s.Companion.LeashSpell && pet->HasAura(s.Companion.LeashSpell,p->GetGUID())?1:0);
if (s.PlannedWalk && !s.Returning) s.WalkTrace.push_back(s.Route[s.Step]);
++s.Step;++s.CompletedLegs;s.LegElapsed=0;
if (s.PlannedWalk && s.Step>=s.OutboundSteps) s.Returning=true;
if(s.Kind=="work_search" && ProfessionWorkAvailable(p,r))
{
s.Route.resize(s.Step);
TC_LOG_INFO("server.adventurebot","PROFESSION_SEARCH_FOUND guid=%u legs=%u",p->GetGUID().GetCounter(),s.CompletedLegs);
}
s.PauseMs=s.PlannedWalk?(s.Step==s.OutboundSteps?4000:0):
s.Kind=="pet_walk"?2500:std::uniform_int_distribution<uint32>(1500,5000)(r.Random);
TC_LOG_INFO("server.adventurebot","LEISURE_WAYPOINT guid=%u kind='%s' reached=%u/%u",p->GetGUID().GetCounter(),s.Kind.c_str(),uint32(s.Step),uint32(s.Route.size()));
}
if (s.Step>=s.Route.size())
{
r.Detail=s.Progressive?"area search reached next observation point":"leisure outing completed at starting point";
TC_LOG_INFO("server.adventurebot","LEISURE_COMPLETE guid=%u kind='%s' legs=%u",p->GetGUID().GetCounter(),s.Kind.c_str(),s.CompletedLegs);
return false;
}
if ((s.StopRequested || !r.MicroTimer) && !s.Returning)
{
if(s.Progressive) {s.Route.resize(s.Step);return false;}
else if (s.PlannedWalk) returnAlongWalk();
else {s.Returning=true;s.Route={s.Origin};s.Step=0;s.PauseMs=0;}
}
if (s.PauseMs>diff) {s.PauseMs-=diff;return true;}
s.PauseMs=0;s.LegElapsed=0;
if (s.Kind=="flight")
{
std::string error;
if (!sClientlessFlightMgr->Start(p,s.Route[s.Step],error))
{r.Detail="leisure takeoff unavailable: "+error;return false;}
s.Flying=true;
}
else
{
bool riding=s.Kind=="ride";
if (riding && !p->IsOutdoors()) return false;
if (riding)
{
auto profile=sClientlessLuaEngine->GetAdventureProfile(p->GetGUID().GetCounter());
if (!sClientlessLuaEngine->PrepareGroundMount(p,std::max(120.0f,profile->GroundMountMinDistance),"leisure-ride")) return false;
}
if (!LocalMove(p,r,s.Route[s.Step],350,riding))
{
if (s.PlannedWalk)
{
if (!s.Returning) {returnAlongWalk();return true;}
r.Detail="leisure return path unavailable";return false;
}
// Scenic samples are optional. Never claim arrival at one that
// MMAP rejected, and never skip the actual return to the origin.
if (s.Step+1<s.Route.size())
{
TC_LOG_INFO("server.adventurebot","LEISURE_WAYPOINT_SKIPPED guid=%u kind='%s' index=%u reason='%s'",
p->GetGUID().GetCounter(),s.Kind.c_str(),uint32(s.Step),r.Detail.c_str());
++s.Step;
return true;
}
r.Detail="leisure return path unavailable";
return false;
}
r.Detail="leisure outing: "+s.Kind;
s.Moving=true;
}
return true;
}
void AdventureBehaviorMgr::ResetLeisure(Player* p,Record& r)
{
if (!r.Leisure.Active) return;
ClientlessCompanionMgr::Reset(p,r.Leisure.Companion);
if (!p->IsFlying() && !p->IsInFlight() && !p->GetTransport())
sClientlessLuaEngine->DismountGroundMount(p,"leisure-outing-ended");
r.Leisure={};
}
bool AdventureBehaviorMgr::RecoveryAvailable(Player* player, Record const& r, AdventureMicroAction const& action) const
{
std::string category = action.Requirement == "sleep" ? "rest_sleep" : "rest_ground";
if (action.Requirement != "sleep" && action.Requirement != "ground") return false;
for (auto const& location : sAdventureLocationMgr->Locations())
{
auto const& p = location.Point;
if (p.Category != category || !Allowed(player, location, "rest") || p.MapId != r.AnchorMap) continue;
if (r.Anchor.GetExactDist(p.X, p.Y, p.Z) <= 30 && std::fabs(p.Z-r.Anchor.GetPositionZ()) <= 12) return true;
}
return false;
}
bool AdventureBehaviorMgr::StartRecovery(Player* player, Record& r, AdventureMicroAction const& action, uint32 duration)
{
if (!RecoveryAvailable(player, r, action)) return false;
std::string category = action.Requirement == "sleep" ? "rest_sleep" : "rest_ground";
std::vector<AdventureLocation const*> choices;
std::vector<double> weights;
for (auto const& location : sAdventureLocationMgr->Locations())
{
auto const& p = location.Point;
if (p.Category != category || !Allowed(player, location, "rest") || p.MapId != r.AnchorMap ||
r.Anchor.GetExactDist(p.X, p.Y, p.Z) > 30 || std::fabs(p.Z-r.Anchor.GetPositionZ()) > 12) continue;
choices.push_back(&location);
weights.push_back(Novelty(r.TargetHistory, "rest:"+p.Id)/(1+player->GetExactDist(p.X,p.Y,p.Z)));
}
while (!choices.empty())
{
auto i = Choose(weights, r.Random);
if (i >= choices.size()) break;
auto const& p = choices[i]->Point;
Position destination(p.X,p.Y,p.Z,p.O);
std::vector<Player*> occupants;
player->GetPlayerListInGrid(occupants, 60);
bool occupied = false;
for (auto other : occupants)
if (other != player && player->IsInPhase(other) && other->GetExactDist(destination) < 1.5f) occupied = true;
if (!occupied && LocalMove(player, r, destination, 120))
{
r.Recovery = {true,true,p.Id,action.Requirement,duration,0};
Remember(r.TargetHistory, "rest:"+p.Id);
r.Detail = "walking to designated resting spot";
TC_LOG_INFO("server.adventurebot", "REST_APPROACH guid=%u point='%s' posture='%s'", player->GetGUID().GetCounter(),p.Id.c_str(),action.Requirement.c_str());
return true;
}
choices.erase(choices.begin()+i);
weights.erase(weights.begin()+i);
}
return false;
}
bool AdventureBehaviorMgr::UpdateRecovery(Player* player, Record& r, uint32 diff)
{
auto& scene = r.Recovery;
auto location = sAdventureLocationMgr->Find(scene.Point);
if (!scene.Active || !location || player->GetMapId() != r.AnchorMap || player->IsInWater() ||
player->IsFalling() || !Allowed(player,*location,"rest") ||
location->Point.Category != (scene.Posture=="sleep" ? "rest_sleep" : "rest_ground")) return false;
auto const& p = location->Point;
if (scene.Approaching)
{
scene.ApproachElapsed += diff;
if (scene.ApproachElapsed > 60000) return false;
auto travel = sClientlessTravelMgr->GetStatus(player->GetGUID().GetCounter());
if (travel && travel->State == ClientlessTravelState::Moving) return true;
if (!travel || travel->State != ClientlessTravelState::Arrived || player->GetExactDist(p.X,p.Y,p.Z) > 1) return false;
std::vector<Player*> occupants;
player->GetPlayerListInGrid(occupants, 3);
for (auto other : occupants)
if (other != player && player->IsInPhase(other) && other->GetExactDist(p.X,p.Y,p.Z) < 1.5f) return false;
player->SetFacingTo(p.O);
player->SetStandState(scene.Posture=="sleep" ? UNIT_STAND_STATE_SLEEP : UNIT_STAND_STATE_SIT);
scene.Approaching = false;
r.Detail = scene.Posture=="sleep" ? "sleeping at designated resting spot" : "resting at designated ground spot";
TC_LOG_INFO("server.adventurebot", "REST_CONFIRMED guid=%u point='%s' posture='%s' durationMs=%u",player->GetGUID().GetCounter(),p.Id.c_str(),scene.Posture.c_str(),scene.Remaining);
return true;
}
if (player->GetExactDist(p.X,p.Y,p.Z) > 1 ||
player->GetStandState() != (scene.Posture=="sleep" ? UNIT_STAND_STATE_SLEEP : UNIT_STAND_STATE_SIT)) return false;
if (scene.Remaining <= diff) return false;
scene.Remaining -= diff;
return true;
}
AdventureSocialContext AdventureBehaviorMgr::SocialContext(Player* player,Record& r)
@@ -374,7 +861,10 @@ AdventureSocialContext AdventureBehaviorMgr::SocialContext(Player* player,Record
auto location=sAdventureLocationMgr->Find(r.Location);
return {r.Anchor,r.AnchorMap,location&&location->Point.Category=="festival",
profile?profile->Locale:"enUS",r.Intent,r.Random,r.TargetHistory,r.VariantHistory,
[this,player,&r](Position const& target,float maxPath){return LocalMove(player,r,target,maxPath);}};
[this,player,&r](Position const& target,float maxPath){return LocalMove(player,r,target,maxPath);},
r.Intent != "rest" || (location && location->Point.Category == "rest_ground"),
location && r.Intent=="economy"?AdventureEconomyPolicy::LocationRequirement(location->Point.Category):"",
location?location->Point.Category:"",profile?profile->PrimaryProfessions:AdventureProfessionPolicy::Professions{}};
}
bool AdventureBehaviorMgr::UpdateSocial(Player* player,Record& r,uint32 diff)
@@ -390,6 +880,43 @@ void AdventureBehaviorMgr::Micro(Player * player, Record & r)
auto l = sAdventureLocationMgr->Find(r.Location);
if (! l) return;
auto social=SocialContext(player,r);
if (r.Intent=="rest")
{
bool suitable = false;
for (auto const& action : sAdventureLocationMgr->Actions())
{
if (!action.Intents.count("rest")) continue;
if ((action.Kind=="recovery" && RecoveryAvailable(player,r,action)) ||
(action.Kind=="social" && AdventureSocialActions::Available(player,action,social)))
{ suitable=true;break; }
}
if (!suitable)
{
r.Detail="no suitable resting place here; seeking another location";
Fallback(player,r);
return;
}
}
if (r.Intent=="economy")
{
bool service=false;
for (auto const& action:sAdventureLocationMgr->Actions())
if (action.Intents.count("economy") && action.Kind=="social" &&
AdventureEconomyPolicy::RequiredService(action.Requirement)!=AdventureEconomyPolicy::Service::None &&
AdventureSocialActions::Available(player,action,social)) {service=true;break;}
if (!service)
{
r.Detail="no suitable economic service here; seeking another location";
sClientlessPerceptionMgr->ScanNow(player,30);
if (auto snapshot=sClientlessPerceptionMgr->GetSnapshot(player->GetGUID().GetCounter()))
for (auto const& entity:snapshot->Entities)
if (entity.Capabilities & (CLIENTLESS_CAP_VENDOR|CLIENTLESS_CAP_BANKER|CLIENTLESS_CAP_AUCTIONEER))
TC_LOG_INFO("server.adventurebot","ECONOMY_SERVICE_UNAVAILABLE guid=%u entry=%u distance=%.2f alive=%u los=%u relation=%u capabilities=%llu",
player->GetGUID().GetCounter(),entity.Entry,entity.Distance,entity.Alive?1:0,entity.InLineOfSight?1:0,
uint32(entity.Relation),static_cast<unsigned long long>(entity.Capabilities));
Fallback(player,r);return;
}
}
sClientlessLuaEngine->DismountGroundMount(player, "intent-micro-action");
GameObject * fire = nullptr, * ore = nullptr, * forge = nullptr;
std::vector < GameObject * > props;
@@ -416,14 +943,24 @@ void AdventureBehaviorMgr::Micro(Player * player, Record & r)
}
std::vector < AdventureMicroAction const * > choices;
std::vector < double > weights;
for (auto const & a : sAdventureLocationMgr->Actions())
for (auto const & catalog : sAdventureLocationMgr->Actions())
{
auto a=ResolvedAction(catalog);
if(a.Kind=="unsupported")continue;
auto w = a.Intents.find(r.Intent);
if (w == a.Intents.end()) continue;
if ((a.Kind=="leisure" || a.Kind=="outing") && !LeisureAvailable(player,r,a)) continue;
if (a.Kind=="recovery" && !RecoveryAvailable(player,r,a)) continue;
if(r.Intent=="leisure" && a.Requirement=="dance" && !social.Festival) continue;
if(a.Kind=="social"&&!AdventureSocialActions::Available(player,a,social))continue;
if ((a.Requirement == "campfire" && ! fire) ||(a.Requirement == "ore" && ! ore) ||(a.Requirement == "forge" && ! forge) ||(a.Requirement == "audience" && ! audience)) continue;
choices.push_back(& a);
if(a.Requirement=="work_search" && ProfessionWorkAvailable(player,r))continue;
choices.push_back(& catalog);
float contextWeight=(a.Requirement=="dance"&&l->Point.Category!="festival")?0.12f:1.0f;
if (r.Intent=="adventure" && !r.ActionHistory.empty() &&
(r.ActionHistory.back()=="outing:explore" || r.ActionHistory.back()=="outing:patrol") &&
(a.Kind=="sit" || a.Kind=="drink" || a.Kind=="eat" ||
a.Requirement=="fire" || a.Requirement=="seat" || a.Requirement=="drink_seated" || a.Requirement=="eat_seated")) contextWeight*=4;
weights.push_back(contextWeight*w->second * Affinity(profile->MicroActionAffinity, a.Key, 1) * Novelty(r.ActionHistory, a.Kind+":"+a.Requirement) * Novelty(r.VariantHistory, "action:" + a.Key));
}
auto n = Choose(weights, r.Random);
@@ -432,7 +969,7 @@ void AdventureBehaviorMgr::Micro(Player * player, Record & r)
r.MicroTimer = 5000;
return;
}
auto const & a = * choices[n];
auto const a = ResolvedAction(*choices[n]);
r.Micro = a.Key;
r.MicroTimer = std::uniform_int_distribution < uint32 >(a.MinSeconds, a.MaxSeconds)(r.Random) * 1000;
Remember(r.ActionHistory, a.Kind+":"+a.Requirement);
@@ -444,10 +981,22 @@ void AdventureBehaviorMgr::Micro(Player * player, Record & r)
if(AdventureSocialActions::Available(player,seated,social))
AdventureSocialActions::Start(player,r.Social,seated,r.MicroTimer,social);
}
if(r.Social.Active)r.Detail="social scene: "+r.Social.Kind;
if (a.Kind=="leisure" || a.Kind=="outing")
{
if (!StartLeisure(player,r,a)) r.MicroTimer=3000;
}
else if (a.Kind=="recovery")
{
if (!StartRecovery(player,r,a,r.MicroTimer)) r.MicroTimer=3000;
}
else if(r.Social.Active)r.Detail="social scene: "+r.Social.Kind;
else if(a.Kind=="social")
{
if(!AdventureSocialActions::Start(player,r.Social,a,r.MicroTimer,social))r.MicroTimer=3000;
if(!AdventureSocialActions::Start(player,r.Social,a,r.MicroTimer,social))
{
r.MicroTimer=3000;
if (r.Intent=="economy") {Fallback(player,r);return;}
}
else r.Detail="social scene: "+a.Requirement;
}
else if (a.Kind == "look") player->SetFacingTo(player->GetOrientation() + std::uniform_real_distribution < float >(- 1.0f, 1.0f)(r.Random));
@@ -501,34 +1050,6 @@ void AdventureBehaviorMgr::Micro(Player * player, Record & r)
player->HandleEmoteCommand(EMOTE_ONESHOT_TALK);
}
}
else if (a.Kind == "work")
{
auto object = a.Requirement == "ore" ? ore : forge;
if (! object)
{
r.MicroTimer = 3000;
return;
}
player->SetFacingToObject(object);
r.WorkObject = object->GetGUID();
r.WorkEmote = a.Emote;
if (player->GetExactDist(object) > 4)
{
Position p;
p.Relocate(object);
float angle = object->GetAngle(player);
p.m_positionX += std::cos(angle) * 2;
p.m_positionY += std::sin(angle) * 2;
if (! LocalMove(player, r, p, 35))
{
r.MicroTimer = 3000;
return;
}
r.WorkPending = true;
r.Detail = "approaching work prop";
}
else player->SetUInt32Value(UNIT_NPC_EMOTESTATE, a.Emote);
}
TC_LOG_INFO("server.adventurebot", "MICRO_START guid=%u intent='%s' location='%s' action='%s' durationMs=%u", player->GetGUID().GetCounter(), r.Intent.c_str(), r.Location.c_str(), a.Key.c_str(), r.MicroTimer);
}
@@ -541,9 +1062,25 @@ void AdventureBehaviorMgr::Update(Player * player, uint32 diff)
r.MicroTimer = r.MicroTimer > diff ? r.MicroTimer - diff : 0;
r.GestureTimer = r.GestureTimer > diff ? r.GestureTimer - diff : 0;
r.RetryTimer = r.RetryTimer > diff ? r.RetryTimer - diff : 0;
auto advanceLeisure=[&]()
{
if (!r.Leisure.Active) return false;
if ((!Unsafe(player) || r.Leisure.Flying || player->IsFlying()) && UpdateLeisure(player,r,diff)) return true;
if(r.Leisure.Progressive && !r.Leisure.Route.empty() && r.Leisure.Step>=r.Leisure.Route.size() &&
player->GetExactDist(r.Leisure.Route.back())<=2)
{
auto site=sAdventureLocationMgr->Find(r.Location);
if(site && site->IsArea)r.Anchor.Relocate(player);
}
ResetMicro(player,r);
r.MicroTimer=3000;
if (r.Dirty) r.State=Phase::Idle;
return true;
};
if (r.Timer > diff)
{
r.Timer -= diff;
advanceLeisure();
return;
}
r.Timer = 1000;
@@ -573,6 +1110,7 @@ void AdventureBehaviorMgr::Update(Player * player, uint32 diff)
}
}
else if (r.Deadline && now >= r.Deadline) SetWanted(player, r, "", "", 0);
if (advanceLeisure()) return;
if (Unsafe(player))
{
if (player->IsInCombat() && ! r.Micro.empty()) ResetMicro(player, r);
@@ -639,6 +1177,11 @@ void AdventureBehaviorMgr::Update(Player * player, uint32 diff)
}
if (r.State == Phase::OnSite)
{
if (r.Recovery.Active)
{
if (UpdateRecovery(player,r,1000)) return;
ResetMicro(player,r);r.MicroTimer=3000;return;
}
if(r.Social.Active)
{
if(UpdateSocial(player,r,1000))return;
@@ -687,6 +1230,7 @@ bool AdventureBehaviorMgr::Command(ChatHandler * handler, char const * args)
uint32 guid = 0;
in >> op;
if (op == "import") return sAdventureLocationMgr->Import(handler);
if (op == "repairimport") return sAdventureLocationMgr->Import(handler,true);
if (op == "reload")
{
bool ok = sAdventureLocationMgr->Reload();
@@ -721,21 +1265,23 @@ bool AdventureBehaviorMgr::Command(ChatHandler * handler, char const * args)
if(!r.Enabled||r.State!=Phase::OnSite||Unsafe(player)||Busy(guid))
{handler->SendSysMessage("Use an idle on-site AdventureBot, without combat or transit.");return false;}
auto context=SocialContext(player,r);
for(auto const& action:sAdventureLocationMgr->Actions())
for(auto const& catalog:sAdventureLocationMgr->Actions())
{
if(action.Key!=key||action.Kind!="social"||!action.Intents.count(r.Intent))continue;
if(!AdventureSocialActions::Available(player,action,context))
{handler->SendSysMessage("Social action unavailable in the current environment.");return false;}
auto action=ResolvedAction(catalog);
if(action.Key!=key||(action.Kind!="social"&&action.Kind!="recovery"&&action.Kind!="leisure"&&action.Kind!="outing")||!action.Intents.count(r.Intent))continue;
bool recovery=action.Kind=="recovery",leisure=action.Kind=="leisure" || action.Kind=="outing";
if(leisure?!LeisureAvailable(player,r,action):(recovery?!RecoveryAvailable(player,r,action):!AdventureSocialActions::Available(player,action,context)))
{handler->SendSysMessage("Action unavailable in the current environment.");return false;}
ResetMicro(player,r);r.Micro=key;
r.MicroTimer=std::uniform_int_distribution<uint32>(action.MinSeconds,action.MaxSeconds)(r.Random)*1000;
Remember(r.ActionHistory,action.Kind+":"+action.Requirement);
Remember(r.VariantHistory,"action:"+action.Key);
bool started=AdventureSocialActions::Start(player,r.Social,action,r.MicroTimer,context);
bool started=leisure?StartLeisure(player,r,action):recovery?StartRecovery(player,r,action,r.MicroTimer):AdventureSocialActions::Start(player,r.Social,action,r.MicroTimer,context);
if(!started){ResetMicro(player,r);r.MicroTimer=3000;}
handler->SendSysMessage(started?"Social scene started through normal navigation and native interactions.":"No reachable social target or suitable resting spot.");
handler->SendSysMessage(started?"Scene started through normal navigation and native interactions.":"No reachable target or suitable resting spot.");
return started;
}
handler->SendSysMessage("Unknown or unsuitable social action key.");return false;
handler->SendSysMessage("Unknown or unsuitable scene action key.");return false;
}
if (op == "plan")
{
@@ -765,7 +1311,7 @@ bool AdventureBehaviorMgr::Command(ChatHandler * handler, char const * args)
auto l = sAdventureLocationMgr->Find(location);
if (! Known(intent) || ! l || ! Allowed(player, * l, intent))
{
handler->SendSysMessage("Use social/adventure/profession and a matching enabled location.");
handler->SendSysMessage("Use social/adventure/profession/rest/leisure and a matching enabled location.");
return false;
}
r.Schedule = false;
@@ -781,6 +1327,6 @@ bool AdventureBehaviorMgr::Command(ChatHandler * handler, char const * args)
handler->SendSysMessage("Behavior stop requested at a safe point.");
return true;
}
handler->SendSysMessage("Usage: clientless adventure import|reload|status <guid>|plan <guid>|schedule <guid> on/off|intent <guid> <intent> <location> [seconds]|micro <guid> <social-action>|stop <guid>");
handler->SendSysMessage("Usage: clientless adventure import|repairimport|reload|status <guid>|plan <guid>|schedule <guid> on/off|intent <guid> <intent> <location> [seconds]|micro <guid> <scene-action>|stop <guid>");
return false;
}
+30 -1
View File
@@ -5,6 +5,7 @@
#include "ObjectGuid.h"
#include "AdventureBehaviorPolicy.h"
#include "AdventureSocialActions.h"
#include "ClientlessCompanionMgr.h"
#include <ctime>
#include <random>
#include <string>
@@ -15,6 +16,7 @@ class ChatHandler;
class GameObject;
struct ClientlessIntent;
struct AdventureLocation;
struct AdventureMicroAction;
struct ClientlessAdventureProfile;
class TC_GAME_API AdventureBehaviorMgr
@@ -28,6 +30,12 @@ public:
bool Command(ChatHandler* handler, char const* args);
private:
enum class Phase {Idle, Travel, OnSite, Switching, Handover};
struct RecoveryScene
{
bool Active=false, Approaching=false;
std::string Point, Posture;
uint32 Remaining=0, ApproachElapsed=0;
};
struct Record
{
bool Enabled=false, Schedule=false, Publishing=false, Queued=false, Dirty=true;
@@ -41,6 +49,17 @@ private:
std::deque<std::string> ActionHistory, VariantHistory;
std::deque<std::string> TargetHistory;
AdventureSocialScene Social;
RecoveryScene Recovery;
struct LeisureScene
{
bool Active=false, Flying=false, Moving=false, StopRequested=false, Returning=false, PlannedWalk=false, Progressive=false;
std::string Kind;
Position Origin;
std::vector<Position> Route, WalkTrace;
std::size_t Step=0, OutboundSteps=0;
uint32 Elapsed=0, LegElapsed=0, PauseMs=0, CompanionWait=0, CompletedLegs=0;
ClientlessCompanionScene Companion;
} Leisure;
std::unordered_set<std::string> Tried;
Position Anchor;
uint32 AnchorMap=0;
@@ -57,7 +76,17 @@ private:
void Micro(Player* player, Record& record);
AdventureSocialContext SocialContext(Player* player, Record& record);
bool UpdateSocial(Player* player, Record& record, uint32 diff);
bool LocalMove(Player* player, Record& record, Position const& target, float maxPath);
bool LocalMove(Player* player, Record& record, Position const& target, float maxPath, bool riding=false);
bool ProfessionWorkAvailable(Player* player,Record& record);
bool FishingDirection(Player* player,float& orientation) const;
bool LeisureAvailable(Player* player, Record const& record, AdventureMicroAction const& action) const;
bool PlanLeisureWalk(Player* player, Record& record);
bool StartLeisure(Player* player, Record& record, AdventureMicroAction const& action);
bool UpdateLeisure(Player* player, Record& record, uint32 diff);
void ResetLeisure(Player* player, Record& record);
bool RecoveryAvailable(Player* player, Record const& record, AdventureMicroAction const& action) const;
bool StartRecovery(Player* player, Record& record, AdventureMicroAction const& action, uint32 duration);
bool UpdateRecovery(Player* player, Record& record, uint32 diff);
std::string AreaNode(Player* player, AdventureLocation const& location);
std::string PickLocation(Player* player, Record& record, std::string const& intent);
bool Allowed(Player* player, AdventureLocation const& location, std::string const& intent) const;
@@ -0,0 +1,53 @@
#ifndef VCORE_ADVENTURE_ECONOMY_POLICY_H
#define VCORE_ADVENTURE_ECONOMY_POLICY_H
#include "AdventureSocialFlow.h"
#include <algorithm>
#include <string>
namespace AdventureEconomyPolicy
{
enum class Service { None, Vendor, Bank, Auction, Any };
inline Service RequiredService(std::string const& kind)
{
if (kind=="vendor_visit" || kind=="goods" || kind=="trade" || kind=="vendor_compare") return Service::Vendor;
if (kind=="bank_visit" || kind=="banking") return Service::Bank;
if (kind=="auction_visit" || kind=="auctions") return Service::Auction;
if (kind=="service_wait" || kind=="service_finish") return Service::Any;
return Service::None;
}
inline std::string LocationRequirement(std::string const& category)
{
if (category=="economy_vendor") return "vendor_visit";
if (category=="economy_bank") return "bank_visit";
if (category=="economy_auction") return "auction_visit";
return "";
}
inline bool Economic(std::string const& kind)
{
return RequiredService(kind)!=Service::None || kind=="supplies";
}
inline bool Matches(std::string const& kind, bool vendor, bool banker, bool auctioneer)
{
switch (RequiredService(kind))
{
case Service::Vendor: return vendor;
case Service::Bank: return banker;
case Service::Auction: return auctioneer;
case Service::Any: return vendor || banker || auctioneer;
default: return false;
}
}
inline std::vector<AdventureSocialFlow::Step> Steps(std::string const& kind, uint32_t duration)
{
using namespace AdventureSocialFlow;
if (kind=="supplies") return {{Cue::Look,3000},{Cue::Nod,2000},{Cue::Listen,std::max(5000u,duration-std::min(duration,5000u))}};
if (kind=="service_wait") return {{Cue::Look,2000},{Cue::Listen,duration}};
if (kind=="service_finish") return {{Cue::Farewell,2000}};
if (RequiredService(kind)==Service::None) return {};
uint32_t const hold=std::max(6000u,duration-std::min(duration,12000u));
if (kind=="trade" || kind=="banking")
return {{Cue::Greet,2000},{Cue::Talk,4000},{Cue::Listen,hold},{Cue::Nod,2000},{Cue::Farewell,2000}};
return {{Cue::Greet,2000},{Cue::Look,hold},{Cue::Listen,4000},{Cue::Nod,2000},{Cue::Farewell,2000}};
}
}
#endif
@@ -0,0 +1,33 @@
#ifndef VCORE_ADVENTURE_EXPLORATION_POLICY_H
#define VCORE_ADVENTURE_EXPLORATION_POLICY_H
#include "AdventureSocialFlow.h"
#include <algorithm>
#include <string>
namespace AdventureExplorationPolicy
{
inline bool Inspection(std::string const& kind)
{
return kind=="inspect" || kind=="inspect_detail" || kind=="point_object";
}
inline bool Untargeted(std::string const& kind)
{
return kind=="scout" || kind=="vista" || kind=="equipment" || kind=="entrance";
}
inline bool Entrance(std::string const& category)
{
return category=="ruins" || category=="dungeon" || category=="mine";
}
inline std::vector<AdventureSocialFlow::Step> Steps(std::string const& kind,uint32_t duration)
{
using namespace AdventureSocialFlow;
uint32_t hold=std::max(4000u,duration-std::min(duration,10000u));
if (kind=="inspect") return {{Cue::Look,hold},{Cue::Nod,2000},{Cue::Listen,4000}};
if (kind=="inspect_detail") return {{Cue::Look,hold},{Cue::Question,3000},{Cue::Point,3000},{Cue::Listen,4000}};
if (kind=="point_object") return {{Cue::Point,2000},{Cue::Listen,hold}};
if (kind=="equipment") return {{Cue::CheckGear,3000},{Cue::Listen,hold},{Cue::Nod,2000}};
if (kind=="entrance") return {{Cue::SurveyLeft,4000},{Cue::SurveyRight,4000},{Cue::CheckGear,3000},{Cue::Listen,hold}};
if (kind=="scout" || kind=="vista") return {{Cue::SurveyLeft,5000},{Cue::Listen,hold},{Cue::SurveyRight,5000},{Cue::Listen,4000}};
return {};
}
}
#endif
+71 -10
View File
@@ -24,6 +24,7 @@ bool AdventureLocationMgr::Reload()
{
_locations.clear();
_types.clear();
_workstations.clear();
_actions.clear();
_dialogs.clear();
_loaded = false;
@@ -37,7 +38,7 @@ bool AdventureLocationMgr::Reload()
l.Point.DatabaseId = f[0].GetUInt32();
l.Point.Id = f[1].GetString();
l.Point.Category = f[2].GetString();
l.Point.MapId = f[3].GetUInt32();
l.Point.MapId = f[3].GetUInt16();
l.Point.X = f[4].GetFloat();
l.Point.Y = f[5].GetFloat();
l.Point.Z = f[6].GetFloat();
@@ -52,7 +53,7 @@ bool AdventureLocationMgr::Reload()
}
while (rows->NextRow());
}
if (auto rows = WorldDatabase.Query("SELECT category,intent,weight FROM vcore_adventure_location_intent WHERE weight>0")) do
if (auto rows = WorldDatabase.Query("SELECT category,intent,weight FROM vcore_adventure_location_intent WHERE weight>=0")) do
{
auto f = rows->Fetch();
_types[f[0].GetString()][f[1].GetString()] = f[2].GetFloat();
@@ -86,6 +87,11 @@ bool AdventureLocationMgr::Reload()
});
}
while (rows->NextRow());
if (WorldDatabase.Query("SHOW TABLES LIKE 'vcore_adventure_workstation'"))
if (auto rows=WorldDatabase.Query("SELECT gameobject_entry,role FROM vcore_adventure_workstation WHERE enabled=1")) do
{
auto f=rows->Fetch();_workstations[f[0].GetUInt32()].insert(f[1].GetString());
} while(rows->NextRow());
_loaded = true;
TC_LOG_INFO("server.adventurebot", "ADVENTURE_CATALOG locations=%u actions=%u lines=%u", uint32(_locations.size()), uint32(_actions.size()), uint32(_dialogs.size()));
return true;
@@ -97,15 +103,23 @@ AdventureLocation const * AdventureLocationMgr::Find(std::string const & key) co
return nullptr;
}
bool AdventureLocationMgr::IsWorkstation(uint32 entry,std::string const& role) const
{
auto it=_workstations.find(entry);return it!=_workstations.end() && it->second.count(role);
}
float AdventureLocationMgr::Weight(AdventureLocation const & location, std::string const & intent) const
{
auto type = _types.find(location.Point.Category);
if (type == _types.end()) return 0;
auto weight = type->second.find(intent);
return weight == type->second.end() ? 0 : weight->second;
if (type != _types.end())
{
auto weight=type->second.find(intent);
if (weight!=type->second.end()) return weight->second;
}
return 0;
}
bool AdventureLocationMgr::Import(ChatHandler * handler)
bool AdventureLocationMgr::Import(ChatHandler * handler, bool repair)
{
using AdventureBehaviorPolicy::Classify;
using AdventureBehaviorPolicy::Normalize;
@@ -128,7 +142,7 @@ bool AdventureLocationMgr::Import(ChatHandler * handler)
Seed s;
s.Id = f[0].GetUInt32();
s.Name = f[1].GetString();
s.Map = f[2].GetUInt32();
s.Map = f[2].GetUInt16();
s.X = f[3].GetFloat();
s.Y = f[4].GetFloat();
s.Z = f[5].GetFloat();
@@ -147,7 +161,7 @@ bool AdventureLocationMgr::Import(ChatHandler * handler)
Seed s;
s.Id = f[0].GetUInt32();
s.Name = f[1].GetString() + " " + f[2].GetString();
s.Map = f[3].GetUInt32();
s.Map = f[3].GetUInt16();
s.X = f[4].GetFloat();
s.Y = f[5].GetFloat();
s.Z = f[6].GetFloat();
@@ -200,7 +214,15 @@ bool AdventureLocationMgr::Import(ChatHandler * handler)
s.Map = anchor->Map;
auto & t = terrains[s.Map];
if (! t) t = sTerrainMgr.LoadTerrain(s.Map);
s.Z = t ? t->GetStaticHeight(phase, s.Map, s.X, s.Y, MAX_HEIGHT, true, MAX_HEIGHT) : INVALID_HEIGHT;
Seed const* ground=anchor;
float groundDistance=std::hypot(s.X-anchor->X,s.Y-anchor->Y);
for (auto const& candidate:seeds)
if (candidate.Source=="service_npc" && candidate.Map==s.Map)
{
float d=std::hypot(s.X-candidate.X,s.Y-candidate.Y);
if (d<groundDistance) {groundDistance=d;ground=&candidate;}
}
s.Z = t ? t->GetStaticHeight(phase, s.Map, s.X, s.Y, ground->Z+3,true,100) : INVALID_HEIGHT;
if (! std::isfinite(s.Z) || s.Z <= INVALID_HEIGHT + 1)
{
++ skipped;
@@ -245,6 +267,45 @@ bool AdventureLocationMgr::Import(ChatHandler * handler)
area = zone;
++ areas;
}
if (auto territory=sAreaTableStore.LookupEntry(zone))
{
uint32 const mask=territory->FactionGroupMask & (FACTION_GROUP_MASK_ALLIANCE|FACTION_GROUP_MASK_HORDE);
s.Faction=mask==FACTION_GROUP_MASK_ALLIANCE?1:mask==FACTION_GROUP_MASK_HORDE?2:0;
}
if (repair)
{
// SMALLINT maps were previously read with GetUInt32, yielding zero
// in Debug. Reproduce the old generated values to protect edits.
auto legacyTerrain=sTerrainMgr.LoadTerrain(0);
float legacyZ=s.Z;
if (s.Source=="guard_poi_inferred")
legacyZ=legacyTerrain?legacyTerrain->GetStaticHeight(phase,0,s.X,s.Y,MAX_HEIGHT,true,MAX_HEIGHT):INVALID_HEIGHT;
uint32 legacyArea=areaAt(0,s.X,s.Y,legacyZ),legacyZone=legacyArea;
if (auto a=sAreaTableStore.LookupEntry(legacyArea)) if (a->ParentAreaID) legacyZone=a->ParentAreaID;
bool legacyIsArea=false;
if (auto a=sAreaTableStore.LookupEntry(legacyZone)) legacyIsArea=Normalize(s.Name)==Normalize(a->AreaName?a->AreaName:"");
std::string legacyType=s.Source=="service_npc"?(s.Type=="tavern"?"tavern":"service"):Classify(s.Name);
if (legacyIsArea) legacyType="zone";
std::string oldRegion,newRegion,key=s.Key,name=s.Name,source=s.Source,type=s.Type;
auto regionFor=[&](uint32 id)
{
if (id==12) return std::string("elwynn");
if (id==1) return std::string("dun_morogh");
if (id==1519) return std::string("stormwind");
if (id==4) return std::string("blasted_lands");
auto a=sAreaTableStore.LookupEntry(id);return Normalize(a&&a->AreaName?a->AreaName:"");
};
oldRegion=regionFor(legacyZone);newRegion=regionFor(zone);
for (auto text:{&oldRegion,&newRegion,&key,&name,&source,&type,&legacyType}) WorldDatabase.EscapeString(*text);
transaction->PAppend("UPDATE vcore_clientless_world_point w JOIN vcore_adventure_location a USING(point_key) "
"SET w.map=%u,w.position_z=%.6f,w.category='%s',a.area_id=%u,a.zone_id=%u,a.region_key='%s',a.is_area=%u,a.faction=%u "
"WHERE w.point_key='%s' AND a.source_type='%s' AND a.source_id=%u AND a.display_name='%s' "
"AND w.map=0 AND ABS(w.position_x-%.6f)<0.001 AND ABS(w.position_y-%.6f)<0.001 AND ABS(w.position_z-%.6f)<0.01 "
"AND w.category='%s' AND a.area_id=%u AND a.zone_id=%u AND a.region_key='%s' AND a.is_area=%u AND a.faction=0",
s.Map,s.Z,type.c_str(),area,zone,newRegion.c_str(),isArea?1u:0u,uint32(s.Faction),
key.c_str(),source.c_str(),s.Id,name.c_str(),s.X,s.Y,legacyZ,legacyType.c_str(),legacyArea,legacyZone,
oldRegion.c_str(),legacyIsArea?1u:0u);
}
metadata(s, isArea, area, zone);
std::string name = s.Name;
WorldDatabase.EscapeString(name);
@@ -261,7 +322,7 @@ bool AdventureLocationMgr::Import(ChatHandler * handler)
s.Type = f[1].GetString();
s.Source = "manual";
s.Id = 0;
s.Map = f[2].GetUInt32();
s.Map = f[2].GetUInt16();
s.X = f[3].GetFloat();
s.Y = f[4].GetFloat();
s.Z = f[5].GetFloat();
+4 -1
View File
@@ -5,6 +5,7 @@
#include <string>
#include <unordered_map>
#include <vector>
#include <unordered_set>
class ChatHandler;
struct AdventureLocation
{
@@ -31,13 +32,15 @@ public:
static AdventureLocationMgr* instance();
bool Reload();
bool EnsureLoaded();
bool Import(ChatHandler* handler);
bool Import(ChatHandler* handler, bool repair = false);
std::vector<AdventureLocation> const& Locations() const { return _locations; }
std::vector<AdventureMicroAction> const& Actions() const { return _actions; }
std::vector<AdventureDialog> const& Dialogs() const { return _dialogs; }
AdventureLocation const* Find(std::string const& key) const;
bool IsWorkstation(uint32 entry,std::string const& role) const;
float Weight(AdventureLocation const& location, std::string const& intent) const;
private:
std::unordered_map<uint32,std::unordered_set<std::string>> _workstations;
bool _loaded = false;
std::vector<AdventureLocation> _locations;
std::vector<AdventureMicroAction> _actions;
@@ -0,0 +1,76 @@
#ifndef VCORE_ADVENTURE_PROFESSION_POLICY_H
#define VCORE_ADVENTURE_PROFESSION_POLICY_H
#include "AdventureSocialFlow.h"
#include <algorithm>
#include <string>
#include <unordered_set>
namespace AdventureProfessionPolicy
{
using Professions=std::unordered_set<std::string>;
enum class Motion { Use, Mine, Chop, Kneel };
struct Task { char const* Kind;char const* Primary;char const* Station;unsigned Skill;Motion Animation;bool Gather; };
inline Task const* Find(std::string const& kind)
{
static Task const tasks[]={
{"work_mining","mining","",186,Motion::Mine,true},
{"ore_inspect","mining","",186,Motion::Use,true},
{"work_herbalism","herbalism","",182,Motion::Kneel,true},
{"work_skinning","skinning","",393,Motion::Kneel,true},
{"work_blacksmithing","blacksmithing","anvil",0,Motion::Mine,false},
{"work_smelting","mining","forge",0,Motion::Use,false},
{"work_engineering","engineering","workbench",0,Motion::Use,false},
{"work_alchemy","alchemy","alchemy",0,Motion::Use,false},
{"work_tailoring","tailoring","sewing",0,Motion::Use,false},
{"work_leatherworking","leatherworking","leather",0,Motion::Use,false},
{"work_enchanting","enchanting","enchanting",0,Motion::Use,false},
{"work_inscription","inscription","inscription",0,Motion::Use,false},
{"work_jewelcrafting","jewelcrafting","jewelcrafting",0,Motion::Use,false},
{"work_wood","","wood",0,Motion::Chop,false},
{"work_cooking","","cooking",0,Motion::Use,false},
{"work_first_aid","","",0,Motion::Use,false}
};
for (auto const& task:tasks) if (kind==task.Kind) return &task;
return nullptr;
}
inline char const* PrimaryForSkill(unsigned skill)
{
switch(skill){case 186:return "mining";case 182:return "herbalism";case 393:return "skinning";
case 164:return "blacksmithing";case 171:return "alchemy";case 333:return "enchanting";
case 202:return "engineering";case 773:return "inscription";case 755:return "jewelcrafting";
case 165:return "leatherworking";case 197:return "tailoring";default:return "";}
}
inline bool PrimaryName(std::string const& name)
{
for(unsigned skill:{186,182,393,164,171,333,202,773,755,165,197}) if(name==PrimaryForSkill(skill))return true;
return false;
}
inline bool SkillAllowed(unsigned skill,Professions const& primary)
{
if (skill==129 || skill==185 || skill==356 || skill==794) return true;
auto name=PrimaryForSkill(skill);return *name && primary.count(name);
}
inline bool Allowed(std::string const& kind,Professions const& primary)
{
if(auto task=Find(kind))return !*task->Primary || primary.count(task->Primary);
return kind=="profession_tools" || kind=="profession_trainer" || kind=="profession_vendor";
}
inline bool Recognized(std::string const& kind)
{
return Find(kind) || kind=="profession_tools" || kind=="profession_trainer" || kind=="profession_vendor";
}
inline bool Untargeted(std::string const& kind){return kind=="profession_tools" || kind=="work_first_aid";}
inline std::string Legacy(std::string const& requirement)
{
return requirement=="ore"?"work_mining":requirement=="forge"?"work_blacksmithing":"";
}
inline std::vector<AdventureSocialFlow::Step> Steps(std::string const& kind,unsigned duration)
{
using namespace AdventureSocialFlow;
if(kind=="profession_trainer" || kind=="profession_vendor")return Conversation();
if(kind=="profession_tools")return {{Cue::CheckGear,3000},{Cue::Listen,std::max(4000u,duration/2)},{Cue::Nod,2000}};
if(kind=="ore_inspect")return {{Cue::Look,std::max(5000u,duration/2)},{Cue::Nod,2000}};
if(!Find(kind))return {};
return {{Cue::Look,2000},{Cue::Work,std::max(5000u,duration-std::min(duration,8000u))},{Cue::Look,4000},{Cue::Nod,2000}};
}
}
#endif
@@ -0,0 +1,85 @@
#include "AdventureProfessionTargets.h"
#include "AdventureProfessionPolicy.h"
#include "AdventureSocialActions.h"
#include "AdventureLocationMgr.h"
#include "Creature.h"
#include "GameObject.h"
#include "GameObjectData.h"
#include "Player.h"
#include "ObjectMgr.h"
#include "SpellMgr.h"
#include "Trainer.h"
#include "DBCStores.h"
#include <cmath>
namespace
{
bool TrainerMatches(Creature* npc,AdventureSocialContext const& context)
{
if(!npc->IsTrainer())return false;
auto trainer=sObjectMgr->GetTrainer(sObjectMgr->GetCreatureDefaultTrainer(npc->GetEntry()));
if(!trainer || trainer->GetTrainerType()!=Trainer::Type::Tradeskill)return false;
for(auto const& spell:trainer->GetSpells())
{
if(AdventureProfessionPolicy::SkillAllowed(spell.ReqSkillLine,context.PrimaryProfessions))return true;
auto bounds=sSpellMgr->GetSkillLineAbilityMapBounds(spell.SpellId);
for(auto it=bounds.first;it!=bounds.second;++it)
if(AdventureProfessionPolicy::SkillAllowed(it->second->SkillLine,context.PrimaryProfessions))return true;
}
return false;
}
}
bool AdventureProfessionTargets::Valid(Player* p,WorldObject* target,std::string const& kind,AdventureSocialContext const& c)
{
using namespace AdventureProfessionPolicy;
if(c.Intent!="profession" || !Allowed(kind,c.PrimaryProfessions) || !target || !target->IsInWorld() ||
target->GetMapId()!=c.MapId || !p->IsInPhase(target) || c.Anchor.GetExactDist(target)>200 ||
std::fabs(target->GetPositionZ()-c.Anchor.GetPositionZ())>25)return false;
auto task=AdventureProfessionPolicy::Find(kind);
if(auto npc=target->ToCreature())
{
if(kind=="work_skinning")return !npc->IsAlive() && npc->getDeathState()==CORPSE &&
npc->GetCreatureType()==CREATURE_TYPE_BEAST && npc->HasFlag(UNIT_FIELD_FLAGS,UNIT_FLAG_SKINNABLE) && p->CanSeeOrDetect(npc) && p->IsWithinLOSInMap(npc);
if(!npc->IsAlive() || npc->IsInCombat() || !p->IsFriendlyTo(npc) || !npc->GetCharmerOrOwnerGUID().IsEmpty())return false;
return kind=="profession_vendor"?npc->IsVendor():kind=="profession_trainer" && TrainerMatches(npc,c);
}
auto go=target->ToGameObject();
if(!go || !task || !go->isSpawned())return false;
if(task->Skill && task->Skill!=393)
{
if(go->GetGoType()!=GAMEOBJECT_TYPE_CHEST || go->getLootState()!=GO_READY || !p->CanSeeOrDetect(go) || !p->IsWithinLOSInMap(go))return false;
auto lock=sLockStore.LookupEntry(go->GetGOInfo()->GetLockId());
if(!lock)return false;
for(unsigned i=0;i<MAX_LOCK_CASE;++i)
if(lock->Type[i]==LOCK_KEY_SKILL && unsigned(SkillByLockType(LockType(lock->Index[i])))==task->Skill)return true;
return false;
}
// Native spell-focus markers may be invisible: they represent the real
// anvil/forge in map geometry. They are approached, never activated.
return *task->Station && sAdventureLocationMgr->IsWorkstation(go->GetEntry(),task->Station) &&
(go->GetGoType()==GAMEOBJECT_TYPE_SPELL_FOCUS || p->CanSeeOrDetect(go));
}
std::vector<WorldObject*> AdventureProfessionTargets::Find(Player* p,std::string const& kind,AdventureSocialContext& c)
{
std::vector<WorldObject*> result;
if(!AdventureProfessionPolicy::Allowed(kind,c.PrimaryProfessions))return result;
auto task=AdventureProfessionPolicy::Find(kind);
if(kind=="work_skinning" || kind=="profession_trainer" || kind=="profession_vendor")
{
std::vector<Creature*> people;p->GetCreatureListWithEntryInGrid(people,0,80);
for(auto npc:people)if(Valid(p,npc,kind,c))result.push_back(npc);
}
else
{
std::vector<GameObject*> objects;p->GetGameObjectListWithEntryInGrid(objects,0,80);
for(auto go:objects)if(Valid(p,go,kind,c))result.push_back(go);
}
if(task && task->Gather)
result.erase(std::remove_if(result.begin(),result.end(),[&](WorldObject* target)
{
auto key="work:"+target->GetGUID().ToString();
unsigned checked=0;
for(auto it=c.TargetHistory.rbegin();it!=c.TargetHistory.rend() && checked<4;++it,++checked)if(*it==key)return true;
return false;
}),result.end());
return result;
}
@@ -0,0 +1,15 @@
#ifndef VCORE_ADVENTURE_PROFESSION_TARGETS_H
#define VCORE_ADVENTURE_PROFESSION_TARGETS_H
#include "Common.h"
#include <string>
#include <vector>
class Player;
class WorldObject;
struct AdventureSocialContext;
class TC_GAME_API AdventureProfessionTargets
{
public:
static std::vector<WorldObject*> Find(Player* player,std::string const& kind,AdventureSocialContext& context);
static bool Valid(Player* player,WorldObject* target,std::string const& kind,AdventureSocialContext const& context);
};
#endif
+248 -24
View File
@@ -1,8 +1,13 @@
#include "AdventureSocialActions.h"
#include "AdventureLocationMgr.h"
#include "AdventureEconomyPolicy.h"
#include "AdventureExplorationPolicy.h"
#include "AdventureProfessionTargets.h"
#include "AdventureBehaviorPolicy.h"
#include "ClientlessPlayerMgr.h"
#include "ClientlessPerceptionMgr.h"
#include "ClientlessTravelMgr.h"
#include "DBCStores.h"
#include "Creature.h"
#include "GameObject.h"
#include "GameObjectData.h"
@@ -26,22 +31,56 @@ namespace
constexpr uint32 CookingFire = 818;
bool InBounds(Player * player, WorldObject const * target, AdventureSocialContext const & c)
{
return target && target->IsInWorld() && target->GetMapId() == c.MapId && c.Anchor.GetExactDist(target) <= 30 && std::fabs(target->GetPositionZ() - c.Anchor.GetPositionZ()) <= 6 && player->IsInPhase(target);
return target && target->IsInWorld() && target->GetMapId() == c.MapId && c.Anchor.GetExactDist(target) <= (c.Intent=="economy" || c.Intent=="adventure" || c.Intent=="profession"?200:30) && std::fabs(target->GetPositionZ() - c.Anchor.GetPositionZ()) <= (c.Intent=="economy" || c.Intent=="adventure" || c.Intent=="profession"?25:6) && player->IsInPhase(target);
}
bool Friendly(Player * player, Unit * unit, AdventureSocialContext const & c)
{
return unit != player && InBounds(player, unit, c) && unit->IsAlive() && ! unit->IsInCombat() && ! unit->IsInFlight() && ! unit->IsFlying() && ! unit->GetTransport() && player->IsFriendlyTo(unit) && ! unit->IsCritter() && ! unit->IsTotem() && unit->GetCharmerOrOwnerGUID().IsEmpty();
}
bool ServiceMatches(Unit* unit, std::string const& kind)
{
return unit && AdventureEconomyPolicy::Matches(kind,unit->IsVendor(),unit->IsBanker(),
unit->HasFlag(UNIT_NPC_FLAGS,UNIT_NPC_FLAG_AUCTIONEER));
}
bool TargetVisible(Player* player, WorldObject* target, std::string const& kind)
{
if (!AdventureEconomyPolicy::Economic(kind) && !AdventureProfessionPolicy::Recognized(kind)) return player->IsWithinLOSInMap(target);
// Counters can obscure the unit's ground/combat sphere while both
// participants can see each other's head. Keep native VMAP geometry.
return player->GetMap()->isInLineOfSight(player->GetPhaseShift(),
player->GetPositionX(),player->GetPositionY(),player->GetPositionZ()+player->GetCollisionHeight(),
target->GetPositionX(),target->GetPositionY(),target->GetPositionZ()+target->GetCollisionHeight(),
LINEOFSIGHT_ALL_CHECKS,VMAP::ModelIgnoreFlags::Nothing);
}
std::vector < Unit * > People(Player * player, std::string const & kind, AdventureSocialContext & c)
{
std::vector < Unit * > people;
if (AdventureEconomyPolicy::Economic(kind))
{
auto snapshot=sClientlessPerceptionMgr->GetSnapshot(player->GetGUID().GetCounter());
if (!snapshot || snapshot->Radius<100 || snapshot->ScanAgeMs>=1000)
{
sClientlessPerceptionMgr->ScanNow(player,100);
snapshot=sClientlessPerceptionMgr->GetSnapshot(player->GetGUID().GetCounter());
}
if (snapshot) for (auto const& entity:snapshot->Entities)
{
if (entity.Type!=ClientlessEntityType::Creature || !entity.Alive ||
!(entity.Capabilities & (CLIENTLESS_CAP_VENDOR|CLIENTLESS_CAP_BANKER|CLIENTLESS_CAP_AUCTIONEER))) continue;
auto unit=ObjectAccessor::GetCreature(*player,ObjectGuid(HighGuid::Unit,entity.Entry,entity.Guid));
if (Friendly(player,unit,c) && ServiceMatches(unit,kind) &&
(c.ServiceKind.empty() || ServiceMatches(unit,c.ServiceKind))) people.push_back(unit);
}
return people;
}
if (kind != "bot")
{
std::vector < Creature * > creatures;
player->GetCreatureListWithEntryInGrid(creatures, 0, 25);
for (auto unit : creatures) if (Friendly(player, unit, c) && player->IsWithinLOSInMap(unit) && ((kind != "counter" && kind != "order") || unit->IsInnkeeper())) people.push_back(unit);
for (auto unit : creatures) if (Friendly(player, unit, c) && (AdventureEconomyPolicy::Economic(kind) || player->IsWithinLOSInMap(unit)) && (AdventureEconomyPolicy::Economic(kind)?(ServiceMatches(unit,kind) && (c.ServiceKind.empty() || ServiceMatches(unit,c.ServiceKind))):
((kind != "counter" && kind != "order") || unit->IsInnkeeper()))) people.push_back(unit);
}
if (kind != "npc" && kind != "counter" && kind != "order")
if (!AdventureEconomyPolicy::Economic(kind) && kind != "npc" && kind != "counter" && kind != "order")
{
std::vector < Player * > players;
player->GetPlayerListInGrid(players, 25);
@@ -56,6 +95,21 @@ namespace
for (auto go : all) if (go->isSpawned() && go->GetGoType() == type && InBounds(player, go, c)) result.push_back(go);
return result;
}
std::vector<GameObject*> InspectionObjects(Player* player,AdventureSocialContext& c)
{
std::vector<GameObject*> all,result;
player->GetGameObjectListWithEntryInGrid(all,0,60);
for (auto go:all)
{
if (!go->isSpawned() || !InBounds(player,go,c) || !player->CanSeeOrDetect(go) || !player->IsWithinLOSInMap(go)) continue;
// Examine visible props/books/signs; never activate them. Ignore
// traps, transports, doors, chests and furniture as investigation targets.
auto type=go->GetGoType();
if (type==GAMEOBJECT_TYPE_GENERIC || type==GAMEOBJECT_TYPE_GOOBER || type==GAMEOBJECT_TYPE_TEXT)
result.push_back(go);
}
return result;
}
GameObject * Fire(Player * player, AdventureSocialContext & c)
{
std::vector < GameObject * > objects;
@@ -72,7 +126,24 @@ namespace
}
bool CanMakeFire(Player * p)
{
return p->HasSpell(CookingFire) && p->IsOutdoors() && ! p->IsInWater() && ! p->IsFalling() && ! p->GetSpellHistory()->HasCooldown(CookingFire) && ! p->IsNonMeleeSpellCast(false);
if (!p->HasSpell(CookingFire) || !p->IsOutdoors() || p->IsInWater() || p->IsFalling() ||
p->GetSpellHistory()->HasCooldown(CookingFire) || p->IsNonMeleeSpellCast(false)) return false;
// Enforce this for every intent and again immediately before casting.
// Districts may inherit city flags from a parent or the enclosing zone.
for (uint32 id : {p->GetAreaId(), p->GetZoneId()})
{
for (uint32 depth = 0; ; ++depth)
{
auto const* area = sAreaTableStore.LookupEntry(id);
if (!area || depth >= 16) return false;
auto const flags = area->GetFlags();
if (flags.HasFlag(AreaFlags::LinkedChat) || flags.HasFlag(AreaFlags::AllowTradeChannel))
return false;
if (!area->ParentAreaID) break;
id = area->ParentAreaID;
}
}
return true;
}
WorldObject * Resolve(Player * p, ObjectGuid guid)
{
@@ -96,15 +167,16 @@ namespace
bool Approach(Player* player,AdventureSocialScene& scene,WorldObject* target,AdventureSocialContext& c)
{
if(!InBounds(player,target,c))return false;
if(player->GetExactDist(target)<=3.8f && AtFront(player,target) &&
player->IsWithinLOS(target->GetPositionX(),target->GetPositionY(),target->GetPositionZ()+1.5f))
if(player->GetExactDist(target)<=3.8f && (scene.Kind=="work_skinning" || AtFront(player,target)) &&
((AdventureEconomyPolicy::Economic(scene.Kind) || AdventureProfessionPolicy::Recognized(scene.Kind))?TargetVisible(player,target,scene.Kind):
player->IsWithinLOS(target->GetPositionX(),target->GetPositionY(),target->GetPositionZ()+1.5f)))
{
scene.Approaching=false;
return true;
}
// People are approached in front of their actual facing, not from the
// bot's current bearing. Furniture may still be reached from any side.
bool person=target->ToUnit()!=nullptr;
bool person=target->ToUnit()!=nullptr && scene.Kind!="work_skinning";
float bearing=person?target->GetOrientation():target->GetAngle(player);
std::vector<float> offsets=person?std::vector<float>{0,0.28f,-0.28f,0.55f,-0.55f}:
std::vector<float>{0,0.7f,-0.7f,1.4f,-1.4f,3.1415926f};
@@ -116,10 +188,10 @@ namespace
float floor=player->GetMap()->GetHeight(player->GetPhaseShift(),p.GetPositionX(),p.GetPositionY(),target->GetPositionZ()+3,true,8);
if(floor<=INVALID_HEIGHT+1||std::fabs(floor-target->GetPositionZ())>3)continue;
p.m_positionZ=floor;
if(!player->GetMap()->isInLineOfSight(player->GetPhaseShift(),p.GetPositionX(),p.GetPositionY(),floor+1.5f,
target->GetPositionX(),target->GetPositionY(),target->GetPositionZ()+1.5f,
if(!player->GetMap()->isInLineOfSight(player->GetPhaseShift(),p.GetPositionX(),p.GetPositionY(),floor+((AdventureEconomyPolicy::Economic(scene.Kind) || AdventureProfessionPolicy::Recognized(scene.Kind))?player->GetCollisionHeight():1.5f),
target->GetPositionX(),target->GetPositionY(),target->GetPositionZ()+((AdventureEconomyPolicy::Economic(scene.Kind) || AdventureProfessionPolicy::Recognized(scene.Kind))?target->GetCollisionHeight():1.5f),
LINEOFSIGHT_ALL_CHECKS,VMAP::ModelIgnoreFlags::Nothing))continue;
if(!c.Move(p,55))continue;
if(!c.Move(p,(AdventureEconomyPolicy::Economic(scene.Kind) || AdventureExplorationPolicy::Inspection(scene.Kind) || AdventureProfessionPolicy::Recognized(scene.Kind))?350:55))continue;
scene.Destination=p;scene.Approaching=true;return true;
}
return false;
@@ -178,6 +250,10 @@ namespace
}
std::vector < Step > Steps(std::string const & kind, uint32 duration)
{
if (AdventureProfessionPolicy::Recognized(kind)) return AdventureProfessionPolicy::Steps(kind,duration);
if (AdventureExplorationPolicy::Inspection(kind) || AdventureExplorationPolicy::Untargeted(kind))
return AdventureExplorationPolicy::Steps(kind,duration);
if (AdventureEconomyPolicy::Economic(kind)) return AdventureEconomyPolicy::Steps(kind,duration);
if (kind == "npc" || kind == "bot") return Conversation();
if (kind == "counter") return
{
@@ -338,10 +414,24 @@ void AdventureSocialActions::Prepare(Player * player)
bool AdventureSocialActions::Available(Player * p, AdventureMicroAction const & action, AdventureSocialContext & c)
{
auto const & kind = action.Requirement;
if (AdventureProfessionPolicy::Recognized(kind)) return c.Intent=="profession" &&
AdventureProfessionPolicy::Allowed(kind,c.PrimaryProfessions) &&
(AdventureProfessionPolicy::Untargeted(kind)?!p->IsInWater() && !p->IsFalling():!AdventureProfessionTargets::Find(p,kind,c).empty());
if (AdventureExplorationPolicy::Inspection(kind)) return c.Intent=="adventure" && !InspectionObjects(p,c).empty();
if (AdventureExplorationPolicy::Untargeted(kind)) return c.Intent=="adventure" &&
(kind!="vista" || p->IsOutdoors()) && (kind!="entrance" || AdventureExplorationPolicy::Entrance(c.LocationCategory));
if (kind=="supplies") return c.Intent=="economy";
if (kind=="vendor_compare") return People(p,kind,c).size()>=2;
if (kind=="service_finish")
{
for (auto unit:People(p,kind,c))
if (std::find(c.TargetHistory.begin(),c.TargetHistory.end(),unit->GetGUID().ToString())!=c.TargetHistory.end()) return true;
return false;
}
if (kind == "seat" || kind == "drink_seated" || kind == "eat_seated") return ! Objects(p, GAMEOBJECT_TYPE_CHAIR, c).empty();
if (kind == "dance") return c.Festival || ! People(p, "audience", c).empty();
if (kind == "fire") return Fire(p, c) || CanMakeFire(p);
if (kind == "make_fire") return ! Fire(p, c) && CanMakeFire(p);
if (kind == "dance") return c.Festival || (c.Intent!="leisure" && ! People(p, "audience", c).empty());
if (kind == "fire") return Fire(p, c) || (c.AllowCreateFire && CanMakeFire(p));
if (kind == "make_fire") return c.AllowCreateFire && ! Fire(p, c) && CanMakeFire(p);
if (kind == "farewell")
{
for (auto unit : People(p, kind, c)) if (std::find(c.TargetHistory.begin(), c.TargetHistory.end(), unit->GetGUID().ToString()) != c.TargetHistory.end()) return true;
@@ -357,7 +447,10 @@ bool AdventureSocialActions::Start(Player * p, AdventureSocialScene & s, Adventu
};
s.Action = a.Key;
s.Kind = a.Requirement;
if(AdventureProfessionPolicy::Recognized(s.Kind) && !Available(p,a,c))return false;
s.Origin.Relocate(p);
s.PreviousSelection = p->GetTarget();
s.Facing=p->GetOrientation();
WorldObject * target = nullptr;
bool approached=false;
if (s.Kind == "seat" || s.Kind == "drink_seated" || s.Kind == "eat_seated")
@@ -372,6 +465,30 @@ bool AdventureSocialActions::Start(Player * p, AdventureSocialScene & s, Adventu
chairs.erase(std::remove(chairs.begin(),chairs.end(),chair),chairs.end());
}
}
else if (AdventureProfessionPolicy::Recognized(s.Kind) && !AdventureProfessionPolicy::Untargeted(s.Kind))
{
auto targets=AdventureProfessionTargets::Find(p,s.Kind,c);
while(!targets.empty())
{
auto candidate=Pick(p,targets,c);
if(!candidate)break;
if(Approach(p,s,candidate,c)){target=candidate;approached=true;break;}
targets.erase(std::remove(targets.begin(),targets.end(),candidate),targets.end());
}
if(!target)return false;
}
else if (AdventureExplorationPolicy::Inspection(s.Kind))
{
auto objects=InspectionObjects(p,c);
while (!objects.empty())
{
auto object=Pick(p,objects,c);
if (!object) break;
if (Approach(p,s,object,c)) {target=object;approached=true;break;}
objects.erase(std::remove(objects.begin(),objects.end(),object),objects.end());
}
if (!target) return false;
}
else if (s.Kind == "fire" || s.Kind == "make_fire") target = Fire(p, c);
else if (s.Kind == "farewell")
{
@@ -382,7 +499,25 @@ bool AdventureSocialActions::Start(Player * p, AdventureSocialScene & s, Adventu
break;
}
}
else if (s.Kind != "dance") target = Pick(p, People(p, s.Kind, c), c);
else if (AdventureEconomyPolicy::RequiredService(s.Kind)!=AdventureEconomyPolicy::Service::None)
{
auto people=People(p,s.Kind,c);
if (s.Kind=="vendor_compare" && people.size()<2) return false;
if (s.Kind=="service_finish")
people.erase(std::remove_if(people.begin(),people.end(),[&](Unit* unit)
{return std::find(c.TargetHistory.begin(),c.TargetHistory.end(),unit->GetGUID().ToString())==c.TargetHistory.end();}),people.end());
// A nearer counter may be blocked. Try other actual service NPCs before
// declaring this visit unavailable; every approach still uses MMAP.
while (!people.empty())
{
auto unit=Pick(p,people,c);
if (!unit) break;
if (Approach(p,s,unit,c)) {target=unit;approached=true;break;}
people.erase(std::remove(people.begin(),people.end(),unit),people.end());
}
if (!target) return false;
}
else if (s.Kind != "dance" && s.Kind!="supplies" && !AdventureExplorationPolicy::Untargeted(s.Kind) && !AdventureProfessionPolicy::Untargeted(s.Kind)) target = Pick(p, People(p, s.Kind, c), c);
if (target)
{
s.Target = target->GetGUID();
@@ -392,7 +527,7 @@ bool AdventureSocialActions::Start(Player * p, AdventureSocialScene & s, Adventu
}
else if (s.Kind == "fire" || s.Kind == "make_fire")
{
if (! CanMakeFire(p)) return false;
if (! c.AllowCreateFire || ! CanMakeFire(p)) return false;
// Native cast places the campfire beside the grounded caster. Require a
// small flat open patch, not water, a ledge, doorway or a room interior.
for (unsigned i = 0; i < 8; ++ i)
@@ -403,8 +538,9 @@ bool AdventureSocialActions::Start(Player * p, AdventureSocialScene & s, Adventu
if (z <= INVALID_HEIGHT + 1 || std::fabs(z - p->GetPositionZ()) > 1.0f || p->GetMap()->GetWaterLevel(p->GetPhaseShift(), x, y) > z + 0.2f || ! p->IsWithinLOS(x, y, z + 1)) return false;
}
}
else if (s.Kind != "dance") return false;
s.Sequence.Start(Steps(s.Kind, duration));
else if (s.Kind != "dance" && s.Kind!="supplies" && !AdventureExplorationPolicy::Untargeted(s.Kind) && !AdventureProfessionPolicy::Untargeted(s.Kind)) return false;
s.ServiceDuration=s.Kind=="vendor_compare"?duration/2:duration;
s.Sequence.Start(Steps(s.Kind, s.ServiceDuration));
s.Active = s.Sequence.Active();
TC_LOG_INFO("server.adventurebot", "SOCIAL_START guid=%u action='%s' target='%s' approach=%u", p->GetGUID().GetCounter(), s.Action.c_str(), s.Target.ToString().c_str(), s.Approaching ? 1 : 0);
return s.Active;
@@ -413,29 +549,50 @@ bool AdventureSocialActions::Start(Player * p, AdventureSocialScene & s, Adventu
bool AdventureSocialActions::Update(Player * p, AdventureSocialScene & s, uint32 diff, AdventureSocialContext & c)
{
if (! s.Active) return false;
if(AdventureProfessionPolicy::Recognized(s.Kind) && (c.Intent!="profession" || !AdventureProfessionPolicy::Allowed(s.Kind,c.PrimaryProfessions)))return false;
s.Elapsed += diff;
if (s.Elapsed > 120000) return false;
if (s.Elapsed > (AdventureEconomyPolicy::Economic(s.Kind)?240000u:120000u)) return false;
if (s.Departing)
{
auto travel=sClientlessTravelMgr->GetStatus(p->GetGUID().GetCounter());
if (travel && travel->State==ClientlessTravelState::Moving) return true;
if (travel && travel->State==ClientlessTravelState::Arrived && p->GetExactDist(s.Destination)<=2)
TC_LOG_INFO("server.adventurebot",AdventureProfessionPolicy::Recognized(s.Kind)?"PROFESSION_VISIT_COMPLETE guid=%u action='%s' visits=%u":"ECONOMY_VISIT_COMPLETE guid=%u action='%s' visits=%u",
p->GetGUID().GetCounter(),s.Action.c_str(),s.ServiceVisits+1);
return false;
}
auto target = Resolve(p, s.Target);
if (! s.Target.IsEmpty())
{
if (! InBounds(p, target, c)) return false;
if(AdventureProfessionPolicy::Recognized(s.Kind) && !AdventureProfessionTargets::Valid(p,target,s.Kind,c))return false;
if (AdventureExplorationPolicy::Inspection(s.Kind))
{
auto object=target->ToGameObject();
if (!object || !object->isSpawned() || !p->CanSeeOrDetect(object)) return false;
if (!s.Approaching && (p->GetExactDist(target)>4 || !TargetVisible(p,target,s.Kind)))
{
if (++s.Repaths>3 || !Approach(p,s,target,c)) return false;
}
}
if (auto unit = target->ToUnit())
{
if (! Friendly(p, unit, c)) return false;
if ((p->GetExactDist(unit) > 5 || !AtFront(p,unit) || !p->IsWithinLOSInMap(unit)) && ! s.Approaching)
if (s.Kind!="work_skinning" && ! Friendly(p, unit, c)) return false;
if (AdventureEconomyPolicy::Economic(s.Kind) && !ServiceMatches(unit,s.Kind)) return false;
if ((p->GetExactDist(unit) > 5 || (s.Kind!="work_skinning" && !AtFront(p,unit)) || !TargetVisible(p,unit,s.Kind)) && ! s.Approaching)
{
if (++ s.Repaths > 3 || ! Approach(p, s, unit, c)) return false;
}
}
}
if(target&&target->ToUnit()&&!s.Approaching&&!p->IsWithinLOSInMap(target))return false;
if(target&&target->ToUnit()&&!s.Approaching&&!TargetVisible(p,target,s.Kind))return false;
if (s.Approaching)
{
auto travel = sClientlessTravelMgr->GetStatus(p->GetGUID().GetCounter());
if (travel && travel->State == ClientlessTravelState::Moving) return true;
if (! travel || travel->State != ClientlessTravelState::Arrived || ! target) return false;
s.Approaching = false;
if (p->GetExactDist(target) > 4 || !AtFront(p,target))
if (p->GetExactDist(target) > 4 || (s.Kind!="work_skinning" && !AtFront(p,target)) || !TargetVisible(p,target,s.Kind))
{
if (++ s.Repaths > 3 || ! Approach(p, s, target, c)) return false;
return true;
@@ -497,6 +654,27 @@ bool AdventureSocialActions::Update(Player * p, AdventureSocialScene & s, uint32
case Cue::Comment : p->HandleEmoteCommand(EMOTE_ONESHOT_TALK);
Say(p, "comment", c);
break;
case Cue::Work :
{
auto task=AdventureProfessionPolicy::Find(s.Kind);
if(!task || !AdventureProfessionPolicy::Allowed(s.Kind,c.PrimaryProfessions))return false;
uint32 emote=EMOTE_STATE_USE_STANDING;
switch(task->Animation)
{
case AdventureProfessionPolicy::Motion::Mine:emote=EMOTE_STATE_WORK_MINING;break;
case AdventureProfessionPolicy::Motion::Chop:emote=EMOTE_STATE_WORK_CHOPWOOD;break;
case AdventureProfessionPolicy::Motion::Kneel:emote=EMOTE_STATE_KNEEL;break;
default:break;
}
p->SetUInt32Value(UNIT_NPC_EMOTESTATE,emote);
TC_LOG_INFO("server.adventurebot","PROFESSION_WORK guid=%u kind='%s' target='%s' emote=%u",p->GetGUID().GetCounter(),s.Kind.c_str(),s.TargetKey.c_str(),emote);
break;
}
case Cue::SurveyLeft : p->SetFacingTo(s.Facing-0.75f); break;
case Cue::SurveyRight : p->SetFacingTo(s.Facing+0.75f); break;
case Cue::Question : p->HandleEmoteCommand(EMOTE_ONESHOT_QUESTION); break;
case Cue::Point : p->HandleEmoteCommand(EMOTE_ONESHOT_POINT); break;
case Cue::CheckGear : p->HandleEmoteCommand(EMOTE_ONESHOT_USE_STANDING); break;
case Cue::Listen : break;
case Cue::React : p->HandleEmoteCommand(Reaction(c));
break;
@@ -521,7 +699,7 @@ bool AdventureSocialActions::Update(Player * p, AdventureSocialScene & s, uint32
break;
case Cue::Fire : if (! Fire(p, c))
{
if (! CanMakeFire(p) || p->CastSpell(p, CookingFire, false) != SPELL_CAST_OK) return false;
if (! c.AllowCreateFire || ! CanMakeFire(p) || p->CastSpell(p, CookingFire, false) != SPELL_CAST_OK) return false;
s.Casting = true;
s.CastElapsed = 0;
TC_LOG_INFO("server.adventurebot", "SOCIAL_FIRE_CAST guid=%u spell=818", p->GetGUID().GetCounter());
@@ -547,7 +725,53 @@ bool AdventureSocialActions::Update(Player * p, AdventureSocialScene & s, uint32
return true;
}
s.Sequence.Update(diff);
return s.Sequence.Active();
if (s.Sequence.Active()) return true;
if(AdventureProfessionPolicy::Recognized(s.Kind))
{
if(auto task=AdventureProfessionPolicy::Find(s.Kind))
if(task->Gather && s.Kind!="ore_inspect")Remember(c.TargetHistory,"work:"+s.TargetKey);
TC_LOG_INFO("server.adventurebot","PROFESSION_WORK_COMPLETE guid=%u kind='%s' target='%s'",p->GetGUID().GetCounter(),s.Kind.c_str(),s.TargetKey.c_str());
if(!target)return false;
Position departure;departure.Relocate(p);
float angle=target->GetAngle(p);
departure.m_positionX+=std::cos(angle)*2;departure.m_positionY+=std::sin(angle)*2;
float height=p->GetMap()->GetHeight(p->GetPhaseShift(),departure.GetPositionX(),departure.GetPositionY(),p->GetPositionZ()+3,true,8);
if(height>INVALID_HEIGHT+1 && std::fabs(height-p->GetPositionZ())<=2)
{
departure.m_positionZ=height;
if(c.Move(departure,20)){s.Destination=departure;s.Departing=true;return true;}
}
return false;
}
if (s.Kind=="vendor_compare" && !s.ServiceVisits)
{
auto people=People(p,s.Kind,c);
people.erase(std::remove_if(people.begin(),people.end(),[&](Unit* unit)
{return unit->GetGUID()==s.Target;}),people.end());
while (!people.empty())
{
auto next=Pick(p,people,c);
if (!next) break;
if (Approach(p,s,next,c))
{
s.Target=next->GetGUID();s.TargetKey=s.Target.ToString();++s.ServiceVisits;
Remember(c.TargetHistory,s.TargetKey);s.Sequence.Start(Steps(s.Kind,s.ServiceDuration));
TC_LOG_INFO("server.adventurebot","ECONOMY_COMPARE_NEXT guid=%u target='%s'",p->GetGUID().GetCounter(),s.TargetKey.c_str());
return true;
}
people.erase(std::remove(people.begin(),people.end(),next),people.end());
}
return false;
}
if (AdventureEconomyPolicy::RequiredService(s.Kind)!=AdventureEconomyPolicy::Service::None)
{
s.Destination=c.Anchor;
if (!c.Move(s.Destination,350)) return false;
s.Departing=true;
TC_LOG_INFO("server.adventurebot","ECONOMY_DEPART guid=%u action='%s'",p->GetGUID().GetCounter(),s.Action.c_str());
return true;
}
return false;
}
void AdventureSocialActions::Reset(Player * p, AdventureSocialScene & s)
@@ -4,6 +4,7 @@
#include "ObjectGuid.h"
#include "Position.h"
#include "AdventureSocialFlow.h"
#include "AdventureProfessionPolicy.h"
#include <deque>
#include <functional>
#include <random>
@@ -13,11 +14,12 @@ struct AdventureMicroAction;
struct AdventureLocation;
struct AdventureSocialScene
{
bool Active=false, Approaching=false, Casting=false, WaitingChair=false, Seated=false;
bool Active=false, Approaching=false, Casting=false, WaitingChair=false, Seated=false, Departing=false;
std::string Action, Kind, TargetKey;
ObjectGuid Target, PreviousSelection;
Position Destination;
uint32 Elapsed=0, CastElapsed=0, CheckTimer=0, Repaths=0;
Position Destination, Origin;
float Facing=0;
uint32 Elapsed=0, CastElapsed=0, CheckTimer=0, Repaths=0, ServiceVisits=0, ServiceDuration=0;
AdventureSocialFlow::Sequence Sequence;
};
struct AdventureSocialContext
@@ -30,6 +32,9 @@ struct AdventureSocialContext
std::deque<std::string>& TargetHistory;
std::deque<std::string>& VariantHistory;
std::function<bool(Position const&,float)> Move;
bool AllowCreateFire=true;
std::string ServiceKind, LocationCategory;
AdventureProfessionPolicy::Professions PrimaryProfessions;
};
class TC_GAME_API AdventureSocialActions
{
+1 -1
View File
@@ -15,7 +15,7 @@ inline bool ConversationFront(float x,float y,float heading,float otherX,float o
return distance>=1.1f&&distance<=3.8f &&
(dx*std::cos(heading)+dy*std::sin(heading))/distance>=std::cos(0.85f);
}
enum class Cue { Greet, Talk, Order, Comment, Listen, React, Nod, Farewell, Look, Toast, Drink, Seat, Dance, Fire, Rest, Eat };
enum class Cue { Greet, Talk, Order, Comment, Listen, React, Nod, Farewell, Look, Toast, Drink, Seat, Dance, Fire, Rest, Eat, SurveyLeft, SurveyRight, Question, Point, CheckGear, Work };
struct Step { Cue Action; uint32_t Duration; };
// A cue fires once; waiting for navigation, native casts or a chair ACK happens
// outside this clock. That prevents animations from being executed while walking.
@@ -14,6 +14,7 @@
#include "Opcodes.h"
#include "Player.h"
#include "MotionMaster.h"
#include "MoveSpline.h"
#include "PathGenerator.h" // VCORE_C819_DEFERRED_START_NODES_V2
#include "GameObject.h"
#include "Map.h"
@@ -33,6 +34,17 @@
namespace
{
bool StartGroundConnection(Player* player, uint32 guid, Position const& target)
{
if (!sClientlessTravelMgr->StartTravel(player, target, true))
return false;
// StartTravel reports that the request was accepted even when its
// synchronous execution query has already rejected the actual path.
auto const* status = sClientlessTravelMgr->GetStatus(guid);
return status && (status->State == ClientlessTravelState::Moving ||
status->State == ClientlessTravelState::Arrived);
}
struct NamedCapability
{
char const* Name;
@@ -606,6 +618,9 @@ bool ClientlessActionExecutorMgr::StartTravelToNode(
"TRAVEL_PLAN_FAILED guid=%u name='%s' action='travel_to_node' destination='%s' reason='%s'",
characterGuid, player->GetName().c_str(), destinationNode.c_str(), error.c_str());
if (TryFallbackFlight(player, characterGuid, record))
return true;
if (record.HardDestination)
{
record.Status.State = ClientlessActionExecutionState::Failed;
@@ -769,6 +784,8 @@ bool ClientlessActionExecutorMgr::StartTravelToLocation(
if (!sClientlessTravelGraphMgr->FindNearestNodeToPosition(
point->MapId, point->X, point->Y, point->Z, anchorNode, proxyDistance))
{
if (TryFallbackFlight(player, characterGuid, record))
return true;
record.Status.State = record.HardDestination
? ClientlessActionExecutionState::Failed
: ClientlessActionExecutionState::Completed;
@@ -791,6 +808,8 @@ bool ClientlessActionExecutorMgr::StartTravelToLocation(
TC_LOG_WARN("server.adventurebot.navigation",
"C820_LOCATION_PLAN_FAILED location='%s' reason='%s'",
locationId.c_str(), error.c_str());
if (TryFallbackFlight(player, characterGuid, record))
return true;
record.Status.State = record.HardDestination
? ClientlessActionExecutionState::Failed
: ClientlessActionExecutionState::Completed;
@@ -889,6 +908,8 @@ bool ClientlessActionExecutorMgr::StartRouteStep(
if (!record.RouteComplete)
{
if (TryFallbackFlight(player, characterGuid, record))
return true;
TC_LOG_WARN("server.adventurebot.navigation",
"TRAVEL_PARTIAL guid=%u name='%s' action='%s' requested='%s' reached='%s' "
"map=%u pos=(%.3f %.3f %.3f)",
@@ -1339,53 +1360,8 @@ bool ClientlessActionExecutorMgr::StartRouteStep(
return false;
}
record.EdgePath.clear();
if (step.EdgeType == "ground" && step.EdgeDatabaseId)
{
if (std::vector<ClientlessTravelEdgePoint> const* points =
sClientlessTravelGraphMgr->GetEdgePoints(step.EdgeDatabaseId))
{
record.EdgePath = *points;
if (step.EdgeReverse)
std::reverse(record.EdgePath.begin(), record.EdgePath.end());
// VCORE_C820_VIRTUAL_EXIT_NODES: select an inclusive end-point
// on the original execution-direction spline before trimming start.
if (step.GroundEndIndex >= 0)
{
std::size_t const end = std::size_t(step.GroundEndIndex);
if (end >= record.EdgePath.size() ||
(step.GroundStartIndex >= 0 &&
std::size_t(step.GroundStartIndex) >= end))
{
record.Status.State = ClientlessActionExecutionState::Failed;
record.Status.Detail = "virtual ground exit indices invalid";
record.Active = false;
return false;
}
record.EdgePath.resize(end + 1);
TC_LOG_INFO("server.adventurebot.navigation",
"C820_EXIT_EXEC edgeDbId=%u endIndex=%u points=%u",
step.EdgeDatabaseId, uint32(end), uint32(record.EdgePath.size()));
}
// VCORE_VIRTUAL_GROUND_EDGE_ENTRY_V1: keep original recorded points,
// only discard the portion before the validated virtual entry.
if (step.GroundStartIndex >= 0)
{
std::size_t const offset = std::size_t(step.GroundStartIndex);
if (offset >= record.EdgePath.size())
{
record.Status.State = ClientlessActionExecutionState::Failed;
record.Status.Detail = "virtual ground entry index outside recorded edge";
record.Active = false;
return false;
}
record.EdgePath.erase(record.EdgePath.begin(), record.EdgePath.begin() + offset);
TC_LOG_INFO("server.adventurebot.navigation",
"VIRTUAL_EDGE_EXEC edgeDbId=%u pointIndex=%u remainingPoints=%u",
step.EdgeDatabaseId, uint32(offset), uint32(record.EdgePath.size()));
}
}
}
if (!PrepareRecordedGroundEdge(record))
return false;
record.Status.State = ClientlessActionExecutionState::Moving;
record.Status.TargetName = "node:" + step.NodeKey;
@@ -1645,6 +1621,72 @@ void ClientlessActionExecutorMgr::UpdatePortalUse(Player* player,
record.Status.Detail = "interactive portal teleport not confirmed within 10 seconds";
}
bool ClientlessActionExecutorMgr::PrepareRecordedGroundEdge(Record& record)
{
auto const& step = record.Route[record.RouteStep];
record.EdgeNextPoint = 0;
record.EdgeSplinePoints.clear();
record.GroundExitCheckMs = 0;
record.ReplanAtGroundEnd = false;
record.GroundFlightExit = false;
if (step.GroundEndIndex >= 0)
{
auto const* endpoint = sClientlessTravelGraphMgr->FindNode(step.GroundContinuationNode);
if (!endpoint)
{
record.Status.State = ClientlessActionExecutionState::Failed;
record.Status.Detail = "virtual exit has no forward endpoint";
record.Active = false;
return false;
}
record.GroundFlightExit = record.RouteStep + 1 < record.Route.size() &&
record.Route[record.RouteStep + 1].EdgeType == "free_flight";
// The speculative exit is never a mandatory stopping point. Keep the
// real edge endpoint and only replan there if no exit becomes usable.
auto& forward = record.Route[record.RouteStep];
forward.NodeKey = endpoint->Key;
forward.NodeType = endpoint->Type;
forward.WorldPointKey = endpoint->WorldPointKey;
forward.MapId = endpoint->MapId;
forward.X = endpoint->X; forward.Y = endpoint->Y;
forward.Z = endpoint->Z; forward.O = endpoint->O;
forward.GroundEndIndex = -1;
record.ReplanAtGroundEnd = true;
record.FinalLocalLegPending = false;
}
record.EdgePath.clear();
if (step.EdgeType == "ground" && step.EdgeDatabaseId)
{
if (std::vector<ClientlessTravelEdgePoint> const* points =
sClientlessTravelGraphMgr->GetEdgePoints(step.EdgeDatabaseId))
{
record.EdgePath = *points;
if (step.EdgeReverse)
std::reverse(record.EdgePath.begin(), record.EdgePath.end());
// VCORE_VIRTUAL_GROUND_EDGE_ENTRY_V1: keep original recorded points,
// only discard the portion before the validated virtual entry.
if (step.GroundStartIndex >= 0)
{
std::size_t const offset = std::size_t(step.GroundStartIndex);
if (offset >= record.EdgePath.size())
{
record.Status.State = ClientlessActionExecutionState::Failed;
record.Status.Detail = "virtual ground entry index outside recorded edge";
record.Active = false;
return false;
}
record.EdgePath.erase(record.EdgePath.begin(), record.EdgePath.begin() + offset);
TC_LOG_INFO("server.adventurebot.navigation",
"VIRTUAL_EDGE_EXEC edgeDbId=%u pointIndex=%u remainingPoints=%u",
step.EdgeDatabaseId, uint32(offset), uint32(record.EdgePath.size()));
}
}
}
return true;
}
bool ClientlessActionExecutorMgr::StartRecordedEdgePath(
Player* player,
uint32 characterGuid,
@@ -1674,23 +1716,35 @@ bool ClientlessActionExecutorMgr::StartRecordedEdgePath(
player->GetPositionZ(),
player->GetOrientation());
path.push_back(current);
record.EdgeSplinePoints.clear();
record.EdgeSplinePoints.push_back(record.EdgeNextPoint);
float lastX = current.GetPositionX();
float lastY = current.GetPositionY();
float lastZ = current.GetPositionZ();
for (ClientlessTravelEdgePoint const& point : record.EdgePath)
for (std::size_t i = record.EdgeNextPoint; i < record.EdgePath.size(); ++i)
{
auto const& point = record.EdgePath[i];
float dx = point.X - lastX;
float dy = point.Y - lastY;
float dz = point.Z - lastZ;
if ((dx * dx + dy * dy + dz * dz) < 0.25f)
{
// Coalesced points have already been passed when their preceding
// spline point is reached. In particular, never rejoin behind the
// departure after skipping the point at the current position.
record.EdgeSplinePoints.back() = i + 1;
if (path.size() == 1)
record.EdgeNextPoint = i + 1;
continue;
}
Position p;
p.Relocate(point.X, point.Y, point.Z, point.O);
path.push_back(p);
record.EdgeSplinePoints.push_back(i + 1);
lastX = point.X;
lastY = point.Y;
@@ -1740,6 +1794,220 @@ bool ClientlessActionExecutorMgr::StartRecordedEdgePath(
return true;
}
bool ClientlessActionExecutorMgr::GetTravelGoal(Record const& record, ClientlessTravelNode& goal) const
{
if (record.Status.ActionName == "travel_to_location")
{
goal.MapId = record.FinalMapId;
goal.X = record.FinalX; goal.Y = record.FinalY;
goal.Z = record.FinalZ; goal.O = record.FinalO;
goal.Key = "location:" + record.FinalLocationId;
return true;
}
if (auto const* node = sClientlessTravelGraphMgr->FindNode(record.PlannedDestinationNode))
{
goal = *node;
return true;
}
return false;
}
bool ClientlessActionExecutorMgr::TryFallbackFlight(Player* player, uint32 characterGuid, Record& record)
{
if (record.FallbackFlightAttempted || player->IsFlying())
return false;
ClientlessTravelNode goal;
if (!GetTravelGoal(record, goal) || goal.MapId != player->GetMapId())
return false;
Position from, target;
from.Relocate(player);
target.Relocate(goal.X, goal.Y, goal.Z, goal.O);
sClientlessFlightMgr->BeginPlanning();
if (!std::isfinite(sClientlessFlightMgr->Estimate(player, goal.MapId, from, target, true)))
return false;
std::string error;
if (!sClientlessFlightMgr->Start(player, target, error))
{
TC_LOG_INFO("server.adventurebot.navigation", "FLIGHT_FALLBACK_UNAVAILABLE guid=%u reason='%s'",
characterGuid, error.c_str());
return false;
}
record.FallbackFlightAttempted = true;
record.EdgePath.clear();
record.GroundExitActive = false;
record.GroundResumeActive = false;
record.RecoveryWanderActive = false;
record.FinalLocalLegActive = false;
record.FinalLocalLegPending = record.Status.ActionName == "travel_to_location";
record.Route.clear();
ClientlessTravelRouteStep source, destination;
source.NodeKey = "flight_fallback_departure";
source.MapId = player->GetMapId();
source.X = from.GetPositionX(); source.Y = from.GetPositionY(); source.Z = from.GetPositionZ();
destination.NodeKey = goal.Key;
destination.MapId = goal.MapId;
destination.X = goal.X; destination.Y = goal.Y; destination.Z = goal.Z; destination.O = goal.O;
destination.EdgeType = "free_flight";
record.Route.push_back(source);
record.Route.push_back(destination);
record.RouteStep = 1;
record.RouteComplete = true;
record.FlightActive = true;
record.Active = true;
record.Status.State = ClientlessActionExecutionState::Moving;
record.Status.Detail = "free flight fallback after ground travel exhausted";
TC_LOG_INFO("server.adventurebot.navigation", "FLIGHT_FALLBACK_STARTED guid=%u target='%s'",
characterGuid, goal.Key.c_str());
return true;
}
void ClientlessActionExecutorMgr::CheckGroundExit(Player* player, uint32 characterGuid, Record& record)
{
if (record.GroundExitCheckMs || record.EdgeNextPoint >= record.EdgePath.size())
return;
constexpr uint32 ExitIntervalMs = 6000;
record.GroundExitCheckMs = ExitIntervalMs;
ClientlessTravelNode goal;
if (!GetTravelGoal(record, goal) || goal.MapId != player->GetMapId())
return;
Position target;
target.Relocate(goal.X, goal.Y, goal.Z, goal.O);
ClientlessPathDiagnostics nav;
bool const nearby = player->GetExactDist(goal.X, goal.Y, goal.Z) <= record.PlannedAnchorRadius;
// AnalyzePath does not cancel or replace the running road spline. A rejected
// probe is not a failed edge, a blacklist entry, or a recovery attempt.
bool const usable = nearby && sClientlessTravelMgr->AnalyzePath(player, target, nav) &&
nav.Complete && nav.FinalDistance <= 1.5f && nav.PathLength <= 500.0f;
TC_LOG_INFO("server.adventurebot.navigation",
"GROUND_EXIT_CHECK guid=%u edgeDbId=%u nextPoint=%u usable=%u",
characterGuid, record.Route[record.RouteStep].EdgeDatabaseId,
uint32(record.EdgeNextPoint), usable ? 1u : 0u);
if (usable)
{
if (StartGroundConnection(player, characterGuid, target))
{
record.GroundExitActive = true;
record.Status.Detail = "validated virtual exit to destination";
return;
}
// The execution query may reject a path that passed the probe. It has
// stopped the spline, so resume the same forward road immediately.
sClientlessTravelMgr->CancelTravel(characterGuid);
StartRecordedEdgePath(player, characterGuid, record);
return;
}
// Preserve ordinary flight exits selected by the cost planner. They are
// optional too, and never require stopping at an unverified virtual point.
if (record.GroundFlightExit)
{
Position from;
from.Relocate(player);
sClientlessFlightMgr->BeginPlanning();
if (std::isfinite(sClientlessFlightMgr->Estimate(player, goal.MapId, from, target)))
{
std::string error;
if (sClientlessFlightMgr->Start(player, target, error))
{
record.GroundExitActive = true;
record.FlightActive = true;
record.Status.Detail = "virtual exit via native free flight";
}
else if (!player->IsSplineEnabled())
StartRecordedEdgePath(player, characterGuid, record);
}
}
}
bool ClientlessActionExecutorMgr::ResumeRecordedEdge(Player* player, uint32 characterGuid, Record& record)
{
record.GroundExitActive = false;
record.FlightActive = false;
sClientlessTravelMgr->CancelTravel(characterGuid);
if (player->IsFlying() || record.EdgePath.empty())
{
record.Status.State = ClientlessActionExecutionState::Failed;
record.Status.Detail = "cannot safely resume recorded road while airborne or without road";
record.Active = false;
return false;
}
// Rejoin only at the next unpassed point, never at a nearest point behind us.
std::size_t const next = std::min(record.EdgeNextPoint, record.EdgePath.size() - 1);
auto const& point = record.EdgePath[next];
Position target;
target.Relocate(point.X, point.Y, point.Z, point.O);
if (StartGroundConnection(player, characterGuid, target))
{
record.GroundResumeActive = true;
record.Status.Detail = "rejoining forward road after virtual exit failed";
return true;
}
// This is an actual failed rejoin, unlike an unavailable optional exit.
return RecoverTravel(player, characterGuid, record,
record.Route[record.RouteStep].EdgeDatabaseId, "recorded-ground-rejoin-failed");
}
void ClientlessActionExecutorMgr::UpdateGroundExit(Player* player, uint32 characterGuid, Record& record)
{
if (record.FlightActive)
{
auto const* flight = sClientlessFlightMgr->GetStatus(characterGuid);
if (flight && (flight->State == ClientlessFlightState::Mounting || flight->State == ClientlessFlightState::Flying))
return;
record.FlightActive = false;
if (!flight || flight->State != ClientlessFlightState::Arrived)
{
ResumeRecordedEdge(player, characterGuid, record);
return;
}
ClientlessTravelNode goal;
if (!GetTravelGoal(record, goal))
{
ResumeRecordedEdge(player, characterGuid, record);
return;
}
Position target;
target.Relocate(goal.X, goal.Y, goal.Z, goal.O);
if (!StartGroundConnection(player, characterGuid, target))
{
ResumeRecordedEdge(player, characterGuid, record);
return;
}
}
auto const* travel = sClientlessTravelMgr->GetStatus(characterGuid);
if (travel && travel->State == ClientlessTravelState::Moving)
return;
if (record.GroundResumeActive)
{
record.GroundResumeActive = false;
if (travel && travel->State == ClientlessTravelState::Arrived)
{
record.GroundExitCheckMs = 6000;
auto const& end = record.EdgePath.back();
if (player->GetExactDist(end.X, end.Y, end.Z) > 2.0f)
StartRecordedEdgePath(player, characterGuid, record);
}
else
RecoverTravel(player, characterGuid, record,
record.Route[record.RouteStep].EdgeDatabaseId, "recorded-ground-rejoin-failed");
return;
}
if (!travel || travel->State != ClientlessTravelState::Arrived)
{
ResumeRecordedEdge(player, characterGuid, record);
return;
}
record.GroundExitActive = false;
record.EdgePath.clear();
record.FinalLocalLegPending = false;
record.Status.State = ClientlessActionExecutionState::Completed;
record.Status.TargetName = record.Status.ActionName == "travel_to_location"
? "location:" + record.FinalLocationId : "node:" + record.PlannedDestinationNode;
record.Status.Detail = "destination reached via validated virtual exit";
record.Active = false;
TC_LOG_INFO("server.adventurebot.navigation", "GROUND_EXIT_ARRIVED guid=%u target='%s'",
characterGuid, record.Status.TargetName.c_str());
}
bool ClientlessActionExecutorMgr::StartRecoveryWander(
Player* player,
uint32 characterGuid,
@@ -1826,9 +2094,12 @@ bool ClientlessActionExecutorMgr::RecoverTravel(
}
bool const bridgeFailure = reason.rfind("local-bridge-", 0) == 0;
bool const forwardProgress = reason == "ground-continuation-replan";
if (!bridgeFailure && record.RecoveryCount >= MaxRecoveries)
if (!bridgeFailure && !forwardProgress && record.RecoveryCount >= MaxRecoveries)
{
if (TryFallbackFlight(player, characterGuid, record))
return true;
record.Status.State = ClientlessActionExecutionState::Failed;
record.Status.Detail = "travel recovery exhausted: " + reason;
record.Active = false;
@@ -1844,10 +2115,12 @@ bool ClientlessActionExecutorMgr::RecoverTravel(
return false;
}
if (!bridgeFailure && reason != "deferred-start-endpoint-replan")
if (!bridgeFailure && !forwardProgress && reason != "deferred-start-endpoint-replan")
++record.RecoveryCount;
sClientlessTravelMgr->CancelTravel(characterGuid);
record.DeferredStartActive = false; // VCORE_C819_DEFERRED_START_NODES_V2
record.GroundExitActive = false;
record.GroundResumeActive = false;
record.EdgePath.clear();
record.TaxiActive = false;
record.TaxiPathId = 0;
@@ -1954,6 +2227,9 @@ bool ClientlessActionExecutorMgr::RecoverTravel(
return StartRouteStep(player, characterGuid, record);
}
if (TryFallbackFlight(player, characterGuid, record))
return true;
if (StartRecoveryWander(player, characterGuid, record, lastError))
return true;
@@ -1976,6 +2252,12 @@ void ClientlessActionExecutorMgr::UpdateTravelToNode(
uint32 characterGuid,
Record& record)
{
if (record.GroundExitActive || record.GroundResumeActive)
{
UpdateGroundExit(player, characterGuid, record);
return;
}
if (record.FlightApproachActive)
{
auto const* approach = sClientlessTravelMgr->GetStatus(characterGuid);
@@ -2208,6 +2490,17 @@ void ClientlessActionExecutorMgr::UpdateTravelToNode(
return;
}
if (player->IsSplineEnabled() && !record.EdgeSplinePoints.empty())
{
int32 const index = player->movespline->currentPathIdx();
if (index >= 0 && std::size_t(index) < record.EdgeSplinePoints.size())
record.EdgeNextPoint = std::max(record.EdgeNextPoint,
record.EdgeSplinePoints[std::size_t(index)]);
CheckGroundExit(player, characterGuid, record);
if (record.GroundExitActive)
return;
}
// A clientless Player can be reported as "stopped" while a server-
// controlled MoveSpline is still running. Unit::UpdateSplineMovement()
// owns the authoritative server position for this movement and updates
@@ -2240,7 +2533,20 @@ void ClientlessActionExecutorMgr::UpdateTravelToNode(
}
record.EdgePath.clear();
// Only after traversing the road to its real endpoint may planning
// retire its optimistic ground-exit estimate. Keep the road usable;
// any later traversal still performs live probes. This prevents the
// planner from cycling between road endpoints on the same stale exit.
auto const& completed = record.Route[record.RouteStep];
record.FailedBridgeKeys.insert("ground_exit_edge:" +
std::to_string(completed.EdgeDatabaseId) + (completed.EdgeReverse ? "R" : "F"));
++record.RouteStep;
if (record.ReplanAtGroundEnd && !record.StopAtNode)
{
record.ReplanAtGroundEnd = false;
RecoverTravel(player, characterGuid, record, 0, "ground-continuation-replan");
return;
}
StartRouteStep(player, characterGuid, record);
return;
}
@@ -2510,6 +2816,7 @@ void ClientlessActionExecutorMgr::Update(uint32 diff)
if (record.Active)
{
record.GroundExitCheckMs = diff >= record.GroundExitCheckMs ? 0 : record.GroundExitCheckMs - diff;
UpdateActive(player, characterGuid, record);
continue;
}
@@ -59,6 +59,14 @@ private:
uint32 DeferredStartAttempts = 0;
std::vector<ClientlessTravelEdgePoint> EdgePath;
std::vector<std::size_t> EdgeSplinePoints;
std::size_t EdgeNextPoint = 0;
uint32 GroundExitCheckMs = 0;
bool ReplanAtGroundEnd = false;
bool GroundExitActive = false;
bool GroundResumeActive = false;
bool GroundFlightExit = false;
bool FallbackFlightAttempted = false;
// C8.10b: active server-side taxi edge.
bool TransportActive = false;
@@ -113,7 +121,13 @@ private:
bool RecoverFlightLeg(Player* player, uint32 characterGuid, Record& record, std::string const& reason);
bool StartPortalUse(Player* player, uint32 characterGuid, Record& record);
void UpdatePortalUse(Player* player, uint32 characterGuid, Record& record);
bool PrepareRecordedGroundEdge(Record& record);
bool StartRecordedEdgePath(Player* player, uint32 characterGuid, Record& record);
bool GetTravelGoal(Record const& record, ClientlessTravelNode& goal) const;
bool TryFallbackFlight(Player* player, uint32 characterGuid, Record& record);
void CheckGroundExit(Player* player, uint32 characterGuid, Record& record);
void UpdateGroundExit(Player* player, uint32 characterGuid, Record& record);
bool ResumeRecordedEdge(Player* player, uint32 characterGuid, Record& record);
bool RecoverTravel(Player* player, uint32 characterGuid, Record& record, uint32 failedEdgeDatabaseId, std::string const& reason);
bool StartRecoveryWander(Player* player, uint32 characterGuid, Record& record, std::string const& reason);
void UpdateTravelToNode(Player* player, uint32 characterGuid, Record& record);
@@ -0,0 +1,160 @@
#include "ClientlessCompanionMgr.h"
#include "Creature.h"
#include "DBCStores.h"
#include "Item.h"
#include "ItemTemplate.h"
#include "Log.h"
#include "MotionMaster.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "Spell.h"
#include "SpellCastRequest.h"
#include "SpellInfo.h"
#include "SpellHistory.h"
#include "SpellMgr.h"
#include "TemporarySummon.h"
namespace
{
bool CompanionSpell(uint32 id)
{
auto spell=sSpellMgr->GetSpellInfo(id);
if (!spell) return false;
for (auto const& effect:spell->Effects)
if (effect.Effect==SPELL_EFFECT_SUMMON)
if (auto properties=sSummonPropertiesStore.LookupEntry(effect.MiscValueB))
if (SummonTitle(properties->Title)==SummonTitle::Companion) return true;
return false;
}
}
bool ClientlessCompanionMgr::Prepare(Player* p,uint32 spell,uint32 leash)
{
if (!p || !CompanionSpell(spell)) return false;
if (!p->HasSpell(spell)) p->LearnSpell(spell,false);
if (leash && !p->GetItemByEntry(leash) && sObjectMgr->GetItemTemplate(leash))
if (!p->AddItem(leash,1))
TC_LOG_WARN("server.adventurebot","COMPANION_EQUIPMENT guid=%u item=%u inventory-full",p->GetGUID().GetCounter(),leash);
return p->HasSpell(spell);
}
bool ClientlessCompanionMgr::Available(Player* p,uint32 spell)
{
return p && p->IsInWorld() && p->IsAlive() && !p->IsInCombat() && !p->GetTransport() &&
!p->IsInFlight() && !p->IsFlying() && !p->IsBeingTeleported() &&
p->HasSpell(spell) && CompanionSpell(spell);
}
Creature* ClientlessCompanionMgr::Resolve(Player* p,ClientlessCompanionScene const& s)
{
auto pet=ObjectAccessor::GetCreature(*p,p->GetCritterGUID());
if (!pet || !pet->IsAlive() || pet->GetOwnerGUID()!=p->GetGUID() ||
pet->GetUInt32Value(UNIT_CREATED_BY_SPELL)!=s.SummonSpell ||
(!s.Companion.IsEmpty() && pet->GetGUID()!=s.Companion)) return nullptr;
return pet;
}
bool ClientlessCompanionMgr::Start(Player* p,ClientlessCompanionScene& s,uint32 spell,uint32 leash)
{
if (!Available(p,spell)) return false;
s={};s.SummonSpell=spell;s.LeashItem=leash;
// Never replace an unrelated companion or touch a hunter/warlock combat pet.
if (!p->GetCritterGUID().IsEmpty())
{
auto pet=Resolve(p,s);
if (!pet) return false;
s.Companion=pet->GetGUID();
}
else
{
if (p->CastSpell(p,spell,false)!=SPELL_CAST_OK) return false;
s.Summoned=true;
}
s.Active=true;
TC_LOG_INFO("server.adventurebot","COMPANION_START guid=%u spell=%u summoned=%u",p->GetGUID().GetCounter(),spell,s.Summoned?1:0);
return true;
}
bool ClientlessCompanionMgr::Update(Player* p,ClientlessCompanionScene& s,uint32 diff)
{
if (!s.Active || !Available(p,s.SummonSpell)) return false;
s.WaitMs+=diff;
auto pet=Resolve(p,s);
if (!pet) return s.Companion.IsEmpty() && s.WaitMs<10000;
if (s.Companion.IsEmpty())
{
s.Companion=pet->GetGUID();s.WaitMs=0;
// Use the normal native follower, with room inside a five-yard leash.
// A pre-existing companion retains its own movement configuration.
if (s.Summoned)
pet->GetMotionMaster()->MoveFollow(p,1.5f,DEFAULT_FOLLOW_ANGLE,false,true,true);
TC_LOG_INFO("server.adventurebot","COMPANION_CONFIRMED guid=%u pet='%s' entry=%u",p->GetGUID().GetCounter(),s.Companion.ToString().c_str(),pet->GetEntry());
}
if (s.Ready)
{
if (!s.OwnLeash || !s.LeashSpell || pet->HasAura(s.LeashSpell,p->GetGUID())) return true;
if (s.Leashed)
{
s.Leashed=false;
TC_LOG_INFO("server.adventurebot","COMPANION_LEASH_LOST guid=%u distance=%.2f",p->GetGUID().GetCounter(),p->GetExactDist(pet));
}
// Retry at a genuinely usable close point, rather than repeatedly
// casting at a pet still behind an obstacle. Keep native range/LOS.
if (s.LeashRetries>=2 || p->GetExactDist(pet)>3.5f || !p->IsWithinLOSInMap(pet)) return true;
++s.LeashRetries;
p->InterruptNonMeleeSpells(false,s.LeashSpell);
s.Ready=false;s.LeashRequested=false;s.WaitMs=0;
return true;
}
if (!s.LeashRequested)
{
s.LeashRequested=true;
if (auto item=p->GetItemByEntry(s.LeashItem))
for (auto const& effect:item->GetTemplate()->Effects)
{
if (effect.Trigger!=ITEM_SPELLTRIGGER_ON_USE || effect.SpellID<=0) continue;
auto spell=sSpellMgr->GetSpellInfo(effect.SpellID);
// Leashes are native channels that explicitly allow movement.
if (!spell || (spell->IsChanneled() && !spell->IsMoveAllowedChannel()) || !p->CanRequestSpellCast(spell)) continue;
// Only submit immediately executable requests, so cleanup cannot
// leave an owned item cast queued behind an unrelated spell.
if (p->GetSpellHistory()->HasGlobalCooldown(spell) || p->IsNonMeleeSpellCast(false))
{s.LeashRequested=false;if (s.WaitMs>=8000) s.Ready=true;return true;}
s.LeashSpell=spell->Id;
if (pet->HasAura(s.LeashSpell,p->GetGUID())) {s.Ready=true;s.Leashed=true;return true;}
WorldPackets::Spells::SpellCastRequest request;
request.SpellID=spell->Id;
request.Target.Flags=TARGET_FLAG_UNIT_MINIPET;
request.Target.Unit=pet->GetGUID();
s.WaitMs=0;
p->RequestSpellCast(std::make_unique<PendingSpellCastRequest>(std::move(request),
SpellCastRequestItemData(item->GetBagSlot(),item->GetSlot(),item->GetGUID())),spell);
s.OwnLeash=true;
TC_LOG_INFO("server.adventurebot","COMPANION_LEASH_REQUEST guid=%u item=%u spell=%u",p->GetGUID().GetCounter(),s.LeashItem,s.LeashSpell);
return true;
}
s.Ready=true;
}
else if (s.LeashSpell && pet->HasAura(s.LeashSpell,p->GetGUID()))
{
s.Ready=true;s.Leashed=true;
TC_LOG_INFO("server.adventurebot","COMPANION_LEASH_CONFIRMED guid=%u spell=%u",p->GetGUID().GetCounter(),s.LeashSpell);
}
else if (s.WaitMs>=8000)
{
s.Ready=true;
TC_LOG_INFO("server.adventurebot","COMPANION_LEASH_UNAVAILABLE guid=%u continuing-with-companion",p->GetGUID().GetCounter());
}
return true;
}
void ClientlessCompanionMgr::Reset(Player* p,ClientlessCompanionScene& s)
{
if (s.Active)
{
// Interrupt only this scene's native casts; never clear unrelated casts.
if (s.Summoned) p->InterruptNonMeleeSpells(false,s.SummonSpell);
if (s.LeashSpell && s.OwnLeash) p->InterruptNonMeleeSpells(false,s.LeashSpell);
if (auto pet=Resolve(p,s))
{
if (s.OwnLeash && s.LeashSpell) pet->RemoveAurasDueToSpell(s.LeashSpell,p->GetGUID());
if (s.Summoned && pet->IsSummon()) pet->ToTempSummon()->UnSummon();
}
TC_LOG_INFO("server.adventurebot","COMPANION_END guid=%u",p->GetGUID().GetCounter());
}
s={};
}
@@ -0,0 +1,25 @@
#ifndef VCORE_CLIENTLESS_COMPANION_MGR_H
#define VCORE_CLIENTLESS_COMPANION_MGR_H
#include "Common.h"
#include "ObjectGuid.h"
#include <string>
class Player;
class Creature;
// Generic native vanity-companion and item-use adapter. No AdventureBot dependency.
struct ClientlessCompanionScene
{
bool Active=false, Summoned=false, Ready=false, LeashRequested=false, OwnLeash=false, Leashed=false;
uint32 SummonSpell=0, LeashItem=0, LeashSpell=0, WaitMs=0, LeashRetries=0;
ObjectGuid Companion;
};
class TC_GAME_API ClientlessCompanionMgr
{
public:
static bool Prepare(Player* player, uint32 summonSpell, uint32 leashItem);
static bool Available(Player* player, uint32 summonSpell);
static bool Start(Player* player, ClientlessCompanionScene& scene, uint32 summonSpell, uint32 leashItem);
static bool Update(Player* player, ClientlessCompanionScene& scene, uint32 diff);
static void Reset(Player* player, ClientlessCompanionScene& scene);
static Creature* Resolve(Player* player, ClientlessCompanionScene const& scene);
};
#endif
@@ -108,7 +108,7 @@ bool ClientlessFlightMgr::Preferred(Player const * player, uint32 map, Position
return false;
}
float ClientlessFlightMgr::Estimate(Player const * player, uint32 map, Position const & from, Position const & to) const
float ClientlessFlightMgr::Estimate(Player const * player, uint32 map, Position const & from, Position const & to, bool recovery) const
{
auto const * entry = sMapStore.LookupEntry(map);
if (! Enabled(player) || ! entry || entry->MapType != MAP_COMMON) return Infinity();
@@ -124,9 +124,11 @@ float ClientlessFlightMgr::Estimate(Player const * player, uint32 map, Position
// and spell checks at the actual departure are mandatory.
bool const preferred = Preferred(player, map, from) || Preferred(player, map, to);
float const distance = Distance(PointOf(from), PointOf(to));
float minimum = preferred ? 60.0f : std::max(100.0f, sConfigMgr->GetFloatDefault("Clientless.Flight.MinDistance", 600));
if (! std::isfinite(distance) || distance < minimum || distance > 20000) return Infinity();
return preferred ? 40 + distance * 0.4f : 180 + distance * 1.5f;
// Recovery keeps native eligibility, but has no RP
// distance threshold or zone preference. Start still validates the flight.
float minimum = recovery ? 0.0f : preferred ? 60.0f : std::max(100.0f, sConfigMgr->GetFloatDefault("Clientless.Flight.MinDistance", 600));
if (! std::isfinite(distance) || distance < minimum || (!recovery && distance > 20000)) return Infinity();
return recovery ? distance : preferred ? 40 + distance * 0.4f : 180 + distance * 1.5f;
}
ClientlessFlightStatus const * ClientlessFlightMgr::GetStatus(uint32 guid) const
+1 -1
View File
@@ -25,7 +25,7 @@ class TC_GAME_API ClientlessFlightMgr
{
public : static ClientlessFlightMgr * instance();
// Estimates are transient alternatives in macro planning, not DB edges.
float Estimate(Player const * player, uint32 mapId, Position const & from, Position const & to) const;
float Estimate(Player const * player, uint32 mapId, Position const & from, Position const & to, bool recovery = false) const;
bool Preferred(Player const * player, uint32 mapId, Position const & position) const;
bool Enabled(Player const * player) const;
void BeginPlanning();
@@ -1,3 +1,4 @@
#include "AdventureProfessionPolicy.h"
#include "ClientlessLuaEngine.h"
#include "ClientlessPlayerMgr.h"
#include "ClientlessActionExecutorMgr.h"
@@ -383,6 +384,8 @@ bool ClientlessLuaEngine::LoadAdventureProfile(Player* player)
profile.GroundMounts = ReadMountDictionary(_state, -1);
profile.CanFreeFly = ReadBoolField(_state, -1, "can_free_fly", false);
profile.FlightMounts = ReadMountDictionary(_state, -1, "flight_mounts");
profile.CompanionSpell = uint32(std::max(0.0f, ReadFloatField(_state, -1, "companion_spell", 0)));
profile.CompanionLeashItem = uint32(std::max(0.0f, ReadFloatField(_state, -1, "companion_leash_item", 0)));
}
lua_pop(_state, 1);
@@ -402,6 +405,25 @@ bool ClientlessLuaEngine::LoadAdventureProfile(Player* player)
}
lua_pop(_state, 1);
lua_getfield(_state,rootTable,"professions");
if(lua_istable(_state,-1))
{
lua_getfield(_state,-1,"primary");
if(lua_istable(_state,-1))
for(size_t index=1;index<=lua_rawlen(_state,-1);++index)
{
lua_rawgeti(_state,-1,index);
if(lua_type(_state,-1)==LUA_TSTRING)
{
std::string name=lua_tostring(_state,-1);
if(AdventureProfessionPolicy::PrimaryName(name))profile.PrimaryProfessions.insert(name);
else TC_LOG_WARN("server.adventurebot","PROFILE_UNKNOWN_PROFESSION guid=%u name='%s'",player->GetGUID().GetCounter(),name.c_str());
}
lua_pop(_state,1);
}
lua_pop(_state,1);
}
lua_pop(_state,1);
uint32 const guid = player->GetGUID().GetCounter();
_profiles[guid] = std::move(profile);
ClientlessAdventureProfile const& stored = _profiles.at(guid);
@@ -416,6 +438,8 @@ bool ClientlessLuaEngine::LoadAdventureProfile(Player* player)
uint32(stored.Personality.size()), uint32(stored.RegionAffinity.size()),
uint32(stored.ActivityAffinity.size()));
TC_LOG_INFO("server.adventurebot","PROFILE_PROFESSIONS guid=%u primary=%u",guid,uint32(stored.PrimaryProfessions.size()));
if (stored.CanGroundMount && stored.GroundMounts.empty())
TC_LOG_WARN("server.adventurebot",
"[AdventureBot][Profile] guid=%u enables ground mounts but defines no ground_mounts", guid);
@@ -6,6 +6,7 @@
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
class ClientlessPlayerController;
@@ -23,6 +24,7 @@ struct TC_GAME_API ClientlessWeightedMount
struct TC_GAME_API ClientlessAdventureProfile
{
std::unordered_set<std::string> PrimaryProfessions;
std::string CharacterName;
std::string HomeRegion;
std::string SourcePath;
@@ -34,6 +36,7 @@ struct TC_GAME_API ClientlessAdventureProfile
bool CanFreeFly = false;
// Flight dictionaries use learned mount SPELL IDs, never display IDs.
std::vector<ClientlessWeightedMount> FlightMounts;
uint32 CompanionSpell = 0, CompanionLeashItem = 0;
// Parsed now; consumed later by the weighted intent/day planner.
std::unordered_map<std::string, float> Personality;
@@ -497,6 +497,12 @@ bool ClientlessTravelGraphMgr::FindNearestNodeToPosition(
return !nodeKey.empty();
}
ClientlessTravelNode const* ClientlessTravelGraphMgr::FindNode(std::string const& key) const
{
auto const it = _nodes.find(key);
return it == _nodes.end() ? nullptr : &it->second;
}
bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
Player const* player,
std::string const& destinationNode,
@@ -860,6 +866,7 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
struct ExitCandidate
{
std::string FromNode;
std::string ToNode;
uint32 EdgeId = 0;
bool Reverse = false;
uint32 EndIndex = 0; // in execution-direction original road points
@@ -933,13 +940,16 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
continue;
ExitCandidate candidate;
candidate.FromNode = edge.FromNode;
candidate.ToNode = edge.ToNode;
candidate.EdgeId = edge.DatabaseId;
candidate.Reverse = edge.Reverse;
candidate.EndIndex = i;
candidate.RoadCost = edge.Cost * (travelled / totalLength);
candidate.DistanceSq = dsq;
candidate.Point = point;
candidate.LocalCost = locationGoal && dsq <= locationAccessRadius*locationAccessRadius
bool const groundExitExhausted = blockedBridgeKeys &&
blockedBridgeKeys->count("ground_exit_edge:" + identity);
candidate.LocalCost = locationGoal && !groundExitExhausted && dsq <= locationAccessRadius*locationAccessRadius
? std::sqrt(dsq) : std::numeric_limits<float>::infinity();
ClientlessTravelNode exitNode;
exitNode.MapId=point.MapId;exitNode.X=point.X;exitNode.Y=point.Y;exitNode.Z=point.Z;
@@ -1603,6 +1613,7 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
route.clear();
return false;
}
route.back().GroundContinuationNode = route.back().NodeKey;
route.back().GroundEndIndex = int32(exit.EndIndex);
route.back().NodeKey = "virtual_ground_exit";
route.back().MapId = exit.Point.MapId;
@@ -1635,6 +1646,7 @@ bool ClientlessTravelGraphMgr::PlanAdaptiveRoute(
end.EdgeDatabaseId = exit.EdgeId;
end.EdgeReverse = exit.Reverse;
end.GroundEndIndex = int32(exit.EndIndex);
end.GroundContinuationNode = exit.ToNode;
end.MapId = exit.Point.MapId;
end.X = exit.Point.X;
end.Y = exit.Point.Y;
@@ -94,6 +94,8 @@ struct TC_GAME_API ClientlessTravelRouteStep
// C8.20: inclusive index in the ORIGINAL execution-direction road points.
// -1 keeps the stored edge endpoint. Applied before GroundStartIndex.
int32 GroundEndIndex = -1;
// A speculative virtual exit retains the real forward endpoint for execution.
std::string GroundContinuationNode;
uint32 MapId = 0;
float X = 0.0f;
@@ -158,6 +160,7 @@ public:
float& distance) const;
uint32 GetNodeCount() const { return uint32(_nodes.size()); }
ClientlessTravelNode const* FindNode(std::string const& key) const;
uint32 GetEdgeCount() const { return _edgeCount; }
std::vector<ClientlessTravelEdgePoint> const* GetEdgePoints(uint32 edgeDatabaseId) const;
+12 -2
View File
@@ -201,7 +201,10 @@ bool ClientlessTravelMgr::LaunchPath(Player* player, TravelRecord& record)
return false;
player->AttackStop();
player->CastStop();
// Preserve native move-allowed channels (for example companion leashes).
// Walking still interrupts casts that actually prevent movement.
if (player->IsMovementPreventedByCasting())
player->CastStop();
player->StopMoving();
player->GetMotionMaster()->Clear();
record.ActiveSpline.clear();
@@ -359,12 +362,19 @@ bool ClientlessTravelMgr::LaunchPath(Player* player, TravelRecord& record)
float const pz = points[i].z;
float const segment = Distance3D(lastX, lastY, lastZ, px, py, pz);
if (segment <= 0.05f)
// Native MoveSpline validation requires at least 0.1 yards between
// internal points. Keep a small margin for coordinate roundoff.
constexpr float MinSplineSegment = 0.11f;
if (segment < MinSplineSegment)
continue;
if (accumulated + segment > MAX_LOCAL_SPLINE_LENGTH)
{
float const remaining = MAX_LOCAL_SPLINE_LENGTH - accumulated;
// Do not append an invalid tiny remainder at the 45-yard split.
// End at the last real point and continue with the next segment.
if (remaining < MinSplineSegment)
break;
float const t = std::max(0.0f, std::min(1.0f, remaining / segment));
Position partial;
@@ -17,10 +17,14 @@ return {
micro_action_affinity = { drink = 1.5, rest = 1.2, speak = 1.0 },
professions = { primary = { "mining", "blacksmithing" } },
movement = {
can_ground_mount = true,
can_free_fly = true,
flight_mounts = { [32235] = 100 }, -- Golden Gryphon, learned mount spell
companion_spell = 10673, -- Bombay Cat, native noncombat companion
companion_leash_item = 37460, -- Rope Pet Leash, native item-use spell
ground_mount_min_distance = 45,
ground_mount_speed_rate = 1.60,
-- [mount display ID] = random weight
@@ -52,5 +56,7 @@ return {
travelling = 0.35,
city_visit = 0.35,
social = 0.60,
leisure = 0.60,
economy = 0.45,
},
}
@@ -6,6 +6,8 @@ return {
home_region = "",
},
professions = { primary = {} }, -- No primary job is implicitly authorized.
movement = {
can_ground_mount = true,
ground_mount_min_distance = 60,
@@ -22,6 +22,9 @@ int main(){
assert(locations==intents&&intents>=6&&intents<=24);
unsigned run=0;std::string last;
for(unsigned h=0;h<24;++h){assert(!day[h].Intent.empty()&&!day[h].Location.empty());if(day[h].Location==last)++run;else{run=1;last=day[h].Location;}assert(run<=4);}
// Intent choice survives a missing location; the location stage cannot veto it.
auto economic=Generate(random,0,[]{return "economy";},[](auto const&){return std::string();});
for(auto const& slot:economic)assert(slot.Intent=="economy"&&slot.Location.empty());
intents=locations=0;
auto roaming=Generate(random,1,[&](){++intents;return "adventure";},[&](auto const&){return "zone"+std::to_string(++locations);});
assert(locations==24);for(unsigned h=1;h<24;++h)assert(roaming[h].Location!=roaming[h-1].Location);
+104
View File
@@ -0,0 +1,104 @@
"""Execute economic role selection against production discovery and cue policy."""
from pathlib import Path
import subprocess,tempfile
root=Path(__file__).resolve().parents[2]
source=(root/'src/server/game/World/AdventureSocialActions.cpp').read_text()
def function(sig):
a=source.index(sig);b=source.index('{',a);depth=1;e=b+1
while depth:depth+=(source[e]=='{')-(source[e]=='}');e+=1
return source[a:e]
code=r'''
#include "AdventureEconomyPolicy.h"
#include <algorithm>
#include "AdventureProfessionPolicy.h"
#include <cassert>
#include <string>
#include <vector>
#include <cmath>
constexpr int UNIT_NPC_FLAGS=0,UNIT_NPC_FLAG_AUCTIONEER=2097152;
enum class HighGuid{Unit};
struct Guid{int value=1;Guid(int v=1):value(v){}Guid(HighGuid,unsigned,unsigned low):value(low){}unsigned GetCounter(){return value;}bool IsEmpty(){return value==0;}};
using ObjectGuid=Guid;
struct WorldObject{
bool inWorld=true,phase=true;unsigned map=0;float x=0,z=0;
bool IsInWorld()const{return inWorld;}unsigned GetMapId()const{return map;}float GetPositionX()const{return x;}float GetPositionY()const{return 0;}float GetPositionZ()const{return z;}float GetCollisionHeight()const{return 2;}
};
struct Unit:WorldObject{
bool vendor=false,banker=false,auction=false,inn=false,alive=true,combat=false,flying=false,transit=false,critter=false,totem=false,friendly=true,spline=false;Guid owner{0},guid;
bool IsVendor(){return vendor;}bool IsBanker(){return banker;}bool HasFlag(int,int){return auction;}bool IsInnkeeper(){return inn;}
bool IsAlive(){return alive;}bool IsInCombat(){return combat;}bool IsInFlight(){return transit;}bool IsFlying(){return flying;}bool GetTransport(){return transit;}
bool IsCritter(){return critter;}bool IsTotem(){return totem;}Guid GetCharmerOrOwnerGUID(){return owner;}Guid GetGUID(){return guid;}bool IsSplineEnabled(){return spline;}
};
struct Creature:Unit{};
namespace VMAP{enum class ModelIgnoreFlags{Nothing};}
constexpr int LINEOFSIGHT_ALL_CHECKS=0;
struct NativeMap{bool clear=true;int queries=0;bool isInLineOfSight(int,float,float,float fromZ,float,float,float toZ,int,VMAP::ModelIgnoreFlags){++queries;assert(fromZ==2&&toZ==2);return clear;}}nativeMap;
struct Player:Unit{
NativeMap* GetMap(){return &nativeMap;}int GetPhaseShift(){return 0;}
std::vector<Creature*> creatures;std::vector<Player*> players;int playerQueries=0;bool los=true;
bool IsInPhase(WorldObject const* u){return u->phase;}bool IsFriendlyTo(Unit*u){return u->friendly;}bool IsWithinLOSInMap(WorldObject*){return los;}
void GetCreatureListWithEntryInGrid(std::vector<Creature*>&out,int,float){out=creatures;}
void GetPlayerListInGrid(std::vector<Player*>&out,float){out=players;++playerQueries;}
};
struct Anchor{float GetExactDist(WorldObject const*u)const{return std::hypot(u->x,u->z);}float GetPositionZ()const{return 0;}};
struct AdventureSocialContext{Anchor Anchor;unsigned MapId=0;std::string ServiceKind,Intent="economy";};
enum class ClientlessEntityType{Creature,Player};
constexpr unsigned CLIENTLESS_CAP_VENDOR=1,CLIENTLESS_CAP_BANKER=8,CLIENTLESS_CAP_AUCTIONEER=32;
struct Entity{ClientlessEntityType Type=ClientlessEntityType::Creature;bool Alive=true;unsigned Capabilities=0,Entry=0,Guid=0;};
struct Snapshot{float Radius=0;unsigned ScanAgeMs=1000;std::vector<Entity> Entities;};
Player* observer=nullptr;
struct Perception{Snapshot snapshot;Snapshot* GetSnapshot(unsigned){return &snapshot;}
void ScanNow(Player*p,float radius){observer=p;snapshot={};snapshot.Radius=radius;
for(auto u:p->creatures)if(std::hypot(u->x,u->z)<=radius) snapshot.Entities.push_back({ClientlessEntityType::Creature,u->alive,unsigned(u->vendor?1:0)|unsigned(u->banker?8:0)|unsigned(u->auction?32:0),0,unsigned(u->guid.value)});
}}perception;auto sClientlessPerceptionMgr=&perception;
namespace ObjectAccessor{Creature* GetCreature(Player&p,ObjectGuid guid){for(auto u:p.creatures)if(u->guid.value==guid.value)return u;return nullptr;}}
struct Managed{bool IsManaged(unsigned){return true;}}managed;auto sClientlessPlayerMgr=&managed;
'''.replace('Anchor Anchor;','struct Anchor Anchor;')
for sig in ('bool InBounds(', 'bool Friendly(', 'bool ServiceMatches(', 'bool TargetVisible(', 'std::vector < Unit * > People('):code+=function(sig)+'\n'
code+=r'''
int main(){
using namespace AdventureEconomyPolicy;using namespace AdventureSocialFlow;
Player p,bot;Creature vendor,banker,auction,inn,guard;
vendor.vendor=true;banker.banker=true;auction.auction=true;inn.inn=true;
p.creatures={&vendor,&banker,&auction,&inn,&guard};int nextGuid=10;for(auto u:p.creatures)u->guid.value=++nextGuid;p.players={&bot};AdventureSocialContext c;
for(auto kind:{"vendor_visit","goods","trade","vendor_compare"}){
auto people=People(&p,kind,c);assert(people.size()==1&&people[0]==&vendor);
}
for(auto kind:{"bank_visit","banking"})assert(People(&p,kind,c)==std::vector<Unit*>{&banker});
for(auto kind:{"auction_visit","auctions"})assert(People(&p,kind,c)==std::vector<Unit*>{&auction});
for(auto kind:{"service_wait","service_finish"})assert(People(&p,kind,c).size()==3);
c.ServiceKind=LocationRequirement("economy_bank");
assert(People(&p,"goods",c).empty());assert(People(&p,"service_wait",c)==std::vector<Unit*>{&banker});
c.ServiceKind=LocationRequirement("economy_auction");
assert(People(&p,"banking",c).empty());assert(People(&p,"service_wait",c)==std::vector<Unit*>{&auction});
c.ServiceKind="";
vendor.x=75;assert(People(&p,"goods",c)==std::vector<Unit*>{&vendor});vendor.x=0;
assert(p.playerQueries==0); // Clientless bots are never mistaken for service NPCs.
vendor.friendly=false;assert(People(&p,"goods",c).empty());vendor.friendly=true;
vendor.alive=false;assert(People(&p,"goods",c).empty());vendor.alive=true;
vendor.combat=true;assert(People(&p,"goods",c).empty());vendor.combat=false;
vendor.owner.value=5;assert(People(&p,"goods",c).empty());vendor.owner.value=0;
vendor.phase=false;assert(People(&p,"goods",c).empty());vendor.phase=true;
vendor.x=201;assert(People(&p,"goods",c).empty());vendor.x=0;
vendor.z=26;assert(People(&p,"goods",c).empty());vendor.z=0;
p.los=false;assert(People(&p,"goods",c)==std::vector<Unit*>{&vendor});p.los=true; // Discovery precedes the MMAP approach and final LOS check.
p.los=false;nativeMap.clear=true;
assert(TargetVisible(&p,&banker,"banking"));assert(nativeMap.queries==1);
assert(!TargetVisible(&p,&banker,"npc")); // Ordinary social visibility is preserved.
nativeMap.clear=false;assert(!TargetVisible(&p,&banker,"banking")); // A real wall still blocks the scene.
nativeMap.clear=true;p.los=true;
assert(People(&p,"order",c)==std::vector<Unit*>{&inn}); // Preserve ordinary innkeeper interactions.
for(auto kind:{"vendor_visit","goods","trade","vendor_compare","bank_visit","banking","auction_visit","auctions"}){
assert(Economic(kind));auto steps=Steps(kind,20000);
assert(steps.front().Action==Cue::Greet&&steps.back().Action==Cue::Farewell);
for(auto step:steps)assert(step.Action!=Cue::Fire&&step.Action!=Cue::Dance&&step.Action!=Cue::Seat);
}
assert(!Economic("npc")&&Steps("unknown",10000).empty());
assert(RequiredService("supplies")==Service::None&&Economic("supplies"));
}
'''
with tempfile.TemporaryDirectory(prefix='vcore-economy-') as folder:
cpp=Path(folder)/'test.cpp';binary=Path(folder)/'test';cpp.write_text(code)
subprocess.run(['c++','-std=c++17','-I',str(root/'src/server/game/World'),str(cpp),'-o',str(binary)],check=True)
subprocess.run([str(binary)],check=True)
print('Economy: native service roles, friendly/phase/LOS/bounds gates, no bot service targets and coherent performative cues passed.')
@@ -0,0 +1,35 @@
"""Execute the additive economy migration, including role-gated authored seeds."""
from pathlib import Path
import re,sqlite3
root=Path(__file__).resolve().parents[2]
db=sqlite3.connect(':memory:')
base=(root/'sql/custom/world/2026_10_26_03_adventure_behavior.sql').read_text()
for table in ('vcore_adventure_location_intent','vcore_adventure_action','vcore_adventure_action_intent','vcore_adventure_dialog'):
text=re.search(r'CREATE TABLE IF NOT EXISTS '+table+r' \((.*?)\) ENGINE=',base,re.S).group(1)
db.execute('CREATE TABLE '+table+' ('+text+')')
db.executescript('''
CREATE TABLE creature(guid INTEGER,id INTEGER,map INTEGER,position_x REAL,position_y REAL,position_z REAL,orientation REAL);
CREATE TABLE creature_template(entry INTEGER PRIMARY KEY,npcflag INTEGER);
INSERT INTO creature_template VALUES(1285,128),(2456,131072),(8719,2097152);
INSERT INTO creature VALUES(1,1285,0,-8831.75,596.356,95.7618,0.942478),(2,2456,0,-8908.35,639.24,99.6516,5.25344),(3,8719,0,-8813.8,661.005,99.616,3.59538);
CREATE TABLE vcore_clientless_world_point(point_key TEXT PRIMARY KEY,category TEXT,map INTEGER,position_x REAL,position_y REAL,position_z REAL,orientation REAL,enabled INTEGER,comment TEXT);
CREATE TABLE vcore_adventure_location(point_key TEXT PRIMARY KEY,display_name TEXT,source_type TEXT,region_key TEXT,zone_id INTEGER,faction INTEGER);
''')
sql=(root/'sql/custom/world/2026_10_27_03_adventure_economy.sql').read_text().replace('INSERT IGNORE','INSERT OR IGNORE')
db.executescript(sql)
assert db.execute("SELECT COUNT(*) FROM vcore_adventure_action_intent WHERE intent='economy'").fetchone()==(11,)
assert not db.execute("SELECT i.action_key FROM vcore_adventure_action_intent i LEFT JOIN vcore_adventure_action a USING(action_key) WHERE a.action_key IS NULL").fetchall()
assert db.execute('SELECT COUNT(*) FROM vcore_adventure_location WHERE faction=1 AND zone_id=1519').fetchone()==(3,)
assert db.execute("SELECT requirement FROM vcore_adventure_action WHERE action_key='economy_compare'").fetchone()==('vendor_compare',)
assert db.execute("SELECT COUNT(*) FROM vcore_adventure_dialog WHERE intent='economy'").fetchone()==(6,)
db.execute("UPDATE vcore_adventure_action_intent SET weight=7.25 WHERE action_key='economy_goods' AND intent='economy'")
db.execute("UPDATE vcore_clientless_world_point SET position_x=-8825 WHERE point_key='stormwind_economy_market'")
db.executescript(sql)
assert db.execute("SELECT weight FROM vcore_adventure_action_intent WHERE action_key='economy_goods' AND intent='economy'").fetchone()==(7.25,)
assert db.execute("SELECT position_x FROM vcore_clientless_world_point WHERE point_key='stormwind_economy_market'").fetchone()==(-8825,)
# Never seed a service location for an NPC that does not actually provide it.
db.execute('DELETE FROM vcore_adventure_location');db.execute('DELETE FROM vcore_clientless_world_point')
db.execute('UPDATE creature_template SET npcflag=0')
db.executescript(sql)
assert db.execute('SELECT COUNT(*) FROM vcore_clientless_world_point').fetchone()==(0,)
print('Economy catalog: eleven actions, three faction-bound service anchors, native flag gates and idempotent preservation passed.')
@@ -0,0 +1,64 @@
"""Exercise production discovery/availability for non-interacting exploration scenes."""
from pathlib import Path
import subprocess,tempfile
root=Path(__file__).resolve().parents[2]
source=(root/'src/server/game/World/AdventureSocialActions.cpp').read_text()
def function(sig):
a=source.index(sig);b=source.index('{',a);depth=1;e=b+1
while depth:depth+=(source[e]=='{')-(source[e]=='}');e+=1
return source[a:e]
code=r'''
#include "AdventureExplorationPolicy.h"
#include "AdventureProfessionPolicy.h"
#include <cassert>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
using uint32=unsigned;
constexpr int GAMEOBJECT_TYPE_GENERIC=5,GAMEOBJECT_TYPE_GOOBER=10,GAMEOBJECT_TYPE_TEXT=9,GAMEOBJECT_TYPE_CHAIR=7;
struct Guid{std::string ToString(){return "prop";}};
struct WorldObject{
bool world=true,phase=true;uint32 map=0;float x=0,z=0;
bool IsInWorld()const{return world;}uint32 GetMapId()const{return map;}float GetPositionZ()const{return z;}
};
struct GameObject:WorldObject{bool spawned=true,visible=true,los=true;int type=5;bool isSpawned(){return spawned;}int GetGoType(){return type;}Guid GetGUID(){return {};}};
struct Position{float GetExactDist(WorldObject const*p)const{return std::hypot(p->x,p->z);}float GetPositionZ()const{return 0;}};
struct Player{
std::vector<GameObject*> objects;bool outdoors=true;
void GetGameObjectListWithEntryInGrid(std::vector<GameObject*>&out,int,float radius){for(auto p:objects)if(std::hypot(p->x,p->z)<=radius)out.push_back(p);}
bool IsInPhase(WorldObject const*p){return p->phase;}bool CanSeeOrDetect(GameObject*p){return p->visible;}bool IsWithinLOSInMap(GameObject*p){return p->los;}bool IsOutdoors(){return outdoors;}bool IsInWater(){return false;}bool IsFalling(){return false;}
};
struct AdventureSocialContext{Position Anchor;uint32 MapId=0;std::string Intent="adventure",LocationCategory="ruins";bool Festival=false,AllowCreateFire=true;std::vector<std::string> TargetHistory;AdventureProfessionPolicy::Professions PrimaryProfessions;};
struct AdventureMicroAction{std::string Requirement;};
std::vector<GameObject*> Objects(Player*,int,AdventureSocialContext&){return {};}
std::vector<GameObject*> People(Player*,std::string const&,AdventureSocialContext&){return {};}
GameObject* Fire(Player*,AdventureSocialContext&){return nullptr;}bool CanMakeFire(Player*){return false;}
struct AdventureProfessionTargets{static std::vector<GameObject*> Find(Player*,std::string const&,AdventureSocialContext&){return {};}};
class AdventureSocialActions{public:static bool Available(Player*,AdventureMicroAction const&,AdventureSocialContext&);};
'''
for sig in ('bool InBounds(', 'std::vector<GameObject*> InspectionObjects(', 'bool AdventureSocialActions::Available('):code+=function(sig)+'\n'
code+=r'''
int main(){
Player p;AdventureSocialContext c;GameObject prop;p.objects={&prop};
for(int type:{5,9,10}){prop.type=type;assert(AdventureSocialActions::Available(&p,{"inspect"},c));}
for(int type:{0,1,3,6,7,11,15,21}){prop.type=type;assert(!AdventureSocialActions::Available(&p,{"inspect"},c));}
prop.type=5;prop.spawned=false;assert(InspectionObjects(&p,c).empty());prop.spawned=true;
prop.visible=false;assert(InspectionObjects(&p,c).empty());prop.visible=true;
prop.los=false;assert(InspectionObjects(&p,c).empty());prop.los=true;
prop.phase=false;assert(InspectionObjects(&p,c).empty());prop.phase=true;
prop.map=1;assert(InspectionObjects(&p,c).empty());prop.map=0;
prop.x=61;assert(InspectionObjects(&p,c).empty());prop.x=50;assert(!InspectionObjects(&p,c).empty());prop.x=0;
c.Intent="leisure";assert(!AdventureSocialActions::Available(&p,{"inspect"},c)&&!AdventureSocialActions::Available(&p,{"scout"},c));c.Intent="adventure";
assert(AdventureSocialActions::Available(&p,{"scout"},c));p.outdoors=false;
assert(!AdventureSocialActions::Available(&p,{"vista"},c)&&AdventureSocialActions::Available(&p,{"equipment"},c));p.outdoors=true;
for(auto category:{"ruins","dungeon","mine"}){c.LocationCategory=category;assert(AdventureSocialActions::Available(&p,{"entrance"},c));}
c.LocationCategory="tavern";assert(!AdventureSocialActions::Available(&p,{"entrance"},c));
// No gameobject use, loot, spellcast or inventory API is available in this adapter.
}
'''
with tempfile.TemporaryDirectory(prefix='vcore-exploration-') as folder:
cpp=Path(folder)/'test.cpp';binary=Path(folder)/'test';cpp.write_text(code)
subprocess.run(['c++','-std=c++17','-I',str(root/'src/server/game/World'),str(cpp),'-o',str(binary)],check=True)
subprocess.run([str(binary)],check=True)
print('Exploration: visible native props only, no chests/traps/furniture, phase/map/LOS/range gates and contextual preparation passed.')
@@ -0,0 +1,34 @@
"""Execute exploration upgrades and location-owned intent mappings twice."""
from pathlib import Path
import re,sqlite3
root=Path(__file__).resolve().parents[2]
db=sqlite3.connect(':memory:')
base=(root/'sql/custom/world/2026_10_26_03_adventure_behavior.sql').read_text()
for table in ('vcore_adventure_location_intent','vcore_adventure_action','vcore_adventure_action_intent','vcore_adventure_dialog'):
text=re.search(r'CREATE TABLE IF NOT EXISTS '+table+r' \((.*?)\) ENGINE=',base,re.S).group(1)
db.execute('CREATE TABLE '+table+' ('+text+')')
for name in ('2026_10_26_03_adventure_behavior.sql','2026_10_26_04_adventure_social.sql','2026_10_27_00_adventure_rest.sql'):
for statement in (root/'sql/custom/world'/name).read_text().split(';'):
match=re.search(r'INSERT IGNORE INTO (vcore_adventure_action|vcore_adventure_action_intent)\s+VALUES\s+(.*)',statement,re.S)
if match:db.executescript('INSERT OR IGNORE INTO '+match[1]+' VALUES '+match[2]+';')
db.executescript('''CREATE TABLE vcore_clientless_world_point(category TEXT);INSERT INTO vcore_clientless_world_point VALUES('tavern'),('settlement'),('zone'),('landmark'),('service'),('bank'),('travel_node');
INSERT INTO vcore_adventure_location_intent VALUES('bank','economy',0),('landmark','economy',2.75);
''')
sql=(root/'sql/custom/world/2026_10_27_04_adventure_exploration.sql').read_text().replace('INSERT IGNORE','INSERT OR IGNORE')
db.executescript(sql)
assert db.execute("SELECT weight FROM vcore_adventure_action_intent WHERE action_key='explore' AND intent='adventure'").fetchone()==(0,)
assert db.execute("SELECT COUNT(*) FROM vcore_adventure_action WHERE action_key LIKE 'adventure_%'").fetchone()==(10,)
assert db.execute("SELECT kind,requirement FROM vcore_adventure_action WHERE action_key='adventure_explore'").fetchone()==('outing','explore')
assert not db.execute('SELECT i.action_key FROM vcore_adventure_action_intent i LEFT JOIN vcore_adventure_action a USING(action_key) WHERE a.action_key IS NULL').fetchall()
assert db.execute("SELECT weight FROM vcore_adventure_location_intent WHERE category='tavern' AND intent='economy'").fetchone()==(4,)
assert db.execute("SELECT weight FROM vcore_adventure_location_intent WHERE category='tavern' AND intent='leisure'").fetchone()==(1,)
assert not db.execute("SELECT * FROM vcore_adventure_location_intent WHERE category='travel_node'").fetchall()
# Explicit category weights and custom action weights survive repeat upgrades.
db.execute("UPDATE vcore_adventure_action_intent SET weight=7.25 WHERE action_key='explore' AND intent='adventure'")
db.execute("UPDATE vcore_adventure_action_intent SET weight=9.5 WHERE action_key='adventure_inspect' AND intent='adventure'")
db.executescript(sql)
assert db.execute("SELECT weight FROM vcore_adventure_action_intent WHERE action_key='explore' AND intent='adventure'").fetchone()==(7.25,)
assert db.execute("SELECT weight FROM vcore_adventure_action_intent WHERE action_key='adventure_inspect' AND intent='adventure'").fetchone()==(9.5,)
assert db.execute("SELECT weight FROM vcore_adventure_location_intent WHERE category='bank' AND intent='economy'").fetchone()==(0,)
assert db.execute("SELECT weight FROM vcore_adventure_location_intent WHERE category='landmark' AND intent='economy'").fetchone()==(2.75,)
print('Exploration catalog: ten actions, shared scenes, default wander replacement, explicit category eligibility and preserved edits passed.')
@@ -20,10 +20,10 @@ struct Object{} object;
struct Map{Object* GetGameObject(int){return &object;}} map;
struct Player {
bool combat=false;uint32 area=87,zone=12,mapId=0;
Guid GetGUID(){return {};}bool IsInWorld(){return true;}bool IsInCombat(){return combat;}
Guid GetGUID(){return {};}bool IsFlying(){return false;}bool IsInWorld(){return true;}bool IsInCombat(){return combat;}
uint32 GetMapId(){return mapId;}uint32 GetAreaId(){return area;}uint32 GetZoneId(){return zone;}
float GetPositionX(){return 0;}float GetPositionY(){return 0;}float GetPositionZ(){return 0;}
float GetExactDist(float,float,float){return 0;}float GetExactDist(Object*){return 0;}
float GetExactDist(Position const&){return 0;}float GetExactDist(float,float,float){return 0;}float GetExactDist(Object*){return 0;}
void SendPlaySpellVisualKit(int,int,int){}void SetUInt32Value(int,uint32){}
bool IsWithinLOSInMap(Object*){return true;}void SetFacingToObject(Object*){}
Map* GetMap(){return &map;}
@@ -54,10 +54,12 @@ public:
std::string Intent="social",Location="tavern",WantedIntent="social",WantedLocation="tavern",Detail,Micro;
time_t Deadline=1000;uint32 Date=20261026,Timer=0,MicroTimer=0,GestureTimer=0,RetryTimer=0;int Hour=5;
struct{bool Active=false;}Social;
struct{bool Active=false;}Recovery;
struct{bool Active=false,Flying=false,Progressive=false;std::vector<Position> Route;size_t Step=0;}Leisure;
AdventureBehaviorPolicy::Day Plan;Position Anchor;uint32 AnchorMap=0;int WorkObject=0;uint32 WorkEmote=0;bool WorkPending=false;
};
std::unordered_map<uint32,Record> _records{{26,{}}};
int begun=0,fallbacks=0,micros=0,resets=0,switches=0,queues=0;
int begun=0,fallbacks=0,micros=0,resets=0,switches=0,queues=0,leisureUpdates=0;
void Update(Player*,uint32);
void Generate(Player*,Record&,uint32){}
void SetWanted(Player*,Record& r,std::string const&i,std::string const&l,time_t d){++switches;r.WantedIntent=i;r.WantedLocation=l;r.Deadline=d;r.Dirty=true;r.State=Phase::Idle;}
@@ -65,7 +67,9 @@ public:
void Fallback(Player*,Record&){++fallbacks;}
void ResetMicro(Player*,Record&r){++resets;r.Micro.clear();}
void Micro(Player*,Record&){++micros;}
bool UpdateLeisure(Player*,Record&,uint32){++leisureUpdates;return true;}
bool UpdateSocial(Player*,Record&,uint32){return true;}
bool UpdateRecovery(Player*,Record&,uint32){return true;}
void Queue(Player*,Record&,std::string const&,std::string const&,std::string const&,bool=false){++queues;}
};
'''
@@ -94,6 +98,9 @@ int main(){
// Deadline discards the old intent rather than extending the visit.
now=1001;r.State=AdventureBehaviorMgr::Phase::OnSite;
mgr.Update(&player,1000);assert(mgr.switches==2&&r.WantedIntent.empty()&&mgr.begun==1);
// Leashed companion distance is serviced at world cadence, not the 1 s planning clock.
r.Timer=1000;r.Leisure.Active=true;mgr.Update(&player,50);
assert(mgr.leisureUpdates==1&&r.Timer==950);
}
'''
with tempfile.TemporaryDirectory(prefix='vcore-adventure-state-') as folder:
+201
View File
@@ -0,0 +1,201 @@
"""Execute leisure scene progression, native flight handover and pet-walk gating."""
from pathlib import Path
import subprocess,tempfile
root=Path(__file__).resolve().parents[2]
source=(root/'src/server/game/World/AdventureBehaviorMgr.cpp').read_text()
def function(signature):
a=source.index(signature);b=source.index('{',a);depth=1;e=b+1
while depth:depth+=(source[e]=='{')-(source[e]=='}');e+=1
return source[a:e]
code=r'''
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <ctime>
#include <random>
#include <string>
#include <vector>
#include <unordered_set>
using uint32=uint32_t;
#define TC_LOG_INFO(...) ((void)0)
constexpr float INVALID_HEIGHT=-100000;
constexpr int BASE_ATTACK=0,ITEM_SUBCLASS_WEAPON_FISHING_POLE=20,UNIT_NPC_EMOTESTATE=0,EMOTE_STATE_FISHING=379;
struct Player;
struct Position{
float m_positionX=0,m_positionY=0,m_positionZ=0,o=0;
void Relocate(float x,float y,float z){m_positionX=x;m_positionY=y;m_positionZ=z;}
void Relocate(Player*);float GetPositionX()const{return m_positionX;}float GetPositionY()const{return m_positionY;}float GetPositionZ()const{return m_positionZ;}
float GetExactDist(Position const&p)const{return std::hypot(std::hypot(m_positionX-p.m_positionX,m_positionY-p.m_positionY),m_positionZ-p.m_positionZ);}
};
struct PositionFullTerrainStatus{bool outdoors=true;};
struct Terrain{
bool outdoors=true;uint32 zone=1519;
void GetFullTerrainStatusForPosition(int,uint32,float,float,float,PositionFullTerrainStatus& s){s.outdoors=outdoors;}
void GetZoneAndAreaId(int,uint32,uint32& z,uint32& a,Position const&){z=zone;a=1;}
}terrain;
struct Map{float water=-1000,ground=0;Terrain* GetTerrain(){return &terrain;}float GetHeight(int,float,float,float,bool,float){return ground;}float GetWaterLevel(int,float,float){return water;}}map;
struct Guid{uint32 GetCounter(){return 26;}};
struct Item{struct Template{uint32 GetSubClass(){return 20;}}t;Template* GetTemplate(){return &t;}}rod;
struct Creature:Position{}cat;
struct Player:Position{
bool alive=true,combat=false,flying=false,outdoors=true,water=false,falling=false,hasRod=false;uint32 emote=0;int stops=0;
uint32 GetMapId(){return 0;}bool IsAlive(){return alive;}bool IsInCombat(){return combat;}bool IsFlying(){return flying;}bool IsInFlight(){return false;}bool GetTransport(){return false;}
bool IsOutdoors(){return outdoors;}bool IsInWater(){return water;}bool IsFalling(){return falling;}Guid GetGUID(){return {};}
int GetPhaseShift(){return 0;}Map* GetMap(){return &map;}Item* GetWeaponForAttack(int,bool){return hasRod?&rod:nullptr;}
void SetFacingTo(float f){o=f;}float GetOrientation(){return o;}void SetUInt32Value(int,uint32 v){emote=v;}uint32 GetUInt32Value(int){return emote;}
bool IsWithinLOS(float,float,float){return true;}
void StopMoving(){++stops;}float GetExactDist(Position const& p){return Position::GetExactDist(p);}float GetExactDist(Creature* p){return Position::GetExactDist(*p);}
};
void Position::Relocate(Player*p){*this=*p;}
bool Unsafe(Player* p){return p->combat||p->flying;}
constexpr int PATHFIND_NORMAL=1;
struct PathGenerator{
struct Point{float x,y,z;};std::vector<Point> path;
static inline bool reachable=true,broken=false;static inline int type=1;
PathGenerator(Player*){}void SetPathLengthLimit(float){}
bool CalculatePath(Position const&a,Position const&b){
path.clear();int n=int(a.GetExactDist(b)/4)+1;
for(int i=0;i<=n;++i){float t=float(i)/n;path.push_back({a.m_positionX+(b.m_positionX-a.m_positionX)*t,a.m_positionY+(b.m_positionY-a.m_positionY)*t+20*std::sin(3.14159265f*t),a.m_positionZ+(b.m_positionZ-a.m_positionZ)*t});}
if(broken && path.size()>5) path[5].z+=10;
return reachable;
}
int GetPathType(){return type;}auto const& GetPath(){return path;}
};
struct ClientlessCompanionScene{uint32 LeashSpell=0;bool Active=false,Ready=false,Leashed=false;};
struct ClientlessCompanionMgr{
static inline bool available=true,ready=true,valid=true;static inline int starts=0,resets=0;
static bool Available(Player*,uint32){return available;}
static bool Start(Player*,ClientlessCompanionScene& s,uint32,uint32){s.Active=true;s.Ready=false;++starts;return available;}
static bool Update(Player*,ClientlessCompanionScene& s,uint32){s.Ready=ready;return valid;}
static Creature* Resolve(Player*,ClientlessCompanionScene const&){return valid?&cat:nullptr;}
static void Reset(Player*,ClientlessCompanionScene& s){if(s.Active)++resets;s={};}
};
struct Profile{bool CanGroundMount=true;float GroundMountMinDistance=45;std::vector<int> GroundMounts{1};uint32 CompanionSpell=10673,CompanionLeashItem=37460;}profile;
struct Lua{int mounts=0,dismounts=0;Profile* GetAdventureProfile(uint32){return &profile;}bool PrepareGroundMount(Player*,float,char const*){++mounts;return true;}void DismountGroundMount(Player*,char const*){++dismounts;}}lua;auto sClientlessLuaEngine=&lua;
struct AdventureMicroAction{std::string Requirement;};
struct Location{struct{std::string Category="leisure";float O=0;}Point;bool IsArea=false;uint32 AreaId=0,ZoneId=1519;}location;
struct Locations{Location* Find(std::string const&){return &location;}}locations;auto sAdventureLocationMgr=&locations;
enum class ClientlessTravelState{Moving,Arrived,PathRejected};
struct ClientlessPathDiagnostics{bool Complete=true,Suspicious=false;float FinalDistance=0,PathLength=1;};
struct Travel{bool AnalyzePath(Player*,Position const&,ClientlessPathDiagnostics&){return true;}struct{ClientlessTravelState State=ClientlessTravelState::Moving;}status;int cancels=0;void CancelTravel(uint32){++cancels;}auto GetStatus(uint32){return &status;}}travel;auto sClientlessTravelMgr=&travel;
enum class ClientlessFlightState{Mounting,Flying,Arrived,Failed};
struct Flight{struct{ClientlessFlightState State=ClientlessFlightState::Mounting;}status;int starts=0;bool enabled=true;Position target;bool Enabled(Player*){return enabled;}bool Start(Player*,Position const&t,std::string&){++starts;target=t;status.State=ClientlessFlightState::Mounting;return true;}auto GetStatus(uint32){return &status;}}flight;auto sClientlessFlightMgr=&flight;
struct Manager{void ClearQueuedActions(uint32){}}manager;auto sClientlessPlayerMgr=&manager;
struct Executor{bool RequestStopAtNode(uint32){return false;}}executor;auto sClientlessActionExecutorMgr=&executor;
class AdventureBehaviorMgr{public:
enum class Phase{Idle,OnSite,Switching};
struct Record{
std::string Intent="leisure",Location="city",WantedIntent,WantedLocation,Detail;Position Anchor;uint32 AnchorMap=0,MicroTimer=240000;std::mt19937 Random{26};
time_t Deadline=0;bool Dirty=false;std::unordered_set<std::string> Tried;Phase State=Phase::OnSite;
struct LeisureScene{bool Active=false,Flying=false,Moving=false,StopRequested=false,Returning=false,PlannedWalk=false,Progressive=false;std::string Kind;Position Origin;std::vector<Position> Route,WalkTrace;std::size_t Step=0,OutboundSteps=0;uint32 Elapsed=0,LegElapsed=0,PauseMs=0,CompanionWait=0,CompletedLegs=0;ClientlessCompanionScene Companion;}Leisure;
};
int moves=0,resets=0;bool lastRiding=false,moveAllowed=true;Position requested;
bool LocalMove(Player*,Record&,Position const& target,float,bool riding=false){++moves;lastRiding=riding;requested=target;travel.status.State=ClientlessTravelState::Moving;return moveAllowed;}
void ResetMicro(Player* p,Record&r){++resets;ResetLeisure(p,r);}
bool ProfessionWorkAvailable(Player*,Record&){return false;}
bool FishingDirection(Player*,float&)const;
bool LeisureAvailable(Player*,Record const&,AdventureMicroAction const&)const;
bool PlanLeisureWalk(Player*,Record&);
bool StartLeisure(Player*,Record&,AdventureMicroAction const&);
bool UpdateLeisure(Player*,Record&,uint32);void ResetLeisure(Player*,Record&);
void SetWanted(Player*,Record&,std::string const&,std::string const&,time_t);
};
'''
for sig in ('bool AdventureBehaviorMgr::FishingDirection(', 'bool AdventureBehaviorMgr::PlanLeisureWalk(', 'bool AdventureBehaviorMgr::LeisureAvailable(', 'bool AdventureBehaviorMgr::StartLeisure(', 'bool AdventureBehaviorMgr::UpdateLeisure(', 'void AdventureBehaviorMgr::ResetLeisure(', 'void AdventureBehaviorMgr::SetWanted('):code+=function(sig)+'\n'
code+=r'''
int main(){
AdventureBehaviorMgr mgr;AdventureBehaviorMgr::Record r;Player p;
assert(mgr.StartLeisure(&p,r,{"walk"})&&r.Leisure.PlannedWalk);
auto expected=r.Leisure.Route.size();auto outward=r.Leisure.OutboundSteps;
assert(outward>=3&&expected==2*outward);
assert(r.Leisure.Origin.GetExactDist(r.Leisure.Route[outward-1])>=65);
for(size_t i=0;i+1<outward;++i) assert(r.Leisure.Route[i].GetExactDist(r.Leisure.Route[2*outward-2-i])==0);
assert(mgr.UpdateLeisure(&p,r,1000)&&mgr.moves==1&&r.Leisure.Step==0&&!mgr.lastRiding);
assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Step==0); // moving is not arrival
bool active=true;for(int i=0;i<150&&active;++i){
if(r.Leisure.Moving){p.Relocate(mgr.requested.GetPositionX(),mgr.requested.GetPositionY(),mgr.requested.GetPositionZ());travel.status.State=ClientlessTravelState::Arrived;}
active=mgr.UpdateLeisure(&p,r,1000);
}
assert(!active&&r.Leisure.CompletedLegs==expected&&p.GetExactDist(r.Leisure.Origin)<0.01f);
mgr.ResetLeisure(&p,r);
// A rejected forward connector triggers the visited return path, never a skip.
assert(mgr.StartLeisure(&p,r,{"walk"}));mgr.moveAllowed=false;
assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Returning&&r.Leisure.Step==0&&r.Leisure.CompletedLegs==0);
assert(!mgr.UpdateLeisure(&p,r,1000)&&r.Detail=="leisure return path unavailable");
mgr.moveAllowed=true;mgr.ResetLeisure(&p,r);
// Timer expiry retraces actually reached checkpoints, including bends.
assert(mgr.StartLeisure(&p,r,{"walk"}));
auto first=r.Leisure.Route[0],second=r.Leisure.Route[1];
assert(mgr.UpdateLeisure(&p,r,50));p.Relocate(first.m_positionX,first.m_positionY,first.m_positionZ);travel.status.State=ClientlessTravelState::Arrived;
assert(mgr.UpdateLeisure(&p,r,50)&&r.Leisure.PauseMs==0);
r.MicroTimer=0;p.Relocate(second.m_positionX,second.m_positionY,second.m_positionZ);travel.status.State=ClientlessTravelState::Arrived;
assert(mgr.UpdateLeisure(&p,r,50)&&r.Leisure.Returning&&r.Leisure.Route.size()==2);
assert(r.Leisure.Route[0].GetExactDist(first)==0&&r.Leisure.Route[1].GetExactDist(r.Leisure.Origin)==0);
mgr.ResetLeisure(&p,r);p.Relocate(0,0,0);r.MicroTimer=240000;
PathGenerator::reachable=false;assert(!mgr.StartLeisure(&p,r,{"walk"}));PathGenerator::reachable=true;
PathGenerator::type=2;assert(!mgr.StartLeisure(&p,r,{"walk"}));PathGenerator::type=1;
PathGenerator::broken=true;assert(!mgr.StartLeisure(&p,r,{"walk"}));PathGenerator::broken=false;
// Indoor seating must not trap the leisure selector. Foot outings can leave,
// and use the outdoor anchor rather than returning to the indoor chair.
p.outdoors=false;p.Relocate(5,0,2);
assert(mgr.LeisureAvailable(&p,r,{"walk"})&&mgr.LeisureAvailable(&p,r,{"roam"}));
assert(mgr.LeisureAvailable(&p,r,{"pet_walk"}));
assert(!mgr.LeisureAvailable(&p,r,{"ride"})&&!mgr.LeisureAvailable(&p,r,{"flight"}));
assert(mgr.StartLeisure(&p,r,{"walk"})&&r.Leisure.Origin.GetExactDist(r.Anchor)==0);
assert(r.Leisure.Route.back().GetExactDist(r.Anchor)==0);
assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Moving&&!mgr.lastRiding);
mgr.ResetLeisure(&p,r);p.outdoors=true;p.Relocate(0,0,0);
// No route is invented where native terrain rejects every candidate or zone differs.
terrain.outdoors=false;assert(!mgr.StartLeisure(&p,r,{"walk"}));terrain.outdoors=true;
terrain.zone=12;assert(!mgr.StartLeisure(&p,r,{"walk"}));terrain.zone=1519;
map.ground=8;assert(!mgr.StartLeisure(&p,r,{"walk"}));map.ground=0; // roof is not a ground stroll waypoint
// Summon and leash must be ready before owner movement starts.
assert(mgr.StartLeisure(&p,r,{"pet_walk"}));ClientlessCompanionMgr::ready=false;
int before=mgr.moves;assert(mgr.UpdateLeisure(&p,r,1000)&&mgr.moves==before);
ClientlessCompanionMgr::ready=true;cat.Relocate(p.GetPositionX(),p.GetPositionY(),0);
assert(mgr.UpdateLeisure(&p,r,1000)&&mgr.moves==before+1);
cat.Relocate(100,100,0);assert(mgr.UpdateLeisure(&p,r,1000)&&!r.Leisure.Moving&&p.stops>0);
bool following=true;for(int i=0;i<25&&following;++i)following=mgr.UpdateLeisure(&p,r,1000);
assert(!following);mgr.ResetLeisure(&p,r);assert(ClientlessCompanionMgr::resets==1);
cat.Relocate(0,0,0);p.Relocate(0,0,0);
// Leashed walking stops before native 5 yd range and waits for a real catch-up.
assert(mgr.StartLeisure(&p,r,{"pet_walk"}));r.Leisure.Companion.Leashed=true;
before=mgr.moves;cat.Relocate(4.1f,0,0);
assert(mgr.UpdateLeisure(&p,r,50)&&mgr.moves==before&&r.Leisure.CompanionWait==50);
cat.Relocate(3.7f,0,0);assert(mgr.UpdateLeisure(&p,r,50)&&mgr.moves==before);
cat.Relocate(3,0,0);assert(mgr.UpdateLeisure(&p,r,50)&&mgr.moves==before+1&&r.Leisure.CompanionWait==0);
mgr.ResetLeisure(&p,r);
// Mounted outings use the same navigation with an explicitly selected riding gait.
assert(mgr.StartLeisure(&p,r,{"ride"}));assert(mgr.UpdateLeisure(&p,r,1000)&&mgr.lastRiding&&lua.mounts>0);mgr.ResetLeisure(&p,r);
// A flight switch waits for native landing and a return to its origin.
assert(mgr.StartLeisure(&p,r,{"flight"}));assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Flying);
p.flying=true;int resets=mgr.resets;mgr.SetWanted(&p,r,"rest","inn",1234);
assert(r.Dirty&&r.Leisure.StopRequested&&mgr.resets==resets&&r.WantedIntent=="rest");
assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Step==0); // mounting/flying cannot fabricate arrival
p.flying=false;flight.status.State=ClientlessFlightState::Arrived;
assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Moving);
p.Relocate(mgr.requested.GetPositionX(),mgr.requested.GetPositionY(),0);travel.status.State=ClientlessTravelState::Arrived;
assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Returning&&r.Leisure.Route.size()==1&&r.Leisure.Flying);
flight.status.State=ClientlessFlightState::Arrived;assert(mgr.UpdateLeisure(&p,r,1000));
p.Relocate(mgr.requested.GetPositionX(),mgr.requested.GetPositionY(),0);travel.status.State=ClientlessTravelState::Arrived;
assert(!mgr.UpdateLeisure(&p,r,1000)&&p.GetExactDist(r.Leisure.Origin)<0.01f);mgr.ResetLeisure(&p,r);
// Angling needs an authored bank, equipped fishing rod and real water.
assert(!mgr.LeisureAvailable(&p,r,{"fish"}));location.Point.Category="leisure_fishing";p.hasRod=true;
assert(!mgr.StartLeisure(&p,r,{"fish"}));map.water=2;
assert(mgr.StartLeisure(&p,r,{"fish"})&&p.emote==EMOTE_STATE_FISHING);
r.MicroTimer=0;assert(!mgr.UpdateLeisure(&p,r,1000));mgr.ResetLeisure(&p,r);
// Adventure foot exploration reuses coherent native routes, never pet/flight scenes.
r.Intent="adventure";r.MicroTimer=180000;p.outdoors=true;p.Relocate(0,0,0);
assert(mgr.LeisureAvailable(&p,r,{"explore"})&&mgr.LeisureAvailable(&p,r,{"patrol"}));
assert(!mgr.LeisureAvailable(&p,r,{"pet_walk"})&&!mgr.LeisureAvailable(&p,r,{"flight"}));
assert(mgr.StartLeisure(&p,r,{"explore"})&&r.Leisure.PlannedWalk&&r.Leisure.Route.size()>=6);
mgr.ResetLeisure(&p,r);p.outdoors=false;assert(!mgr.LeisureAvailable(&p,r,{"explore"}));
}
'''
with tempfile.TemporaryDirectory(prefix='vcore-leisure-') as folder:
cpp=Path(folder)/'test.cpp';binary=Path(folder)/'test';cpp.write_text(code)
subprocess.run(['c++','-std=c++17',str(cpp),'-o',str(binary)],check=True)
subprocess.run([str(binary)],check=True)
print('Leisure: substantial curved MMAP routes, mirrored return, interrupted return, rejected corridors, actual arrival, companion follow, riding, flight and fishing passed.')
@@ -0,0 +1,33 @@
"""Execute the leisure migration against its catalog schema and test idempotence."""
from pathlib import Path
import sqlite3
root=Path(__file__).resolve().parents[2]
db=sqlite3.connect(':memory:')
# The existing seed schemas are translated only for SQLite's engine/type syntax.
base=(root/'sql/custom/world/2026_10_26_03_adventure_behavior.sql').read_text()
import re
for table in ('vcore_adventure_location_intent','vcore_adventure_action','vcore_adventure_action_intent','vcore_adventure_dialog'):
text=re.search(r'CREATE TABLE IF NOT EXISTS '+table+r' \((.*?)\) ENGINE=',base,re.S).group(1)
db.execute('CREATE TABLE '+table+' ('+text+')')
db.executescript('''
CREATE TABLE game_tele(name TEXT PRIMARY KEY,map INTEGER,position_x REAL,position_y REAL,position_z REAL,orientation REAL);
INSERT INTO game_tele VALUES ('TradeDistrict',0,-8835.7,620.485,93.2388,0.772328);
CREATE TABLE vcore_clientless_world_point(point_key TEXT PRIMARY KEY,category TEXT,map INTEGER,position_x REAL,position_y REAL,position_z REAL,orientation REAL,enabled INTEGER,comment TEXT);
CREATE TABLE vcore_adventure_location(point_key TEXT PRIMARY KEY,display_name TEXT,source_type TEXT,region_key TEXT,zone_id INTEGER,faction INTEGER);
''')
for path in ('2026_10_26_03_adventure_behavior.sql','2026_10_26_04_adventure_social.sql','2026_10_27_00_adventure_rest.sql'):
# Populate only the existing shared action rows. No unrelated world data is needed.
sql=(root/'sql/custom/world'/path).read_text()
for statement in sql.split(';'):
match=re.search(r'INSERT IGNORE INTO (vcore_adventure_action|vcore_adventure_action_intent)\s+VALUES\s+(.*)',statement,re.S)
if match:db.executescript('INSERT OR IGNORE INTO '+match[1]+' VALUES '+match[2]+';')
sql=(root/'sql/custom/world/2026_10_27_02_adventure_leisure.sql').read_text().replace('INSERT IGNORE','INSERT OR IGNORE')
db.executescript(sql)
assert not db.execute("SELECT i.action_key FROM vcore_adventure_action_intent i LEFT JOIN vcore_adventure_action a USING(action_key) WHERE i.intent='leisure' AND a.action_key IS NULL").fetchall()
assert db.execute("SELECT kind,requirement FROM vcore_adventure_action WHERE action_key='leisure_pet_walk'").fetchone()==('leisure','pet_walk')
assert db.execute("SELECT zone_id,faction FROM vcore_adventure_location WHERE point_key='stormwind_leisure_trade'").fetchone()==(1519,1)
db.execute("UPDATE vcore_adventure_action_intent SET weight=11 WHERE action_key='leisure_pet_walk' AND intent='leisure'")
db.executescript(sql)
assert db.execute("SELECT weight FROM vcore_adventure_action_intent WHERE action_key='leisure_pet_walk' AND intent='leisure'").fetchone()==(11,)
assert db.execute("SELECT COUNT(*) FROM vcore_clientless_world_point WHERE point_key='stormwind_leisure_trade'").fetchone()==(1,)
print('Leisure migration: native city anchor, shared scene references, pet-walk mapping and preservation of edited weights passed.')
@@ -0,0 +1,60 @@
"""Check production map reads with native Debug Field getters and location weights."""
from pathlib import Path
import re,subprocess,tempfile
root=Path(__file__).resolve().parents[2]
source=(root/'src/server/game/World/AdventureLocationMgr.cpp').read_text()
field=(root/'src/server/database/Database/Field.cpp').read_text()
def function(text,sig):
a=text.index(sig);b=text.index('{',a);depth=1;e=b+1
while depth:depth+=(text[e]=='{')-(text[e]=='}');e+=1
return text[a:e]
code=r'''
#include "AdventureEconomyPolicy.h"
#include <cassert>
#include <cstdlib>
#include <cstdint>
#include <string>
#include <unordered_map>
using uint16=uint16_t;using uint32=uint32_t;
#define TRINITY_DEBUG
enum class DatabaseFieldTypes{Int16,Int32};
struct Field{
struct{char const* value;bool raw=false;}data;DatabaseFieldTypes type;mutable int errors=0;
bool IsType(DatabaseFieldTypes t)const{return type==t;}void LogWrongType(char const*)const{++errors;}
uint16 GetUInt16()const;uint32 GetUInt32()const;
};
'''
code+=function(field,'uint16 Field::GetUInt16() const')+'\n'+function(field,'uint32 Field::GetUInt32() const')
code+=r'''
struct AdventureLocation{struct{uint32 MapId=0;std::string Category;}Point;bool IsArea=false;};
struct AdventureLocationMgr{std::unordered_map<std::string,std::unordered_map<std::string,float>> _types;float Weight(AdventureLocation const&,std::string const&)const;};
'''
code+=function(source,'float AdventureLocationMgr::Weight(')
reload_map=re.search(r'l.Point.MapId = f\[3\].*?;',source).group()
tele_map=re.search(r's.Map = f\[2\].*?;',source).group()
npc_map=re.search(r's.Map = f\[3\].*?;',source).group()
code+=r'''
int main(){
for(auto value:{"0","1","530","571"}){
Field f[4]={{{value},DatabaseFieldTypes::Int16},{{value},DatabaseFieldTypes::Int16},{{value},DatabaseFieldTypes::Int16},{{value},DatabaseFieldTypes::Int16}};
struct{uint32 Map=0;}s;AdventureLocation l;
'''+reload_map+tele_map+'''assert(l.Point.MapId==uint32(std::strtoul(value,nullptr,10))&&s.Map==l.Point.MapId);
'''+npc_map+r'''assert(s.Map==l.Point.MapId&&f[2].errors==0&&f[3].errors==0);
// Reproduce the original Debug failure, instead of treating every getter alike.
assert(f[3].GetUInt32()==0&&f[3].errors==1);
}
AdventureLocationMgr mgr;AdventureLocation place;
for(auto category:{"zone","settlement","tavern","bank","service","landmark","wilderness","mine","workshop"}){
place.Point.Category=category;assert(mgr.Weight(place,"economy")==0);mgr._types[category]["economy"]=4;assert(mgr.Weight(place,"economy")==4);assert(mgr.Weight(place,"social")==0);
}
// Edited weights, including an explicit zero, override economy defaults.
place.Point.Category="settlement";mgr._types["settlement"]["economy"]=0;
assert(mgr.Weight(place,"economy")==0);mgr._types["settlement"]["economy"]=2.75;
assert(mgr.Weight(place,"economy")==2.75);
}
'''
with tempfile.TemporaryDirectory(prefix='vcore-location-import-') as folder:
cpp=Path(folder)/'test.cpp';binary=Path(folder)/'test';cpp.write_text(code)
subprocess.run(['c++','-std=c++17','-I',str(root/'src/server/game/World'),str(cpp),'-o',str(binary)],check=True)
subprocess.run([str(binary)],check=True)
print('Locations: actual Debug SMALLINT reads retain continent IDs; explicit location eligibility and edited weight overrides passed.')
@@ -24,14 +24,17 @@ struct ObjectGuid{void Clear(){}};
struct SocialScene{};
struct AdventureSocialActions{static void Reset(Player*,SocialScene&){}};
class AdventureBehaviorMgr{public:
struct Record{SocialScene Social;std::string Micro="explore";int MicroTimer=100,GestureTimer=100;ObjectGuid WorkObject;int WorkEmote=0;bool WorkPending=false;};
struct RecoveryScene{bool Active=false;};
struct Record{SocialScene Social;RecoveryScene Recovery;std::string Micro="explore";int MicroTimer=100,GestureTimer=100;ObjectGuid WorkObject;int WorkEmote=0;bool WorkPending=false;};
void ResetLeisure(Player*,Record&){}
void ResetMicro(Player*,Record&);
};
'''
code+=s[a:b]+r'''
int main(){
AdventureBehaviorMgr mgr;AdventureBehaviorMgr::Record r;Player p;
mgr.ResetMicro(&p,r);assert(p.stops==1&&travel.cancels==1&&r.Micro.empty());
r.Recovery.Active=true;
mgr.ResetMicro(&p,r);assert(p.stops==1&&travel.cancels==1&&r.Micro.empty()&&!r.Recovery.Active);
r.Micro="drink";p.transport=true;mgr.ResetMicro(&p,r);assert(p.stops==1&&travel.cancels==1);
p.transport=false;p.flying=true;r.Micro="look";mgr.ResetMicro(&p,r);assert(p.stops==1);
p.flying=false;busy=true;r.Micro="look";mgr.ResetMicro(&p,r);assert(p.stops==1);
+117
View File
@@ -0,0 +1,117 @@
"""Exercise production recovery selection/clock with native navigation adapters."""
from pathlib import Path
import subprocess
import tempfile
root = Path(__file__).resolve().parents[2]
source = (root / 'src/server/game/World/AdventureBehaviorMgr.cpp').read_text()
def function(signature):
start = source.index(signature)
body = source.index('{', start)
end, depth = body + 1, 1
while depth:
depth += (source[end] == '{') - (source[end] == '}')
end += 1
return source[start:end]
code = r'''
#include <cassert>
#include <cmath>
#include <cstdint>
#include <string>
#include <vector>
using uint32=uint32_t;
#define TC_LOG_INFO(...) ((void)0)
constexpr int UNIT_STAND_STATE_SLEEP=3,UNIT_STAND_STATE_SIT=1;
struct Position {
float x=0,y=0,z=0;
float GetPositionZ()const{return z;}
float GetExactDist(float a,float b,float c)const{return std::sqrt((x-a)*(x-a)+(y-b)*(y-b)+(z-c)*(z-c));}
};
struct Guid{uint32 GetCounter(){return 26;}};
struct Player:Position {
uint32 map=0;bool water=false,falling=false,phase=true;int stand=0,poses=0;
std::vector<Player*> occupants;
uint32 GetMapId(){return map;}Guid GetGUID(){return {};}
bool IsInWater(){return water;}bool IsFalling(){return falling;}
void GetPlayerListInGrid(std::vector<Player*>& out,float){out=occupants;}
bool IsInPhase(Player* p){return p->phase;}
void SetFacingTo(float){}void SetStandState(int value){stand=value;++poses;}
int GetStandState(){return stand;}
};
struct AdventureMicroAction{std::string Requirement;};
struct AdventureLocation {
struct {std::string Id="bed",Category="rest_sleep";uint32 MapId=0;float X=0,Y=0,Z=0,O=0;}Point;
bool allowed=true;
};
struct Locations {
std::vector<AdventureLocation> points;
auto const& Locations()const{return points;}
AdventureLocation const* Find(std::string const& id){for(auto const& p:points)if(p.Point.Id==id)return &p;return nullptr;}
}locations;
auto sAdventureLocationMgr=&locations;
enum class ClientlessTravelState{Moving,Arrived,PathRejected};
struct Travel {
struct Status{ClientlessTravelState State=ClientlessTravelState::Moving;}status;
bool present=true;Status const* GetStatus(uint32){return present?&status:nullptr;}
}travel;
auto sClientlessTravelMgr=&travel;
class AdventureBehaviorMgr {
public:
struct RecoveryScene{bool Active=true,Approaching=true;std::string Point="bed",Posture="sleep";uint32 Remaining=3000,ApproachElapsed=0;};
struct Record{Position Anchor;uint32 AnchorMap=0;RecoveryScene Recovery;std::string Detail;};
bool Allowed(Player*,AdventureLocation const& p,std::string const&)const{return p.allowed;}
bool RecoveryAvailable(Player*,Record const&,AdventureMicroAction const&)const;
bool UpdateRecovery(Player*,Record&,uint32);
};
'''
# Avoid a class/member name collision in the adapter.
code = code.replace('struct Locations {', 'struct LocationStore {')
code += function('bool AdventureBehaviorMgr::RecoveryAvailable(')
code += function('bool AdventureBehaviorMgr::UpdateRecovery(')
code += r'''
int main(){
AdventureBehaviorMgr mgr;AdventureBehaviorMgr::Record r;Player p;
AdventureMicroAction sleep{"sleep"},ground{"ground"},unknown{"anything"};
assert(!mgr.RecoveryAvailable(&p,r,sleep));
locations.points.push_back({});
assert(mgr.RecoveryAvailable(&p,r,sleep));
assert(!mgr.RecoveryAvailable(&p,r,ground)&&!mgr.RecoveryAvailable(&p,r,unknown));
auto& point=locations.points[0];
point.Point.Category="tavern";assert(!mgr.RecoveryAvailable(&p,r,sleep)); // no arbitrary tavern floor
point.Point.Category="rest_sleep";point.Point.MapId=1;assert(!mgr.RecoveryAvailable(&p,r,sleep));
point.Point.MapId=0;point.allowed=false;assert(!mgr.RecoveryAvailable(&p,r,sleep));
point.allowed=true;point.Point.X=31;assert(!mgr.RecoveryAvailable(&p,r,sleep));
point.Point.X=0;point.Point.Z=13;assert(!mgr.RecoveryAvailable(&p,r,sleep));point.Point.Z=0;
// Walking cannot consume sleep time or trigger the sleep pose.
for(int n=0;n<10;++n)assert(mgr.UpdateRecovery(&p,r,1000));
assert(p.poses==0&&r.Recovery.Remaining==3000);
travel.status.State=ClientlessTravelState::Arrived;p.x=2;
assert(!mgr.UpdateRecovery(&p,r,1000)&&p.poses==0);p.x=0;
Player occupant;p.occupants={&occupant};
assert(!mgr.UpdateRecovery(&p,r,1000)&&p.poses==0);p.occupants.clear();
assert(mgr.UpdateRecovery(&p,r,1000)&&p.stand==UNIT_STAND_STATE_SLEEP&&!r.Recovery.Approaching);
assert(mgr.UpdateRecovery(&p,r,1000));assert(mgr.UpdateRecovery(&p,r,1000));
assert(!mgr.UpdateRecovery(&p,r,1000)); // full duration starts after arrival
r.Recovery={};travel.status.State=ClientlessTravelState::PathRejected;
assert(!mgr.UpdateRecovery(&p,r,1000));
travel.status.State=ClientlessTravelState::Moving;r.Recovery.ApproachElapsed=60000;
assert(!mgr.UpdateRecovery(&p,r,1000));
r.Recovery={};travel.status.State=ClientlessTravelState::Arrived;
locations.points.clear();assert(!mgr.UpdateRecovery(&p,r,1000)); // removed on catalog reload
locations.points.push_back({});p.water=true;assert(!mgr.UpdateRecovery(&p,r,1000));p.water=false;
p.falling=true;assert(!mgr.UpdateRecovery(&p,r,1000));p.falling=false;
p.map=1;assert(!mgr.UpdateRecovery(&p,r,1000));p.map=0;
r.Recovery.Posture="ground";assert(!mgr.UpdateRecovery(&p,r,1000));
locations.points[0].Point.Category="rest_ground";
assert(mgr.UpdateRecovery(&p,r,1000)&&p.stand==UNIT_STAND_STATE_SIT);
p.stand=0;assert(!mgr.UpdateRecovery(&p,r,1000)); // externally interrupted posture
}
'''
with tempfile.TemporaryDirectory(prefix='vcore-rest-') as folder:
cpp, binary = Path(folder) / 'test.cpp', Path(folder) / 'test'
cpp.write_text(code)
subprocess.run(['c++', '-std=c++17', str(cpp), '-o', str(binary)], check=True)
subprocess.run([str(binary)], check=True)
print('Recovery: authored places, bounds, arrival, occupancy, full rest duration, failure, reload and interruption passed.')
@@ -0,0 +1,77 @@
"""Execute the additive rest-fire migration and the production context policy."""
from pathlib import Path
import sqlite3
import subprocess
import tempfile
root = Path(__file__).resolve().parents[2]
sql = (root / 'sql/custom/world/2026_10_27_01_adventure_rest_fire.sql').read_text()
sql = sql.replace('INSERT IGNORE', 'INSERT OR IGNORE')
db = sqlite3.connect(':memory:')
db.executescript('''
CREATE TABLE vcore_adventure_action (
action_key TEXT PRIMARY KEY,kind TEXT,min_seconds INTEGER,max_seconds INTEGER,requirement TEXT,emote_id INTEGER);
CREATE TABLE vcore_adventure_action_intent (
action_key TEXT,intent TEXT,weight REAL,PRIMARY KEY(action_key,intent));
INSERT INTO vcore_adventure_action VALUES ('social_fire','social',25,50,'fire',0);
INSERT INTO vcore_adventure_action_intent VALUES ('social_fire','social',1);
''')
db.executescript(sql)
assert db.execute("SELECT kind,min_seconds,max_seconds,requirement FROM vcore_adventure_action WHERE action_key='rest_fire'").fetchone() == ('social',45,75,'fire')
assert db.execute("SELECT intent,weight FROM vcore_adventure_action_intent WHERE action_key='rest_fire'").fetchall() == [('rest',3.0)]
db.execute("UPDATE vcore_adventure_action_intent SET weight=7 WHERE action_key='rest_fire'")
db.executescript(sql)
assert db.execute("SELECT weight FROM vcore_adventure_action_intent WHERE action_key='rest_fire'").fetchone() == (7.0,)
assert db.execute("SELECT max_seconds FROM vcore_adventure_action WHERE action_key='social_fire'").fetchone() == (50,)
source = (root / 'src/server/game/World/AdventureBehaviorMgr.cpp').read_text()
a = source.index('AdventureSocialContext AdventureBehaviorMgr::SocialContext(')
b = source.index('\nbool AdventureBehaviorMgr::UpdateSocial(', a)
method = source[a:b]
code = r'''
#include "AdventureProfessionPolicy.h"
#include <cassert>
#include "AdventureEconomyPolicy.h"
#include <cstdint>
#include <deque>
#include <functional>
#include <random>
#include <string>
using uint32=uint32_t;
struct Position{};
struct Guid{uint32 GetCounter(){return 26;}};
struct Player{Guid GetGUID(){return {};}};
struct Profile{AdventureProfessionPolicy::Professions PrimaryProfessions;std::string Locale="deDE";}profile;
struct Lua{Profile* GetAdventureProfile(uint32){return &profile;}}lua;
auto sClientlessLuaEngine=&lua;
struct Location{struct{std::string Category;}Point;}location;
struct Locations{bool present=true;Location* Find(std::string const&){return present?&location:nullptr;}}locations;
auto sAdventureLocationMgr=&locations;
struct AdventureSocialContext{
Position Anchor;uint32 MapId;bool Festival;std::string Locale,Intent;
std::mt19937& Random;std::deque<std::string>& TargetHistory;std::deque<std::string>& VariantHistory;
std::function<bool(Position const&,float)> Move;bool AllowCreateFire=true;std::string ServiceKind,LocationCategory;AdventureProfessionPolicy::Professions PrimaryProfessions;
};
class AdventureBehaviorMgr{public:
struct Record{Position Anchor;uint32 AnchorMap=0;std::string Location,Intent;std::mt19937 Random;std::deque<std::string> TargetHistory,VariantHistory;};
bool LocalMove(Player*,Record&,Position const&,float){return true;}
AdventureSocialContext SocialContext(Player*,Record&);
};
'''
code += method + r'''
int main(){
Player p;AdventureBehaviorMgr mgr;AdventureBehaviorMgr::Record r;r.Intent="rest";
for(auto category:{"tavern","settlement","rest_seat","rest_sleep"}){
location.Point.Category=category;assert(!mgr.SocialContext(&p,r).AllowCreateFire);
}
location.Point.Category="rest_ground";assert(mgr.SocialContext(&p,r).AllowCreateFire);
locations.present=false;assert(!mgr.SocialContext(&p,r).AllowCreateFire);locations.present=true;
location.Point.Category="tavern";r.Intent="social";assert(mgr.SocialContext(&p,r).AllowCreateFire);
r.Intent="adventure";assert(mgr.SocialContext(&p,r).AllowCreateFire);
}
'''
with tempfile.TemporaryDirectory(prefix='vcore-rest-fire-policy-') as folder:
cpp, binary = Path(folder)/'test.cpp', Path(folder)/'test'
cpp.write_text(code)
subprocess.run(['c++','-std=c++17','-I',str(root/'src/server/game/World'),str(cpp),'-o',str(binary)],check=True)
subprocess.run([str(binary)],check=True)
print('Rest fire: shared scene mapping, additive migration, preserved weights and authored creation-place policy passed.')
+79 -11
View File
@@ -10,6 +10,12 @@ def function(signature):
return source[a:c]
code=r'''
#include "AdventureSocialFlow.h"
#include "AdventureEconomyPolicy.h"
#include "AdventureExplorationPolicy.h"
#include <functional>
#include <deque>
#include <algorithm>
#include "AdventureProfessionPolicy.h"
#include <cassert>
#include <string>
#include <vector>
@@ -17,56 +23,72 @@ code=r'''
using uint32=uint32_t;
using namespace AdventureSocialFlow;
#define TC_LOG_INFO(...) ((void)0)
constexpr float INVALID_HEIGHT=-100000;
constexpr int EMOTE_STATE_USE_STANDING=69,EMOTE_STATE_WORK_MINING=233,EMOTE_STATE_WORK_CHOPWOOD=234,EMOTE_STATE_KNEEL=68;
constexpr int CookingFire=818,UNIT_NPC_EMOTESTATE=0,UNIT_STAND_STATE_SIT=1,UNIT_STAND_STATE_SIT_LOW_CHAIR=4,CMSG_GAMEOBJ_USE=1;
constexpr int EMOTE_ONESHOT_QUESTION=6,EMOTE_ONESHOT_POINT=25,EMOTE_ONESHOT_USE_STANDING=432;
constexpr int EMOTE_ONESHOT_WAVE=3,EMOTE_ONESHOT_TALK=1,EMOTE_ONESHOT_YES=273,EMOTE_ONESHOT_CHEER=4,EMOTE_STATE_DANCE=10;
constexpr int SPELL_VISUAL_KIT_DRINK=438,SPELL_VISUAL_KIT_FOOD=406,GAMEOBJECT_TYPE_CHAIR=7,SPELL_CAST_OK=0;
struct ObjectGuid{int id=0;uint32 GetCounter(){return id;}bool IsEmpty()const{return !id;}std::string ToString()const{return std::to_string(id);}};
struct Unit;struct GameObject;
struct ObjectGuid{int id=0;uint32 GetCounter(){return id;}bool operator==(ObjectGuid const& other)const{return id==other.id;}bool IsEmpty()const{return !id;}std::string ToString()const{return std::to_string(id);}};
struct Unit;struct GameObject;struct Player;
struct WorldObject{
int kind=0;ObjectGuid guid{1};virtual ~WorldObject()=default;
float x=0,y=0,z=0,heading=0;float GetPositionX(){return x;}float GetOrientation(){return heading;}float GetPositionY(){return y;}float GetPositionZ(){return z;}
float GetAngle(WorldObject*p){return std::atan2(p->y-y,p->x-x);}
Unit* ToUnit();GameObject* ToGameObject();ObjectGuid GetGUID(){return guid;}
};
struct Unit:WorldObject{Unit(){kind=1;}};
struct Unit:WorldObject{bool service=false;Unit(){kind=1;}};
struct GameObject:WorldObject{
struct Info{float size=1;struct{int triggeredEvent=0;uint32 chairslots=1,chairheight=1;}chair;}info;
GameObject(){kind=2;guid.id=2;}
bool spawned=true;bool isSpawned(){return spawned;}
int GetGoType(){return GAMEOBJECT_TYPE_CHAIR;}Info* GetGOInfo(){return &info;}
};
Unit* WorldObject::ToUnit(){return kind==1?static_cast<Unit*>(this):nullptr;}
GameObject* WorldObject::ToGameObject(){return kind==2?static_cast<GameObject*>(this):nullptr;}
struct WorldPacket{WorldPacket(int,int){}WorldPacket& operator<<(ObjectGuid){return *this;}};
struct Session{int uses=0;void HandleGameObjectUseOpcode(WorldPacket&){++uses;}};
struct Position{float m_positionX=0,m_positionY=0,m_positionZ=0;void Relocate(Player*);float GetPositionX()const{return m_positionX;}float GetPositionY()const{return m_positionY;}float GetPositionZ()const{return m_positionZ;}};
struct NativeMap{float GetHeight(int,float,float,float,bool,float){return 0;}}nativeMap;
struct Player:Unit{
Player(){x=2;}
std::vector<int> emotes;int faces=0,casts=0;bool casting=false,sit=false,los=true;float distance=2;Session session;
ObjectGuid selection;
bool visible=true;bool CanSeeOrDetect(GameObject*){return visible;}
NativeMap* GetMap(){return &nativeMap;}int GetPhaseShift(){return 0;}
bool IsBeingTeleported(){return false;}bool IsSitState(){return sit;}bool IsNonMeleeSpellCast(bool){return casting;}
float GetExactDist(WorldObject*){return distance;}void SetFacingToObject(WorldObject*){++faces;}
void SetFacingTo(float){}int chairStand=5;uint32 GetStandState(){return sit?chairStand:0;}
float GetExactDist(Position const&){return distance;}float GetExactDist(WorldObject*){return distance;}void SetFacingToObject(WorldObject*){++faces;}
std::vector<float> headings;void SetFacingTo(float f){heading=f;headings.push_back(f);}int chairStand=5;uint32 GetStandState(){return sit?chairStand:0;}
void SetSelection(ObjectGuid g){selection=g;}void SetUInt32Value(int,int){}
void HandleEmoteCommand(int id){emotes.push_back(id);}void SendPlaySpellVisualKit(int,int,int){}
void SetStandState(int){sit=true;}int CastSpell(Player*,int,bool){++casts;casting=true;return SPELL_CAST_OK;}
bool IsWithinLOSInMap(WorldObject*){return los;}bool IsWithinLOS(float,float,float){return los;}bool GetGameObjectIfCanInteractWith(ObjectGuid){return true;}Session* GetSession(){return &session;}
};
struct Position{};
struct AdventureSocialContext{};
void Position::Relocate(Player*p){m_positionX=p->x;m_positionY=p->y;m_positionZ=p->z;}
struct AdventureSocialContext{bool AllowCreateFire=true;std::string Intent="social";AdventureProfessionPolicy::Professions PrimaryProfessions;Position Anchor;std::deque<std::string> TargetHistory;int departures=0;bool reachable=true;bool Move(Position const&,float){++departures;return reachable;}};
struct AdventureSocialScene{
bool Active=true,Approaching=false,Casting=false,WaitingChair=false,Seated=false;
std::string Action="social_npc",Kind="npc",TargetKey;ObjectGuid Target{1},PreviousSelection;Position Destination;
uint32 Elapsed=0,CastElapsed=0,CheckTimer=0,Repaths=0;Sequence Sequence;
bool Active=true,Approaching=false,Casting=false,WaitingChair=false,Seated=false,Departing=false;
std::string Action="social_npc",Kind="npc",TargetKey;ObjectGuid Target{1},PreviousSelection;Position Destination,Origin;float Facing=0;
uint32 Elapsed=0,CastElapsed=0,CheckTimer=0,Repaths=0,ServiceVisits=0,ServiceDuration=30000;Sequence Sequence;
};
WorldObject* target=nullptr;GameObject* fire=nullptr;bool friendly=true,approach=true;
WorldObject* Resolve(Player*,ObjectGuid g){return g.id?target:nullptr;}
bool InBounds(Player*,WorldObject* t,AdventureSocialContext&){return t!=nullptr;}
bool Friendly(Player*,Unit*,AdventureSocialContext&){return friendly;}
bool ServiceMatches(Unit* unit,std::string const&){return unit->service;}
bool TargetVisible(Player* p,WorldObject* target,std::string const&){return p->IsWithinLOSInMap(target);}
std::vector<Unit*> services;
std::vector<Unit*> People(Player*,std::string const&,AdventureSocialContext&){return services;}
template<class T>T* Pick(Player*,std::vector<T*> const& choices,AdventureSocialContext&){return choices.empty()?nullptr:choices[0];}
void Remember(std::deque<std::string>& history,std::string key){history.push_back(key);}
bool Approach(Player*,AdventureSocialScene& s,WorldObject*,AdventureSocialContext&){s.Approaching=true;return approach;}
GameObject* Fire(Player*,AdventureSocialContext&){return fire;}
bool CanMakeFire(Player*){return true;}
void Say(Player*,std::string const&,AdventureSocialContext&){}
uint32 Reaction(AdventureSocialContext&){return 11;}
enum class ClientlessTravelState{Moving,Arrived};
enum class ClientlessTravelState{Moving,Arrived,Rejected};
struct Travel{struct{ClientlessTravelState State=ClientlessTravelState::Moving;}status;auto GetStatus(uint32){return &status;}}travel;auto sClientlessTravelMgr=&travel;
struct AdventureProfessionTargets{static bool Valid(Player*,WorldObject*,std::string const&,AdventureSocialContext const&){return true;}};
class AdventureSocialActions{public:static bool Update(Player*,AdventureSocialScene&,uint32,AdventureSocialContext&);};
'''.replace('Sequence Sequence;', 'AdventureSocialFlow::Sequence Sequence;')
code+=function('bool OnChair(')+'\n'+function('bool AtFront(')+'\n'+function('std::vector < Step > Steps(')+'\n'+function('bool AdventureSocialActions::Update(')
@@ -119,10 +141,56 @@ int main(){
assert(AdventureSocialActions::Update(&p,s,1000,c)&&p.casts==1);
p.casting=false;active=true;for(unsigned i=0;i<12&&active;++i)active=AdventureSocialActions::Update(&p,s,1000,c);
assert(!active&&p.casts==1);
// A disappeared fire must not be recreated in a rest context that forbids creation.
c.AllowCreateFire=false;s={};s.Target.id=0;s.Sequence.Start(Steps("fire",30000));
auto castCount=p.casts;assert(!AdventureSocialActions::Update(&p,s,1000,c)&&p.casts==castCount);
c.AllowCreateFire=true;
// Conversely, a real newly spawned object is confirmed and approached.
s={};s.Target.id=0;s.Sequence.Start(Steps("fire",30000));
assert(AdventureSocialActions::Update(&p,s,1000,c));p.casting=false;fire=&chair;
assert(AdventureSocialActions::Update(&p,s,1000,c)&&s.Target.id==2&&s.Approaching&&!s.Casting);
// Service roles must still match while the scene is running.
target=&npc;fire=nullptr;npc.service=false;p.sit=false;p.x=2;p.distance=2;
s={};s.Kind="banking";s.Sequence.Start(Steps(s.Kind,15000));
assert(!AdventureSocialActions::Update(&p,s,1000,c));npc.service=true;
// The visit stays active until actual departure arrival, without more cues.
s={};s.Kind="banking";s.Sequence.Start(Steps(s.Kind,15000));
for(int i=0;i<60&&!s.Departing;++i) assert(AdventureSocialActions::Update(&p,s,1000,c));
assert(s.Departing&&c.departures==1);auto gestures=p.emotes.size();
travel.status.State=ClientlessTravelState::Moving;
assert(AdventureSocialActions::Update(&p,s,1000,c)&&p.emotes.size()==gestures);
travel.status.State=ClientlessTravelState::Rejected;assert(!AdventureSocialActions::Update(&p,s,1000,c));
// Comparing explicitly changes to a different vendor, before departing.
Unit other;other.guid.id=3;other.service=true;services={&npc,&other};
s={};s.Kind="vendor_compare";s.Sequence.Start(Steps(s.Kind,12000));travel.status.State=ClientlessTravelState::Arrived;
for(int i=0;i<60&&!s.ServiceVisits;++i) assert(AdventureSocialActions::Update(&p,s,1000,c));
assert(s.ServiceVisits==1&&s.Target.id==3&&s.Approaching&&!s.Departing);
target=&other;
for(int i=0;i<60&&!s.Departing;++i) assert(AdventureSocialActions::Update(&p,s,1000,c));
assert(s.Departing);assert(!AdventureSocialActions::Update(&p,s,1000,c));
// No second vendor: never pretend a successful comparison or departure.
target=&npc;services={&npc};s={};s.Kind="vendor_compare";s.Sequence.Start({{Cue::Listen,1}});
assert(AdventureSocialActions::Update(&p,s,1000,c));
assert(!AdventureSocialActions::Update(&p,s,1000,c)&&!s.ServiceVisits&&!s.Departing);
// Investigation waits for genuine arrival, then only gestures at the object.
s={};s.Kind="inspect_detail";s.Target.id=2;s.Sequence.Start(Steps(s.Kind,15000));
target=&chair;p.sit=false;p.x=2;p.distance=2;p.visible=p.los=true;chair.spawned=true;
s.Approaching=true;travel.status.State=ClientlessTravelState::Moving;
auto oldUses=p.session.uses,oldCasts=p.casts;auto oldEmotes=p.emotes.size();
assert(AdventureSocialActions::Update(&p,s,1000,c)&&p.emotes.size()==oldEmotes);
travel.status.State=ClientlessTravelState::Arrived;
active=true;for(int i=0;i<60&&active;++i) active=AdventureSocialActions::Update(&p,s,1000,c);
assert(!active&&p.session.uses==oldUses&&p.casts==oldCasts);
assert(std::find(p.emotes.begin()+oldEmotes,p.emotes.end(),EMOTE_ONESHOT_POINT)!=p.emotes.end());
s={};s.Kind="inspect";s.Target.id=2;s.Sequence.Start(Steps(s.Kind,15000));chair.spawned=false;
oldEmotes=p.emotes.size();assert(!AdventureSocialActions::Update(&p,s,1000,c)&&p.emotes.size()==oldEmotes);chair.spawned=true;
p.visible=false;assert(!AdventureSocialActions::Update(&p,s,1000,c));p.visible=true;
// Looking around uses a fixed initial direction rather than spinning each tick.
s={};s.Kind="scout";s.Target.id=0;s.Facing=1.2f;s.Sequence.Start(Steps(s.Kind,15000));p.headings.clear();
active=true;for(int i=0;i<60&&active;++i) active=AdventureSocialActions::Update(&p,s,1000,c);
assert(!active&&p.headings.size()==2&&std::fabs(p.headings[0]-0.45f)<0.001&&std::fabs(p.headings[1]-1.95f)<0.001);
}
'''
with tempfile.TemporaryDirectory(prefix='vcore-social-test-') as folder:
+46 -5
View File
@@ -9,15 +9,22 @@ def function(signature):
return s[a:e]
code=r'''
#include "AdventureBehaviorPolicy.h"
#include "AdventureProfessionPolicy.h"
#include <cassert>
#include "AdventureExplorationPolicy.h"
#include <cstdint>
#include <unordered_map>
using uint32=uint32_t;
using AdventureBehaviorPolicy::Normalize;
constexpr uint32 CookingFire=818;
struct GameObject{bool active=true,bounds=true;uint32 spell=818;std::string name="Localised unknown name";float distance=2;bool isSpawned(){return active;}std::string GetName(){return name;}uint32 GetSpellId(){return spell;}};
struct AdventureSocialContext{};
struct Guid{std::string ToString(){return "fire";}};
struct GameObject{Guid GetGUID(){return {};}bool active=true,bounds=true;uint32 spell=818;std::string name="Localised unknown name";float distance=2;bool isSpawned(){return active;}std::string GetName(){return name;}uint32 GetSpellId(){return spell;}};
struct AdventureSocialContext{std::string Intent,LocationCategory;bool AllowCreateFire=true,Festival=false;std::vector<std::string> TargetHistory;AdventureProfessionPolicy::Professions PrimaryProfessions;};
struct AdventureMicroAction{std::string Requirement;};
constexpr int GAMEOBJECT_TYPE_CHAIR=7;
struct History{bool cooldown=false;bool HasCooldown(uint32 id){assert(id==818);return cooldown;}};
struct Player {
uint32 area=1,zone=1;uint32 GetAreaId(){return area;}uint32 GetZoneId(){return zone;}
bool learned=false,outdoors=true,water=false,falling=false,casting=false;int learns=0;History history;std::vector<GameObject*> objects;
bool HasSpell(uint32 id){assert(id==818);return learned;}void LearnSpell(uint32 id,bool dependent){assert(id==818&&!dependent);learned=true;++learns;}
bool IsOutdoors(){return outdoors;}bool IsInWater(){return water;}bool IsFalling(){return falling;}bool IsNonMeleeSpellCast(bool){return casting;}
@@ -25,10 +32,20 @@ struct Player {
void GetGameObjectListWithEntryInGrid(std::vector<GameObject*>& out,int,float){out=objects;}
};
bool InBounds(Player*,GameObject* go,AdventureSocialContext&){return go->bounds;}
enum class AreaFlags:uint32 {LinkedChat=0x100,AllowTradeChannel=8};
struct Flags{uint32 value=0;bool HasFlag(AreaFlags flag)const{return (value&uint32(flag))!=0;}};
struct Area{uint32 ParentAreaID=0,flags=0;Flags GetFlags()const{return {flags};}};
struct Areas{std::unordered_map<uint32,Area> rows{{1,{}},{2,{0,0x100}},{3,{2,0}},{4,{0,8}}};
Area const* LookupEntry(uint32 id){auto i=rows.find(id);return i==rows.end()?nullptr:&i->second;}}areas;
auto& sAreaTableStore=areas;
struct Spells{bool exists=true;void* GetSpellInfo(uint32 id){assert(id==818);return exists?this:nullptr;}}spells;auto sSpellMgr=&spells;
class AdventureSocialActions{public:static void Prepare(Player*);};
std::vector<GameObject*> Objects(Player*,int,AdventureSocialContext&){return {};}
std::vector<GameObject*> People(Player*,std::string const&,AdventureSocialContext&){return {};}
std::vector<GameObject*> InspectionObjects(Player*,AdventureSocialContext&){return {};}
struct AdventureProfessionTargets{static std::vector<GameObject*> Find(Player*,std::string const&,AdventureSocialContext&){return {};}};
class AdventureSocialActions{public:static void Prepare(Player*);static bool Available(Player*,AdventureMicroAction const&,AdventureSocialContext&);};
'''
code+=function('GameObject * Fire(')+'\n'+function('bool CanMakeFire(')+'\n'+function('void AdventureSocialActions::Prepare(')
code+=function('GameObject * Fire(')+'\n'+function('bool CanMakeFire(')+'\n'+function('void AdventureSocialActions::Prepare(')+'\n'+function('bool AdventureSocialActions::Available(')
code+=r'''
int main(){
Player p;AdventureSocialContext c;GameObject fire;
@@ -37,8 +54,32 @@ int main(){
p.history.cooldown=true;assert(!CanMakeFire(&p));p.history.cooldown=false;
p.outdoors=false;assert(!CanMakeFire(&p));p.outdoors=true;
p.water=true;assert(!CanMakeFire(&p));p.water=false;p.falling=true;assert(!CanMakeFire(&p));p.falling=false;
// City rules apply even outdoors, including a city parent's unflagged district.
p.area=p.zone=2;assert(!CanMakeFire(&p));
p.area=3;p.zone=1;assert(!CanMakeFire(&p));
p.area=1;p.zone=2;assert(!CanMakeFire(&p));
p.area=p.zone=4;assert(!CanMakeFire(&p)); // trade-channel town flag
p.area=999;p.zone=1;assert(!CanMakeFire(&p)); // unknown data never grants permission
areas.rows[5]={5,0};p.area=5;assert(!CanMakeFire(&p)); // malformed hierarchy
p.area=p.zone=1;assert(CanMakeFire(&p));
p.objects={&fire};p.area=p.zone=2;
assert(AdventureSocialActions::Available(&p,{"fire"},c)); // existing city fire remains usable
assert(!AdventureSocialActions::Available(&p,{"make_fire"},c));
p.outdoors=false;assert(AdventureSocialActions::Available(&p,{"fire"},c));p.outdoors=true;
p.objects.clear();assert(!AdventureSocialActions::Available(&p,{"fire"},c));
p.area=p.zone=1;
p.objects={&fire};assert(Fire(&p,c)==&fire); // native spell ID works regardless of locale/name
fire.spell=0;assert(Fire(&p,c)==nullptr);fire.name="Basic Campfire";assert(Fire(&p,c)==&fire);
// Rest may create a fire only at an authored ground-rest location. Existing
// fires remain usable in a tavern or at another suitable resting location.
fire.spell=818;c.AllowCreateFire=false;
assert(AdventureSocialActions::Available(&p,{"fire"},c));
assert(!AdventureSocialActions::Available(&p,{"make_fire"},c));
p.objects.clear();assert(!AdventureSocialActions::Available(&p,{"fire"},c));
c.AllowCreateFire=true;assert(AdventureSocialActions::Available(&p,{"fire"},c));
assert(AdventureSocialActions::Available(&p,{"make_fire"},c));
p.outdoors=false;assert(!AdventureSocialActions::Available(&p,{"fire"},c));p.outdoors=true;
p.objects={&fire};
fire.active=false;assert(Fire(&p,c)==nullptr);fire.active=true;fire.bounds=false;assert(Fire(&p,c)==nullptr);
}
'''
@@ -46,4 +87,4 @@ with tempfile.TemporaryDirectory(prefix='vcore-social-fire-') as folder:
cpp=Path(folder)/'test.cpp';binary=Path(folder)/'test';cpp.write_text(code)
subprocess.run(['c++','-std=c++17','-I',str(root/'src/server/game/World'),str(cpp),'-o',str(binary)],check=True)
subprocess.run([str(binary)],check=True)
print('Native fire detection by spell ID, idempotent shared spell provisioning, cooldown and outdoor/water checks passed without a profession gate.')
print('Shared fire: native detection/provisioning, buildings/city/parent/zone restrictions, existing fires and outdoor/water checks passed.')
+6 -1
View File
@@ -7,6 +7,8 @@ a=s.index('bool ClientlessTravelMgr::StartTravel(');b=s.index('void ClientlessTr
start=s[a:b]
a=s.index(' player->GetMotionMaster()->MoveSmoothPath(');b=s.index(' Position const& segmentEnd',a)
launch=s[a:b]
a=s.index(' player->AttackStop();');b=s.index(' player->StopMoving();',a)
cast_setup=s[a:b]
code=r'''
#include <cassert>
#include <cstdint>
@@ -19,6 +21,7 @@ struct Position{float GetPositionX()const{return 0;}float GetPositionY()const{re
struct Guid{uint32 GetCounter(){return 26;}};
struct Motion{bool walk=false;int calls=0;void MoveSmoothPath(uint32,Position const*,std::size_t,bool w){walk=w;++calls;}}motion;
struct Player{
bool blocked=false;int castsStopped=0;void AttackStop(){}bool IsMovementPreventedByCasting(){return blocked;}void CastStop(){++castsStopped;}
bool IsInWorld(){return true;}Guid GetGUID(){return {};}uint32 GetMapId(){return 0;}Motion* GetMotionMaster(){return &motion;}
};
enum class ClientlessTravelState{Moving};
@@ -36,9 +39,11 @@ public:
code+=launch+r'''
return true;}
};
'''+start+r'''
'''+start+'\nvoid prepareMovement(Player* player){\n'+cast_setup+'\n}\n'+r'''
int main(){
ClientlessTravelMgr mgr;Player p;Position goal;
prepareMovement(&p);assert(p.castsStopped==0); // native move-allowed channel survives
p.blocked=true;prepareMovement(&p);assert(p.castsStopped==1);p.blocked=false;
mgr.StartTravel(&p,goal,false,false,true);
assert(motion.walk&&mgr._records[26].Walk&&!mgr._records[26].AllowGroundMount);
mgr.LaunchPath(&p,mgr._records[26]);assert(motion.walk); // repath preserves gait
@@ -0,0 +1,113 @@
"""Execute the production native companion/item adapter against deterministic core doubles."""
from pathlib import Path
import subprocess,tempfile
root=Path(__file__).resolve().parents[2]
source=(root/'src/server/game/World/ClientlessCompanionMgr.cpp').read_text()
header=(root/'src/server/game/World/ClientlessCompanionMgr.h').read_text()
header='\n'.join(line for line in header.splitlines() if not line.startswith('#include'))
code=r'''
#include <cassert>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include <optional>
#include <unordered_map>
using uint32=uint32_t;using uint8=uint8_t;
#define TC_GAME_API
#define TC_LOG_INFO(...) ((void)0)
#define TC_LOG_WARN(...) ((void)0)
struct ObjectGuid {int id=0;bool IsEmpty()const{return !id;}bool operator==(ObjectGuid b)const{return id==b.id;}bool operator!=(ObjectGuid b)const{return id!=b.id;}std::string ToString()const{return std::to_string(id);}uint32 GetCounter()const{return id;}};
constexpr float DEFAULT_FOLLOW_ANGLE=1.0f;
constexpr uint32 SPELL_EFFECT_SUMMON=28,UNIT_CREATED_BY_SPELL=1,ITEM_SPELLTRIGGER_ON_USE=0,TARGET_FLAG_UNIT_MINIPET=0x10000,SPELL_CAST_OK=0;
enum class SummonTitle{Pet,Companion};
struct Properties{int Title=1;}props;
struct PropertyStore{Properties const* LookupEntry(uint32 id){return id==41?&props:nullptr;}}propertyStore;auto& sSummonPropertiesStore=propertyStore;
struct SpellInfo {uint32 Id=10673;bool channel=false,moveAllowed=false;struct Effect{uint32 Effect=28,MiscValueB=41;};std::vector<Effect> Effects{{}};bool IsChanneled()const{return channel;}bool IsMoveAllowedChannel()const{return moveAllowed;}};
struct Spells{SpellInfo cat,leash;Spells(){leash.Id=48722;leash.Effects.clear();}SpellInfo const* GetSpellInfo(uint32 id){return id==10673?&cat:id==48722?&leash:nullptr;}}spells;auto sSpellMgr=&spells;
struct ItemTemplate{struct Effect{int Trigger=0,SpellID=48722;};std::vector<Effect> Effects{{}};}itemTemplate;
struct Item{ItemTemplate const* GetTemplate(){return &itemTemplate;}uint8 GetBagSlot(){return 255;}uint8 GetSlot(){return 24;}ObjectGuid GetGUID(){return {7};}}item;
struct Objects{ItemTemplate const* GetItemTemplate(uint32 id){return id==37460?&itemTemplate:nullptr;}}objects;auto sObjectMgr=&objects;
namespace WorldPackets::Spells {struct SpellCastRequest{int SpellID=0;struct {uint32 Flags=0;std::optional<ObjectGuid> Unit;}Target;};}
struct SpellCastRequestItemData{uint8 bag,slot;ObjectGuid guid;SpellCastRequestItemData(uint8 b,uint8 s,ObjectGuid g):bag(b),slot(s),guid(g){}};
struct PendingSpellCastRequest{WorldPackets::Spells::SpellCastRequest request;SpellCastRequestItemData item;PendingSpellCastRequest(WorldPackets::Spells::SpellCastRequest&& r,SpellCastRequestItemData i):request(r),item(i){}};
struct Creature{
struct Motion{int follows=0;template<class T>void MoveFollow(T*,float distance,float,bool formation,bool catchUp,bool face){assert(distance==1.5f&&!formation&&catchUp&&face);++follows;}}motion;
Motion* GetMotionMaster(){return &motion;}
ObjectGuid guid{2},owner{26};uint32 created=10673;bool alive=true,aura=false,summon=true;int dismissals=0,removals=0;
bool IsAlive(){return alive;}ObjectGuid GetOwnerGUID(){return owner;}ObjectGuid GetGUID(){return guid;}uint32 GetUInt32Value(uint32){return created;}uint32 GetEntry(){return 7385;}
bool HasAura(uint32 id,ObjectGuid caster){return aura&&id==48722&&caster.id==26;}
void RemoveAurasDueToSpell(uint32 id,ObjectGuid caster){assert(id==48722&&caster.id==26);aura=false;++removals;}
bool IsSummon(){return summon;}Creature* ToTempSummon(){return this;}void UnSummon(){++dismissals;alive=false;}
};
struct History{bool cooldown=false;bool HasGlobalCooldown(SpellInfo const*){return cooldown;}};
struct Player{
bool learned=false,alive=true,combat=false,transport=false,taxi=false,flying=false,teleport=false,hasItem=false,casting=false,requestAllowed=true,applyLeash=true;
Creature* pet=nullptr;int casts=0,learns=0,adds=0,requests=0,interrupts=0;History history;
float GetExactDist(Creature*){return 2;}bool IsWithinLOSInMap(Creature*){return true;}
ObjectGuid GetGUID(){return {26};}bool IsInWorld(){return true;}bool IsAlive(){return alive;}bool IsInCombat(){return combat;}bool GetTransport(){return transport;}bool IsInFlight(){return taxi;}bool IsFlying(){return flying;}bool IsBeingTeleported(){return teleport;}
bool HasSpell(uint32 id){return id==10673&&learned;}void LearnSpell(uint32 id,bool){assert(id==10673);learned=true;++learns;}
Item* GetItemByEntry(uint32 id){return hasItem&&id==37460?&item:nullptr;}bool AddItem(uint32 id,int count){assert(id==37460&&count==1);++adds;hasItem=true;return true;}
ObjectGuid GetCritterGUID(){return pet?pet->guid:ObjectGuid{};}
int CastSpell(Player*,uint32 id,bool triggered){assert(id==10673&&!triggered);++casts;return SPELL_CAST_OK;}
bool CanRequestSpellCast(SpellInfo const*){return requestAllowed;}History* GetSpellHistory(){return &history;}bool IsNonMeleeSpellCast(bool){return casting;}
void RequestSpellCast(std::unique_ptr<PendingSpellCastRequest> r,SpellInfo const* s){
assert(s->Id==48722&&r->request.SpellID==48722&&r->request.Target.Flags==TARGET_FLAG_UNIT_MINIPET);
assert(r->request.Target.Unit->id==2&&r->item.guid.id==7&&r->item.bag==255&&r->item.slot==24);
++requests;if(pet&&applyLeash)pet->aura=true;
}
void InterruptNonMeleeSpells(bool,uint32 id){assert(id==10673||id==48722);++interrupts;}
};
namespace ObjectAccessor{Creature* GetCreature(Player& p,ObjectGuid guid){return p.pet&&p.pet->guid==guid?p.pet:nullptr;}}
'''
code+=header+'\n'+source[source.index('namespace\n'):]+r'''
int main(){
Player p;ClientlessCompanionScene s;Creature cat;
assert(!ClientlessCompanionMgr::Prepare(&p,123,37460));assert(p.learns==0&&p.adds==0);
assert(ClientlessCompanionMgr::Prepare(&p,10673,37460));assert(p.learns==1&&p.adds==1);
assert(ClientlessCompanionMgr::Prepare(&p,10673,37460));assert(p.learns==1&&p.adds==1);
p.combat=true;assert(!ClientlessCompanionMgr::Start(&p,s,10673,37460));p.combat=false;
assert(ClientlessCompanionMgr::Start(&p,s,10673,37460)&&p.casts==1&&s.Summoned);
for(int i=0;i<4;++i)assert(ClientlessCompanionMgr::Update(&p,s,1000)&&!s.Ready);
p.pet=&cat;assert(ClientlessCompanionMgr::Update(&p,s,1000)&&s.Companion.id==2&&p.requests==1&&!s.Ready&&cat.motion.follows==1);
assert(ClientlessCompanionMgr::Update(&p,s,1000)&&s.Ready&&cat.aura);
ClientlessCompanionMgr::Reset(&p,s);assert(cat.dismissals==1&&cat.removals==1&&!s.Active);
// Reuse a matching pre-existing pet and leash without taking ownership of either.
cat.alive=true;cat.aura=true;p.pet=&cat;
assert(ClientlessCompanionMgr::Start(&p,s,10673,37460)&&!s.Summoned);
assert(ClientlessCompanionMgr::Update(&p,s,1000)&&s.Ready&&!s.OwnLeash);
ClientlessCompanionMgr::Reset(&p,s);assert(cat.dismissals==1&&cat.removals==1&&cat.aura&&cat.motion.follows==1);
// Another owner's or another spell's companion must not be replaced or used.
cat.owner.id=99;assert(!ClientlessCompanionMgr::Start(&p,s,10673,37460));cat.owner.id=26;
cat.created=999;assert(!ClientlessCompanionMgr::Start(&p,s,10673,37460));cat.created=10673;
// Missing optional item does not block a real pet walk.
p.hasItem=false;cat.aura=false;assert(ClientlessCompanionMgr::Start(&p,s,10673,37460));
assert(ClientlessCompanionMgr::Update(&p,s,1000)&&s.Ready);ClientlessCompanionMgr::Reset(&p,s);
// The native item request is deferred until it can execute immediately.
p.hasItem=true;p.history.cooldown=true;assert(ClientlessCompanionMgr::Start(&p,s,10673,37460));
auto requests=p.requests;assert(ClientlessCompanionMgr::Update(&p,s,1000)&&!s.Ready&&p.requests==requests);
p.history.cooldown=false;p.applyLeash=false;assert(ClientlessCompanionMgr::Update(&p,s,1000));
for(int i=0;i<8;++i)assert(ClientlessCompanionMgr::Update(&p,s,1000));assert(s.Ready&&!cat.aura);
p.pet=nullptr;assert(!ClientlessCompanionMgr::Update(&p,s,1000));ClientlessCompanionMgr::Reset(&p,s);
// No fabricated companion after an unsuccessful normal summon cast.
assert(ClientlessCompanionMgr::Start(&p,s,10673,37460));
bool active=true;for(int i=0;i<10&&active;++i)active=ClientlessCompanionMgr::Update(&p,s,1000);
assert(!active&&!s.Ready);ClientlessCompanionMgr::Reset(&p,s);
// Immobile channels are skipped; native move-allowed leash channels are used.
p.pet=&cat;spells.leash.channel=true;assert(ClientlessCompanionMgr::Start(&p,s,10673,37460));
requests=p.requests;assert(ClientlessCompanionMgr::Update(&p,s,1000)&&s.Ready&&p.requests==requests);
ClientlessCompanionMgr::Reset(&p,s);spells.leash.moveAllowed=true;p.applyLeash=true;
assert(ClientlessCompanionMgr::Start(&p,s,10673,37460));
assert(ClientlessCompanionMgr::Update(&p,s,1000)&&p.requests==requests+1);
assert(ClientlessCompanionMgr::Update(&p,s,1000)&&s.Ready&&cat.aura);
cat.aura=false;requests=p.requests;
assert(ClientlessCompanionMgr::Update(&p,s,1000)&&!s.Ready&&s.LeashRetries==1);
assert(ClientlessCompanionMgr::Update(&p,s,1000)&&p.requests==requests+1);
assert(ClientlessCompanionMgr::Update(&p,s,1000)&&s.Ready&&s.Leashed);
}
'''
with tempfile.TemporaryDirectory(prefix='vcore-companion-') as folder:
cpp=Path(folder)/'test.cpp';binary=Path(folder)/'test';cpp.write_text(code)
subprocess.run(['c++','-std=c++17',str(cpp),'-o',str(binary)],check=True)
subprocess.run([str(binary)],check=True)
print('Native companion: provisioning, delayed summon, ownership, real item target, leash confirmation/timeout, reuse and cleanup passed.')
+7 -1
View File
@@ -56,7 +56,7 @@ class ClientlessFlightMgr{public:
uint32 areaCode=87,zoneCode=12;
std::pair<uint32,uint32> AreaZone(Player const*,uint32,Position const&)const{return {areaCode,zoneCode};}
bool Enabled(Player const*)const;bool Preferred(Player const*,uint32,Position const&)const;
float Estimate(Player const*,uint32,Position const&,Position const&)const;
float Estimate(Player const*,uint32,Position const&,Position const&,bool recovery=false)const;
void Fail(Player*,Record&,std::string const&);
};
'''
@@ -79,6 +79,12 @@ int main(){
assert(mgr.Estimate(&p,571,a,b)<roadBias); // explicit zone preference
b.x=90;assert(std::isfinite(mgr.Estimate(&p,571,a,b)));
mgr.zoneCode=12;assert(!std::isfinite(mgr.Estimate(&p,0,a,b)));
// Recovery ignores normal RP minimum distance and zone preference, not native permissions.
assert(std::isfinite(mgr.Estimate(&p,0,a,b,true)));
p.license=false;assert(!std::isfinite(mgr.Estimate(&p,0,a,b,true)));p.license=true;
area.fly=false;assert(!std::isfinite(mgr.Estimate(&p,0,a,b,true)));area.fly=true;
b.x=21000;assert(!std::isfinite(mgr.Estimate(&p,0,a,b)));
assert(std::isfinite(mgr.Estimate(&p,0,a,b,true)));
// Unknown metadata is not an invented blanket prohibition; actual native
// mount capability/cast at departure still decides in Start().
areas.known=false;b.x=1000;assert(std::isfinite(mgr.Estimate(&p,0,a,b)));areas.known=true;
@@ -0,0 +1,54 @@
"""Execute native follow speed selection for server-driven player splines."""
from pathlib import Path
import subprocess
import tempfile
root = Path(__file__).resolve().parents[2]
source = (root / 'src/server/game/Movement/MovementGenerators/FollowMovementGenerator.cpp').read_text()
start = source.index('inline float GetVelocity(')
end = source.index('\nstatic void ApplyCatchUpMod', start)
code = r'''
#include <cassert>
enum UnitMoveType { MOVE_WALK, MOVE_RUN, MOVE_FLIGHT };
struct Spline {
bool finalized=true;float velocity=2.5f;
bool Finalized(){return finalized;}float Velocity(){return velocity;}
};
struct Unit {
bool player=false,flying=false,walking=false;
Spline spline;Spline* movespline=&spline;
struct { unsigned flags=0;unsigned GetMovementFlags(){return flags;} }m_movementInfo;
bool IsPlayer(){return player;}bool IsFlying(){return flying;}bool IsWalking(){return walking;}
float GetSpeed(UnitMoveType t){return t==MOVE_WALK?2.5f:t==MOVE_FLIGHT?14.f:7.f;}
};
UnitMoveType SelectSpeedType(unsigned flags){return flags?MOVE_WALK:MOVE_RUN;}
'''
code += source[start:end]
code += r'''
int main(){
Unit cat,target;target.player=true;target.spline.finalized=false;
// SetWalk changes spline speed while player movement flags still select running.
assert(target.m_movementInfo.flags==0);
assert(GetVelocity(&cat,&target,true)==2.5f);
// Explicit server velocities, running splines and creature targets follow suit.
target.spline.velocity=1.75f;assert(GetVelocity(&cat,&target,true)==1.75f);
target.spline.velocity=7.f;assert(GetVelocity(&cat,&target,true)==7.f);
target.player=false;target.spline.velocity=3.f;assert(GetVelocity(&cat,&target,true)==3.f);
// Ordinary client movement retains native flag-based gait selection.
target.player=true;target.spline.finalized=true;
assert(GetVelocity(&cat,&target,true)==7.f);
target.m_movementInfo.flags=1;assert(GetVelocity(&cat,&target,true)==2.5f);
target.player=false;assert(GetVelocity(&cat,&target,true)==7.f);
// Independent followers keep their own walk/run/flight speed.
cat.walking=true;assert(GetVelocity(&cat,&target,false)==2.5f);
cat.walking=false;assert(GetVelocity(&cat,&target,false)==7.f);
cat.flying=true;assert(GetVelocity(&cat,&target,false)==14.f);
}
'''
with tempfile.TemporaryDirectory(prefix='vcore-follow-velocity-') as folder:
cpp = Path(folder) / 'test.cpp'
binary = Path(folder) / 'test'
cpp.write_text(code)
subprocess.run(['c++', '-std=c++17', str(cpp), '-o', str(binary)], check=True)
subprocess.run([str(binary)], check=True)
print('Native follow: actual player/creature spline velocity, client gait and independent follower speeds passed.')
+10 -3
View File
@@ -60,18 +60,25 @@ ClientlessTravelGraphMgr g;node(g,"A",0);node(g,"B",1000);edge(g,"A","B",1,1000)
for(float x:{0.f,100.f,700.f,900.f,1000.f}){ClientlessTravelEdgePoint p;p.X=x;g._edgePoints[1].push_back(p);}
ClientlessTravelLocationGoal goal;goal.X=700;goal.Y=20;goal.AnchorRadius=50;
// A stored path exists. Its interior exit must still win without remote MMAP.
auto r=plan(g,{},goal,1);assert(r.back().GroundEndIndex==2);assert(r.back().X==700);
auto r=plan(g,{},goal,1);assert(r.back().GroundEndIndex==2);assert(r.back().X==700);assert(r.back().GroundContinuationNode=="B");
// Preserve a valid entry even when another entry reaches the endpoint cheaper.
Player p;p.x=100;r=plan(g,p,goal,900);assert(r.front().NodeKey=="virtual_ground_entry");assert(r.back().GroundStartIndex==1);assert(r.back().GroundEndIndex==2);
// Reverse direction uses execution-order indices.
ClientlessTravelGraphMgr rev=g;rev._edges.clear();edge(rev,"B","A",1,1000,true);p.x=900;goal.X=100;r=plan(rev,p,goal,1);assert(r.back().EdgeReverse);assert(r.back().GroundStartIndex==1);assert(r.back().GroundEndIndex==3);
ClientlessTravelGraphMgr rev=g;rev._edges.clear();edge(rev,"B","A",1,1000,true);p.x=900;goal.X=100;r=plan(rev,p,goal,1);assert(r.back().EdgeReverse);assert(r.back().GroundStartIndex==1);assert(r.back().GroundEndIndex==3);assert(r.back().GroundContinuationNode=="A");
// Blacklist the nearest failed exit: select the next eligible waypoint.
goal.X=700;goal.AnchorRadius=250;r=plan(g,{},goal,1,{"exit:1:2F"});assert(r.back().GroundEndIndex==3);
// Once a whole direction has been traversed without a usable exit, its stale
// estimate must not cause another optimistic exit plan. The actual road stays usable.
{auto blocked=std::unordered_set<std::string>{"ground_exit_edge:1F"};
std::vector<ClientlessTravelRouteStep> partial;bool complete;std::string reached,error;
goal.AnchorRadius=50;
Player startAtA;assert(g.PlanAdaptiveRoute(&startAtA,"B",1,200,partial,complete,reached,error,nullptr,&blocked,&goal));
assert(!complete);for(auto const& step:partial)assert(step.NodeKey!="virtual_ground_exit");}
// Regular anchors use the same estimated local cost without remote MMAP.
goal.X=1000;goal.Y=0;goal.AnchorRadius=10;r=plan(g,{},goal,1);assert(r.back().NodeKey=="B");assert(r.back().GroundEndIndex==-1);
// Forward entry after a transport can exit on the same road before its endpoint.
ClientlessTravelGraphMgr f=g;node(f,"S",-1000);node(f,"T",100);edge(f,"S","T",2,1,false,"taxi");p.x=-1000;goal.X=700;goal.Y=20;goal.AnchorRadius=50;PathGenerator::available=true;PathGenerator::maxDistance=150;
r=plan(f,p,goal,1);assert(r.back().GroundStartIndex==1);assert(r.back().GroundEndIndex==2);assert(r[r.size()-2].NodeKey=="virtual_ground_entry");
r=plan(f,p,goal,1);assert(r.back().GroundStartIndex==1);assert(r.back().GroundEndIndex==2);assert(r.back().GroundContinuationNode=="B");assert(r[r.size()-2].NodeKey=="virtual_ground_entry");
// A regular node outside the exit radius must not win an optimistic local leg.
ClientlessTravelGraphMgr detour=g;node(detour,"N",300,0);edge(detour,"A","N",5,1,false,"taxi");goal.X=700;goal.Y=20;goal.AnchorRadius=1000;p.x=0;PathGenerator::available=false;
r=plan(detour,p,goal,1);assert(r.back().NodeKey=="virtual_ground_exit");
@@ -0,0 +1,194 @@
"""Execute production optional-exit methods with deterministic movement doubles."""
from pathlib import Path
import subprocess
import tempfile
root = Path(__file__).resolve().parents[2]
source = (root / 'src/server/game/World/ClientlessActionExecutorMgr.cpp').read_text()
header = (root / 'src/server/game/World/ClientlessActionExecutorMgr.h').read_text()
graph = (root / 'src/server/game/World/ClientlessTravelGraphMgr.h').read_text()
def method(signature):
start = source.index(signature)
opening = source.index('{', start)
depth, end = 1, opening + 1
while depth:
depth += (source[end] == '{') - (source[end] == '}')
end += 1
return source[start:end]
preamble = r'''
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <iostream>
#include <limits>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
using uint8=uint8_t; using uint32=uint32_t; using uint64=uint64_t; using int32=int32_t;
#define TC_GAME_API
#define TC_LOG_INFO(...) ((void)0)
#define TC_LOG_WARN(...) ((void)0)
#define TC_LOG_DEBUG(...) ((void)0)
struct Player;
struct ClientlessAction;
struct Position {
float x=0,y=0,z=0,o=0;
void Relocate(float a,float b,float c,float d=0){x=a;y=b;z=c;o=d;}
void Relocate(Player const*);
float GetPositionX()const{return x;} float GetPositionY()const{return y;}
float GetPositionZ()const{return z;} float GetOrientation()const{return o;}
};
struct Motion {
int launches=0;std::vector<Position> path;
void MoveSmoothPath(int,Position const* p,std::size_t count,bool){++launches;path.assign(p,p+count);}
};
struct Player {
float x=0; bool spline=true, flying=false;Motion motion;
bool IsFlying()const{return flying;}bool IsSplineEnabled()const{return spline;}
bool IsInWorld()const{return true;}uint32 GetMapId()const{return 0;}
float GetPositionX()const{return x;}float GetPositionY()const{return 0;}float GetPositionZ()const{return 0;}
float GetOrientation()const{return 0;}
float GetExactDist(float a,float b,float c)const{return std::sqrt((x-a)*(x-a)+b*b+c*c);}
std::string GetName()const{return "Arthoran";}
Motion* GetMotionMaster(){return &motion;}
};
void Position::Relocate(Player const* p){x=p->x;y=z=o=0;}
enum class ClientlessTravelState {Moving,Arrived,PathRejected};
struct ClientlessPathDiagnostics {bool Complete=false;float FinalDistance=0,PathLength=0;};
struct TravelStatus {ClientlessTravelState State=ClientlessTravelState::Moving;};
struct Travel {
bool reachable=false, starts=true,rejectOnStart=false;int probes=0, launches=0,cancels=0;float targetX=0;TravelStatus status;
bool AnalyzePath(Player*,Position const&,ClientlessPathDiagnostics& nav){++probes;nav.Complete=reachable;nav.PathLength=40;return true;}
bool StartTravel(Player* p,Position const& pos,bool){++launches;targetX=pos.x;p->spline=false;status.State=starts&&!rejectOnStart?ClientlessTravelState::Moving:ClientlessTravelState::PathRejected;return starts;}
void CancelTravel(uint32){++cancels;}
TravelStatus const* GetStatus(uint32){return &status;}
} travel;
auto sClientlessTravelMgr=&travel;
enum class ClientlessFlightState {Mounting,Flying,Arrived,Failed};
struct FlightStatus {ClientlessFlightState State=ClientlessFlightState::Mounting;};
struct Flight {
bool enabled=false,starts=false,recovery=false;int launches=0;FlightStatus status;
void BeginPlanning(){}
float Estimate(Player*,uint32,Position const&,Position const&,bool fallback=false){recovery=fallback;return enabled?100:std::numeric_limits<float>::infinity();}
bool Start(Player*,Position const&,std::string&){++launches;return starts;}
FlightStatus const* GetStatus(uint32){return &status;}
} flight;
auto sClientlessFlightMgr=&flight;
struct Lua {void PrepareGroundMount(Player*,float,char const*){}} lua;
auto sClientlessLuaEngine=&lua;
'''
structs = graph[graph.index('struct TC_GAME_API ClientlessTravelNode'):graph.index('class TC_GAME_API ClientlessTravelGraphMgr')]
cls = header[header.index('enum class ClientlessActionExecutionState'):header.index('#define sClientlessActionExecutorMgr')].replace('private:', 'public:')
doubles = r'''
struct Graph {
ClientlessTravelNode node;
std::vector<ClientlessTravelEdgePoint> points;
auto GetEdgePoints(uint32){return &points;}
ClientlessTravelNode const* FindNode(std::string const& key){return key==node.Key?&node:nullptr;}
} graph;
auto sClientlessTravelGraphMgr=&graph;
static int recoveries=0;
bool ClientlessActionExecutorMgr::RecoverTravel(Player*,uint32,Record&,uint32,std::string const&){++recoveries;return false;}
'''
methods = '\n'.join(method(sig) for sig in (
'bool StartGroundConnection(',
'bool ClientlessActionExecutorMgr::PrepareRecordedGroundEdge(',
'bool ClientlessActionExecutorMgr::GetTravelGoal(',
'bool ClientlessActionExecutorMgr::TryFallbackFlight(',
'void ClientlessActionExecutorMgr::CheckGroundExit(',
'bool ClientlessActionExecutorMgr::ResumeRecordedEdge(',
'void ClientlessActionExecutorMgr::UpdateGroundExit(',
'bool ClientlessActionExecutorMgr::StartRecordedEdgePath(',
))
tests = r'''
int main(){
using Record=ClientlessActionExecutorMgr::Record;
ClientlessActionExecutorMgr mgr;Player p;Record r;
r.Active=true;r.Status.ActionName="travel_to_location";r.FinalX=100;r.FinalLocationId="inn";
r.Route.resize(2);r.Route[0].NodeKey="entry";r.Route[1].NodeKey="gate";
r.Route[1].EdgeType="ground";r.Route[1].EdgeDatabaseId=42;r.RouteStep=1;
for(float x:{0.f,20.f,40.f,60.f,80.f,100.f,120.f}){
ClientlessTravelEdgePoint point;point.X=x;r.EdgePath.push_back(point);
}
r.EdgeNextPoint=2;r.RecoveryCount=4;r.ReplanAtGroundEnd=true;
// A virtual plan keeps the entire road after its proposed exit, to the real node.
graph.node.Key="gate";graph.node.X=120;graph.points=r.EdgePath;
Record virtualPlan=r;virtualPlan.Route[1].NodeKey="virtual_ground_exit";
virtualPlan.Route[1].GroundContinuationNode="gate";
virtualPlan.Route[1].GroundStartIndex=1;virtualPlan.Route[1].GroundEndIndex=3;
virtualPlan.Route.push_back({});virtualPlan.Route.back().EdgeType="free_flight";
assert(mgr.PrepareRecordedGroundEdge(virtualPlan));
assert(virtualPlan.Route[1].NodeKey=="gate" && virtualPlan.Route[1].GroundEndIndex==-1);
assert(virtualPlan.Route.size()==3 && virtualPlan.ReplanAtGroundEnd && virtualPlan.GroundFlightExit);
assert(virtualPlan.EdgePath.front().X==20 && virtualPlan.EdgePath.back().X==120);
// Reverse execution retains the same forward-only continuation in execution order.
virtualPlan=r;virtualPlan.Route[1].NodeKey="virtual_ground_exit";
virtualPlan.Route[1].GroundContinuationNode="gate";virtualPlan.Route[1].EdgeReverse=true;
virtualPlan.Route[1].GroundStartIndex=1;virtualPlan.Route[1].GroundEndIndex=3;
assert(mgr.PrepareRecordedGroundEdge(virtualPlan));
assert(virtualPlan.EdgePath.front().X==100 && virtualPlan.EdgePath.back().X==0);
// Twenty failed probes, not five: same route, edge, direction and active spline.
for(int i=0;i<20;++i){
r.GroundExitCheckMs=0;mgr.CheckGroundExit(&p,26,r);
assert(r.Active && p.spline && !r.GroundExitActive && r.EdgePath.size()==7);
assert(r.RouteStep==1 && r.Route.size()==2 && r.Route[1].NodeKey=="gate");
assert(r.RecoveryCount==4 && recoveries==0 && travel.cancels==0 && travel.launches==0);
assert(r.FailedBridgeKeys.empty() && r.FailedEdgeDatabaseIds.empty());
mgr.CheckGroundExit(&p,26,r); // clock has not elapsed: do not probe again
}
assert(travel.probes==20 && r.GroundExitCheckMs==6000);
// A later valid probe selects the exit without pre-emptive route replacement.
travel.reachable=true;r.GroundExitCheckMs=0;mgr.CheckGroundExit(&p,26,r);
assert(r.GroundExitActive && !p.spline && travel.launches==1 && r.EdgePath.size()==7);
// If the accepted exit subsequently fails, reconnect only to the NEXT road point.
p.x=35;travel.status.State=ClientlessTravelState::PathRejected;
mgr.UpdateGroundExit(&p,26,r);
assert(r.GroundResumeActive && !r.GroundExitActive && travel.targetX==40 && recoveries==0);
p.x=40;travel.status.State=ClientlessTravelState::Arrived;mgr.UpdateGroundExit(&p,26,r);
assert(!r.GroundResumeActive && p.motion.launches==1 && p.motion.path.front().x==40);
assert(r.EdgeNextPoint==3 && r.EdgeSplinePoints.front()==3);
for(auto const& point:p.motion.path)assert(point.x>=40); // no replay towards entry
// An execution-query rejection after a successful probe resumes road immediately.
travel.starts=false;r.GroundExitCheckMs=0;p.spline=true;
mgr.CheckGroundExit(&p,26,r);assert(!r.GroundExitActive && p.motion.launches==2 && recoveries==0);
travel.starts=true;travel.rejectOnStart=true;r.GroundExitCheckMs=0;p.spline=true;
mgr.CheckGroundExit(&p,26,r);assert(!r.GroundExitActive && p.motion.launches==3 && recoveries==0);
travel.rejectOnStart=false;
// A successful local exit completes the original destination.
r.GroundExitCheckMs=0;mgr.CheckGroundExit(&p,26,r);
p.x=100;travel.status.State=ClientlessTravelState::Arrived;mgr.UpdateGroundExit(&p,26,r);
assert(!r.Active && r.Status.State==ClientlessActionExecutionState::Completed);
assert(r.Status.TargetName=="location:inn" && r.EdgePath.empty() && recoveries==0);
// A normal graph route with no speculative virtual end can also take a valid exit.
r.Active=true;r.ReplanAtGroundEnd=false;r.EdgePath.resize(1);r.EdgeNextPoint=0;r.GroundExitCheckMs=0;
mgr.CheckGroundExit(&p,26,r);assert(r.GroundExitActive);
// Missing regular travel triggers the separate recovery flight policy.
Record fallback;fallback.Status.ActionName="travel_to_location";fallback.FinalX=260;
flight.enabled=true;flight.starts=true;
assert(mgr.TryFallbackFlight(&p,26,fallback));
assert(flight.recovery && fallback.FlightActive && fallback.RouteStep==1);
assert(fallback.Route.back().X==260 && fallback.FinalLocalLegPending);
assert(!mgr.TryFallbackFlight(&p,26,fallback)); // no repeated accepted fallback
// A failed flight departure from an optional exit also preserves forward road.
Record air;air=r;air.GroundExitActive=false;air.GroundFlightExit=true;air.GroundExitCheckMs=0;
air.EdgeNextPoint=0;air.EdgePath.resize(2);air.EdgePath[0].X=120;air.EdgePath[1].X=140;
travel.reachable=false;flight.starts=false;p.spline=false;
auto before=p.motion.launches;mgr.CheckGroundExit(&p,26,air);
assert(!flight.recovery && p.motion.launches==before+1 && !air.GroundExitActive);
// End-point rejoin does not launch an empty spline and fail the journey.
air.GroundResumeActive=true;air.GroundExitActive=false;p.x=140;travel.status.State=ClientlessTravelState::Arrived;
before=p.motion.launches;mgr.UpdateGroundExit(&p,26,air);
assert(!air.GroundResumeActive && p.motion.launches==before && air.Active);
std::cout<<"optional exits: repeated probes, retained route, delayed success, forward resume, flight fallback passed\n";
}
'''
with tempfile.TemporaryDirectory(prefix='vcore-ground-exit-') as folder:
cpp = Path(folder) / 'test.cpp'
binary = Path(folder) / 'test'
cpp.write_text(preamble + structs + cls + doubles + methods + tests)
subprocess.run(['c++', '-std=c++17', '-Wall', '-Wextra', '-Wno-unused-parameter', str(cpp), '-o', str(binary)], check=True)
subprocess.run([str(binary)], check=True)
@@ -0,0 +1,57 @@
"""Check production local spline splitting against native MoveSpline validation."""
from pathlib import Path
import subprocess,tempfile
root=Path(__file__).resolve().parents[2]
s=(root/'src/server/game/World/ClientlessTravelMgr.cpp').read_text()
a=s.index(' std::vector<Position> spline;');b=s.index(' if (spline.size() < 2)',a)
build=s[a:b]
n=(root/'src/server/game/Movement/Spline/MoveSpline.cpp').read_text()
a=n.index('bool MoveSplineInitArgs::_checkPathLengths()');b=n.index('\n/// ===',a);native=n[a:b]
code=r'''
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <random>
#include <vector>
using uint32=uint32_t;
constexpr float MAX_LOCAL_SPLINE_LENGTH=45;
struct Vector3{
float x=0,y=0,z=0;
Vector3 operator-(Vector3 b)const{return {x-b.x,y-b.y,z-b.z};}
Vector3 operator+(Vector3 b)const{return {x+b.x,y+b.y,z+b.z};}
Vector3 operator/(float f)const{return {x/f,y/f,z/f};}
float length()const{return std::hypot(std::hypot(x,y),z);}
};
struct Position:Vector3{
void Relocate(float a,float b,float c,float){x=a;y=b;z=c;}
float GetPositionX()const{return x;}float GetPositionY()const{return y;}float GetPositionZ()const{return z;}
};
struct Player{float GetPositionX(){return 0;}float GetPositionY(){return 0;}float GetPositionZ(){return 0;}float GetOrientation(){return 0;}};
float Distance3D(float ax,float ay,float az,float bx,float by,float bz){return (Vector3{ax,ay,az}-Vector3{bx,by,bz}).length();}
struct MoveSplineInitArgs{std::vector<Vector3>path;struct{bool UncompressedPath=false;}flags;bool _checkPathLengths();};
'''+native+'\nstd::vector<Position> Build(Player* player,std::vector<Vector3> const& points){\n'+build+'\nreturn spline;\n}\n'+r'''
void check(std::vector<Position> const& p){
assert(p.size()>=2);MoveSplineInitArgs native;float length=0;
for(auto v:p)native.path.push_back({v.x,v.y,v.z});
assert(native._checkPathLengths());
for(std::size_t i=1;i<p.size();++i){float d=(p[i]-p[i-1]).length();assert(d>=0.1f);length+=d;}
assert(length<=45.001f);
}
int main(){
Player p;
// Native validation really rejects the old tiny clipped remainder.
MoveSplineInitArgs bad;bad.path={{0,0,0},{44.95f,0,0},{45,0,0}};assert(!bad._checkPathLengths());
auto clipped=Build(&p,{{0,0,0},{4,0,0},{20,0,0},{44.95f,0,0},{49,0,0}});
check(clipped);assert(clipped.back().x==44.95f);
auto tiny=Build(&p,{{0,0,0},{1,0,0},{1.06f,0,0},{2,0,0}});check(tiny);assert(tiny.size()==3);
auto normal=Build(&p,{{0,0,0},{10,0,0},{30,0,0},{50,0,0}});check(normal);assert(normal.back().x==45);
std::mt19937 rng(26);std::uniform_real_distribution<float> step(0.001f,5);
for(int trial=0;trial<1000;++trial){std::vector<Vector3> points{{0,0,0}};float x=0;for(int i=0;i<60;++i){x+=step(rng);points.push_back({x,0,0});}check(Build(&p,points));}
}
'''
with tempfile.TemporaryDirectory(prefix='vcore-spline-split-') as folder:
cpp=Path(folder)/'test.cpp';binary=Path(folder)/'test';cpp.write_text(code)
subprocess.run(['c++','-std=c++17',str(cpp),'-o',str(binary)],check=True)
subprocess.run([str(binary)],check=True)
print('Production local spline clipping passes native path-length validation, tiny remainders and 1000 sampled paths.')
+13 -3
View File
@@ -5,9 +5,19 @@ import tempfile
ROOT = Path(__file__).resolve().parents[2]
source = (ROOT / 'src/server/game/World/ClientlessActionExecutorMgr.cpp').read_text()
a = source.index('bool ClientlessActionExecutorMgr::StartPortalUse(')
b = source.index('bool ClientlessActionExecutorMgr::StartRecordedEdgePath(', a)
methods = source[a:b]
def production_method(signature):
start = source.index(signature)
opening = source.index('{', start)
depth, end = 1, opening + 1
while depth:
depth += (source[end] == '{') - (source[end] == '}')
end += 1
return source[start:end]
methods = '\n'.join(production_method(signature) for signature in (
'bool ClientlessActionExecutorMgr::StartPortalUse(',
'void ClientlessActionExecutorMgr::UpdatePortalUse(',
))
harness = r'''
#include <algorithm>
#include <cassert>