fix(waypoints): Display configuration and client refresh

- Fix waypoint markers showing as human models in 11.2.5 client
- Use Elven Wisp (displayId 1824) which renders correctly
- Add runtime display config via Lua shared data (no recompile needed)
- Add client refresh (DestroyForNearbyPlayers + UpdateObjectVisibilityOnCreate)
- Graceful MySQL error handling (no crash on bad MCP queries)
- Fix displayId arg parsing in VisualizeWaypointPath
- Add documentation for display IDs and configuration
This commit is contained in:
2025-12-02 22:55:11 -05:00
parent 90d3f69242
commit c2e1b557cc
4 changed files with 130 additions and 16 deletions

View File

@@ -0,0 +1,108 @@
# Waypoint Display Configuration
## Overview
Waypoint markers are visual indicators spawned at each node of a creature's patrol path. They use the `VISUAL_WAYPOINT` creature entry (ID 1) and are only visible to GMs.
## Display IDs for 11.2.5 Client
**Working Display IDs:**
| ID | Name | Description |
|----|------|-------------|
| 1824 | Elven Wisp | Blue floating wisp - **recommended for waypoints** |
| 169 | Rock Elemental | Small rock creature |
| 31366 | Green Circle | Targeting circle - used for spawn markers |
**Non-Working Display IDs:**
| ID | Name | Issue |
|----|------|-------|
| 17188 | Blue Skull | Shows as human model in 11.2.5 |
| 17519 | Red Orb | Shows as human model in 11.2.5 |
| 10045 | Wisp | Shows as human model in 11.2.5 |
## Runtime Configuration via Lua Shared Data
Display IDs are configurable at runtime without recompiling:
```lua
-- In admin_handlers.lua
local DEFAULT_DISPLAYS = {
waypoint_marker = 1824, -- Elven Wisp
waypoint_highlight = 1824, -- Same, differentiated by scale
spawn_marker = 31366, -- Green targeting circle
}
```
### Changing via MCP
```
-- Set waypoint marker to rock elemental
mcp0_shared_data_write(key="config_display_waypoint_marker", value="169")
-- Then reload Lua and hide/show waypoints
.reload eluna
```
### Pattern for Configurable Values
```lua
local function GetDisplayId(key)
local sharedKey = "config_display_" .. key
local value = GetSharedData(sharedKey)
if value and value ~= "" then
local num = tonumber(value)
if num then return num end
end
return DEFAULT_DISPLAYS[key] or 1824
end
```
## C++ Client Refresh Pattern
**Critical:** `SetDisplayId()` alone does NOT update creatures already visible to the client. You must force a refresh:
```cpp
summon->SetDisplayId(displayId, true);
summon->SetObjectScale(0.5f);
// Force client to see the change
summon->DestroyForNearbyPlayers();
summon->UpdateObjectVisibilityOnCreate();
```
Without these calls, the client shows cached appearance from when the creature spawned.
## Files Modified
### C++ Changes
- `src/server/game/Movement/Waypoints/WaypointManager.cpp`
- Added client refresh after SetDisplayId
- Default display: 1824 (Elven Wisp)
- `src/server/game/LuaEngine/methods/TrinityCore/CreatureMethods.h`
- Fixed displayId arg parsing for `VisualizeWaypointPath(player, displayId)`
- `src/server/database/Database/MySQLConnection.cpp`
- Graceful handling of `ER_BAD_FIELD_ERROR`, `ER_NO_SUCH_TABLE`, `ER_PARSE_ERROR`
- No longer crashes server on bad MCP queries
### Lua Changes
- `lua_scripts/admin_handlers.lua`
- Added `DEFAULT_DISPLAYS` table
- Added `GetDisplayId()`, `SetDisplayIdConfig()`, `InitDisplayConfigs()`
- AMS handlers: `SET_DISPLAY_CONFIG`, `GET_DISPLAY_CONFIGS`
### Database Changes
- `creature_template_model` for `CreatureID = 1` set to `CreatureDisplayID = 1824`
## Lessons Learned
1. **Client version matters** - Display IDs from older WoW versions may not render correctly in modern clients
2. **Test displays in-game** - Use `.npc add <entry>` to spawn creatures and verify their appearance before using displayIds
3. **Client caches creature appearance** - Must force refresh with `DestroyForNearbyPlayers()` + `UpdateObjectVisibilityOnCreate()`
4. **Prefer Lua for config values** - Allows testing without 5+ minute recompiles. Use MCP to change values at runtime.
5. **Shared data clears on restart** - `InitDisplayConfigs()` re-initializes defaults on server start

