diff --git a/araxiaonline/araxia_docs/ELUNA_RELOAD_FIX.md b/araxiaonline/araxia_docs/ELUNA_RELOAD_FIX.md new file mode 100644 index 0000000000..d0f331d573 --- /dev/null +++ b/araxiaonline/araxia_docs/ELUNA_RELOAD_FIX.md @@ -0,0 +1,227 @@ +# Eluna Reload Issue - Root Cause Analysis & Fix + +**Date:** November 30, 2025 +**Issue:** `.reload eluna` command does not properly reload scripts +**Status:** ✅ FIXED + +--- + +## Root Cause Identified + +**`Eluna::UpdateEluna()` is NEVER called!** + +The reload mechanism works as follows: + +1. `.reload eluna` command calls `sElunaLoader->ReloadElunaForMap(RELOAD_GLOBAL_STATE)` +2. This reloads the bytecode cache (works correctly) +3. Then calls `e->ReloadEluna()` which just sets `reload = true` +4. The actual reload happens in `Eluna::UpdateEluna()`: + +```cpp +void Eluna::UpdateEluna(uint32 diff) +{ + if (reload && sElunaLoader->GetCacheState() == SCRIPT_CACHE_READY) +#if defined ELUNA_TRINITY + if (GetQueryProcessor().Empty()) +#endif + _ReloadEluna(); // This is where scripts actually reload! + + eventMgr->UpdateProcessors(diff); +#if defined ELUNA_TRINITY + GetQueryProcessor().ProcessReadyCallbacks(); +#endif +} +``` + +**Problem:** `UpdateEluna()` is defined but never called from the main game loops! + +--- + +## What Should Be Happening + +### For Global (World) Eluna: +```cpp +// In World::Update(uint32 diff) - should exist but doesn't +if (Eluna* e = GetEluna()) + e->UpdateEluna(diff); +``` + +### For Per-Map Eluna: +```cpp +// In Map::Update(uint32 diff) - should exist but doesn't +if (Eluna* e = GetEluna()) + e->UpdateEluna(diff); +``` + +--- + +## Current Flow (Broken) + +``` +.reload eluna + │ + ▼ +ReloadElunaForMap(RELOAD_GLOBAL_STATE) + │ + ├─► ReloadScriptCache() → Thread recompiles bytecode → WORKS ✓ + │ + └─► e->ReloadEluna() → Sets reload = true + │ + ▼ + (DEAD END - nothing checks this flag!) +``` + +--- + +## What OnWorldUpdate Does (NOT the same!) + +`Eluna::OnWorldUpdate()` is a HOOK that fires Lua callbacks: +```cpp +void Eluna::OnWorldUpdate(uint32 diff) +{ + START_HOOK(WORLD_EVENT_ON_UPDATE); + HookPush(diff); + CallAllFunctions(binding, key); // Calls Lua handlers +} +``` + +This is called from TrinityCore but it does NOT call `UpdateEluna()`! + +--- + +## Fix Options + +### Option 1: Call UpdateEluna from hooks (Minimal Change) + +Add to `OnWorldUpdate` and `OnMapUpdate`: + +```cpp +void Eluna::OnWorldUpdate(uint32 diff) +{ + UpdateEluna(diff); // ADD THIS LINE + + START_HOOK(WORLD_EVENT_ON_UPDATE); + HookPush(diff); + CallAllFunctions(binding, key); +} +``` + +**Pros:** Single-line fix, minimal code change +**Cons:** Reload only happens when OnWorldUpdate is called (which requires scripts to register for it) + +### Option 2: Explicit calls in World.cpp and Map.cpp (Proper Fix) + +**World.cpp - Add to Update():** +```cpp +void World::Update(uint32 diff) +{ + // ... existing code ... + + // Update Eluna (process reload, events, async queries) + if (Eluna* e = GetEluna()) + e->UpdateEluna(diff); + + // ... rest of update ... +} +``` + +**Map.cpp - Add to Update():** +```cpp +void Map::Update(uint32 diff) +{ + // ... existing code ... + + // Update Eluna for this map + if (Eluna* e = GetEluna()) + e->UpdateEluna(diff); + + // ... rest of update ... +} +``` + +**Pros:** Proper integration, reload always works +**Cons:** More code changes, need to find right location in Update functions + +### Option 3: Make reload synchronous (Quick Fix) + +Change `ReloadEluna()` to call `_ReloadEluna()` directly: + +```cpp +void Eluna::ReloadEluna() +{ + // Instead of: reload = true; + _ReloadEluna(); // Do it now! +} +``` + +**Pros:** Immediate reload, simplest fix +**Cons:** May have threading issues, doesn't update event processors properly + +--- + +## Recommended Fix: Option 2 + +Add explicit `UpdateEluna()` calls to both `World::Update()` and `Map::Update()`. + +This is the proper fix because: +1. `UpdateEluna` does more than just reload - it processes events and queries +2. It should be called every update tick regardless of reload +3. It matches how other subsystems are updated + +--- + +## Files Modified ✅ + +### 1. `src/server/game/World/World.cpp` + +Added to `World::Update()` after sWorldUpdateTime.UpdateWithDiff: + +```cpp +///- Update Eluna (process reload flag, timed events, async queries) +if (Eluna* e = GetEluna()) + e->UpdateEluna(diff); +``` + +### 2. `src/server/game/Maps/Map.cpp` + +Added to `Map::Update()` at the beginning of the function: + +```cpp +///- Update Eluna for this map (process reload flag, timed events, async queries) +if (Eluna* e = GetEluna()) + e->UpdateEluna(t_diff); +``` + +### 3. `src/server/game/LuaEngine/LuaEngine.cpp` + +Added INFO-level logging to show each script as it loads: + +```cpp +ELUNA_LOG_INFO("[Eluna]: Loaded script: %s", it->filepath.c_str()); +``` + +--- + +## Testing the Fix + +After implementing: + +1. Start server, verify scripts load normally +2. Modify a Lua script (add a print statement) +3. Run `.reload eluna` +4. Verify: + - "Reloading Eluna scripts..." message appears + - Script init messages appear (print statements from init.lua) + - New script changes take effect + +--- + +## Why This Wasn't Caught Before + +The Eluna codebase has hooks (`OnWorldUpdate`, `OnMapUpdate`) that sound like they should handle this, but they're actually Lua event dispatchers, not the internal update function. + +The naming is confusing: +- `OnWorldUpdate()` = Fire Lua event WORLD_EVENT_ON_UPDATE +- `UpdateEluna()` = Internal maintenance (reload, events, queries) + +These are completely different functions! diff --git a/araxiaonline/araxia_docs/admin_npcdata/04_WAYPOINT_EDITING_PLAN.md b/araxiaonline/araxia_docs/admin_npcdata/04_WAYPOINT_EDITING_PLAN.md new file mode 100644 index 0000000000..3ff2ab2b0f --- /dev/null +++ b/araxiaonline/araxia_docs/admin_npcdata/04_WAYPOINT_EDITING_PLAN.md @@ -0,0 +1,423 @@ +# Phase 4: In-Game Waypoint Editing + +**Date:** November 29, 2025 +**Status:** 📋 Planning +**Depends On:** Phase 3 (Waypoint Visualization) ✅ + +--- + +## Goal + +Create an intuitive in-game workflow for spawning creatures and defining their patrol paths without using GM commands or database editing. + +--- + +## User Workflow + +### Step 1: Spawn Creature +1. Player opens AraxiaTrinityAdmin panel +2. Player selects "Add NPC" tab +3. Player searches for creature template (e.g., "Tawny Grisette") +4. Player clicks "Spawn Here" +5. **Creature spawns at player position, facing player's direction** + +### Step 2: Define Waypoints +1. Player walks to first waypoint location +2. Player clicks "Add Waypoint" button +3. Waypoint marker appears at that location +4. Repeat: walk → click → marker appears +5. Chain as many waypoints as needed + +### Step 3: Complete Path +1. Player clicks "Finish Path" button +2. Path is saved to database +3. Creature begins patrolling the path +4. **Patrol Mode: Ping-pong (back and forth)** + +--- + +## UI Requirements + +### Add NPC Panel Enhancements +- [ ] "Spawn Here" spawns creature facing player's orientation +- [ ] After spawn, auto-target the new creature + +### New Waypoint Editing Mode +- [ ] "Start Path" button - begins waypoint recording +- [ ] "Add Waypoint" button - adds current player position as waypoint +- [ ] "Undo Last" button - removes last waypoint +- [ ] "Cancel" button - discards all waypoints +- [ ] "Finish Path" button - saves and activates + +### Visual Feedback +- [ ] Show waypoint markers as they're added (reuse visualization) +- [ ] Show numbered labels on markers (1, 2, 3...) +- [ ] Highlight current waypoint being added +- [ ] Show line connecting waypoints (if possible) + +--- + +## Server Requirements + +### New Eluna Methods Needed + +```lua +-- Spawn creature with specific orientation +creature = SpawnCreature(entry, x, y, z, orientation) + +-- Create new waypoint path +pathId = CreateWaypointPath(creatureGUID) + +-- Add waypoint to path +AddWaypointNode(pathId, nodeId, x, y, z, delay, moveType) + +-- Set path on creature (DB + runtime) +SetCreatureWaypointPath(creatureGUID, pathId) + +-- Reload creature's movement +ReloadCreatureMovement(creatureGUID) +``` + +### Database Operations +- Insert into `waypoint_path` table +- Insert into `waypoint_path_node` table +- Update `creature` table with path_id +- Update `creature_template` movement type if needed + +### Path Configuration +- **Move Type:** Walk (0) or Run (1) +- **Delay:** Time to pause at each waypoint (ms) +- **Flags:** Ping-pong / Cyclic / etc. + +--- + +## Technical Investigation ✅ COMPLETE + +### Answers Found + +**1. How does TrinityCore assign new path IDs?** +```sql +-- Get max path ID from waypoint_path_node table +SELECT MAX(PathId) FROM waypoint_path_node; +-- New path ID = max + 1 +``` +Prepared statement: `WORLD_SEL_WAYPOINT_PATH_NODE_MAX_PATHID` + +**2. Can we insert via Eluna DB queries?** +Yes! Use `WorldDBQuery()` and `WorldDBExecute()` for selects and inserts. + +**3. How do we reload a creature's path without restart?** +```cpp +// Reload path data from DB into memory +sWaypointMgr->ReloadPath(pathId); + +// Load path onto creature and start movement +creature->LoadPath(pathId); +creature->SetDefaultMovementType(WAYPOINT_MOTION_TYPE); +creature->GetMotionMaster()->Initialize(); +``` + +**4. What's the flag for ping-pong patrol?** +```cpp +WaypointPathFlags::FollowPathBackwardsFromEndToStart = 0x01 +``` +This is exactly what we need! + +**5. Can we modify paths at runtime?** +Yes - insert to DB, then call `sWaypointMgr->ReloadPath(pathId)`. + +--- + +## Implementation Details Found + +### Creating Permanent Creature (from cs_npc.cpp) +```cpp +// Create creature at position +Creature* creature = Creature::CreateCreature(entry, map, position); + +// Inherit player's phase +PhasingHandler::InheritPhaseShift(creature, player); + +// Save to database +creature->SaveToDB(map->GetId(), { map->GetDifficultyID() }); + +// Get the DB GUID +ObjectGuid::LowType db_guid = creature->GetSpawnId(); + +// Cleanup and recreate from DB (loads vendor data, quests, etc.) +creature->CleanupsBeforeDelete(); +delete creature; +creature = Creature::CreateCreatureFromDB(db_guid, map, true, true); + +// Add to grid +sObjectMgr->AddCreatureToGrid(sObjectMgr->GetCreatureData(db_guid)); +``` + +### Creating Waypoint Path (from cs_wp.cpp) +```cpp +// 1. Create the path entry +stmt = WorldDatabase.GetPreparedStatement(WORLD_INS_WAYPOINT_PATH); +stmt->setUInt32(0, pathId); // PathId +stmt->setUInt8(1, AsUnderlyingType(WaypointMoveType::Walk)); // MoveType (0=walk, 1=run) +stmt->setUInt8(2, AsUnderlyingType(WaypointPathFlags::FollowPathBackwardsFromEndToStart)); // Flags (0x01 = ping-pong!) +stmt->setNull(3); // Velocity +stmt->setString(4, "Created by AraxiaTrinityAdmin"); // Comment +WorldDatabase.Execute(stmt); + +// 2. Add waypoint nodes +stmt = WorldDatabase.GetPreparedStatement(WORLD_INS_WAYPOINT_PATH_NODE); +stmt->setUInt32(0, pathId); +stmt->setUInt32(1, nodeId); // 0, 1, 2, 3... +stmt->setFloat(2, x); +stmt->setFloat(3, y); +stmt->setFloat(4, z); +stmt->setFloat(5, orientation); // Optional +WorldDatabase.Execute(stmt); +``` + +### Assigning Path to Creature (from cs_wp.cpp) +```cpp +// 1. Update creature_addon table +stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_CREATURE_ADDON_PATH); +stmt->setUInt32(0, pathId); +stmt->setUInt64(1, creatureGuidLow); +WorldDatabase.Execute(stmt); + +// 2. Update creature movement type +stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_CREATURE_MOVEMENT_TYPE); +stmt->setUInt8(0, WAYPOINT_MOTION_TYPE); // = 2 +stmt->setUInt64(1, creatureGuidLow); +WorldDatabase.Execute(stmt); + +// 3. Apply at runtime +creature->LoadPath(pathId); +creature->SetDefaultMovementType(WAYPOINT_MOTION_TYPE); +creature->GetMotionMaster()->Initialize(); +``` + +### Key Prepared Statements +| Statement | Purpose | +|-----------|---------| +| `WORLD_SEL_WAYPOINT_PATH_NODE_MAX_PATHID` | Get highest path ID | +| `WORLD_SEL_WAYPOINT_PATH_NODE_MAX_NODEID` | Get highest node ID for a path | +| `WORLD_INS_WAYPOINT_PATH` | Create new path | +| `WORLD_INS_WAYPOINT_PATH_NODE` | Add node to path | +| `WORLD_UPD_CREATURE_ADDON_PATH` | Assign path to creature | +| `WORLD_INS_CREATURE_ADDON` | Create creature addon if missing | +| `WORLD_UPD_CREATURE_MOVEMENT_TYPE` | Set creature movement type | + +### Source Files Investigated +- `cs_npc.cpp` - HandleNpcAddCommand (permanent creature spawning) +- `cs_wp.cpp` - HandleWpAddCommand, HandleWpLoadCommand (waypoint management) +- `WaypointManager.h` - ReloadPath, VisualizePath, DevisualizePath +- `WaypointDefines.h` - WaypointPathFlags (ping-pong = 0x01) + +--- + +## Implementation Phases + +### Phase 4.1: Spawn with Orientation +- Add orientation parameter to creature spawn +- Update AddNPC panel to use player facing + +### Phase 4.2: Path Creation Infrastructure +- Investigate DB schema and path creation +- Create Eluna methods for path manipulation +- Test creating paths via Lua + +### Phase 4.3: Waypoint Recording UI +- Add "waypoint mode" to UI +- Track waypoints in client memory +- Send waypoint list to server on "Finish" + +### Phase 4.4: Path Persistence & Activation +- Save path to database +- Assign path to creature +- Reload creature movement +- Verify patrol works + +### Phase 4.5: Polish +- Numbered waypoint markers +- Undo functionality +- Path editing (modify existing paths) +- Delete waypoints + +--- + +## Path Types Reference + +From TrinityCore `WaypointDefines.h`: + +```cpp +enum WaypointMoveType : uint32 +{ + WAYPOINT_MOVE_TYPE_WALK = 0, + WAYPOINT_MOVE_TYPE_RUN = 1, + WAYPOINT_MOVE_TYPE_LAND = 2, // Flying creatures + WAYPOINT_MOVE_TYPE_TAKEOFF = 3 +}; + +enum WaypointPathFlags : uint32 +{ + WAYPOINT_PATH_FLAG_NONE = 0x00, + WAYPOINT_PATH_FLAG_FOLLOW_PATH_BACKWARDS = 0x01, // Ping-pong! + // Others... +}; +``` + +**Key Flag:** `WAYPOINT_PATH_FLAG_FOLLOW_PATH_BACKWARDS` = Ping-pong patrol + +--- + +## Database Schema Reference + +### `waypoint_path` +| Column | Type | Description | +|--------|------|-------------| +| PathId | int | Unique path identifier | +| MoveType | tinyint | Walk/Run/etc | +| Flags | int | Path behavior flags | +| Comment | varchar | Description | + +### `waypoint_path_node` +| Column | Type | Description | +|--------|------|-------------| +| PathId | int | FK to waypoint_path | +| NodeId | int | Order in path (1, 2, 3...) | +| PositionX | float | World X coordinate | +| PositionY | float | World Y coordinate | +| PositionZ | float | World Z coordinate | +| Orientation | float | Optional facing | +| Delay | int | Pause time in ms | + +### `creature` +| Column | Type | Description | +|--------|------|-------------| +| guid | int | Unique creature instance | +| waypointPathId | int | FK to waypoint_path | +| MovementType | tinyint | 0=Idle, 1=Random, 2=Waypoint | + +--- + +## Success Criteria + +**Phase 4 Complete When:** +- [ ] Can spawn creature facing player direction +- [ ] Can walk around and add waypoints with button clicks +- [ ] Waypoints visualize as they're added +- [ ] Can finish path and save to database +- [ ] Creature immediately starts patrolling +- [ ] Patrol goes back and forth (ping-pong) +- [ ] Path persists across server restart + +--- + +## Performance-First Design + +**Goal:** Feel native - zero perceptible lag, instant feedback. + +### Design Principles +1. **Single C++ call per operation** - No Lua→C++→Lua→C++ round-trips +2. **Batch DB writes** - One transaction for entire path +3. **Memory-first, DB-second** - Update WaypointMgr cache directly +4. **Prepared statements only** - No string concatenation SQL +5. **Minimal data transfer** - Client sends coords, server does everything else + +### High-Performance API + +#### `player:SpawnCreatureAtPlayer(entry)` → creature +Single call: get position → create creature → save DB → return creature + +#### `CreateWaypointPath(nodes, flags, moveType)` → pathId +Single call with batched transaction: +- Generate path ID +- INSERT path +- INSERT ALL nodes (batched) +- COMMIT (one DB round-trip) +- ReloadPath into memory +- Return pathId + +#### `creature:AssignWaypointPath(pathId)` → bool +Single call: +- UPDATE creature_addon +- UPDATE movement type +- LoadPath (memory) +- Initialize motion +- Creature patrols immediately + +### Data Flow +``` +CLIENT SERVER +────── ────── +Click "Add Waypoint" + └─► Store {x,y,z} locally (NO server call) + +Click "Add Waypoint" + └─► Store {x,y,z} locally (NO server call) + +Click "Finish Path" + └─► Send ALL waypoints ───────► CreateWaypointPath() + in single message └─► Batched transaction + └─► Return pathId + ◄─── pathId ──────────────────────┘ + + └─► Send AssignPath ──────────► creature:AssignWaypointPath() + └─► Creature patrols! +``` + +### Performance Target +| Operation | Target | Method | +|-----------|--------|--------| +| Spawn creature | <5ms | Single C++ call | +| Create 10-node path | <15ms | Batched transaction | +| Assign + activate | <5ms | Memory operations | +| **Total workflow** | **<50ms** | **Feels instant** | + +--- + +## C++ Methods to Implement + +### 1. PlayerMethods.h - SpawnCreatureAtPlayer +```cpp +int SpawnCreatureAtPlayer(Eluna* E, Player* player) +{ + uint32 entry = E->CHECKVAL(2); + // Create at player pos, save to DB, return creature +} +``` + +### 2. GlobalMethods.h - CreateWaypointPath +```cpp +int CreateWaypointPath(Eluna* E) +{ + // Parse Lua table of nodes + // Batched transaction insert + // ReloadPath + // Return pathId +} +``` + +### 3. CreatureMethods.h - AssignWaypointPath +```cpp +int AssignWaypointPath(Eluna* E, Creature* creature) +{ + uint32 pathId = E->CHECKVAL(2); + // Update DB + LoadPath + Initialize +} +``` + +--- + +## Implementation Order + +1. **SpawnCreatureAtPlayer** - Quick win, immediately useful +2. **AssignWaypointPath** - Can test with existing paths +3. **CreateWaypointPath** - Batched path creation +4. **Client UI** - Waypoint recording mode + +--- + +## Links + +- [Waypoint Visualization](./03_WAYPOINT_VISUALIZATION.md) - Foundation for this work +- [Progress Tracker](./02_PROGRESS.md) - Session logs diff --git a/src/server/game/LuaEngine/LuaEngine.cpp b/src/server/game/LuaEngine/LuaEngine.cpp index b5e05d8c48..681be93af0 100644 --- a/src/server/game/LuaEngine/LuaEngine.cpp +++ b/src/server/game/LuaEngine/LuaEngine.cpp @@ -250,7 +250,7 @@ void Eluna::RunScripts() if (ExecuteCall(1, 0)) { // Successfully called require on the script - ELUNA_LOG_DEBUG("[Eluna]: Successfully loaded `%s`", it->filepath.c_str()); + ELUNA_LOG_INFO("[Eluna]: Loaded script: %s", it->filepath.c_str()); ++count; continue; } diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 4794aa145a..52dc8414ef 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -658,6 +658,10 @@ void Map::UpdatePlayerZoneStats(uint32 oldZone, uint32 newZone) void Map::Update(uint32 t_diff) { + ///- Update Eluna for this map (process reload flag, timed events, async queries) + if (Eluna* e = GetEluna()) + e->UpdateEluna(t_diff); + _dynamicTree.update(t_diff); /// update worldsessions for existing players for (m_mapRefIter = m_mapRefManager.begin(); m_mapRefIter != m_mapRefManager.end(); ++m_mapRefIter) diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 08b032671b..7262ee2307 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -2162,6 +2162,10 @@ void World::Update(uint32 diff) sWorldUpdateTime.UpdateWithDiff(diff); + ///- Update Eluna (process reload flag, timed events, async queries) + if (Eluna* e = GetEluna()) + e->UpdateEluna(diff); + ///- Update the different timers for (int i = 0; i < WUPDATE_COUNT; ++i) {