WIP: checkpoint AdventureBot intents and unfinished profession implementation
This commit is contained in:
1 parent
119d63ed1c
commit
722b513ee0
83 files changed
+11946
-199
No files matched your search
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
+97
@@ -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
|
||||
+12
@@ -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.
|
||||
+30
@@ -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.
|
||||
+24
@@ -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.
|
||||
+32
@@ -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.
|
||||
+29
@@ -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.
|
||||
+107
@@ -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.
|
||||
+24
@@ -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)
|
||||
@@ -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.
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 ↦}
|
||||
@@ -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:
|
||||
|
||||
@@ -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 ↦}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);
|
||||
|
||||
@@ -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.')
|
||||
@@ -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:
|
||||
|
||||
@@ -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.')
|
||||
@@ -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.')
|
||||
@@ -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.')
|
||||
@@ -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.')
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user