View File

@@ -592,17 +592,15 @@ bool MySQLConnection::_HandleMySQLErrno(uint32 errNo, uint8 attempts /*= 5*/)
case ER_DUP_ENTRY:
return false;
// Outdated table or database structure - terminate core
// Outdated table or database structure - log error but don't terminate
// (Changed from ABORT to allow recovery from bad external queries like MCP)
case ER_BAD_FIELD_ERROR:
case ER_NO_SUCH_TABLE:
TC_LOG_ERROR("sql.sql", "Your database structure is not up to date. Please make sure you've executed all queries in the sql/updates folders.");
std::this_thread::sleep_for(std::chrono::seconds(10));
ABORT();
TC_LOG_ERROR("sql.sql", "Database structure error (bad field or missing table). Query failed but server will continue.");
TC_LOG_ERROR("sql.sql", "If this is a schema issue, run sql/updates. If from external query (MCP), fix the query.");
return false;
case ER_PARSE_ERROR:
TC_LOG_ERROR("sql.sql", "Error while parsing SQL. Core fix required.");
std::this_thread::sleep_for(std::chrono::seconds(10));
ABORT();
TC_LOG_ERROR("sql.sql", "Error while parsing SQL. Query failed but server will continue.");
return false;
default:
TC_LOG_ERROR("sql.sql", "Unhandled MySQL errno {}. Unexpected behaviour possible.", errNo);

View File

@@ -1575,12 +1575,16 @@ namespace LuaCreature
// Optional player parameter for phase inheritance (arg 2)
Player* phaseSource = E->CHECKOBJ<Player>(2, false);
// Optional display ID parameter (arg 3, or arg 2 if no player provided)
// Display ID is always arg 3 when player is provided
Optional<uint32> displayId;
int displayIdArg = phaseSource ? 3 : 2;
if (!lua_isnoneornil(E->L, displayIdArg))
if (!lua_isnoneornil(E->L, 3))
{
displayId = E->CHECKVAL<uint32>(displayIdArg);
displayId = E->CHECKVAL<uint32>(3);
}
else if (!phaseSource && !lua_isnoneornil(E->L, 2) && lua_isnumber(E->L, 2))
{
// If no player but arg 2 is a number, use it as displayId
displayId = E->CHECKVAL<uint32>(2);
}
sWaypointMgr->VisualizePath(creature, path, displayId);

View File

@@ -237,11 +237,15 @@ void WaypointMgr::VisualizePath(Unit* owner, WaypointPath const* path, Optional<
if (!summon)
continue;
if (displayId)
{
summon->SetDisplayId(*displayId, true);
summon->SetObjectScale(0.5f);
}
// Always set display - use passed displayId or default to Elven Wisp (1824)
constexpr uint32 DEFAULT_WAYPOINT_DISPLAY = 1824; // Elven Wisp - works in 11.2.5
summon->SetDisplayId(displayId.value_or(DEFAULT_WAYPOINT_DISPLAY), true);
summon->SetObjectScale(0.5f);
// Force client to refresh the creature's appearance
// (SetDisplayId alone doesn't update already-spawned creatures on client)
summon->DestroyForNearbyPlayers();
summon->UpdateObjectVisibilityOnCreate();
_nodeToVisualWaypointGUIDsMap[pathNodePair] = summon->GetGUID();
_visualWaypointGUIDToNodeMap[summon->GetGUID()] = std::pair<WaypointPath const*, WaypointNode const*>(path, &node);