Add AdventureBot social interactions and player-led groups

This commit is contained in:
Nico committed 2026-10-28 01:11:54 +01:00
1 parent 39a515dbd1
commit e333f2f9fc
29 files changed
+2003 -29

No files matched your search

+48
View File
@@ -1,3 +1,51 @@
# Aktuell: Interaktionen und spielergeführte Gruppen umgesetzt
Stand 28.10.2026. Nutzer beauftragte Umsetzung nach gemeinsamer Planung.
Branch feature/clientless-player-core; vorher sauber. Nutzer hat den lokalen
Commit dieses Stands beauftragt; kein Push. Neuer AdventureInteractionMgr/Policy,
generische GUID-Queues für Textemotes und tatsächliche Kampfhilfe, native
Einladungsantworten, intent-/persönlichkeitsabhängige Annahme, MMAP-Folgen und
TravelGraph-Handoff, grundlegende Gruppen-Kampfunterstützung. Spieler/Pet-
Selbstverteidigung im bestehenden CombatMgr, keine eigenständigen PvP-Angriffe.
Details und Grenzen in doc/clientless/interactions.md.
Austritt nur außerhalb Kampf/Tod/Instanz/Transport/Flug. Intent-/Locationwechsel
oder manuelle Blockfrist erlaubt Entscheidung; group_loyalty beeinflusst Bleiben
(Arthoran0.35, Standard0.25). Native AFK oder beobachtete Inaktivität des
Anführers volle600s; Führungswechsel beginnt neues Zeitfenster. Chat/Emotes,
Zauber/Itemanfragen, Bewegung/Blick/Ziel, Kampf/Casts zählen als Aktivität.
Native Gruppenmitgliedschaft bleibt über Neustart; Beobachtungsfristen starten
neu. Rollenprofile, autonome Bot-Gruppen, freie Chatgespräche und eigenständige
PvP-Erstangriffe bleiben ausdrücklich spätere Arbeit.
Debug-Build-j10 erfolgreich installiert. 57 Testskripte bestanden: vollständiger
56er-Lauf plus neuer Social-Queue-Concurrencytest; der anschließend erweiterte
Combat-Test für echten Spielerfokus zusätzlich erneut erfolgreich. Native
Testserver PID59086, frische Ready-Meldung und Port8085 bestätigt. Arthoran26
online, Persönlichkeit8Werte, neue Diagnose clientless interaction26 erfolgreich:
sociability0.65/loyalty0.35, keine Gruppe. Echte Kreaturen-Selbstverteidigung
nach Neustart weiterhin beobachtet, kein Beleg für Live-PvP/Gruppenannahme.
Arthorans unveränderte initiale Profilvorgabe startete bei Login die normale
Goldhain-Reise. clientless adventure stop26 wurde angefordert, letzte Diagnose
noch Switching/waiting for safe player/transport state. Keine Teleportierung,
kein temporärer Profiloverride; normale Herkunftsdateien bleiben erhalten.
Für Live-Abnahme Position und Reise zuerst erneut prüfen. Gruppeneinladungen
werden in Flug-/Transport-/Kampfzuständen bis zur sicheren Lage zurückgestellt.
Beim sauberen Shutdown der ALTEN, vor Installation laufenden Binärdatei trat
Object.h:448 GetMap ASSERTION FAILED m_currMap auf. AlterProzess49765 endete;
keine fremden Prozesse beendet. Das ist ein beobachteter Shutdownfehler des
Ausgangsstands, Ursache hier nicht untersucht/behoben. Neuer Start erfolgreich.
Offene Gameplay-Abnahmen: echte Spielereinladungen, zwei gleichzeitige Bots,
PvP/Pet-Verteidigung, native Gruppenportale/kartenübergreifendes Folgen,
zehn echte Minuten AFK. Klassenrotationen außer Krieger und Supportrollen
weiterhin begrenzt. Belege: checkpoints/2026-10-28-interactions/.
Logs /tmp/vcore-interactions-{build-final,install}.log und zugehörige Tests.
---
# Aktuell: Selbstversorgung und gemeinsame Nebenberufe abgeschlossen
Stand 27.10.2026. Branch feature/clientless-player-core, vorheriger Commit
@@ -0,0 +1,22 @@
[1/10] 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/10] Building CXX object src/server/game/CMakeFiles/game.dir/World/AdventureCombatMgr.cpp.o
[3/10] Building CXX object src/server/game/CMakeFiles/game.dir/World/ClientlessLuaEngine.cpp.o
[4/10] Building CXX object src/server/game/CMakeFiles/game.dir/World/AdventureInteractionMgr.cpp.o
[5/10] Building CXX object src/server/game/CMakeFiles/game.dir/World/AdventureSupplyMgr.cpp.o
[6/10] 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:2341:20: warning: unused variable ‘loaded’ [-Wunused-variable]
2341 | 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:2378:20: warning: unused variable ‘loaded’ [-Wunused-variable]
2378 | 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:2402:20: warning: unused variable ‘loaded’ [-Wunused-variable]
2402 | bool const loaded = sClientlessWorldPointMgr->Reload();
| ^~~~~~
[7/10] Building CXX object src/server/game/CMakeFiles/game.dir/World/AdventureBehaviorMgr.cpp.o
[8/10] Linking CXX static library src/server/game/libgame.a
[9/10] Linking CXX static library src/server/scripts/libscripts.a
[10/10] Linking CXX executable src/server/worldserver/worldserver
@@ -0,0 +1 @@
Production combat focus, damage ordering, risk filters, corpse radius/delay, alternate paths and malus-free persisted fallback passed.
@@ -0,0 +1,5 @@
/tmp/tmp2j7tsqhj/test.cpp: In member function ‘void AdventureInteractionMgr::Leave(Player*, Record&, const char*)’:
/tmp/tmp2j7tsqhj/test.cpp:140:69: warning: unused parameter ‘reason’ [-Wunused-parameter]
140 | void AdventureInteractionMgr::Leave(Player* p,Record& r,char const* reason)
| ~~~~~~~~~~~~^~~~~~
production interaction manager: invitations, factions, reservations, follow, AFK and deferred departure passed
@@ -0,0 +1 @@
Production social/helper queues: concurrent GUID-only delivery, bounded queues, human/self/zero-damage gates, pet attribution and lethal-cleanup ordering passed
@@ -0,0 +1,114 @@
Validating spell scripts...
Spell `85123` Effect `Index: EFFECT_0 Target: 7` of script `spell_siege_cannon`
did not match dbc effect data - handler bound to hook `OnObjectAreaTargetSelect`
of SpellScript won't be executed
>> Validated 3470 scripts in 12 ms
Loading SmartAI scripts...
>> Loaded SmartAIMgr Helpers in 16 ms
>> Loaded 13506 SmartAI scripts in 167 ms
Loading Calendar data...
>> Loaded 0 calendar events in 1 ms
>> Loaded 0 calendar invites in 0 ms
Loading Petitions...
>> Loaded 0 petitions.
Loading Signatures...
>> Loaded 0 Petition signs!
Loading Summon Properties parameter data...
>> Loaded 78 summon properties parameters in 1 ms
Loading Item loot...
>> Loaded 0 stored item loots
>> Loaded 0 stored item money
Initialize query data...
>> Initialized query cache data in 1019 ms
Initialize commands...
Initialize game time and timers
Mail timer set to: 1260, mail return is called every 1440 minutes
Starting Map System
Starting Game Event system...
Initialize AuctionHouseBot...
Initializing Opcodes...
Starting Arena Season...
Starting Battleground System
>> Loaded 15 battlegrounds in 0 ms
Starting Outdoor PvP System
>> Loaded 5 outdoor PvP definitions in 1 ms
Starting Battlefield System
>> Loaded 2 battlefields in 0 ms
Loading Warden Checks...
Loading Warden Action Overrides...
Deleting expired bans...
Initializing quest reset times...
Calculate random battleground reset time...
Calculate guild limitation(s) reset time...
Calculate next currency reset time...
World initialized in 0 minutes 13 seconds
TC>TC>TrinityCore rev. 39a515dbd13e+ 2026-10-27 23:56:43 +0100 (feature/clientle
ss-player-core branch) (Unix, Debug, Static) (worldserver-daemon) ready...
clientless login 26
TC>Registered clientless session for character GUID 26 (account 6), state=Loadin
g.
Created clientless player session for character GUID 26 (account 6).
ClientlessPlayerMgr started character GUID 26 (account 6).
Clientless login started for 'Arthoran' (GUID 26, account 6).
TC> Loading saved respawn time of 1790175804 for spawnid (0,323981) - spawn does
not exist, ignoring
Loading saved respawn time of 1790090644 for spawnid (0,396680) - spawn does not
exist, ignoring
Loading saved respawn time of 1790019808 for spawnid (0,396681) - spawn does not
exist, ignoring
Loading saved respawn time of 1790019853 for spawnid (0,396682) - spawn does not
exist, ignoring
Initialized transport GameObject grids: map=0 instance=0 grids=4 candidates=15
Clientless player character GUID 26 (account 6) reached state=Online.
ClientlessLua initialized, script root '/home/wow_admin/wow_cata_server/source/T
rinityCore/src/server/scripts/Custom/clientless'.
[AdventureBot][Profile] loaded guid=26 name='Arthoran' source='/home/wow_admin/w
ow_cata_server/source/TrinityCore/src/server/scripts/Custom/clientless/adventure
/profiles/arthoran.lua' home='elwynn' canGroundMount=1 mounts=1 mountMinDistance
=45.0 mountSpeedRate=1.60 personality=8 regionAffinity=4 activityAffinity=7
PROFILE_PROFESSIONS guid=26 primary=2
ClientlessLua[26]: Arthoran ready; no automatic test journey queued
ClientlessLua[26]: executed adventure override script '/home/wow_admin/wow_cata_
server/source/TrinityCore/src/server/scripts/Custom/clientless/adventure/overrid
es/arthoran.lua'.
ADVENTURE_CATALOG locations=2973 actions=84 lines=76
INTENT_TRAVEL guid=26 intent='social' location='goldshire_tavern' area=0
[AdventureBot] loaded 3966 clientless world points.
[AdventureBot] loaded travel graph: 993 node(s), 1765 directed edge(s).
clientless interaction 26
TC>Interactions guid=26 grouped=0 leader=0 partner=0 intent='social' schedule=''
leaderIdleSeconds=0 afkSeconds=0 sociability=0.65 loyalty=0.35
TC> SUPPLY_RESTOCK guid=26 day=20261028 daily=0 afterCombat=1
COMBAT_START guid=26 target='GUID Full: 0xf130a1fe000006ce Type: Creature Entry:
41470 Low: 1742' training=0 defending=1 until=0
COMBAT_CAST guid=26 target='GUID Full: 0x000000000000001a Type: Player Low: 26'
spell=6673
COMBAT_CAST guid=26 target='GUID Full: 0xf130a1fe000006ce Type: Creature Entry:
41470 Low: 1742' spell=78
COMBAT_CAST guid=26 target='GUID Full: 0xf130a1fe000006ce Type: Creature Entry:
41470 Low: 1742' spell=12294
COMBAT_CAST guid=26 target='GUID Full: 0xf130a1fe000006ce Type: Creature Entry:
41470 Low: 1742' spell=772
COMBAT_END guid=26 target='GUID Full: 0xf130a1fe000006ce Type: Creature Entry: 4
1470 Low: 1742' training=0
INTENT_TRAVEL guid=26 intent='social' location='goldshire_tavern' area=0
SUPPLY_RESTOCK guid=26 day=20261028 daily=0 afterCombat=1
COMBAT_START guid=26 target='GUID Full: 0xf130a1ff000006d0 Type: Creature Entry:
41471 Low: 1744' training=0 defending=1 until=0
COMBAT_CAST guid=26 target='GUID Full: 0xf130a1ff000006d0 Type: Creature Entry:
41471 Low: 1744' spell=12294
COMBAT_END guid=26 target='GUID Full: 0xf130a1ff000006d0 Type: Creature Entry: 4
1471 Low: 1744' training=0
SUPPLY_RESTOCK guid=26 day=20261028 daily=0 afterCombat=1
INTENT_TRAVEL guid=26 intent='social' location='goldshire_tavern' area=0
clientless adventure stop 26
TC>Behavior stop requested at a safe point.
TC> clientless adventure status 26
TC>clientless interaction 26
TC>Adventure GUID 26: enabled=1 schedule=0 phase=3 intent='social' location='gol
dshire_tavern' micro='' deadline=0 detail='waiting for safe player/transport sta
te'
TC> Interactions guid=26 grouped=0 leader=0 partner=0 intent='social' schedule='
'
leaderIdleSeconds=0 afkSeconds=0 sociability=0.65 loyalty=0.35
TC>
@@ -0,0 +1,968 @@
TrinityCore rev. 39a515dbd13e+ 2026-10-27 23:56:43 +0100 (feature/clientless-pla
yer-core branch) (Unix, Debug, Static) (worldserver-daemon)
<Ctrl-C> to stop.
______ __
/\__ _\ __ __/\ \__
\/_/\ \/ _ __ /\_\ ___ /\_\ \, _\ __ __
\ \ \/\`'__\/\ \ /' _ `\/\ \ \ \/ /\ \/\ \
\ \ \ \ \/ \ \ \/\ \/\ \ \ \ \ \_\ \ \_\ \
\ \_\ \_\ \ \_\ \_\ \_\ \_\ \__\\/`____ \
\/_/\/_/ \/_/\/_/\/_/\/_/\/__/ `/___/> \
C O R E /\___/
http://TrinityCore.org \/__/
Using configuration file /home/wow_admin/wow_cata_server/server/debug/etc/worlds
erver.conf.
Using SSL version: OpenSSL 3.5.5 27 Jan 2026 (library: OpenSSL 3.5.5 27 Jan 2026
)
Using Boost version: 1.90.0
Automatic database updates are disabled for all databases!
Realm running as realm ID 2 region 2 battlegroup 1
Using World DB: TDB 434.22011
Using enUS DBC Locale
Will clear `logs` table of entries older than 1209600 seconds every 10 minutes.
Client cache version set to: 22011
Using DataDir /home/wow_admin/wow_cata_server/data/
WORLD: MMap data directory is: /home/wow_admin/wow_cata_server/data/mmaps
VMap support included. LineOfSight: 1, getHeight: 1, indoorCheck: 1
VMap data directory is: /home/wow_admin/wow_cata_server/data/vmaps
Missing name DungeonFinder.LowPopulation.Enable in config file /home/wow_admin/w
ow_cata_server/server/debug/etc/worldserver.conf, add "DungeonFinder.LowPopulati
on.Enable = 0" to this file
Missing name DungeonFinder.LowPopulation.FallbackDelay in config file /home/wow_
admin/wow_cata_server/server/debug/etc/worldserver.conf, add "DungeonFinder.LowP
opulation.FallbackDelay = 60" to this file
Missing name DungeonFinder.LowPopulation.MinPlayers in config file /home/wow_adm
in/wow_cata_server/server/debug/etc/worldserver.conf, add "DungeonFinder.LowPopu
lation.MinPlayers = 1" to this file
Missing name DungeonFinder.LowPopulation.SoloGroupOutsideTimeout in config file
/home/wow_admin/wow_cata_server/server/debug/etc/worldserver.conf, add "DungeonF
inder.LowPopulation.SoloGroupOutsideTimeout = 300" to this file
Loading Trinity strings...
>> Loaded 991 trinity strings in 7 ms
Initialize data stores...
>> Initialized 167 DBC data stores in 857 ms
>> Initialized 5 DB2 data stores in 200 ms
>> Loading Cinematic Camera files
>> Loaded 0 cinematic waypoint sets in 0 ms
Loading IP Location Database...
Initializing PlayerDump tables...
>> Initialized tables for PlayerDump in 14 ms.
Loading SpellInfo store...
>> Loaded SpellInfo store in 110 ms
Loading SpellInfo corrections...
>> Loaded SpellInfo corrections in 22 ms
Loading SkillLineAbilityMultiMap Data...
>> Loaded 8384 SkillLineAbility MultiMap Data in 3 ms
Loading SpellInfo custom attributes...
>> Loaded 195 spell custom attributes from DB in 2 ms
>> Loaded SpellInfo custom attributes in 76 ms
Loading SpellInfo diminishing infos...
>> Loaded SpellInfo diminishing infos in 5 ms
Loading SpellInfo immunity infos...
>> Loaded SpellInfo immunity infos in 16 ms
Loading GameObject models...
>> Loaded 3255 GameObject models in 3 ms
Loading Script Names...
>> Loaded 4057 ScriptNames in 1376 ms
Loading Instance Template...
>> Loaded 91 instance templates in 0 ms
Loading instances...
>> Loaded instances in 6 ms
Loading character cache store...
Loaded character infos for 13 characters in 0 ms
Loading Broadcast texts...
>> Loaded 73039 broadcast texts in 360 ms
>> Loaded 73039 broadcast text locales in 1637 ms
Loading Localization strings...
>> Loaded 29790 creature locale strings in 405 ms
>> Loaded 19670 gameobject_template_locale strings in 226 ms
>> Loaded 14986 Quest locale strings in 999 ms
>> Loaded 1946 PageText locale strings in 109 ms
>> Loaded 453 points_of_interest locale strings in 4 ms
>> Loaded 0 quest_greeting locales. DB table `quest_greeting_locale` is empty.
>> Localization strings loaded in 1758 ms
Loading Account Roles and Permissions...
>> Loaded 666 permission definitions, 650 linked permissions and 4 default permi
ssions in 343 ms
Loading Page Texts...
>> Loaded 1962 page texts in 13 ms
Loading Game Object Templates...
>> Loaded 29501 game object templates in 156 ms
Loading Game Object template addons...
>> Loaded 29495 game object template addons in 41 ms
Loading Transport templates...
>> Loaded 43 transport templates in 9 ms
Loading Transport animations and rotations...
Loading Transport spawns...
>> Spawned 0 continent transports in 0 ms
Loading Spell Rank Data...
>> Loaded 174 spell rank records in 1 ms
Loading Spell Required Data...
>> Loaded 22 spell required records in 1 ms
Loading Spell Group types...
>> Loaded 550 spell group definitions in 2 ms
Loading Spell Learn Skills...
>> Loaded 137 Spell Learn Skills from DBC in 5 ms
Loading SpellInfo SpellSpecific and AuraState...
>> Loaded SpellInfo SpellSpecific and AuraState in 10 ms
Loading Spell Learn Spells...
The spell 17002 listed in `spell_learn_spell` attempts learning talent spell 248
67, skipped.
The spell 24866 listed in `spell_learn_spell` attempts learning talent spell 248
64, skipped.
The spell 33872 listed in `spell_learn_spell` attempts learning talent spell 471
79, skipped.
The spell 33873 listed in `spell_learn_spell` attempts learning talent spell 471
80, skipped.
>> Loaded 24 spell learn spells, 512 found in Spell.dbc and 31 from TalentTab.db
c in 7 ms
Loading Spell Proc conditions and data...
>> Loaded 1556 spell proc conditions and data in 5 ms
Generating spell proc data from SpellMap...
Spell Id 36488 has SPELL_ATTR3_CAN_PROC_FROM_PROCS attribute and no restriction
on what spells can cause it to proc and no cooldown. This spell can cause infini
te proc loops. Proc data for this spell was not generated, data in `spell_proc`
table is required for it to function!
Spell Id 39440 has SPELL_ATTR3_CAN_PROC_FROM_PROCS attribute and no restriction
on what spells can cause it to proc and no cooldown. This spell can cause infini
te proc loops. Proc data for this spell was not generated, data in `spell_proc`
table is required for it to function!
Spell Id 47261 has SPELL_ATTR3_CAN_PROC_FROM_PROCS attribute and no restriction
on what spells can cause it to proc and no cooldown. This spell can cause infini
te proc loops. Proc data for this spell was not generated, data in `spell_proc`
table is required for it to function!
Spell Id 47262 has SPELL_ATTR3_CAN_PROC_FROM_PROCS attribute and no restriction
on what spells can cause it to proc and no cooldown. This spell can cause infini
te proc loops. Proc data for this spell was not generated, data in `spell_proc`
table is required for it to function!
Spell Id 67726 has SPELL_ATTR3_CAN_PROC_FROM_PROCS attribute and no restriction
on what spells can cause it to proc and no cooldown. This spell can cause infini
te proc loops. Proc data for this spell was not generated, data in `spell_proc`
table is required for it to function!
Spell Id 67736 has SPELL_ATTR3_CAN_PROC_FROM_PROCS attribute and no restriction
on what spells can cause it to proc and no cooldown. This spell can cause infini
te proc loops. Proc data for this spell was not generated, data in `spell_proc`
table is required for it to function!
Spell Id 67740 has SPELL_ATTR3_CAN_PROC_FROM_PROCS attribute and no restriction
on what spells can cause it to proc and no cooldown. This spell can cause infini
te proc loops. Proc data for this spell was not generated, data in `spell_proc`
table is required for it to function!
Spell Id 67744 has SPELL_ATTR3_CAN_PROC_FROM_PROCS attribute and no restriction
on what spells can cause it to proc and no cooldown. This spell can cause infini
te proc loops. Proc data for this spell was not generated, data in `spell_proc`
table is required for it to function!
Spell Id 85556 has SPELL_ATTR3_CAN_PROC_FROM_PROCS attribute and no restriction
on what spells can cause it to proc and no cooldown. This spell can cause infini
te proc loops. Proc data for this spell was not generated, data in `spell_proc`
table is required for it to function!
>> Generated spell proc data for 1620 spells in 8 ms
Loading Aggro Spells Definitions...
>> Loaded 44 SpellThreatEntries in 2 ms
Loading Spell Group Stack Rules...
>> Loaded 41 spell group stack rules in 2 ms
>> Parsing SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT stack rules...
>> Parsed 23 SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT stack rules in 0 ms
Loading NPC Texts...
>> Loaded 8406 npc texts in 92 ms
Loading Enchant Spells Proc datas...
>> Loaded 45 enchant proc data definitions in 1 ms
Loading Item Random Enchantments Table...
>> Loaded 29217 Item Enchantment definitions in 25 ms
Loading Disables
>> Loaded 5211 disables in 10 ms
Loading Items...
>> Loaded 54296 item templates in 39 ms
Loading Item set names...
>> Loaded 622 item addon templates in 2 ms
>> Loaded 21 item script names in 0 ms
Loading Creature Model Based Info Data...
>> Loaded 31775 creature model based info in 33 ms
Loading Creature templates...
>> Loaded 64891 creature template models in 77 ms
>> Loaded 46633 creature definitions in 580 ms
Loading Equipment templates...
>> Loaded 16167 equipment templates in 23 ms
Loading Creature template addons...
Creature (Entry: 47203) has SPELL_AURA_CONTROL_VEHICLE aura 87978 defined in `au
ras` field in `creature_template_addon`.
>> Loaded 46613 creature template addons in 73 ms
Loading Reputation Reward Rates...
>> Loaded 16 reputation_reward_rate in 0 ms
Loading Creature Reward OnKill Data...
>> Loaded 2343 creature award reputation definitions in 7 ms
Loading Reputation Spillover Data...
>> Loaded 28 reputation_spillover_template in 2 ms
Loading Points Of Interest Data...
>> Loaded 603 Points of Interest definitions in 2 ms
Loading Database Game Tables...
>> Loaded 0 npc total health game table entries in 5 ms
>> Loaded 0 npc damage by class game table entries in 4 ms
Loading Creature Base Stats...
>> Loaded 400 creature base stats in 8 ms
Loading Spawn Group Templates...
>> Loaded 98 spawn group templates in 1 ms
Loading Creature Data...
Table `creature` has creature (GUID: 396679 Entry: 32491) with disallowed `unit_
flags` 32768, removing incorrect flag.
>> Loaded 221565 creatures in 1182 ms
Loading Temporary Summon Data...
>> Loaded 869 temp summons in 4 ms
Loading pet levelup spells...
>> Loaded 263 pet levelup and default spells for 50 families in 8 ms
Loading pet default spells additional to levelup spells...
>> Loaded addition spells for 0 pet spell data entries in 2 ms
Loading summonable creature templates...
>> Loaded 427 summonable creature templates in 10 ms
Loading Creature Addon Data...
>> Loaded 52002 creature addons in 93 ms
Loading Creature Movement Overrides...
>> Loaded 0 creature movement overrides. DB table `creature_movement_override` i
s empty!
Loading Creature Movement Info...
>> Loaded 270 creature movement info override entries in 1 ms
Loading Gameobject Data...
>> Loaded 78938 gameobjects in 392 ms
Loading Spawn Group Data...
>> Loaded 94612 spawn group members in 58 ms
Loading instance spawn groups...
>> Loaded 27 instance spawn groups in 2 ms
Loading GameObject Addon Data...
>> Loaded 24493 gameobject addons in 38 ms
Loading GameObject Quest Items...
>> Loaded 1352 gameobject quest items in 3 ms
Loading Creature Quest Items...
>> Loaded 5361 creature quest items in 8 ms
Loading Creature Sparring Data...
>> Loaded 0 creature sparring templates in 1 ms
Loading Creature Linked Respawn...
>> Loaded 5239 linked respawns in 7 ms
Loading Weather Data...
>> Loaded 36 weather definitions in 0 ms
Loading Quests...
Quest 29507 has `RequiredCurrencyId1` = 393 but `RequiredCurrencyCount1` = 0, qu
est can't be done.
Quest 29283 has `RequiredCurrencyId1` = 416 but `RequiredCurrencyCount1` = 0, qu
est can't be done.
Quest 29281 has `RequiredCurrencyId1` = 416 but `RequiredCurrencyCount1` = 0, qu
est can't be done.
Quest 29279 has `RequiredCurrencyId1` = 416 but `RequiredCurrencyCount1` = 0, qu
est can't be done.
Quest 29214 has `RequiredCurrencyId1` = 416 but `RequiredCurrencyCount1` = 0, qu
est can't be done.
Quest 29199 has `RequiredCurrencyId1` = 416 but `RequiredCurrencyCount1` = 0, qu
est can't be done.
Quest 29181 has `RequiredCurrencyId1` = 416 but `RequiredCurrencyCount1` = 0, qu
est can't be done.
Quest 13478 has `RequiredCurrencyId1` = 392 but `RequiredCurrencyCount1` = 0, qu
est can't be done.
Quest 13476 has `RequiredCurrencyId1` = 392 but `RequiredCurrencyCount1` = 0, qu
est can't be done.
Quest 8371 has `RequiredCurrencyId1` = 392 but `RequiredCurrencyCount1` = 0, que
st can't be done.
Quest 8368 has `RequiredCurrencyId1` = 392 but `RequiredCurrencyCount1` = 0, que
st can't be done.
Quest 8367 has `RequiredCurrencyId1` = 392 but `RequiredCurrencyCount1` = 0, que
st can't be done.
>> Loaded 14991 quests definitions in 347 ms
Checking Quest Disables
>> Checked 5124 quest disables in 0 ms
Loading Quest POI
>> Loaded 13941 quest POI definitions in 189 ms
Loading Quest Money Rewards
>> Loaded 100 quest money reward entries in 1 ms
Loading Quests Starters and Enders...
Loading GO Start Quest Data...
>> Loaded 618 quest relations from gameobject_queststarter in 2 ms
Loading GO End Quest Data...
>> Loaded 517 quest relations from gameobject_questender in 2 ms
Loading Creature Start Quest Data...
>> Loaded 8725 quest relations from creature_queststarter in 10 ms
Loading Creature End Quest Data...
>> Loaded 9136 quest relations from creature_questender in 12 ms
Loading Quests Greetings...
>> Loaded 0 quest greetings. DB table `quest_greeting` is empty.
Loading Objects Pooling Data...
>> Loaded 1721 objects pools in 3 ms
Loading Creatures Pooling Data...
>> Loaded 835 creatures in pools in 2 ms
Loading Gameobject Pooling Data...
>> Loaded 7404 gameobject in pools in 10 ms
Loading Mother Pooling Data...
>> Loaded 1218 pools in mother pools in 3 ms
Starting objects pooling system...
Loading Quest Pooling Data...
>> Loaded 56 daily, 5 weekly and 0 monthly quest pools in 2 ms
Loading Game Event Data...
>> Loaded 156 holiday dates in 1 ms
>> Loaded 78 game events in 1 ms.
Loading Game Event Saves Data...
>> Loaded 0 game event saves in game events. DB table `game_event_save` is empty
.
Loading Game Event Prerequisite Data...
>> Loaded 0 game event prerequisites in game events. DB table `game_event_prereq
uisite` is empty.
Loading Game Event Creature Data...
>> Loaded 2790 creatures in game events in 2 ms.
Loading Game Event GO Data...
>> Loaded 21863 gameobjects in game events in 10 ms.
Loading Game Event Model/Equipment Change Data...
>> Loaded 22 model/equipment changes in game events in 2 ms.
Loading Game Event Quest Data...
>> Loaded 327 quests additions in game events in 1 ms.
Loading Game Event GO Quest Data...
>> Loaded 189 quests additions in game events in 1 ms.
Loading Game Event Quest Condition Data...
>> Loaded 0 quest event conditions in game events. DB table `game_event_quest_co
ndition` is empty.
Loading Game Event Condition Data...
>> Loaded 0 conditions in game events. DB table `game_event_condition` is empty.
Loading Game Event Condition Save Data...
>> Loaded 0 condition saves in game events. DB table `game_event_condition_save`
is empty.
Loading Game Event NPCflag Data...
>> Loaded 0 npcflags in game events. DB table `game_event_npcflag` is empty.
Loading Game Event Seasonal Quest Relations...
>> Loaded 690 quests additions in game events in 1 ms.
Loading Game Event Vendor Additions Data...
>> Loaded 2 vendor additions in game events in 1 ms.
Loading Game Event Battleground Holiday Data...
>> Loaded 8 battleground holidays in game events in 1 ms.
Loading Game Event Pool Data...
>> Loaded 2 pools for game events in 0 ms.
Loading UNIT_NPC_FLAG_SPELLCLICK Data...
>> Loaded 702 spellclick definitions in 3 ms
Loading Vehicle Template Accessories...
>> Loaded 431 Vehicle Template Accessories in 2 ms
Loading Vehicle Accessories...
>> Loaded 0 Vehicle Accessories in 0 ms
Loading Vehicle Seat Addon Data...
>> Loaded 15 Vehicle Seat Addon entries in 1 ms
Loading SpellArea Data...
>> Loaded 872 spell area requirements in 4 ms
Loading AreaTrigger definitions...
>> Loaded 316 area trigger teleport definitions in 2 ms
Loading Access Requirements...
>> Loaded 152 access requirement definitions in 1 ms
Loading Quest Area Triggers...
>> Loaded 49 quest trigger points in 1 ms
Loading Tavern Area Triggers...
>> Loaded 175 tavern triggers in 1 ms
Loading AreaTrigger script names...
>> Loaded 229 areatrigger scripts in 0 ms
Loading LFG entrance positions...
>> Loaded 68 lfg dungeon templates in 2 ms
Loading Dungeon boss data...
>> Loaded 617 instance encounters in 2 ms
Loading LFG rewards...
>> Loaded 20 lfg dungeon rewards in 1 ms
Loading Graveyard-zone links...
>> Loaded 855 graveyard-zone links in 2 ms
Loading Graveyard Orientations...
>> Loaded 1016 graveyard orientations in 2 ms
Loading spell pet auras...
>> Loaded 34 spell pet auras in 1 ms
Loading Spell target coordinates...
>> Loaded 1438 spell teleport coordinates in 4 ms
Loading linked spells...
>> Loaded 413 linked spells in 1 ms
Loading Player Create Data...
>> Loaded 91 player create definitions in 1 ms
Loading Player Create Items Data...
>> Loaded 1 custom player create items in 1 ms
Loading Player Create Skill Data...
>> Loaded player create skills in 1 ms
Loading Player Create Custom Spell Data...
>> Loaded 4507 custom player create spells in 1 ms
Loading Player Create Cast Spell Data...
>> Loaded 63 player create cast spells in 1 ms
Loading Player Create Action Data...
>> Loaded 352 player create actions in 1 ms
Loading Player Create Level Stats Data...
>> Loaded 850 level stats definitions in 5 ms
Loading Player Create XP Data...
>> Loaded 84 xp for level definitions in 1 ms
Loading Exploration BaseXP Data...
>> Loaded 80 BaseXP definitions in 1 ms
Loading Pet Name Parts...
>> Loaded 313 pet name parts in 1 ms
Loading the max pet number...
>> Loaded the max pet number: 28 in 0 ms
Loading pet level stats...
>> Loaded 2720 level pet stats definitions in 5 ms
Loading Player level dependent mail rewards...
>> Loaded 24 level dependent mail rewards in 1 ms
Loading creature loot templates...
>> Loaded 528193 creature loot templates in 634 ms
Loading fishing loot templates...
>> Loaded 266 fishing loot templates in 2 ms
Loading gameobject loot templates...
>> Loaded 19834 gameobject loot templates in 32 ms
Loading item loot templates...
>> Loaded 5031 item loot templates in 16 ms
Loading mail loot templates...
>> Loaded 90 mail loot templates in 0 ms
Loading milling loot templates...
>> Loaded 51 milling loot templates in 4 ms
Loading pickpocketing loot templates...
>> Loaded 10981 pickpocketing loot templates in 20 ms
Loading skinning loot templates...
>> Loaded 3390 skinning loot templates in 10 ms
Loading disenchanting loot templates...
>> Loaded 180 disenchanting loot templates in 1 ms
Loading prospecting loot templates...
>> Loaded 43 prospecting loot templates in 4 ms
Loading spell loot templates...
>> Loaded 292 spell loot templates in 3 ms
Loading reference loot templates...
>> Loaded refence loot templates in 48 ms
Loading Skill Discovery Table...
>> Loaded 347 skill discovery definitions in 3 ms.
Loading Skill Extra Item Table...
>> Loaded 225 spell specialization definitions in 1 ms
Loading Skill Perfection Data Table...
>> Loaded 72 spell perfection definitions in 0 ms.
Loading Skill Fishing base level requirements...
>> Loaded 95 areas for fishing base skill level in 0 ms
Loading Archaeology store...
>> Loaded 180 archaeology sites in 1 ms
>> Loaded 180 archaeology site polygons in 157 ms
>> Loaded 9 archaeology branches in 0 ms
>> Loaded 139 archaeology projects in 1 ms
>> Calculated 9 archaeology branch chances in 0 ms
Loading Achievements...
>> Loaded 47 achievement references in 0 ms.
Loading Achievement Criteria Lists...
>> Loaded 9803 achievement criteria and 952 guild achievement crieteria in 5 ms.
Loading Achievement Criteria Data...
>> Loaded 1291 additional achievement criteria data in 2 ms
Loading Achievement Rewards...
>> Loaded 120 achievement rewards in 1 ms.
Loading Achievement Reward Locales...
>> Loaded 0 achievement reward locale strings. DB table `achievement_reward_loc
ale` is empty.
Loading Completed Achievements...
>> Loaded 77 realm first completed achievements in 1 ms.
Loading Item Auctions...
>> Loaded 0 auction items. DB table `auctionhouse` or `item_instance` is empty!
Loading Auctions...
>> Loaded 0 auctions. DB table `auctionhouse` is empty.
Loading Guild XP for level...
>> Loaded 24 xp for guild level definitions in 1 ms
Loading Guild rewards...
>> Loaded 42 guild reward definitions in 0 ms
Initializing Guild Profession Data Store...
Loading Guild Challenges...
>> Loaded 4 guild challenge definitions in 1 ms
Loading Guilds...
Loading guilds definitions...
>> Loaded 1 guild definitions in 2 ms
Loading guild ranks...
>> Loaded 5 guild ranks in 0 ms
Loading guild members...
>> Loaded 4 guild members in 5 ms
Loading bank tab rights...
>> Loaded 30 bank tab rights in 1 ms
Loading guild event logs...
>> Loaded 8 guild event logs in 2 ms
Loading guild bank event logs...
>> Loaded 22 guild bank event logs in 0 ms
Loading Guild News...
>> Loaded 0 guild event logs. DB table `guild_newslog` is empty.
Loading guild bank tabs...
>> Loaded 6 guild bank tabs in 0 ms
>> Loaded 22 guild bank tab items in 1 ms
>> Validated data of loaded guilds in 0 ms
Loading guild finder guild-related settings...
>> Loaded 0 guild finder guild-related settings. Table `guild_finder_guild_setti
ngs` is empty.
Loading guild finder membership requests...
>> Loaded 0 guild finder membership requests. Table `guild_finder_applicant` is
empty.
Loading ArenaTeams...
>> Loaded 0 arena teams. DB table `arena_team` is empty!
Loading Groups...
>> Loaded 0 group definitions. DB table `groups` is empty!
Loading ReservedNames...
>> Loaded 0 reserved player names. DB table `reserved_name` is empty!
Loading GameObjects for quests...
>> Loaded 2750 GameObjects for quests in 5 ms
Loading BattleMasters...
>> Loaded 171 battlemaster entries in 3 ms
Loading GameTeleports...
>> Loaded 1531 GameTeleports in 5 ms
Loading Trainers...
>> Loaded 88 Trainers in 10 ms
Loading Gossip menu...
>> Loaded 8225 gossip_menu IDs in 10 ms
Loading Gossip menu options...
>> Loaded 5837 gossip_menu_option entries in 18 ms
Loading Creature Template Gossip...
creature_template_gossip has gossip definitions for menu id 55002 but this menu
doesn't exist
creature_template_gossip has gossip definitions for menu id 55002 but this menu
doesn't exist
creature_template_gossip has gossip definitions for menu id 6268 but this menu d
oesn't exist
creature_template_gossip has gossip definitions for menu id 9856 but this menu d
oesn't exist
creature_template_gossip has gossip definitions for menu id 10844 but this menu
doesn't exist
creature_template_gossip has gossip definitions for menu id 10852 but this menu
doesn't exist
creature_template_gossip has gossip definitions for menu id 2522 but this menu d
oesn't exist
creature_template_gossip has gossip definitions for menu id 12720 but this menu
doesn't exist
creature_template_gossip has gossip definitions for menu id 12718 but this menu
doesn't exist
creature_template_gossip has gossip definitions for menu id 13160 but this menu
doesn't exist
>> Loaded 5435 creature template gossip menus in 8 ms
Loading Creature trainers...
>> Loaded 1187 default trainers in 4 ms
Loading Vendors...
Table `(game_event_)npc_vendor` for Vendor (Entry: 44972) have in item list non-
existed item (60822, type 1), ignore
>> Loaded 48278 Vendors in 71 ms
Loading Waypoints...
>> Loaded 110626 waypoints in 177 ms
Loading Waypoint Addons...
>> Loaded 639 waypoint addon data in 2 ms
Loading SmartAI Waypoints...
>> Loaded 586 SmartAI waypoint paths (total 7064 waypoints) in 10 ms
Loading Creature Formations...
>> Loaded 2517 creatures in formations in 5 ms
Loading World State templates...
Table `world_state` contains a world state 3490 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3490 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3491 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3491 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3600 with invalid MapID (1134), map i
gnored
Table `world_state` contains a world state 3600 with invalid MapID (1504), map i
gnored
Table `world_state` contains a world state 3600 with invalid MapID (1505), map i
gnored
Table `world_state` contains a world state 3600 with invalid MapID (1552), map i
gnored
Table `world_state` contains a world state 3600 with invalid MapID (1672), map i
gnored
Table `world_state` contains a world state 3600 with invalid MapID (1825), map i
gnored
Table `world_state` contains a world state 3600 with invalid MapID (1911), map i
gnored
Table `world_state` contains a world state 3600 with invalid MapID (2167), map i
gnored
Table `world_state` contains a world state 3600 with invalid MapID (2373), map i
gnored
Table `world_state` contains a world state 3600 with invalid MapID (2509), map i
gnored
Table `world_state` contains a world state 3600 with invalid MapID (2511), map i
gnored
Table `world_state` contains a world state 3600 with invalid MapID (2547), map i
gnored
Table `world_state` contains a world state 3601 with invalid MapID (1134), map i
gnored
Table `world_state` contains a world state 3601 with invalid MapID (1504), map i
gnored
Table `world_state` contains a world state 3601 with invalid MapID (1505), map i
gnored
Table `world_state` contains a world state 3601 with invalid MapID (1552), map i
gnored
Table `world_state` contains a world state 3601 with invalid MapID (1672), map i
gnored
Table `world_state` contains a world state 3601 with invalid MapID (1825), map i
gnored
Table `world_state` contains a world state 3601 with invalid MapID (1911), map i
gnored
Table `world_state` contains a world state 3601 with invalid MapID (2167), map i
gnored
Table `world_state` contains a world state 3601 with invalid MapID (2373), map i
gnored
Table `world_state` contains a world state 3601 with invalid MapID (2509), map i
gnored
Table `world_state` contains a world state 3601 with invalid MapID (2511), map i
gnored
Table `world_state` contains a world state 3601 with invalid MapID (2547), map i
gnored
Table `world_state` contains a world state 3610 with invalid MapID (1134), map i
gnored
Table `world_state` contains a world state 3610 with invalid MapID (1504), map i
gnored
Table `world_state` contains a world state 3610 with invalid MapID (1505), map i
gnored
Table `world_state` contains a world state 3610 with invalid MapID (1552), map i
gnored
Table `world_state` contains a world state 3610 with invalid MapID (1672), map i
gnored
Table `world_state` contains a world state 3610 with invalid MapID (1825), map i
gnored
Table `world_state` contains a world state 3610 with invalid MapID (1911), map i
gnored
Table `world_state` contains a world state 3610 with invalid MapID (2167), map i
gnored
Table `world_state` contains a world state 3610 with invalid MapID (2373), map i
gnored
Table `world_state` contains a world state 3610 with invalid MapID (2509), map i
gnored
Table `world_state` contains a world state 3610 with invalid MapID (2511), map i
gnored
Table `world_state` contains a world state 3610 with invalid MapID (2547), map i
gnored
Table `world_state` contains a world state 3680 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3680 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3681 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3681 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3698 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3698 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3699 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3699 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3700 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3700 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3701 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3701 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3702 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3702 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3703 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3703 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3704 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3704 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3705 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3705 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3706 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3706 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3710 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3710 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3711 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3711 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3712 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3712 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3713 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3713 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3714 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3714 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3749 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3749 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3750 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3750 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3751 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3751 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3752 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3752 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3753 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3753 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3754 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3754 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3755 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3755 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3756 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3756 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3757 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3757 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3758 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3758 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3759 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3759 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3760 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3760 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3761 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3761 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3762 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3762 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3763 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3763 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3764 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3764 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3765 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3765 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3766 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3766 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3767 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3767 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3768 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3768 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3769 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3769 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3770 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3770 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3771 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3771 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3772 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3772 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3773 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3773 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3781 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3781 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3801 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3801 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3802 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3802 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3803 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 3803 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 3815 with invalid MapID (1544), map i
gnored
Table `world_state` contains a world state 3816 with invalid MapID (1544), map i
gnored
Table `world_state` contains a world state 4022 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 4022 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 4023 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 4023 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 4024 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 4024 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 4025 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 4025 with invalid AreaID (10176), are
a ignored
Table `world_state` contains a world state 4354 with invalid MapID (2118), map i
gnored
Table `world_state` contains a world state 4354 with invalid AreaID (10176), are
a ignored
>> Loaded 526 world state templates 4 ms
>> Loaded 41 saved world state values 24 ms
Loading Persistend World Variables...
>> Loaded 9 world variables in 4 ms
Loading Terrain World Map definitions...
>> Loaded 4 terrain world maps in 0 ms.
Loading Terrain Swap Default definitions...
>> Loaded 4 terrain swap defaults in 1 ms.
Loading Phase Area definitions...
>> Loaded 25 phase areas in 1 ms.
Loading Conditions...
>> Loaded 15912 conditions in 45 ms
Loading faction change achievement pairs...
>> Loaded 124 faction change achievement pairs in 2 ms
Loading faction change spell pairs...
>> Loaded 113 faction change spell pairs in 1 ms
Loading faction change quest pairs...
>> Loaded 250 faction change quest pairs in 1 ms
Loading faction change item pairs...
>> Loaded 752 faction change item pairs in 2 ms
Loading faction change reputation pairs...
>> Loaded 17 faction change reputation pairs in 1 ms
Loading faction change title pairs...
>> Loaded 22 faction change title pairs in 1 ms
Loading GM tickets...
>> Loaded 2 GM tickets in 0 ms
Loading GM surveys...
>> Loaded GM Survey count from database in 1 ms
Loading client addons...
>> Loaded 35 known addons in 0 ms
Returning old mails...
>> No expired mails found.
Loading Autobroadcasts...
>> Loaded 0 autobroadcasts definitions. DB table `autobroadcast` is empty for th
is realm!
Loading spell_scripts...
>> Loaded 143 script definitions in 1 ms
Loading event_scripts...
>> Loaded 461 script definitions in 2 ms
Loading waypoint_scripts...
>> Loaded 422 script definitions in 2 ms
Loading spell script names...
Scriptname: `spell_vcore_dungeon_horn` spell (Id: 120000) does not exist.
Scriptname: `spell_vcore_dungeon_horn` spell (Id: 120001) does not exist.
Scriptname: `spell_vcore_dungeon_horn` spell (Id: 120002) does not exist.
Scriptname: `spell_vcore_dungeon_horn` spell (Id: 120003) does not exist.
>> Loaded 3282 spell script names in 7 ms
Loading Creature Texts...
>> Loaded 16797 creature texts for 3997 creatures in 53 ms
Loading Creature Text Locales...
Loading creature StaticFlags overrides...
>> Loaded 133 creature static flag overrides in 1 ms
Loading Taxi node level definitions...
>> Loaded 274 taxi node level definitions in 1 ms
Initializing Scripts...
Loading Script Waypoints for 54 creature(s)...
>> Loaded 1377 Script Waypoint nodes in 4 ms
>> Loaded spline chain data for 85 chains, consisting of 272 splines with 1414 w
aypoints in 4 ms
Loading C++ scripts
Script named 'world_map_set_faction_worldstates_609' does not have a script name
assigned in database.
Script named 'item_gor_dreks_ointment' does not have a script name assigned in d
atabase.
>> Loaded 4106 C++ scripts in 146 ms
Validating spell scripts...
Spell `85123` Effect `Index: EFFECT_0 Target: 7` of script `spell_siege_cannon`
did not match dbc effect data - handler bound to hook `OnObjectAreaTargetSelect`
of SpellScript won't be executed
>> Validated 3470 scripts in 12 ms
Loading SmartAI scripts...
>> Loaded SmartAIMgr Helpers in 16 ms
>> Loaded 13506 SmartAI scripts in 167 ms
Loading Calendar data...
>> Loaded 0 calendar events in 1 ms
>> Loaded 0 calendar invites in 0 ms
Loading Petitions...
>> Loaded 0 petitions.
Loading Signatures...
>> Loaded 0 Petition signs!
Loading Summon Properties parameter data...
>> Loaded 78 summon properties parameters in 1 ms
Loading Item loot...
>> Loaded 0 stored item loots
>> Loaded 0 stored item money
Initialize query data...
>> Initialized query cache data in 1019 ms
Initialize commands...
Initialize game time and timers
Mail timer set to: 1260, mail return is called every 1440 minutes
Starting Map System
Starting Game Event system...
Initialize AuctionHouseBot...
Initializing Opcodes...
Starting Arena Season...
Starting Battleground System
>> Loaded 15 battlegrounds in 0 ms
Starting Outdoor PvP System
>> Loaded 5 outdoor PvP definitions in 1 ms
Starting Battlefield System
>> Loaded 2 battlefields in 0 ms
Loading Warden Checks...
Loading Warden Action Overrides...
Deleting expired bans...
Initializing quest reset times...
Calculate random battleground reset time...
Calculate guild limitation(s) reset time...
Calculate next currency reset time...
World initialized in 0 minutes 13 seconds
TC>TC>TrinityCore rev. 39a515dbd13e+ 2026-10-27 23:56:43 +0100 (feature/clientle
ss-player-core branch) (Unix, Debug, Static) (worldserver-daemon) ready...
clientless login 26
TC>Registered clientless session for character GUID 26 (account 6), state=Loadin
g.
Created clientless player session for character GUID 26 (account 6).
ClientlessPlayerMgr started character GUID 26 (account 6).
Clientless login started for 'Arthoran' (GUID 26, account 6).
TC> Loading saved respawn time of 1790175804 for spawnid (0,323981) - spawn does
not exist, ignoring
Loading saved respawn time of 1790090644 for spawnid (0,396680) - spawn does not
exist, ignoring
Loading saved respawn time of 1790019808 for spawnid (0,396681) - spawn does not
exist, ignoring
Loading saved respawn time of 1790019853 for spawnid (0,396682) - spawn does not
exist, ignoring
Initialized transport GameObject grids: map=0 instance=0 grids=4 candidates=15
Clientless player character GUID 26 (account 6) reached state=Online.
ClientlessLua initialized, script root '/home/wow_admin/wow_cata_server/source/T
rinityCore/src/server/scripts/Custom/clientless'.
[AdventureBot][Profile] loaded guid=26 name='Arthoran' source='/home/wow_admin/w
ow_cata_server/source/TrinityCore/src/server/scripts/Custom/clientless/adventure
/profiles/arthoran.lua' home='elwynn' canGroundMount=1 mounts=1 mountMinDistance
=45.0 mountSpeedRate=1.60 personality=8 regionAffinity=4 activityAffinity=7
PROFILE_PROFESSIONS guid=26 primary=2
ClientlessLua[26]: Arthoran ready; no automatic test journey queued
ClientlessLua[26]: executed adventure override script '/home/wow_admin/wow_cata_
server/source/TrinityCore/src/server/scripts/Custom/clientless/adventure/overrid
es/arthoran.lua'.
ADVENTURE_CATALOG locations=2973 actions=84 lines=76
INTENT_TRAVEL guid=26 intent='social' location='goldshire_tavern' area=0
[AdventureBot] loaded 3966 clientless world points.
[AdventureBot] loaded travel graph: 993 node(s), 1765 directed edge(s).
clientless interaction 26
TC>Interactions guid=26 grouped=0 leader=0 partner=0 intent='social' schedule=''
leaderIdleSeconds=0 afkSeconds=0 sociability=0.65 loyalty=0.35
TC> SUPPLY_RESTOCK guid=26 day=20261028 daily=0 afterCombat=1
@@ -0,0 +1,57 @@
test_adventure_area_node.py: PASS
test_adventure_behavior_policy.py: PASS
test_adventure_combat_exercise.py: PASS
test_adventure_combat_preparation.py: PASS
test_adventure_combat_reactions.py: PASS
test_adventure_economy.py: PASS
test_adventure_economy_catalog.py: PASS
test_adventure_exploration.py: PASS
test_adventure_exploration_catalog.py: PASS
test_adventure_graph_handover.py: PASS
test_adventure_instance.py: PASS
test_adventure_instance_autoresume.py: PASS
test_adventure_instance_begin.py: PASS
test_adventure_instance_catalog.py: PASS
test_adventure_instance_persistence.py: PASS
test_adventure_intent_state.py: PASS
test_adventure_interactions.py: PASS
test_adventure_leisure.py: PASS
test_adventure_leisure_catalog.py: PASS
test_adventure_location_import.py: PASS
test_adventure_micro_reset.py: PASS
test_adventure_profession_catalog.py: PASS
test_adventure_profession_profile.py: PASS
test_adventure_profession_sightline.py: PASS
test_adventure_profession_targets.py: PASS
test_adventure_repair_catalog.py: PASS
test_adventure_rest.py: PASS
test_adventure_rest_fire.py: PASS
test_adventure_rest_space.py: PASS
test_adventure_rest_targets.py: PASS
test_adventure_social.py: PASS
test_adventure_social_fire.py: PASS
test_adventure_supplies.py: PASS
test_adventure_supply_catalog.py: PASS
test_adventure_supply_items.py: PASS
test_adventure_training_catalog.py: PASS
test_adventure_walk.py: PASS
test_clientless_companion.py: PASS
test_clientless_damage_notifications.py: PASS
test_clientless_near_teleport_observer.py: PASS
test_default_phase.py: PASS
test_flight_area_query.py: PASS
test_flight_handoff.py: PASS
test_flight_policy.py: PASS
test_follow_spline_velocity.py: PASS
test_free_flight.py: PASS
test_free_flight_planner.py: PASS
test_ground_exit_continuation.py: PASS
test_local_spline_segments.py: PASS
test_lua_reload.py: PASS
test_player_pure_max_skill.py: PASS
test_portal_transfer.py: PASS
test_portal_use.py: PASS
test_taxi_import_disabled.py: PASS
test_transport.py: PASS
test_transport_grid_loading.py: PASS
56/56 passed; failures: []
@@ -0,0 +1,8 @@
Debug build -j10 and installation succeeded. Test worldserver59086 freshly ready, port8085 listening.
56/56 full suite passed; additional new social notification concurrency test passed (57 scripts total).
Final expanded combat test with player focus also passed.
Native login26 and interaction diagnostic succeeded; sociability0.65 loyalty0.35.
Real creature defense after restart observed. No live player invitation/PvP/pet/portal/ten-minute AFK claim.
Normal initial Goldshire trip started on login; behavior stop requested at safe point, last observed switching/waiting for safe state.
OLD executable shutdown observed Object.h:448 GetMap ASSERTION FAILED m_currMap; process49765 exited before installation. Cause not investigated; new startup succeeded.
Local commit requested by user; no push. No DB migrations. No temporary profile overrides.
+103
View File
@@ -0,0 +1,103 @@
# AdventureBot interactions and player-led groups
Implemented 2026-10-28 on feature/clientless-player-core. No autonomous bot-to-bot
party formation, role assignment, healer/tank AI or proactive PvP aggression.
PlayerAI.cpp supplies spell/attack foundations and charmed-player follow logic;
it does not implement invitation decisions. AdventureBots keep independent
controllers and use the native party invitation response handler.
## Encounters
Directed native text emotes greet/hello/wave/bow/salute/smile/thank and
rude/threaten/roar generate GUID-only notices in ClientlessPlayerMgr. Map workers
never call AdventureBot policy directly. Queues are bounded and delivered on the
world thread. Participants must be alive, mutually visible, in the same map,
instance and phase, within 25 yards and in line of sight.
Replies wait 1–3 seconds, briefly interrupt the current activity, face the other
player and use native animation emotes. Friendly encounters wave/bow; hostility
or hostile emotes produce a negative gesture. There is no automatic attack just
because another player is hostile. Original intent deadlines are retained.
The global reply cooldown is 15 seconds, the same partner has 120 seconds.
Direct chat/free conversation parsing is not implemented.
Bots in social/leisure/rest can occasionally initiate a five-second encounter
with another available AdventureBot within 12 yards. The lower character GUID
initiates and reserves both participants. The peer must also have a compatible
intent. Sociability weights initiation. Pair cooldown is five minutes; native
animation replies never feed back into incoming text-emote events. Occasional
localized 'Safe travels' accompanies greetings. This is a short greeting, not a
multi-line conversation or synchronized shared activity.
Real positive damage by another player or their pet against the bot's active
combat target can queue a thank-you. Damage attribution is captured after damage
rejection/duel clamping and before lethal cleanup; no threat-only inference.
The thank-you is deferred until combat and the existing self-care finish, and
requires the helper still to be nearby and friendly. Dummy training is excluded.
## Invitations and group behavior
An actual native invitation is answered after 2–4 seconds in a safe state.
Acceptance probability clamps sociability plus an intent adjustment to [0,1]:
rest −0.50, profession/economy −0.25, adventure/instance +0.20, other +0.10.
Inviter must be alive and friendly. Bots do not accept invitations from other
clientless players yet; autonomous bot grouping is explicitly deferred.
Invitations are declined inside dungeons. Normal faction/group restrictions run
before the invitation reaches the bot; none are bypassed.
Membership suspends autonomous intent execution, clears its queued actions and
retains its deadline. Native membership survives server restart. Bots follow the
current leader with checked local MMAP paths, aiming about three yards behind
and stopping within five yards. Longer/cross-map routes use existing
travel_to_node actions toward a nearby TravelGraph node at the leader's location;
no new teleport shortcut is introduced. Coverage of the graph and native portal
rules determines whether these routes work. An active graph trip is allowed to
finish before local follow resumes. Transport/flight states are not interrupted.
Unreachable destinations are retried conservatively; an active leader does not
cause a timeout merely because the route is missing.
Nearby (40 yards) group members' actual combat targets or attackers can be
supported with the existing damage executor. Self-defense retains priority and
the first actual damager focus. Assist requires native attack permission, valid
visibility/phase, a verified path, at least 30% HP, and excludes world bosses and
creatures more than two levels above the bot. Other classes currently retain
basic autoattacks; class-specific support roles remain future work.
## Leaving
Never voluntarily leave while dead, fighting, in a dungeon/raid, on a transport,
or in flight. Pending conditions are reevaluated when safe:
- A changed scheduled intent/location permits leaving. Personality
`group_loyalty` is the chance of staying for that new block (default 0.25,
Arthoran 0.35). The choice is made once per observed change, not each tick.
A new date generates/loads the native daily plan even while grouped.
For manual intents, expiry of the original deadline also changes this token.
- The leader being explicitly AFK for ten minutes, or ten minutes of observed
inactivity, permits leaving. Position/orientation, selection, combat,
casting, native chat, text emotes, spell and item requests count as activity.
Sampling cannot detect every conceivable stationary action. Native AFK takes
precedence while set. A new leader starts a fresh observation window; returning
from AFK ends the explicit AFK window. Observation clocks restart after a
server restart, so a restart does not immediately evict a bot.
Before voluntarily leaving, a localized farewell is sent to the native party
chat. Native RemoveMember performs removal. Removal/disbanding by other players
also resumes autonomous scheduling at the current time, retaining deadlines.
## Diagnostics and verification
`clientless interaction 26` reports membership, leader, partner, intent,
schedule observation, idle/AFK seconds, sociability and loyalty. No state changes.
`tests/clientless/test_adventure_interactions.py` compiles and executes production
manager code with deterministic native-world adapters: invitation acceptance and
rejection, group following/path rejection, leader changes, 599/600-second
boundaries, activity, deferred exits, directed group emotes, hostile replies,
pair reservation, cooldowns, graph handoff and deferred thanks.
`test_adventure_combat_reactions.py` also covers player target permission and
player damage notices while preserving corpse recovery regression checks.
These tests do not replace live client acceptance. Multi-client player invites,
real PvP/pet defense, cross-map/instance group following, ten real minutes of AFK,
and two simultaneously online AdventureBots require separate gameplay checks.
+3
View File
@@ -890,6 +890,9 @@ bool Unit::HasBreakableByDamageCrowdControlAura(Unit* excludeCasterChannel) cons
victim->ToCreature()->LowerPlayerDamageReq(health < damage ? health : damage);
}
if (damage)
sClientlessPlayerMgr->SharedTargetDamage(victim, attacker, std::min(health, damage));
if (health <= damage)
{
if (victim->GetTypeId() == TYPEID_PLAYER && victim != attacker)
+7
View File
@@ -16,6 +16,7 @@
*/
#include "WorldSession.h"
#include "ClientlessPlayerMgr.h"
#include <cstdlib>
#include "AccountMgr.h"
@@ -104,6 +105,7 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData)
}
Player* sender = GetPlayer();
sClientlessPlayerMgr->MarkActivity(sender);
//TC_LOG_DEBUG("misc", "CHAT: packet received. type %u, lang %u", type, lang);
@@ -569,6 +571,7 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData)
void WorldSession::HandleAddonMessagechatOpcode(WorldPacket& recvData)
{
Player* sender = GetPlayer();
sClientlessPlayerMgr->MarkActivity(sender);
ChatMsg type;
switch (recvData.GetOpcode())
@@ -808,6 +811,7 @@ void WorldSession::HandleSendTextEmoteOpcode(WorldPackets::Chat::SendTextEmote&
return;
}
sClientlessPlayerMgr->MarkActivity(GetPlayer());
sScriptMgr->OnPlayerTextEmote(GetPlayer(), packet.SoundIndex, packet.EmoteID, packet.Target);
EmotesTextEntry const* em = sEmotesTextStore.LookupEntry(packet.EmoteID);
@@ -848,6 +852,9 @@ void WorldSession::HandleSendTextEmoteOpcode(WorldPackets::Chat::SendTextEmote&
GetPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DO_EMOTE, packet.EmoteID, 0, 0, unit);
if (unit && unit->ToPlayer())
sClientlessPlayerMgr->TextEmote(_player, unit->ToPlayer(), packet.EmoteID);
//Send scripted event call
if (unit && unit->GetTypeId() == TYPEID_UNIT && ((Creature*)unit)->AI())
((Creature*)unit)->AI()->ReceiveEmote(GetPlayer(), packet.EmoteID);
@@ -16,6 +16,7 @@
*/
#include "WorldSession.h"
#include "ClientlessPlayerMgr.h"
#include "Archaeology.h"
#include "Common.h"
#include "Config.h"
@@ -93,6 +94,7 @@ void WorldSession::HandleClientCastFlags(WorldPacket& recvPacket, uint8 castFlag
void WorldSession::HandleUseItemOpcode(WorldPackets::Spells::UseItem& packet)
{
sClientlessPlayerMgr->MarkActivity(GetPlayer());
Player* user = _player;
// ignore for remote control state
@@ -265,6 +267,7 @@ void WorldSession::HandleGameobjectReportUse(WorldPacket& recvPacket)
void WorldSession::HandleCastSpellOpcode(WorldPackets::Spells::CastSpell& cast)
{
sClientlessPlayerMgr->MarkActivity(GetPlayer());
// ignore for remote control state (for player case)
Unit* mover = GetGameClient()->GetActivelyMovedUnit();
if (!mover || (mover != _player && mover->IsPlayer()))
@@ -1763,3 +1763,27 @@ std::string AdventureBehaviorMgr::CombatIntent(Player* player) const
(it->second.State!=Phase::Travel && it->second.State!=Phase::OnSite)) return {};
return it->second.Intent;
}
std::string AdventureBehaviorMgr::InteractionIntent(Player* player) const
{
auto it=_records.find(player->GetGUID().GetCounter());
return it==_records.end()?std::string():it->second.Intent;
}
std::string AdventureBehaviorMgr::InteractionSchedule(Player* player)
{
auto it=_records.find(player->GetGUID().GetCounter());
if(it==_records.end())return {};
auto& r=it->second;
auto local=Local(GameTime::GetGameTime());
if(r.Schedule && r.Date!=DateOf(local)){Generate(player,r,DateOf(local));r.Hour=-1;}
if(!r.Schedule)return (r.Deadline && GameTime::GetGameTime()>=r.Deadline?"expired:":"")+r.Intent+":"+r.Location;
auto const& slot=r.Plan[Local(GameTime::GetGameTime()).tm_hour];
return slot.Intent+":"+slot.Location;
}
void AdventureBehaviorMgr::InterruptForInteraction(Player* player)
{
InterruptForCombat(player);
auto it=_records.find(player->GetGUID().GetCounter());
if(it!=_records.end())it->second.Detail="social/group interaction; original deadline retained";
}
@@ -30,7 +30,10 @@ public:
void Update(Player* player, uint32 diff);
bool Command(ChatHandler* handler, char const* args);
void InterruptForCombat(Player* player);
void InterruptForInteraction(Player* player);
std::string CombatIntent(Player* player) const;
std::string InteractionIntent(Player* player) const;
std::string InteractionSchedule(Player* player);
private:
enum class Phase {Idle, Travel, OnSite, Switching, Handover};
struct RecoveryScene
+34 -13
View File
@@ -1,4 +1,5 @@
#include "AdventureCombatMgr.h"
#include "AdventureInteractionMgr.h"
#include "AdventureCombatPolicy.h"
#include "AdventureBehaviorMgr.h"
#include "ClientlessLuaEngine.h"
@@ -30,7 +31,7 @@ class AdventureCombatMgr::Fighter : public PlayerAI
public:
explicit Fighter(Player* player):PlayerAI(player) { }
void UpdateAI(uint32) override { }
void Tick(Creature* target,uint32 diff)
void Tick(Unit* target,uint32 diff)
{
_rend=_rend>diff?_rend-diff:0;_filler=_filler>diff?_filler-diff:0;
me->SetFacingToObject(target);
@@ -59,13 +60,16 @@ private:
uint32 _rend=0,_filler=0;
};
AdventureCombatMgr* AdventureCombatMgr::instance(){static AdventureCombatMgr mgr;return &mgr;}
bool AdventureCombatMgr::Valid(Player* p,Creature* c,bool training) const
bool AdventureCombatMgr::Valid(Player* p,Unit* c,bool training) const
{
return c && c->IsAlive() && c->IsInWorld() && p->IsInPhase(c) && p->CanSeeOrDetect(c) &&
p->IsValidAttackTarget(c) && !c->GetControllingPlayer() && !c->IsEvadingAttacks() &&
(training?(c->GetScriptName()=="npc_training_dummy" && c->getLevel()<=p->getLevel()+2 && !c->isWorldBoss()):c->GetScriptName()!="npc_training_dummy");
if(!c || !c->IsAlive() || !c->IsInWorld() || !p->IsInPhase(c) ||
!p->CanSeeOrDetect(c) || !p->IsValidAttackTarget(c))return false;
if(auto creature=c->ToCreature())
return !creature->IsEvadingAttacks() && (!training || !creature->GetControllingPlayer()) &&
(training?(creature->GetScriptName()=="npc_training_dummy" && creature->getLevel()<=p->getLevel()+2 && !creature->isWorldBoss()):creature->GetScriptName()!="npc_training_dummy");
return !training && c->ToPlayer(); // Native PvP/sanctuary/duel checks above.
}
bool AdventureCombatMgr::Reachable(Player* p,Creature* c) const
bool AdventureCombatMgr::Reachable(Player* p,Unit* c) const
{
if(!p->IsWithinLOSInMap(c))return false;
if(p->IsWithinMeleeRange(c))return true;
@@ -75,7 +79,7 @@ bool AdventureCombatMgr::Reachable(Player* p,Creature* c) const
}
bool AdventureCombatMgr::Safe(Player* p,Creature* c) const
{
if(!Valid(p,c,false) || p->GetExactDist(c)>40 || (c->GetVictim() && c->GetVictim()!=p))return false;
if(!Valid(p,c,false) || c->GetControllingPlayer() || p->GetExactDist(c)>40 || (c->GetVictim() && c->GetVictim()!=p))return false;
std::vector<Creature*> others;p->GetCreatureListWithEntryInGrid(others,0,35);
unsigned hostiles=0;
for(auto other:others)if(other->IsAlive() && p->IsInPhase(other) && p->IsHostileTo(other) &&
@@ -95,7 +99,7 @@ Creature* AdventureCombatMgr::Dummy(Player* p) const
return best;
}
bool AdventureCombatMgr::TrainingAvailable(Player* p) const{return Dummy(p)!=nullptr;}
bool AdventureCombatMgr::Start(Player* p,Record& r,Creature* c,bool training,bool defending,time_t until)
bool AdventureCombatMgr::Start(Player* p,Record& r,Unit* c,bool training,bool defending,time_t until)
{
if(!Valid(p,c,training) || !p->IsAlive() || p->IsBeingTeleported() || p->GetTransport() || p->IsInFlight() || p->IsCharmed())return false;
sAdventureBehaviorMgr->InterruptForCombat(p);
@@ -122,8 +126,8 @@ bool AdventureCombatMgr::Attack(Player* p,uint32 spawn)
}
void AdventureCombatMgr::DamageTaken(Player* p,Unit* attacker,uint32 damage)
{
auto c=attacker?attacker->ToCreature():nullptr;
if(!damage || !p->IsAlive() || !c || c->GetControllingPlayer())return;
auto c=attacker;
if(!damage || !p->IsAlive() || !c)return;
auto& r=_records[p->GetGUID().GetCounter()];
if(std::find(r.Damagers.begin(),r.Damagers.end(),c->GetGUID())==r.Damagers.end())r.Damagers.push_back(c->GetGUID());
}
@@ -131,6 +135,8 @@ void AdventureCombatMgr::Finish(Player* p,Record& r)
{
TC_LOG_INFO("server.adventurebot","COMBAT_END guid=%u target='%s' training=%u",p->GetGUID().GetCounter(),r.Target.ToString().c_str(),r.Training);
bool training=r.Training;
if(!training && r.Helper)sAdventureInteractionMgr->CombatHelp(p,r.Helper);
r.Helper.Clear();
p->AttackStop();p->CastStop();p->StopMoving();p->GetMotionMaster()->Clear();p->SetSelection(ObjectGuid::Empty);
sClientlessTravelMgr->CancelTravel(p->GetGUID().GetCounter());
if(training && std::all_of(p->getAttackers().begin(),p->getAttackers().end(),[](Unit* u)
@@ -253,15 +259,15 @@ bool AdventureCombatMgr::Update(Player* p,uint32 diff)
// target. Keep all subsequent damagers ordered for after that focus dies.
if(!r.Defending)while(!r.Damagers.empty())
{
auto id=r.Damagers.front();r.Damagers.pop_front();auto c=ObjectAccessor::GetCreature(*p,id);
auto id=r.Damagers.front();r.Damagers.pop_front();auto c=ObjectAccessor::GetUnit(*p,id);
if(!Valid(p,c,false))continue;
if(r.AI)Finish(p,r);
Start(p,r,c,false,true,0);break;
}
if(!r.AI && !p->IsInCombat() && !r.Cooldown)
if(!r.AI && !p->GetGroup() && !p->IsInCombat() && !r.Cooldown)
if(auto c=Proactive(p))Start(p,r,c,false,false,0);
if(!r.AI)return false;
auto c=ObjectAccessor::GetCreature(*p,r.Target);
auto c=ObjectAccessor::GetUnit(*p,r.Target);
if(!Valid(p,c,r.Training) || (r.Until && GameTime::GetGameTime()>=r.Until) || p->GetExactDist(c)>80)
{Finish(p,r);return true;}
if(!Reachable(p,c))
@@ -291,3 +297,18 @@ bool AdventureCombatMgr::Busy(Player* player) const
auto it=_records.find(player->GetGUID().GetCounter());
return it!=_records.end() && (it->second.AI || it->second.Dead || !it->second.Damagers.empty());
}
bool AdventureCombatMgr::Assist(Player* p,Unit* target)
{
if(!p->GetGroup() || Busy(p) || !Valid(p,target,false) || p->GetExactDist(target)>40 ||
!Reachable(p,target) || p->GetHealthPct()<30)return false;
if(auto c=target->ToCreature())
if(c->isWorldBoss() || c->getLevel()>p->getLevel()+2)return false;
return Start(p,_records[p->GetGUID().GetCounter()],target,false,false,0);
}
void AdventureCombatMgr::SharedTargetDamage(Player* p,ObjectGuid helper,ObjectGuid target)
{
auto it=_records.find(p->GetGUID().GetCounter());
if(it!=_records.end() && it->second.Target==target && !it->second.Training)it->second.Helper=helper;
}
+6 -4
View File
@@ -22,23 +22,25 @@ public:
bool Train(Player* player,uint32 durationMs,time_t deadline=0);
bool Attack(Player* player,uint32 creatureSpawnId);
void Stop(Player* player);
bool Assist(Player* player,Unit* target);
void SharedTargetDamage(Player* player,ObjectGuid helper,ObjectGuid target);
bool Busy(Player* player) const;
private:
class Fighter;
struct Record
{
std::shared_ptr<Fighter> AI;
ObjectGuid Target;
ObjectGuid Target,Helper;
std::deque<ObjectGuid> Damagers;
bool Training=false,Defending=false,Suspended=false,Dead=false,CemeteryPending=false;
time_t Until=0,NextCorpseSearch=0;
std::vector<Position> FailedCorpsePoints;
uint32 Timer=0,UnreachableMs=0,Cooldown=0;
};
bool Start(Player* player,Record& record,Creature* target,bool training,bool defending,time_t until);
bool Valid(Player* player,Creature* target,bool training) const;
bool Start(Player* player,Record& record,Unit* target,bool training,bool defending,time_t until);
bool Valid(Player* player,Unit* target,bool training) const;
bool Safe(Player* player,Creature* target) const;
bool Reachable(Player* player,Creature* target) const;
bool Reachable(Player* player,Unit* target) const;
Creature* Dummy(Player* player) const;
Creature* Proactive(Player* player) const;
bool Death(Player* player,Record& record);
@@ -0,0 +1,254 @@
#include "AdventureInteractionMgr.h"
#include "AdventureInteractionPolicy.h"
#include "AdventureBehaviorMgr.h"
#include "AdventureCombatMgr.h"
#include "ClientlessLuaEngine.h"
#include "ClientlessPlayerMgr.h"
#include "ClientlessTravelMgr.h"
#include "ClientlessActionExecutorMgr.h"
#include "ClientlessTravelGraphMgr.h"
#include "Chat.h"
#include "Creature.h"
#include "GameTime.h"
#include "Group.h"
#include "Map.h"
#include "MotionMaster.h"
#include "ObjectAccessor.h"
#include "Opcodes.h"
#include "PartyPackets.h"
#include "Player.h"
#include "Random.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "Log.h"
#include <vector>
namespace
{
float Sociability(Player* p)
{
auto profile=sClientlessLuaEngine->GetAdventureProfile(p->GetGUID().GetCounter());
if(profile){auto it=profile->Personality.find("sociability");if(it!=profile->Personality.end())return it->second;}
return 0.5f;
}
float Loyalty(Player* p)
{
auto profile=sClientlessLuaEngine->GetAdventureProfile(p->GetGUID().GetCounter());
if(profile){auto it=profile->Personality.find("group_loyalty");if(it!=profile->Personality.end())return std::max(0.0f,std::min(1.0f,it->second));}
return 0.25f;
}
bool German(Player* p)
{
auto profile=sClientlessLuaEngine->GetAdventureProfile(p->GetGUID().GetCounter());
return profile && profile->Locale=="deDE";
}
bool HostileEmote(uint32 emote)
{return emote==TEXT_EMOTE_RUDE || emote==TEXT_EMOTE_THREATEN || emote==TEXT_EMOTE_ROAR;}
bool FriendlyEmote(uint32 emote)
{
switch(emote)
{
case TEXT_EMOTE_GREET:case TEXT_EMOTE_HELLO:case TEXT_EMOTE_WAVE:case TEXT_EMOTE_BOW:
case TEXT_EMOTE_SALUTE:case TEXT_EMOTE_SMILE:case TEXT_EMOTE_THANK:return true;
default:return false;
}
}
}
AdventureInteractionMgr* AdventureInteractionMgr::instance(){static AdventureInteractionMgr mgr;return &mgr;}
bool AdventureInteractionMgr::Visible(Player* p,Player* other,float distance) const
{
return other && other!=p && other->IsAlive() && other->IsInWorld() && p->IsInMap(other) &&
p->IsInPhase(other) && p->CanSeeOrDetect(other) && other->CanSeeOrDetect(p) && p->GetExactDist(other)<=distance && p->IsWithinLOSInMap(other);
}
void AdventureInteractionMgr::PartySay(Player* p,char const* german,char const* english)
{
auto group=p->GetGroup();if(!group)return;
WorldPacket packet;
ChatHandler::BuildChatPacket(packet,CHAT_MSG_PARTY,p->GetTeam()==ALLIANCE?LANG_COMMON:LANG_ORCISH,p,nullptr,German(p)?german:english);
group->BroadcastPacket(&packet,false);
}
void AdventureInteractionMgr::Leave(Player* p,Record& r,char const* reason)
{
PartySay(p,"Ich muss weiter. Gute Reise!","I must be going. Safe travels!");
TC_LOG_INFO("server.adventurebot","INTERACTION_GROUP_LEAVE guid=%u reason='%s'",p->GetGUID().GetCounter(),reason);
p->GetGroup()->RemoveMember(p->GetGUID(),GROUP_REMOVEMETHOD_LEAVE);
sAdventureBehaviorMgr->InterruptForInteraction(p);
r.Grouped=false;r.Leader.Clear();r.Invite.Clear();r.Partner.Clear();r.SceneUntil=0;
}
void AdventureInteractionMgr::Emote(Player* p,Player* source,uint32 emote)
{
if(!Visible(p,source,25) || p->IsInCombat() || !p->IsAlive() || p->GetMap()->IsDungeon() ||
p->IsBeingTeleported() || p->GetTransport() || p->IsInFlight() || p->IsCharmed())return;
auto now=GameTime::GetGameTime();auto& r=_records[p->GetGUID().GetCounter()];
if(r.Partner || now<r.NextSocial || (!FriendlyEmote(emote) && !HostileEmote(emote)))return;
auto it=r.Encounters.find(source->GetGUID());if(it!=r.Encounters.end() && now<it->second)return;
r.Partner=source->GetGUID();r.Reply=emote;r.ReplyAt=now+urand(1,3);r.SceneUntil=r.ReplyAt+2;
r.NextSocial=now+15;r.Encounters[source->GetGUID()]=now+120;
}
void AdventureInteractionMgr::Reply(Player* p,Player* other,uint32 emote)
{
p->SetStandState(UNIT_STAND_STATE_STAND);p->SetFacingToObject(other);
// Hostility alone never grants permission to attack. Native attack checks
// remain in combat; these are visual reactions without combat side effects.
bool friendly=p->IsFriendlyTo(other) && !HostileEmote(emote);
p->HandleEmoteCommand(friendly?(emote==TEXT_EMOTE_BOW?EMOTE_ONESHOT_BOW:EMOTE_ONESHOT_WAVE):EMOTE_ONESHOT_NO);
if(emote==TEXT_EMOTE_GREET && friendly && urand(0,2)==0)
p->Say(German(p)?"Gute Reise!":"Safe travels!",p->GetTeam()==ALLIANCE?LANG_COMMON:LANG_ORCISH,other);
TC_LOG_INFO("server.adventurebot","INTERACTION_EMOTE guid=%u other=%u friendly=%u",p->GetGUID().GetCounter(),other->GetGUID().GetCounter(),friendly);
}
void AdventureInteractionMgr::CombatHelp(Player* p,ObjectGuid helper)
{ _records[p->GetGUID().GetCounter()].Helper=helper; }
void AdventureInteractionMgr::Offline(Player* p){_records.erase(p->GetGUID().GetCounter());}
bool AdventureInteractionMgr::Update(Player* p,uint32 diff)
{
auto& r=_records[p->GetGUID().GetCounter()];auto now=GameTime::GetGameTime();
if(r.Timer>diff){r.Timer-=diff;return r.Grouped || r.SceneUntil>now;}
r.Timer=1000;
bool unsafe=!p->IsAlive() || p->IsInCombat() || sAdventureCombatMgr->Busy(p) ||
p->IsBeingTeleported() || p->GetTransport() || p->IsInFlight() || p->IsCharmed();
if(unsafe){r.Partner.Clear();r.SceneUntil=0;return p->GetGroup()!=nullptr;}
for(auto it=r.Encounters.begin();it!=r.Encounters.end();)if(now>=it->second)it=r.Encounters.erase(it);else ++it;
if(r.Helper)
{
auto helper=ObjectAccessor::FindPlayer(r.Helper);r.Helper.Clear();
if(Visible(p,helper,25) && p->IsFriendlyTo(helper))
p->Say(German(p)?"Danke für die Hilfe!":"Thank you for the help!",p->GetTeam()==ALLIANCE?LANG_COMMON:LANG_ORCISH,helper);
}
if(auto invite=p->GetGroupInvite())
{
auto leader=invite->GetLeaderGUID();
if(r.Invite!=leader){r.Invite=leader;r.InviteAt=now+urand(2,4);}
if(now>=r.InviteAt)
{
auto inviter=ObjectAccessor::FindPlayer(leader);
bool accept=inviter && inviter->IsAlive() && p->IsFriendlyTo(inviter) && !p->GetMap()->IsDungeon() &&
!inviter->GetSession()->IsClientless() && frand(0,1)<AdventureInteractionPolicy::Willingness(sAdventureBehaviorMgr->InteractionIntent(p),Sociability(p));
WorldPackets::Party::PartyInviteResponse response{WorldPacket(CMSG_PARTY_INVITE_RESPONSE)};response.Accept=accept;
p->GetSession()->HandlePartyInviteResponseOpcode(response);r.Invite.Clear();
TC_LOG_INFO("server.adventurebot","INTERACTION_INVITE guid=%u leader=%u accepted=%u",p->GetGUID().GetCounter(),leader.GetCounter(),accept);
}
}
else r.Invite.Clear();
if(r.Partner)
{
auto other=ObjectAccessor::FindPlayer(r.Partner);
if(!Visible(p,other,25) || now>=r.SceneUntil){r.Partner.Clear();r.SceneUntil=0;}
else if(now>=r.ReplyAt)
{
sAdventureBehaviorMgr->InterruptForInteraction(p);Reply(p,other,r.Reply);r.ReplyAt=r.SceneUntil+1;
}
if(r.Partner)return true;
}
if(auto group=p->GetGroup())
{
auto leader=ObjectAccessor::FindPlayer(group->GetLeaderGUID());
if(!r.Grouped)
{
r.Grouped=true;r.Schedule=sAdventureBehaviorMgr->InteractionSchedule(p);
r.Partner.Clear();r.SceneUntil=0;
sAdventureBehaviorMgr->InterruptForInteraction(p);
PartySay(p,"Ich begleite euch eine Weile.","I'll accompany you for a while.");
}
if(r.Leader!=group->GetLeaderGUID())
{
r.Leader=group->GetLeaderGUID();r.LastActivity=now;r.AfkSince=0;
if(leader){r.LeaderPosition.Relocate(leader);r.LeaderMap=leader->GetMapId();r.Selection=leader->GetTarget();}
}
if(leader)
{
r.LastActivity=std::max(r.LastActivity,sClientlessPlayerMgr->LastActivity(leader->GetGUID()));
if(r.LeaderMap!=leader->GetMapId() || r.LeaderPosition.GetExactDist(leader)>0.1f ||
r.LeaderPosition.GetOrientation()!=leader->GetOrientation() || r.Selection!=leader->GetTarget() ||
leader->IsInCombat() || leader->HasUnitState(UNIT_STATE_CASTING))r.LastActivity=now;
r.LeaderPosition.Relocate(leader);r.LeaderMap=leader->GetMapId();r.Selection=leader->GetTarget();
if(leader->isAFK()){if(!r.AfkSince)r.AfkSince=now;}else r.AfkSince=0;
}
if(AdventureInteractionPolicy::CanLeave(p->IsAlive(),p->IsInCombat(),p->GetMap()->IsDungeon(),p->GetTransport() || p->IsInFlight()))
{
if(r.Schedule!=sAdventureBehaviorMgr->InteractionSchedule(p))
{
r.Schedule=sAdventureBehaviorMgr->InteractionSchedule(p);
if(frand(0,1)>=Loyalty(p)){Leave(p,r,"intent-change");return false;}
}
if(AdventureInteractionPolicy::LeaderExpired(now,r.AfkSince?r.AfkSince:r.LastActivity)){Leave(p,r,"leader-afk");return false;}
}
// Existing warrior executor supplies damage assistance. Roles/healing
// deliberately remain a later profile-driven extension.
for(auto member=group->GetFirstMember();member;member=member->next())
{
auto ally=member->GetSource();if(!ally || !ally->IsAlive() || !p->IsInMap(ally) || p->GetExactDist(ally)>40)continue;
auto target=ally->IsInCombat()?ally->GetVictim():nullptr;
if(!target)for(auto attacker:ally->getAttackers())if(attacker->GetVictim()==ally){target=attacker;break;}
if(target && sAdventureCombatMgr->Assist(p,target))return true;
}
if(!leader || leader==p || !leader->IsAlive())return true;
auto execution=sClientlessActionExecutorMgr->GetStatus(p->GetGUID().GetCounter());
if(execution && (execution->State==ClientlessActionExecutionState::Moving || execution->State==ClientlessActionExecutionState::SelectingTarget))return true;
if(!p->IsInMap(leader) || p->GetExactDist(leader)>=100)
{
if(now>=r.NextRoute)
{
r.NextRoute=now+15;
std::string node;float distance=0;
sClientlessTravelGraphMgr->EnsureLoaded();
if(sClientlessTravelGraphMgr->FindNearestNodeToPosition(leader->GetMapId(),leader->GetPositionX(),leader->GetPositionY(),leader->GetPositionZ(),node,distance) && distance<1000)
{
ClientlessAction action;action.Name="travel_to_node";action.Parameters["node"]=node;
sClientlessPlayerMgr->QueueAction(p->GetGUID().GetCounter(),std::move(action));
}
}
return true;
}
if(!p->IsInPhase(leader) || !p->CanSeeOrDetect(leader))return true;
if(p->GetExactDist(leader)>5 && p->GetExactDist(leader)<100)
{
auto nav=sClientlessTravelMgr->GetStatus(p->GetGUID().GetCounter());
if(!nav || nav->State!=ClientlessTravelState::Moving || nav->Target.GetExactDist(leader)>5)
{
Position end;leader->GetNearPoint(p,end.m_positionX,end.m_positionY,end.m_positionZ,3,leader->GetOrientation()+3.14159265f);
ClientlessPathDiagnostics path;
if(sClientlessTravelMgr->AnalyzePath(p,end,path) && path.Complete && !path.Suspicious && path.PathLength<150)
sClientlessTravelMgr->StartTravel(p,end);
}
}
else if(p->GetExactDist(leader)<=5)sClientlessTravelMgr->CancelTravel(p->GetGUID().GetCounter());
return true;
}
if(r.Grouped){r.Grouped=false;r.Leader.Clear();sAdventureBehaviorMgr->InterruptForInteraction(p);}
auto intent=sAdventureBehaviorMgr->InteractionIntent(p);
if(now>=r.NextEncounter && now>=r.NextSocial && !p->GetMap()->IsDungeon() && (intent=="social" || intent=="leisure" || intent=="rest"))
{
r.NextEncounter=now+urand(30,60);
std::vector<Player*> people;p->GetPlayerListInGrid(people,12);
for(auto other:people)
{
if(!Visible(p,other,12) || !sClientlessLuaEngine->GetAdventureProfile(other->GetGUID().GetCounter()) ||
p->GetGUID().GetCounter()>=other->GetGUID().GetCounter() || other->IsInCombat() || other->GetGroup())continue;
auto& peer=_records[other->GetGUID().GetCounter()];
auto peerIntent=sAdventureBehaviorMgr->InteractionIntent(other);
if((peerIntent!="social" && peerIntent!="leisure" && peerIntent!="rest") || other->HasUnitState(UNIT_STATE_CASTING))continue;
if(peer.Partner || peer.NextSocial>now || r.Encounters.count(other->GetGUID()) || frand(0,1)>Sociability(p))continue;
// One initiator reserves both participants; animation replies do
// not generate incoming text-emote events, preventing loops.
r.Partner=other->GetGUID();r.Reply=p->IsFriendlyTo(other)?TEXT_EMOTE_GREET:TEXT_EMOTE_THREATEN;
r.ReplyAt=now+1;r.SceneUntil=now+5;r.Encounters[other->GetGUID()]=now+300;
peer.Partner=p->GetGUID();peer.Reply=r.Reply;peer.ReplyAt=now+3;peer.SceneUntil=now+5;
peer.NextSocial=now+60;peer.Encounters[p->GetGUID()]=now+300;
return true;
}
}
return false;
}
void AdventureInteractionMgr::Report(ChatHandler* handler,Player* p) const
{
auto it=_records.find(p->GetGUID().GetCounter());
if(it==_records.end()){handler->SendSysMessage("No interaction state yet.");return;}
auto const& r=it->second;auto now=GameTime::GetGameTime();
handler->PSendSysMessage("Interactions guid=%u grouped=%u leader=%u partner=%u intent='%s' schedule='%s'",
p->GetGUID().GetCounter(),p->GetGroup()?1:0,r.Leader.GetCounter(),r.Partner.GetCounter(),
sAdventureBehaviorMgr->InteractionIntent(p).c_str(),r.Schedule.c_str());
handler->PSendSysMessage(" leaderIdleSeconds=%llu afkSeconds=%llu sociability=%.2f loyalty=%.2f",
static_cast<unsigned long long>(r.LastActivity && now>=r.LastActivity?now-r.LastActivity:0),
static_cast<unsigned long long>(r.AfkSince && now>=r.AfkSince?now-r.AfkSince:0),Sociability(p),Loyalty(p));
}
@@ -0,0 +1,41 @@
#ifndef VCORE_ADVENTURE_INTERACTION_MGR_H
#define VCORE_ADVENTURE_INTERACTION_MGR_H
#include "Common.h"
#include "ObjectGuid.h"
#include "Position.h"
#include <ctime>
#include <string>
#include <unordered_map>
class Player;
class ChatHandler;
class TC_GAME_API AdventureInteractionMgr
{
public:
static AdventureInteractionMgr* instance();
bool Update(Player* player,uint32 diff);
void Emote(Player* player,Player* source,uint32 emote);
void Offline(Player* player);
void CombatHelp(Player* player,ObjectGuid helper);
void Report(ChatHandler* handler,Player* player) const;
private:
struct Record
{
uint32 Timer=0;
ObjectGuid Leader,Invite,Partner,Selection,Helper;
time_t NextRoute=0,NextEncounter=0;
Position LeaderPosition;
uint32 LeaderMap=0;
time_t InviteAt=0,LastActivity=0,AfkSince=0,ReplyAt=0,SceneUntil=0,NextSocial=0;
uint32 Reply=0;
bool Grouped=false;
std::string Schedule;
std::unordered_map<ObjectGuid,time_t> Encounters;
};
bool Visible(Player* player,Player* other,float distance) const;
void Reply(Player* player,Player* other,uint32 emote);
void Leave(Player* player,Record& record,char const* reason);
void PartySay(Player* player,char const* german,char const* english);
std::unordered_map<uint32,Record> _records;
};
#define sAdventureInteractionMgr AdventureInteractionMgr::instance()
#endif
@@ -0,0 +1,20 @@
#ifndef VCORE_ADVENTURE_INTERACTION_POLICY_H
#define VCORE_ADVENTURE_INTERACTION_POLICY_H
#include <algorithm>
#include <cstdint>
#include <string>
namespace AdventureInteractionPolicy
{
constexpr std::int64_t LeaderTimeout=600;
inline float Willingness(std::string const& intent,float sociability)
{
float bias=intent=="rest"?-0.5f:intent=="profession"||intent=="economy"?-0.25f:
intent=="adventure"||intent=="instance"?0.2f:0.1f;
return std::max(0.0f,std::min(1.0f,sociability+bias));
}
inline bool CanLeave(bool alive,bool combat,bool dungeon,bool transport)
{return alive && !combat && !dungeon && !transport;}
inline bool LeaderExpired(std::int64_t now,std::int64_t lastActivity)
{return lastActivity>0 && now>=lastActivity && now-lastActivity>=LeaderTimeout;}
}
#endif
@@ -5,6 +5,7 @@
#include "ClientlessTravelMgr.h"
#include "AdventureBehaviorMgr.h"
#include "AdventureCombatMgr.h"
#include "AdventureInteractionMgr.h"
#include "AdventureSupplyMgr.h"
#include "AdventureSocialActions.h"
#include "Config.h"
@@ -223,12 +224,17 @@ public:
void Update(Player* player, uint32 diff) override
{
if(sAdventureSupplyMgr->Update(player,diff,sAdventureCombatMgr->Busy(player)))return;
if(!sAdventureCombatMgr->Update(player,diff))sAdventureBehaviorMgr->Update(player,diff);
if(!sAdventureCombatMgr->Update(player,diff) && !sAdventureInteractionMgr->Update(player,diff))
sAdventureBehaviorMgr->Update(player,diff);
}
void OnSharedTargetDamage(Player* player,ObjectGuid helper,ObjectGuid target) override
{ sAdventureCombatMgr->SharedTargetDamage(player,helper,target); }
void OnTextEmote(Player* player, Player* source, uint32 emote) override
{ sAdventureInteractionMgr->Emote(player,source,emote); }
void OnDamageTaken(Player* player, Unit* attacker, uint32 damage) override
{ sAdventureCombatMgr->DamageTaken(player, attacker, damage); }
void OnPlayerLogout(Player* player) override
{ sAdventureSupplyMgr->Offline(player); sAdventureCombatMgr->Offline(player); sAdventureBehaviorMgr->Offline(player); }
{ sAdventureInteractionMgr->Offline(player); sAdventureSupplyMgr->Offline(player); sAdventureCombatMgr->Offline(player); sAdventureBehaviorMgr->Offline(player); }
};
}
+50 -1
View File
@@ -1,4 +1,5 @@
#include "ClientlessPlayerMgr.h"
#include "GameTime.h"
#include "ClientlessTravelMgr.h"
#include "ClientlessTransportMgr.h"
#include "ClientlessPerceptionMgr.h"
@@ -154,7 +155,9 @@ void ClientlessPlayerMgr::Update(uint32 diff)
// Damage can arrive from different map workers. Hand GUID-only notices to
// the world-thread controller, preserving order within each character.
decltype(_damageNotices) damageNotices;
{ std::lock_guard<std::mutex> lock(_damageMutex); damageNotices.swap(_damageNotices); }
decltype(_emoteNotices) emoteNotices;
decltype(_helpNotices) helpNotices;
{ std::lock_guard<std::mutex> lock(_damageMutex); damageNotices.swap(_damageNotices); emoteNotices.swap(_emoteNotices); helpNotices.swap(_helpNotices); }
// CLIENTLESS_PLAYER_CORE_C8_2_TRAVEL_ROBUSTNESS
sClientlessTravelMgr->Update(diff);
@@ -199,6 +202,12 @@ void ClientlessPlayerMgr::Update(uint32 diff)
auto notices=damageNotices.find(characterGuid);
if(notices!=damageNotices.end())for(auto const& notice:notices->second)
record.Controller->OnDamageTaken(player,ObjectAccessor::GetUnit(*player,notice.Attacker),notice.Damage);
auto emotes=emoteNotices.find(characterGuid);
if(emotes!=emoteNotices.end())for(auto const& notice:emotes->second)
record.Controller->OnTextEmote(player,ObjectAccessor::FindPlayer(notice.Source),notice.Emote);
auto helpers=helpNotices.find(characterGuid);
if(helpers!=helpNotices.end())for(auto const& notice:helpers->second)
record.Controller->OnSharedTargetDamage(player,notice.Helper,notice.Target);
record.Controller->Update(player, diff);
}
}
@@ -219,3 +228,43 @@ void ClientlessPlayerMgr::DamageTaken(Player* player, Unit* attacker, uint32 dam
std::lock_guard<std::mutex> lock(_damageMutex);
_damageNotices[player->GetGUID().GetCounter()].push_back({attacker->GetGUID(),damage});
}
void ClientlessPlayerMgr::TextEmote(Player* source, Player* target, uint32 emote)
{
if(!source || !target || source==target || !target->GetSession()->IsClientless())return;
std::lock_guard<std::mutex> lock(_damageMutex);
auto& queue=_emoteNotices[target->GetGUID().GetCounter()];
if(queue.size()<8)queue.push_back({source->GetGUID(),emote});
}
void ClientlessPlayerMgr::MarkActivity(Player* player)
{
if(!player)return;
std::lock_guard<std::mutex> lock(_damageMutex);
auto now=GameTime::GetGameTime();
if(_activity.size()>4096)
{
for(auto it=_activity.begin();it!=_activity.end();)
if(now-it->second>600)it=_activity.erase(it);else ++it;
}
_activity[player->GetGUID()]=now;
}
time_t ClientlessPlayerMgr::LastActivity(ObjectGuid player)
{
std::lock_guard<std::mutex> lock(_damageMutex);
auto it=_activity.find(player);return it==_activity.end()?0:it->second;
}
void ClientlessPlayerMgr::SharedTargetDamage(Unit* victim,Unit* attacker,uint32 damage)
{
auto helper=attacker?attacker->GetControllingPlayer():nullptr;
if(!damage || !helper || !victim || helper==victim)return;
std::lock_guard<std::mutex> lock(_damageMutex);
for(auto unit:victim->getAttackers())
if(auto player=unit->ToPlayer())
if(player!=helper && player->GetSession()->IsClientless())
{
auto& queue=_helpNotices[player->GetGUID().GetCounter()];
if(queue.size()<8)queue.push_back({helper->GetGUID(),victim->GetGUID()});
}
}
@@ -39,6 +39,8 @@ public:
virtual void OnPlayerLogout(Player*) { }
// Notification after health was reduced, never the mutable pre-damage event.
virtual void OnDamageTaken(Player*, Unit*, uint32) { }
virtual void OnTextEmote(Player*, Player*, uint32) { }
virtual void OnSharedTargetDamage(Player*, ObjectGuid, ObjectGuid) { }
};
class TC_GAME_API ClientlessPlayerMgr
@@ -52,6 +54,10 @@ public:
bool IsManaged(uint32 characterGuid) const;
void DamageTaken(Player* player, Unit* attacker, uint32 damage);
void TextEmote(Player* source, Player* target, uint32 emote);
void SharedTargetDamage(Unit* victim,Unit* attacker,uint32 damage);
void MarkActivity(Player* player);
time_t LastActivity(ObjectGuid player);
uint32 GetAccountId(uint32 characterGuid) const;
uint32 GetManagedCount() const { return uint32(_records.size()); }
@@ -79,6 +85,11 @@ private:
};
struct DamageNotice { ObjectGuid Attacker; uint32 Damage; };
struct HelpNotice { ObjectGuid Helper,Target; };
std::unordered_map<uint32,std::deque<HelpNotice>> _helpNotices;
struct EmoteNotice { ObjectGuid Source; uint32 Emote; };
std::unordered_map<uint32,std::deque<EmoteNotice>> _emoteNotices;
std::unordered_map<ObjectGuid,time_t> _activity;
std::mutex _damageMutex;
std::unordered_map<uint32,std::deque<DamageNotice>> _damageNotices;
std::unordered_map<uint32, ManagedPlayer> _records;
@@ -34,6 +34,7 @@ return {
},
personality = {
group_loyalty = 0.35, -- Chance to stay when the scheduled intent changes.
sociability = 0.65,
adventurousness = 0.70,
wanderlust = 0.30,
@@ -18,6 +18,7 @@ return {
},
personality = {
group_loyalty = 0.25, -- Chance to stay when the scheduled intent changes.
sociability = 0.50,
adventurousness = 0.50,
wanderlust = 0.50,
@@ -20,6 +20,7 @@
#include "ClientlessWorldPointMgr.h"
#include "AdventureBehaviorMgr.h"
#include "AdventureCombatMgr.h"
#include "AdventureInteractionMgr.h"
#include "AdventureSupplyMgr.h"
#include "ClientlessTravelGraphMgr.h"
#include "ClientlessRouteRecorderMgr.h"
@@ -60,6 +61,18 @@
#include <unordered_map>
#include <unordered_set>
namespace
{
bool InteractionStatus(ChatHandler* handler,char const* args)
{
std::istringstream input(args?args:"");uint32 guid=0;input>>guid;
auto session=sWorld->FindClientlessSession(guid);auto player=session?session->GetPlayer():nullptr;
if(!player || !sClientlessPlayerMgr->IsManaged(guid))
{handler->SendSysMessage("Usage: clientless interaction <online-managed-guid>");return false;}
sAdventureInteractionMgr->Report(handler,player);return true;
}
}
namespace ClientlessCombatPreparation
{
struct Gear {uint8 Slot;uint32 Entry;std::string Enchantments;uint8 RandomType;uint32 RandomId;};
@@ -2504,6 +2517,7 @@ static bool HandleClientlessTravelReload(ChatHandler* handler, char const* /*arg
static std::vector<ChatCommand> clientlessCommands =
{
{ "combat", rbac::RBAC_PERM_COMMAND_GM, true, &ClientlessCombatPreparation::Command, "" },
{ "interaction", rbac::RBAC_PERM_COMMAND_GM, true, &InteractionStatus, "" },
{ "luareload", rbac::RBAC_PERM_COMMAND_GM, true, &HandleClientlessLuaReload, "" },
{ "transport", rbac::RBAC_PERM_COMMAND_GM, true, &HandleClientlessTransport, "" },
{ "login", rbac::RBAC_PERM_COMMAND_GM, true, &HandleClientlessLogin, "" },
@@ -33,9 +33,14 @@ struct Position{
float GetExactDist(Position const* b)const{return GetExactDist(*b);}
};
struct Map{float GetHeight(int,float,float,float,bool,float){return 0;}}map;
struct Creature;
struct Unit:Position{virtual Creature* ToCreature(){return nullptr;}};
struct Creature:Unit{ObjectGuid guid;bool valid=true,alive=true,controlled=false,evading=false,boss=false,phase=true,visible=true,inWorld=true;unsigned level=60;std::string script="mob";Creature(unsigned n):guid{n}{}ObjectGuid GetGUID(){return guid;}Creature* ToCreature()override{return this;}bool GetControllingPlayer(){return controlled;}bool IsAlive(){return alive;}bool IsInWorld(){return inWorld;}bool IsEvadingAttacks(){return evading;}bool isWorldBoss(){return boss;}unsigned getLevel(){return level;}std::string GetScriptName(){return script;}};
struct Creature;struct Player;
struct Unit:Position{
ObjectGuid guid;bool valid=true,alive=true,phase=true,visible=true,inWorld=true;
virtual Creature* ToCreature(){return nullptr;}virtual Player* ToPlayer(){return nullptr;}
virtual bool IsAlive(){return alive;}bool IsInWorld(){return inWorld;}virtual ObjectGuid GetGUID(){return guid;}
void GetNearPoint(Player*,float&a,float&b,float&c,float,float){a=x;b=y;c=z;}float GetAngle(Player*){return 0;}
};
struct Creature:Unit{bool controlled=false,evading=false,boss=false;unsigned level=60;std::string script="mob";Creature(unsigned n){guid={n};}ObjectGuid GetGUID(){return guid;}Creature* ToCreature()override{return this;}bool GetControllingPlayer(){return controlled;}bool IsAlive(){return alive;}bool IsInWorld(){return inWorld;}bool IsEvadingAttacks(){return evading;}bool isWorldBoss(){return boss;}unsigned getLevel(){return level;}std::string GetScriptName(){return script;}};
struct Player;
struct Corpse:Position{
time_t released=100;int saves=0;ObjectGuid GetGUID(){return {700};}time_t GetGhostTime(){return released;}int GetType(){return 0;}
@@ -45,10 +50,10 @@ struct WorldPacket{int opcode;WorldPacket(int o,int):opcode(o){}template<class T
struct Session{Player* owner=nullptr;int releases=0,reclaims=0;void HandleRepopRequestOpcode(WorldPacket&);void HandleReclaimCorpseOpcode(WorldPacket&);};
struct Player:Unit{
bool alive=true,ghost=false,teleport=false,combat=false,melee=true,los=true;unsigned delay=30;int saves=0,revives=0,bones=0,graveyards=0;bool sickness=false;int durability=75;
Session session;Player(){session.owner=this;}ObjectGuid GetGUID(){return {26};}bool IsAlive(){return alive;}bool IsInWorld(){return true;}bool IsInCombat(){return combat;}bool IsBeingTeleported(){return teleport;}bool GetTransport(){return false;}bool IsInFlight(){return false;}bool IsCharmed(){return false;}
Session session;Player(){session.owner=this;guid={26};}ObjectGuid GetGUID(){return guid;}Player* ToPlayer()override{return this;}bool GetGroup(){return false;}bool IsAlive(){return alive;}bool IsInWorld(){return true;}bool IsInCombat(){return combat;}bool IsBeingTeleported(){return teleport;}bool GetTransport(){return false;}bool IsInFlight(){return false;}bool IsCharmed(){return false;}
unsigned GetMapId(){return 0;}unsigned GetInstanceId(){return 0;}bool InBattleground(){return false;}bool InArena(){return false;}bool HasFlag(int,int){return ghost;}Session*GetSession(){return &session;}Corpse*GetCorpse(){return &corpse;}
uint32 GetCorpseReclaimDelay(bool){return delay;}Map*GetMap(){return &map;}int GetPhaseShift(){return 0;}
bool IsInPhase(Creature*c){return c->phase;}bool CanSeeOrDetect(Creature*c){return c->visible;}bool IsValidAttackTarget(Creature*c){return c->valid;}unsigned getLevel(){return 60;}bool IsWithinMeleeRange(Creature*){return melee;}float GetCombatReach(){return 1;}float GetAngle(Player*){return 0;}
bool IsInPhase(Unit*c){return c->phase;}bool CanSeeOrDetect(Unit*c){return c->visible;}bool IsValidAttackTarget(Unit*c){return c->valid;}unsigned getLevel(){return 60;}bool IsWithinMeleeRange(Unit*){return melee;}float GetCombatReach(){return 1;}float GetAngle(Player*){return 0;}
void SaveToDB(){++saves;}void RepopAtGraveyard(){++graveyards;teleport=true;Relocate(-100,0,0);}void ResurrectPlayer(float hp,bool sick){assert(hp==0.5f);sickness=sick;alive=true;ghost=false;++revives;}void SpawnCorpseBones(){++bones;}
void StopMoving(){}
};
@@ -59,7 +64,7 @@ void Session::HandleReclaimCorpseOpcode(WorldPacket&){++reclaims;assert(corpse.I
void nearPoint(Creature*,Player*,float&,float&,float&,float,float){}
struct FakeCreature:Creature{using Creature::Creature;void GetNearPoint(Player*,float&x,float&y,float&z,float,float){x=this->x;y=this->y;z=this->z;}float GetAngle(Player*){return 0;}};
namespace GameTime{time_t now=100;time_t GetGameTime(){return now;}}
namespace ObjectAccessor{std::unordered_map<unsigned,Creature*> objects;Creature* GetCreature(Player&,ObjectGuid g){auto i=objects.find(g.id);return i==objects.end()?nullptr:i->second;}}
namespace ObjectAccessor{std::unordered_map<unsigned,Unit*> objects;Unit* GetUnit(Player&,ObjectGuid g){auto i=objects.find(g.id);return i==objects.end()?nullptr:i->second;}}
struct ClientlessPathDiagnostics{bool Complete=true,Suspicious=false;float PathLength=100;};
enum class ClientlessTravelState{Moving,Arrived,Stuck,PathRejected};
struct TravelStatus{ClientlessTravelState State=ClientlessTravelState::Arrived;Position Target;};
@@ -78,11 +83,11 @@ code=code.replace('bool GetControllingPlayer(){return controlled;}bool IsAlive()
# Forward declaration needed in Creature's adapter.
code=code.replace('struct Creature;','struct Creature;struct Player;',1)
code+=header+r'''
class AdventureCombatMgr::Fighter{public:int ticks=0;void Tick(Creature*,uint32){++ticks;}};
class AdventureCombatMgr::Fighter{public:int ticks=0;void Tick(Unit*,uint32){++ticks;}};
bool AdventureCombatMgr::Reachable(Player*p,Creature*)const{return p->los;}
bool AdventureCombatMgr::Reachable(Player*p,Unit*)const{return p->los;}
Creature* AdventureCombatMgr::Proactive(Player*)const{return nullptr;}
bool AdventureCombatMgr::Start(Player*,Record&r,Creature*c,bool train,bool defense,time_t until){r.AI=std::make_shared<Fighter>();r.Target=c->guid;r.Training=train;r.Defending=defense;r.Until=until;return true;}
bool AdventureCombatMgr::Start(Player*,Record&r,Unit*c,bool train,bool defense,time_t until){r.AI=std::make_shared<Fighter>();r.Target=c->GetGUID();r.Training=train;r.Defending=defense;r.Until=until;return true;}
void AdventureCombatMgr::Finish(Player*,Record&r){r.AI.reset();r.Target.Clear();r.Defending=false;r.Training=false;r.Cooldown=10000;}
'''
for name in ('bool AdventureCombatMgr::Valid(', 'void AdventureCombatMgr::DamageTaken(', 'bool AdventureCombatMgr::Death(', 'bool AdventureCombatMgr::Update('):code+='\n'+function(name)
@@ -101,6 +106,9 @@ int main(){
assert(SegmentDistance(5,3,0,0,0,0,10,0,0)==3);
AdventureCombatMgr mgr;Player p;Creature a(1),b(2),c(3);ObjectAccessor::objects={{1,&a},{2,&b},{3,&c}};
Player enemy;enemy.guid={99};ObjectAccessor::objects[99]=&enemy;
assert(mgr.Valid(&p,&enemy,false));assert(!mgr.Valid(&p,&enemy,true));
enemy.valid=false;assert(!mgr.Valid(&p,&enemy,false));enemy.valid=true;
assert(mgr.Valid(&p,&a,false));assert(!mgr.Valid(&p,&a,true));
Creature dummy(4);dummy.script="npc_training_dummy";assert(mgr.Valid(&p,&dummy,true));assert(!mgr.Valid(&p,&dummy,false));
dummy.level=63;assert(!mgr.Valid(&p,&dummy,true));dummy.level=60;
@@ -110,6 +118,7 @@ int main(){
dummy.controlled=true;assert(!mgr.Valid(&p,&dummy,true));dummy.controlled=false;
dummy.valid=false;assert(!mgr.Valid(&p,&dummy,true));
auto&r=mgr._records[26];
mgr.DamageTaken(&p,&enemy,1);assert(r.Damagers.front().id==99);r.Damagers.clear();
mgr.DamageTaken(&p,&a,0);assert(r.Damagers.empty());
mgr.DamageTaken(&p,&a,5);mgr.DamageTaken(&p,&b,5);mgr.Update(&p,500);
assert(r.Target.id==1 && r.Defending); // first actual damager
@@ -117,6 +126,10 @@ int main(){
a.alive=false;mgr.Update(&p,500);assert(!r.AI);mgr.Update(&p,500);assert(r.Target.id==2);
mgr.Finish(&p,r);r.Damagers.clear();mgr.Start(&p,r,&c,true,false,500);
mgr.DamageTaken(&p,&b,1);mgr.Update(&p,500);assert(r.Target.id==2 && r.Defending && !r.Training);
// Actual player attackers use the same first-damager focus and native PvP permission.
mgr.Finish(&p,r);r.Damagers.clear();mgr.DamageTaken(&p,&enemy,1);mgr.Update(&p,500);
assert(r.Target.id==99 && r.Defending);mgr.DamageTaken(&p,&c,1);mgr.Update(&p,500);assert(r.Target.id==99);
enemy.valid=false;mgr.Update(&p,500);assert(!r.AI);r.Damagers.clear();
// Release through native handler and save ghost plus corpse timestamp.
mgr.Finish(&p,r);p.alive=false;p.ghost=false;r.Dead=false;mgr.Death(&p,r);
assert(p.ghost && p.session.releases==1 && p.saves==1 && corpse.saves==1);
@@ -0,0 +1,118 @@
"""Run production interaction manager against deterministic native-world adapters."""
from pathlib import Path
import subprocess
import tempfile
root=Path(__file__).resolve().parents[2]
header=(root/'src/server/game/World/AdventureInteractionMgr.h').read_text()
header='\n'.join(line for line in header.splitlines() if not line.startswith('#include')).replace('private:','public:')
source=(root/'src/server/game/World/AdventureInteractionMgr.cpp').read_text()
source='\n'.join(line for line in source.splitlines() if not line.startswith('#include'))
code=r'''
#include "AdventureInteractionPolicy.h"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <ctime>
#include <string>
#include <unordered_map>
#include <vector>
using uint32=unsigned;
#define TC_GAME_API
#define TC_LOG_INFO(...) ((void)0)
struct ObjectGuid { unsigned id=0;unsigned GetCounter()const{return id;}void Clear(){id=0;}explicit operator bool()const{return id!=0;}bool operator==(ObjectGuid b)const{return id==b.id;}bool operator!=(ObjectGuid b)const{return id!=b.id;}};
namespace std { template<>struct hash<ObjectGuid>{size_t operator()(ObjectGuid g)const{return g.id;}}; }
struct Position {
float m_positionX=0,m_positionY=0,m_positionZ=0,o=0;
void Relocate(Position const*p){*this=*p;}
float GetPositionX()const{return m_positionX;}float GetPositionY()const{return m_positionY;}float GetPositionZ()const{return m_positionZ;}float GetOrientation()const{return o;}
float GetExactDist(Position const*p)const{return std::hypot(m_positionX-p->m_positionX,m_positionY-p->m_positionY);}
};
enum {TEXT_EMOTE_RUDE=1,TEXT_EMOTE_THREATEN,TEXT_EMOTE_ROAR,TEXT_EMOTE_GREET,TEXT_EMOTE_HELLO,TEXT_EMOTE_WAVE,TEXT_EMOTE_BOW,TEXT_EMOTE_SALUTE,TEXT_EMOTE_SMILE,TEXT_EMOTE_THANK,EMOTE_ONESHOT_BOW,EMOTE_ONESHOT_WAVE,EMOTE_ONESHOT_NO,UNIT_STAND_STATE_STAND,ALLIANCE,LANG_COMMON,LANG_ORCISH,CHAT_MSG_PARTY,GROUP_REMOVEMETHOD_LEAVE,CMSG_PARTY_INVITE_RESPONSE,UNIT_STATE_CASTING};
struct WorldPacket { explicit WorldPacket(int=0){} };
struct Player;struct Group;
namespace WorldPackets {namespace Party {struct PartyInviteResponse{explicit PartyInviteResponse(WorldPacket&&){}bool Accept=false;};}}
struct Session {Player*owner=nullptr;bool clientless=false;bool IsClientless(){return clientless;}void HandlePartyInviteResponseOpcode(WorldPackets::Party::PartyInviteResponse&);};
struct Map {bool dungeon=false;bool IsDungeon(){return dungeon;}};
struct Unit:Position {Player*victim=nullptr;std::vector<Unit*> attackers;virtual ~Unit()=default;virtual struct Creature*ToCreature(){return nullptr;}Unit*GetVictim(){return reinterpret_cast<Unit*>(victim);}auto&getAttackers(){return attackers;}};
struct Creature:Unit{};
struct Member {Player*player=nullptr;Member*nextMember=nullptr;Player*GetSource(){return player;}Member*next(){return nextMember;}};
struct Player:Unit {
ObjectGuid guid,selection;bool alive=true,inWorld=true,combat=false,teleport=false,transport=false,flight=false,charmed=false,casting=false,afk=false,visible=true,phase=true,los=true;
unsigned mapid=0,team=ALLIANCE;Session session;Group*group=nullptr,*invite=nullptr;Map map;int gestures=0,says=0,posture=0;uint32 lastEmote=0;
explicit Player(unsigned id=26){guid.id=id;session.owner=this;}
ObjectGuid GetGUID(){return guid;}bool IsAlive(){return alive;}bool IsInWorld(){return inWorld;}bool IsInCombat(){return combat;}bool IsBeingTeleported(){return teleport;}bool GetTransport(){return transport;}bool IsInFlight(){return flight;}bool IsCharmed(){return charmed;}bool HasUnitState(int){return casting;}
bool IsInMap(Player*p){return mapid==p->mapid;}bool IsInPhase(Player*p){return phase && p->phase;}bool CanSeeOrDetect(Player*p){return p->visible;}bool IsWithinLOSInMap(Player*p){return los && p->los;}bool IsFriendlyTo(Player*p){return team==p->team;}
Map*GetMap(){return &map;}unsigned GetMapId(){return mapid;}Session*GetSession(){return &session;}Group*GetGroup(){return group;}Group*GetGroupInvite(){return invite;}unsigned GetTeam(){return team;}ObjectGuid GetTarget(){return selection;}bool isAFK(){return afk;}
void SetStandState(int s){posture=s;}void SetFacingToObject(Player*){}void HandleEmoteCommand(uint32 e){++gestures;lastEmote=e;}void Say(char const*,int,Player*){++says;}
void GetNearPoint(Player*,float&x,float&y,float&z,float,float){x=m_positionX;y=m_positionY;z=m_positionZ;}
std::vector<Player*> people;void GetPlayerListInGrid(std::vector<Player*>&out,float){out=people;}
};
struct Group {ObjectGuid leader;Member first;int removals=0,messages=0;ObjectGuid GetLeaderGUID(){return leader;}Member*GetFirstMember(){return first.player?&first:nullptr;}void BroadcastPacket(WorldPacket*,bool){++messages;}void RemoveMember(ObjectGuid,int){++removals;assert(first.player);first.player->group=nullptr;}};
int accepts=0,declines=0;
void Session::HandlePartyInviteResponseOpcode(WorldPackets::Party::PartyInviteResponse&p){assert(owner->invite);if(p.Accept){++accepts;owner->group=owner->invite;}else ++declines;owner->invite=nullptr;}
struct ChatHandler{void SendSysMessage(char const*){}template<class...Args>void PSendSysMessage(char const*,Args...){}static void BuildChatPacket(WorldPacket&,int,int,Player*,Player*,char const*){}};
namespace GameTime {time_t now=100;time_t GetGameTime(){return now;}}
unsigned urand(unsigned a,unsigned){return a;}float roll=0;float frand(float,float){return roll;}
namespace ObjectAccessor {std::unordered_map<ObjectGuid,Player*> players;Player*FindPlayer(ObjectGuid id){auto it=players.find(id);return it==players.end()?nullptr:it->second;}}
struct Profile {std::string Locale="deDE";std::unordered_map<std::string,float> Personality;};
struct Lua {std::unordered_map<unsigned,Profile> profiles;Profile const*GetAdventureProfile(unsigned id){auto it=profiles.find(id);return it==profiles.end()?nullptr:&it->second;}}lua;auto sClientlessLuaEngine=&lua;
struct Behavior {int interrupts=0;std::string intent="social",schedule="social:inn";std::string InteractionIntent(Player*){return intent;}std::string InteractionSchedule(Player*){return schedule;}void InterruptForInteraction(Player*){++interrupts;}}behavior;auto sAdventureBehaviorMgr=&behavior;
struct Combat {bool busy=false;int assists=0;bool Busy(Player*){return busy;}bool Assist(Player*,Unit*){++assists;return true;}}combat;auto sAdventureCombatMgr=&combat;
struct ClientlessAction{std::string Name;std::unordered_map<std::string,std::string> Parameters;};
struct Core {time_t activity=0;int queued=0;time_t LastActivity(ObjectGuid){return activity;}bool QueueAction(unsigned,ClientlessAction){++queued;return true;}}core;auto sClientlessPlayerMgr=&core;
enum class ClientlessTravelState{Moving,Arrived};struct Nav {ClientlessTravelState State=ClientlessTravelState::Arrived;Position Target;};
struct ClientlessPathDiagnostics{bool Complete=true,Suspicious=false;float PathLength=10;};
struct Travel {Nav nav;bool active=false,valid=true;int starts=0,cancels=0;Nav*GetStatus(unsigned){return active?&nav:nullptr;}bool AnalyzePath(Player*,Position const&,ClientlessPathDiagnostics&){return valid;}void StartTravel(Player*,Position const&p){++starts;nav.Target=p;nav.State=ClientlessTravelState::Moving;active=true;}void CancelTravel(unsigned){++cancels;active=false;}}travel;auto sClientlessTravelMgr=&travel;
enum class ClientlessActionExecutionState{Moving,SelectingTarget,Completed};struct Execution {ClientlessActionExecutionState State=ClientlessActionExecutionState::Completed;};
struct Executor {Execution status;Execution*GetStatus(unsigned){return &status;}}executor;auto sClientlessActionExecutorMgr=&executor;
struct Graph {void EnsureLoaded(){}bool FindNearestNodeToPosition(unsigned,float,float,float,std::string&node,float&distance){node="entrance";distance=10;return true;}}graph;auto sClientlessTravelGraphMgr=&graph;
'''+header+'\n'+source+r'''
int main(){
using namespace AdventureInteractionPolicy;
assert(Willingness("adventure",.65f)>Willingness("profession",.65f));assert(Willingness("rest",0)==0);
assert(!LeaderExpired(699,100));assert(LeaderExpired(700,100));assert(!LeaderExpired(99,100));
assert(CanLeave(true,false,false,false));assert(!CanLeave(true,true,false,false));assert(!CanLeave(true,false,true,false));
AdventureInteractionMgr mgr;Player bot(26),leader(21),other(27);bot.session.clientless=true;other.session.clientless=true;
ObjectAccessor::players={{bot.guid,&bot},{leader.guid,&leader},{other.guid,&other}};
lua.profiles[26].Personality["sociability"]=.65f;lua.profiles[26].Personality["group_loyalty"]=0;lua.profiles[27]={};
Group group;group.leader=leader.guid;group.first.player=&bot;
auto tick=[&](time_t now){GameTime::now=now;return mgr.Update(&bot,1000);};
// Native invitation response creates actual membership, then suspends intent.
bot.invite=&group;tick(100);assert(!bot.group);tick(102);assert(bot.group && accepts==1);
assert(mgr._records[26].Grouped && behavior.interrupts==1);
// Follow uses verified local MMAP paths; invalid paths never move the player.
leader.m_positionX=20;tick(103);assert(travel.starts==1);travel.active=false;travel.valid=false;leader.m_positionX=30;tick(104);assert(travel.starts==1);travel.valid=true;
// Changing leader resets both AFK and inactivity timers.
mgr._records[26].AfkSince=1;group.leader=other.guid;tick(105);assert(mgr._records[26].AfkSince==0 && mgr._records[26].LastActivity==105);
group.leader=leader.guid;tick(106);
// Chat/spells count as activity even while the leader stands still.
core.activity=650;tick(650);tick(1249);assert(bot.group);tick(1250);assert(!bot.group && group.removals==1);
// Hour intent change is deferred inside instances and during combat.
core.activity=0;bot.group=&group;behavior.schedule="adventure:road";tick(1300);behavior.schedule="economy:city";
bot.map.dungeon=true;tick(1301);assert(bot.group);bot.map.dungeon=false;bot.combat=true;tick(1302);assert(bot.group);bot.combat=false;tick(1303);assert(!bot.group);
// Native AFK needs a full ten minutes even if manually set; no early exit.
bot.group=&group;tick(1400);leader.afk=true;tick(1401);tick(2000);assert(bot.group);tick(2001);assert(!bot.group);leader.afk=false;
// Grouped bots also answer directed emotes; responses do not create events.
leader.m_positionX=0;bot.group=&group;tick(2100);mgr.Emote(&bot,&leader,TEXT_EMOTE_WAVE);tick(2101);assert(bot.gestures==1);tick(2102);assert(bot.gestures==1);tick(2103);assert(bot.group);
// Friendly greeting versus hostile contact; no combat call is made.
bot.group=nullptr;tick(2120);mgr.Emote(&bot,&other,TEXT_EMOTE_WAVE);tick(2121);assert(bot.lastEmote==EMOTE_ONESHOT_WAVE);tick(2123);
other.team=999;mgr._records[26].Encounters.clear();mgr._records[26].NextSocial=0;mgr.Emote(&bot,&other,TEXT_EMOTE_THREATEN);tick(2124);assert(bot.lastEmote==EMOTE_ONESHOT_NO && combat.assists==0);tick(2126);
// Invisible/distant targets and repeated emotes are ignored.
auto gestures=bot.gestures;other.visible=false;mgr.Emote(&bot,&other,TEXT_EMOTE_WAVE);tick(2130);assert(bot.gestures==gestures);other.visible=true;
// Bot encounters reserve both participants; cooldown prevents new loops.
other.team=ALLIANCE;bot.people={&other};mgr._records.clear();tick(2200);assert(mgr._records[26].Partner==other.guid && mgr._records[27].Partner==bot.guid);
tick(2201);GameTime::now=2203;mgr.Update(&other,1000);assert(other.gestures==1);tick(2205);GameTime::now=2205;mgr.Update(&other,1000);tick(2265);assert(!mgr._records[26].Partner);
// Bot-origin invitations are declined; autonomous bot grouping is deferred.
mgr._records.clear();group.leader=other.guid;bot.invite=&group;tick(2300);tick(2302);assert(!bot.group && declines==1);
// Missing leader map uses the existing graph executor, without teleporting.
group.leader=leader.guid;bot.group=&group;leader.mapid=1;tick(2400);assert(core.queued==1);executor.status.State=ClientlessActionExecutionState::Moving;tick(2416);assert(core.queued==1);
// Pending help is acknowledged only outside combat, after caller's self-care.
mgr.CombatHelp(&bot,leader.guid);leader.mapid=0;executor.status.State=ClientlessActionExecutionState::Completed;bot.combat=true;auto says=bot.says;tick(2417);assert(bot.says==says);bot.combat=false;tick(2418);assert(bot.says==says+1);
mgr.Offline(&bot);assert(!mgr._records.count(26));
}
'''
with tempfile.TemporaryDirectory() as d:
p=Path(d);(p/'test.cpp').write_text(code)
subprocess.run(['g++','-std=c++17','-Wall','-Wextra','-I',str(root/'src/server/game/World'),str(p/'test.cpp'),'-o',str(p/'test')],check=True)
subprocess.run([str(p/'test')],check=True)
print('production interaction manager: invitations, factions, reservations, follow, AFK and deferred departure passed')
@@ -0,0 +1,56 @@
"""Exercise production map-worker social/helper queues concurrently."""
from pathlib import Path
import subprocess
import tempfile
root=Path(__file__).resolve().parents[2]
source=(root/'src/server/game/World/ClientlessPlayerMgr.cpp').read_text()
def function(signature):
a=source.index(signature);b=source.index('{',a);e=b+1;depth=1
while depth:depth+=(source[e]=='{')-(source[e]=='}');e+=1
return source[a:e]
code=r'''
#include <cassert>
#include <deque>
#include <mutex>
#include <thread>
#include <unordered_map>
#include <vector>
using uint32=unsigned;
struct ObjectGuid{unsigned id;unsigned GetCounter()const{return id;}bool operator==(ObjectGuid b)const{return id==b.id;}};
struct Player;
struct Unit {ObjectGuid guid;Player*owner=nullptr;std::vector<Unit*> attackers;ObjectGuid GetGUID(){return guid;}virtual Player*ToPlayer(){return nullptr;}Player*GetControllingPlayer(){return owner;}auto&getAttackers(){return attackers;}};
struct Session {bool clientless=true;bool IsClientless(){return clientless;}};
struct Player:Unit {Session session;Player(){owner=this;}Player*ToPlayer()override{return this;}Session*GetSession(){return &session;}};
struct ClientlessPlayerMgr {
struct EmoteNotice{ObjectGuid Source;uint32 Emote;};struct HelpNotice{ObjectGuid Helper,Target;};
std::mutex _damageMutex;std::unordered_map<uint32,std::deque<EmoteNotice>> _emoteNotices;std::unordered_map<uint32,std::deque<HelpNotice>> _helpNotices;
void TextEmote(Player*,Player*,uint32);void SharedTargetDamage(Unit*,Unit*,uint32);
};
'''+function('void ClientlessPlayerMgr::TextEmote(')+'\n'+function('void ClientlessPlayerMgr::SharedTargetDamage(')+r'''
int main(){
ClientlessPlayerMgr mgr;std::vector<Player> bots(16);std::vector<std::thread> threads;
Player helper;helper.guid={77};helper.session.clientless=false;
for(unsigned i=0;i<bots.size();++i){bots[i].guid={i+1};threads.emplace_back([&,i]{
Unit target;target.guid={900+i};target.attackers={&bots[i],&helper};
for(unsigned n=1;n<=1000;++n){mgr.TextEmote(&helper,&bots[i],n);mgr.SharedTargetDamage(&target,&helper,n);}
});}
for(auto&t:threads)t.join();
for(unsigned i=0;i<bots.size();++i){
auto const&emotes=mgr._emoteNotices[i+1];assert(emotes.size()==8);
auto const&helps=mgr._helpNotices[i+1];assert(helps.size()==8);
for(unsigned n=0;n<8;++n){assert(emotes[n].Source.id==77 && emotes[n].Emote==n+1);assert(helps[n].Helper.id==77 && helps[n].Target.id==900+i);}
}
mgr._emoteNotices.clear();mgr.TextEmote(nullptr,&bots[0],1);mgr.TextEmote(&helper,&helper,1);mgr.TextEmote(&bots[0],&helper,1);assert(mgr._emoteNotices.empty());
mgr._helpNotices.clear();Unit target;target.guid={1000};target.attackers={&bots[0]};
mgr.SharedTargetDamage(&target,&helper,0);mgr.SharedTargetDamage(&target,nullptr,10);assert(mgr._helpNotices.empty());
Unit pet;pet.guid={800};pet.owner=&helper;mgr.SharedTargetDamage(&target,&pet,10);assert(mgr._helpNotices[1].front().Helper.id==77);
}
'''
with tempfile.TemporaryDirectory() as d:
p=Path(d);(p/'test.cpp').write_text(code)
subprocess.run(['g++','-std=c++17','-pthread',str(p/'test.cpp'),'-o',str(p/'test')],check=True)
subprocess.run([str(p/'test')],check=True)
unit=(root/'src/server/game/Entities/Unit/Unit.cpp').read_text();start=unit.index('uint32 Unit::DealDamage(')
hook=unit.index('sClientlessPlayerMgr->SharedTargetDamage(',start)
assert unit.index('GetCommandStatus(CHEAT_GOD)',start)<hook<unit.index('Unit::Kill(attacker, victim, durabilityLoss);',hook)
print('Production social/helper queues: concurrent GUID-only delivery, bounded queues, human/self/zero-damage gates, pet attribution and lethal-cleanup ordering passed')