diff --git a/araxiaonline/WAYPOINT_DETAILS_IMPLEMENTATION.md b/araxiaonline/WAYPOINT_DETAILS_IMPLEMENTATION.md new file mode 100644 index 0000000000..cf36a7b26b --- /dev/null +++ b/araxiaonline/WAYPOINT_DETAILS_IMPLEMENTATION.md @@ -0,0 +1,188 @@ +# Waypoint Detail Panel Implementation + +**Date:** November 30, 2025 +**Status:** Complete - Ready for Testing +**Requires:** Server rebuild (C++ changes) + +## Overview + +Implemented comprehensive waypoint visualization and inspection system allowing admins to: +- View all waypoints for a creature path in a clickable list +- See detailed information about each waypoint (position, orientation, delay, etc.) +- Click waypoints in the list to highlight them in the world +- Target waypoint markers in the world to auto-select them in the UI +- Keep NPC panel focused while inspecting waypoints + +## Architecture + +### Client-Server Communication + +**New AMS Handlers (Server → Client):** +- `GET_WAYPOINT_DETAILS` - Request all nodes for a path +- `SELECT_WAYPOINT` - Highlight a specific waypoint +- `GET_WAYPOINT_FOR_GUID` - Lookup waypoint from creature GUID + +**New AMS Responses (Client ← Server):** +- `WAYPOINT_DETAILS_RESPONSE` - Array of waypoint nodes with full data +- `WAYPOINT_SELECTED_RESPONSE` - Confirmation of waypoint selection +- `WAYPOINT_FOR_GUID_RESPONSE` - Path/node IDs for a visual waypoint GUID + +### C++ Bindings + +**New WaypointManager Methods:** +```cpp +bool GetPathAndNodeByVisualGUID(ObjectGuid guid, uint32& outPathId, uint32& outNodeId) const; +``` + +**New Global Lua Function:** +```lua +pathId, nodeId = GetWaypointNodeForVisualGUID(guidLow) +``` + +## UI Components + +### Waypoints Tab Layout + +``` +┌─────────────────────────────────────┐ +│ Waypoint List (Top 50%) │ +├─────────────────────────────────────┤ +│ Waypoint Details (Bottom 50%) │ +│ [Deselect] │ +└─────────────────────────────────────┘ +``` + +**List Features:** +- Clickable node entries (Node 1, Node 2, etc.) +- Highlighted selected node (green background) +- Scrollable for paths with many nodes +- Auto-populated when "Show Waypoints" is clicked + +**Detail Panel Features:** +- Node ID and detailed information +- Position (X, Y, Z coordinates) +- Orientation angle +- Delay in milliseconds +- Move type and action IDs +- Deselect button to close detail view + +## Implementation Details + +### Panel Locking + +When waypoints are shown: +1. `panelLockedToNPC` flag is set to `true` +2. NPC panel data remains loaded even when targeting other creatures +3. Targeting waypoint markers doesn't unload NPC info +4. Flag is cleared when "Hide Waypoints" is clicked + +### Waypoint Target Detection + +When panel is locked and player targets a creature: +1. Extract GUID from target +2. Send `GET_WAYPOINT_FOR_GUID` to server +3. Server looks up path/node IDs using `GetPathAndNodeByVisualGUID` +4. Client receives response and auto-selects waypoint in list +5. Waypoints tab automatically switches to show detail + +### Data Flow + +``` +User clicks "Show Waypoints" + ↓ +SHOW_WAYPOINTS sent to server + ↓ +Server visualizes path (spawns markers) + ↓ +WAYPOINTS_RESPONSE received + ↓ +GET_WAYPOINT_DETAILS sent to server + ↓ +WAYPOINT_DETAILS_RESPONSE received with all nodes + ↓ +Waypoints tab populated with clickable list + ↓ +User clicks waypoint in list + ↓ +Detail panel shows (split view) + ↓ +User targets waypoint marker in world + ↓ +GET_WAYPOINT_FOR_GUID sent to server + ↓ +WAYPOINT_FOR_GUID_RESPONSE received + ↓ +Waypoint auto-selected in list, detail shown +``` + +## Files Modified + +### C++ (Requires Rebuild) + +**`WaypointManager.h`** +- Added `GetPathAndNodeByVisualGUID()` declaration + +**`WaypointManager.cpp`** +- Implemented `GetPathAndNodeByVisualGUID()` to lookup path/node from visual creature GUID + +**`GlobalMethods.h`** +- Added `GetWaypointNodeForVisualGUID()` Lua binding +- Registered in method table + +### Server Lua + +**`admin_handlers.lua`** +- Added `GET_WAYPOINT_DETAILS` handler - returns array of waypoint nodes +- Added `SELECT_WAYPOINT` handler - highlights waypoint in world +- Added `GET_WAYPOINT_FOR_GUID` handler - looks up path/node from creature GUID + +### Client Lua + +**`NPCInfoPanel.lua`** +- Implemented waypoint list UI with scrollable frame +- Implemented waypoint detail panel with split view +- Added `UpdateWaypointList()` and `UpdateWaypointDetail()` functions +- Added `RequestWaypointDetails()` to fetch data from server +- Added `WAYPOINT_DETAILS_RESPONSE` handler +- Added `WAYPOINT_FOR_GUID_RESPONSE` handler +- Integrated panel locking when showing waypoints +- Enhanced `PLAYER_TARGET_CHANGED` to detect waypoint markers +- Added waypoint detail request when showing waypoints + +## Testing Checklist + +- [ ] Server compiles without errors +- [ ] Server loads admin_handlers.lua successfully +- [ ] Target NPC with waypoints +- [ ] Click "Show Waypoints" button +- [ ] Waypoints tab populates with node list +- [ ] Click a waypoint in the list +- [ ] Detail panel shows with waypoint information +- [ ] Target a waypoint marker in the world +- [ ] Waypoint auto-selects in the list +- [ ] NPC panel data remains loaded during waypoint inspection +- [ ] Click "Hide Waypoints" to deactivate +- [ ] Panel lock is cleared + +## Known Limitations + +- Visual spell effects for waypoint highlighting not yet implemented (placeholder) +- Waypoint modification/creation not yet implemented +- No persistence of waypoint data between sessions (by design) + +## Future Enhancements + +1. Add visual spell effects to highlight selected waypoints +2. Implement waypoint creation/editing UI +3. Add waypoint deletion functionality +4. Persist waypoint inspection state in SavedVariables +5. Add waypoint search/filter functionality +6. Implement waypoint path visualization (lines between nodes) + +## Notes for Developer + +- The `panelLockedToNPC` flag is crucial for keeping NPC data loaded +- GUID extraction from `UnitGUID()` uses pattern matching - ensure it matches creature GUID format +- Waypoint nodes are 1-indexed in the UI but use their actual IDs from the database +- The split view resizes dynamically - ensure scroll frames handle height changes +- Always call `UpdateWaypointList()` after `UpdateWaypointDetail()` to refresh highlighting diff --git a/araxiaonline/client_addons/AraxiaTrinityAdmin/UI/Panels/NPCInfoPanel.lua b/araxiaonline/client_addons/AraxiaTrinityAdmin/UI/Panels/NPCInfoPanel.lua index d584d263bd..2e3f6fe579 100644 --- a/araxiaonline/client_addons/AraxiaTrinityAdmin/UI/Panels/NPCInfoPanel.lua +++ b/araxiaonline/client_addons/AraxiaTrinityAdmin/UI/Panels/NPCInfoPanel.lua @@ -91,6 +91,24 @@ local function UpdateGMButton() end end +-- Function to sync GM state from server +local function SyncGMState() + if not AMS then return end + + -- Request current GM state from server + AMS.Send("GET_PLAYER_DATA", {}) +end + +-- Handler for player data response +if AMS then + AMS.RegisterHandler("PLAYER_DATA_RESPONSE", function(data) + if data and data.success and data.isGM ~= nil then + _G.AraxiaTrinityAdminGMMode = data.isGM + UpdateGMButton() + end + end) +end + gmButton:SetScript("OnClick", function() -- Toggle GM mode _G.AraxiaTrinityAdminGMMode = not _G.AraxiaTrinityAdminGMMode @@ -117,6 +135,14 @@ end) UpdateGMButton() +-- Sync GM state on panel show +npcPanel:HookScript("OnShow", function() + SyncGMState() +end) + +-- Initial sync +SyncGMState() + -- ============================================================================ -- Tab Buttons -- ============================================================================ @@ -385,17 +411,260 @@ local waypointContent = CreateFrame("Frame", nil, rightContentFrame) waypointContent:SetAllPoints(rightContentFrame) waypointContent:Hide() -local waypointTitle = waypointContent:CreateFontString(nil, "OVERLAY", "GameFontNormal") -waypointTitle:SetPoint("TOP", waypointContent, "TOP", 0, -8) -waypointTitle:SetText("Waypoint Editor") +-- Waypoint data storage +local currentWaypointPathId = nil +local currentWaypointNodes = {} +local selectedWaypointNodeId = nil +local panelLockedToNPC = false -- Lock panel to NPC when showing waypoints --- Waypoint info text area -local waypointInfoText = waypointContent:CreateFontString(nil, "OVERLAY", "GameFontHighlight") -waypointInfoText:SetPoint("TOPLEFT", waypointContent, "TOPLEFT", 12, -35) -waypointInfoText:SetPoint("TOPRIGHT", waypointContent, "TOPRIGHT", -12, -35) -waypointInfoText:SetJustifyH("LEFT") -waypointInfoText:SetJustifyV("TOP") -waypointInfoText:SetText("|cFF888888No creature selected.|r\n\nSelect a creature to view waypoint options.") +-- Waypoint list (top half - 60% of height) +local waypointListContainer = CreateFrame("Frame", nil, waypointContent) +waypointListContainer:SetPoint("TOPLEFT", waypointContent, "TOPLEFT", 8, -8) +waypointListContainer:SetPoint("RIGHT", waypointContent, "RIGHT", -8, 0) +waypointListContainer:SetHeight(200) + +local waypointListScroll = CreateFrame("ScrollFrame", nil, waypointListContainer, "UIPanelScrollFrameTemplate") +waypointListScroll:SetPoint("TOPLEFT", waypointListContainer, "TOPLEFT", 0, 0) +waypointListScroll:SetPoint("BOTTOMRIGHT", waypointListContainer, "BOTTOMRIGHT", -22, 0) + +local waypointListChild = CreateFrame("Frame", nil, waypointListScroll) +waypointListChild:SetWidth(280) +waypointListChild:SetHeight(100) +waypointListScroll:SetScrollChild(waypointListChild) + +-- Divider line between list and detail +local waypointDivider = waypointContent:CreateTexture(nil, "ARTWORK") +waypointDivider:SetHeight(1) +waypointDivider:SetPoint("TOPLEFT", waypointListContainer, "BOTTOMLEFT", 0, -4) +waypointDivider:SetPoint("RIGHT", waypointContent, "RIGHT", -8, 0) +waypointDivider:SetColorTexture(0.4, 0.4, 0.4, 0.8) + +-- Detail panel (bottom half - always visible when waypoint selected) +local waypointDetailPanel = CreateFrame("Frame", nil, waypointContent, "BackdropTemplate") +waypointDetailPanel:SetPoint("TOPLEFT", waypointDivider, "BOTTOMLEFT", 0, -4) +waypointDetailPanel:SetPoint("BOTTOMRIGHT", waypointContent, "BOTTOMRIGHT", -8, 8) +waypointDetailPanel:SetBackdrop({ + bgFile = "Interface/Tooltips/UI-Tooltip-Background", + edgeFile = "Interface/Tooltips/UI-Tooltip-Border", + tile = true, tileSize = 16, edgeSize = 12, + insets = { left = 3, right = 3, top = 3, bottom = 3 } +}) +waypointDetailPanel:SetBackdropColor(0.1, 0.1, 0.1, 0.8) +waypointDetailPanel:SetBackdropBorderColor(0.3, 0.3, 0.3, 1) + +-- Detail panel content +local detailTitle = waypointDetailPanel:CreateFontString(nil, "OVERLAY", "GameFontNormal") +detailTitle:SetPoint("TOPLEFT", waypointDetailPanel, "TOPLEFT", 10, -8) +detailTitle:SetText("Select a waypoint above") + +-- Detail info text +local detailInfoText = waypointDetailPanel:CreateFontString(nil, "OVERLAY", "GameFontHighlight") +detailInfoText:SetPoint("TOPLEFT", waypointDetailPanel, "TOPLEFT", 10, -26) +detailInfoText:SetPoint("RIGHT", waypointDetailPanel, "RIGHT", -80, 0) +detailInfoText:SetJustifyH("LEFT") +detailInfoText:SetJustifyV("TOP") +detailInfoText:SetText("") + +-- Action buttons container (right side of detail panel) +local detailActionsContainer = CreateFrame("Frame", nil, waypointDetailPanel) +detailActionsContainer:SetPoint("TOPRIGHT", waypointDetailPanel, "TOPRIGHT", -8, -8) +detailActionsContainer:SetSize(65, 100) + +-- Teleport button +local teleportBtn = CreateFrame("Button", nil, detailActionsContainer, "UIPanelButtonTemplate") +teleportBtn:SetSize(60, 22) +teleportBtn:SetPoint("TOP", detailActionsContainer, "TOP", 0, 0) +teleportBtn:SetText("Teleport") +teleportBtn:Disable() + +teleportBtn:SetScript("OnClick", function() + if not selectedWaypointNodeId or not currentWaypointPathId then return end + + -- Find the selected node data + local node = nil + for _, n in ipairs(currentWaypointNodes) do + if n.id == selectedWaypointNodeId then + node = n + break + end + end + + if node and AMS then + print("|cFF00FF00[ATA]|r Teleporting to waypoint " .. node.id) + AMS.Send("TELEPORT_TO_WAYPOINT", { + x = node.x, + y = node.y, + z = node.z, + orientation = node.orientation + }) + end +end) + +teleportBtn:SetScript("OnEnter", function(self) + GameTooltip:SetOwner(self, "ANCHOR_LEFT") + GameTooltip:AddLine("Teleport to Waypoint", 1, 1, 1) + GameTooltip:AddLine("Teleports you to this waypoint location", 0.7, 0.7, 0.7) + GameTooltip:Show() +end) + +teleportBtn:SetScript("OnLeave", function() + GameTooltip:Hide() +end) + +-- Buttons to hold waypoint node entries +local waypointNodeButtons = {} + +-- Forward declare functions +local UpdateWaypointList +local UpdateWaypointDetail + +-- Function to update waypoint detail display +UpdateWaypointDetail = function() + if not selectedWaypointNodeId then + detailTitle:SetText("Select a waypoint above") + detailInfoText:SetText("") + teleportBtn:Disable() + return + end + + local node = nil + for _, n in ipairs(currentWaypointNodes) do + if n.id == selectedWaypointNodeId then + node = n + break + end + end + + if not node then + teleportBtn:Disable() + return + end + + detailTitle:SetText("Node " .. node.id) + + local detailStr = string.format( + "|cFFFFD700Position:|r %.1f, %.1f, %.1f\n" .. + "|cFFFFD700Orientation:|r %.2f\n" .. + "|cFFFFD700Delay:|r %dms\n" .. + "|cFFFFD700Move Type:|r %d", + node.x, node.y, node.z, + node.orientation, + node.delay, + node.moveType + ) + + detailInfoText:SetText(detailStr) + teleportBtn:Enable() +end + +-- Function to update waypoint list display +UpdateWaypointList = function() + print("|cFF00FF00[ATA]|r UpdateWaypointList called: pathId=" .. (currentWaypointPathId or 0) .. ", nodes=" .. #currentWaypointNodes) + + -- Hide all existing buttons + for _, btn in ipairs(waypointNodeButtons) do + btn:Hide() + end + + if not currentWaypointPathId or #currentWaypointNodes == 0 then + -- Show empty message + print("|cFFFF0000[ATA]|r No waypoint data to display") + waypointListChild:SetHeight(40) + return + end + + -- Create/update buttons for each node + for i, node in ipairs(currentWaypointNodes) do + local btn = waypointNodeButtons[i] + if not btn then + btn = CreateFrame("Button", nil, waypointListChild) + btn:SetHeight(24) + btn:EnableMouse(true) + btn:RegisterForClicks("AnyUp") + btn:SetHighlightTexture("Interface/QuestFrame/UI-QuestTitleHighlight", "ADD") + + btn.bg = btn:CreateTexture(nil, "BACKGROUND") + btn.bg:SetAllPoints() + btn.bg:SetColorTexture(0.2, 0.2, 0.2, 0.5) + + btn.text = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal") + btn.text:SetPoint("LEFT", btn, "LEFT", 8, 0) + btn.text:SetJustifyH("LEFT") + + btn:SetScript("OnClick", function(self) + local clickedNode = self.nodeData + if not clickedNode then return end + + selectedWaypointNodeId = clickedNode.id + UpdateWaypointDetail() + UpdateWaypointList() -- Refresh to show selection highlight + + -- Send SELECT_WAYPOINT to server to highlight in world + if AMS and currentWaypointPathId then + print("|cFF00FF00[ATA]|r Sending SELECT_WAYPOINT: path=" .. currentWaypointPathId .. " node=" .. clickedNode.id) + AMS.Send("SELECT_WAYPOINT", { pathId = currentWaypointPathId, nodeId = clickedNode.id }) + end + end) + + waypointNodeButtons[i] = btn + end + + btn.nodeData = node + btn:ClearAllPoints() + btn:SetPoint("TOPLEFT", waypointListChild, "TOPLEFT", 0, -((i-1) * 26)) + btn:SetPoint("RIGHT", waypointListChild, "RIGHT", 0, 0) + + -- Highlight selected node + if node.id == selectedWaypointNodeId then + btn.bg:SetColorTexture(0.3, 0.5, 0.3, 0.7) + btn.text:SetTextColor(1, 1, 1, 1) + else + btn.bg:SetColorTexture(0.2, 0.2, 0.2, 0.5) + btn.text:SetTextColor(1, 0.82, 0, 1) + end + + btn.text:SetText("Node " .. node.id) + btn:Show() + end + + -- Update scroll child size to fit all buttons + local totalHeight = #currentWaypointNodes * 26 + waypointListChild:SetHeight(totalHeight) + print("|cFF00FF00[ATA]|r Set scroll child height to " .. totalHeight) +end + +-- Deselect button no longer needed in split view layout +-- (kept for code compatibility but hidden) + +-- Request waypoint details from server +local function RequestWaypointDetails(pathId) + if not AMS then return end + + currentWaypointPathId = pathId + currentWaypointNodes = {} + selectedWaypointNodeId = nil + + AMS.Send("GET_WAYPOINT_DETAILS", { pathId = pathId }) +end + +-- Handler for waypoint details response +if AMS then + AMS.RegisterHandler("WAYPOINT_DETAILS_RESPONSE", function(data) + if not data or not data.success then + print("|cFFFF0000[ATA]|r Failed to get waypoint details: " .. (data.error or "Unknown error")) + return + end + + print("|cFF00FF00[ATA]|r Received waypoint details: pathId=" .. (data.pathId or 0) .. ", nodes=" .. #(data.nodes or {})) + + currentWaypointPathId = data.pathId + currentWaypointNodes = data.nodes or {} + selectedWaypointNodeId = nil + + print("|cFF00FF00[ATA]|r Updating waypoint list with " .. #currentWaypointNodes .. " nodes") + UpdateWaypointList() + end) +end -- Waypoint action buttons container local waypointButtonContainer = CreateFrame("Frame", nil, waypointContent) @@ -1053,6 +1322,12 @@ function npcPanel:Update(requestServerData, forceRefresh) -- Enable waypoint button if creature has a waypoint path if data and data.movement and data.movement.waypointPath then waypointButton:Enable() + + -- Auto-load waypoint details for the Waypoints tab + local pathId = data.movement.waypointPath.pathId + if pathId and pathId > 0 then + RequestWaypointDetails(pathId) + end else waypointButton:Disable() end @@ -1072,40 +1347,8 @@ function npcPanel:Update(requestServerData, forceRefresh) npcModel:SetModel("interface/buttons/talktomequestionmark.m2") end - -- Update Waypoint tab content - if npcData then - local waypointLines = {} - table.insert(waypointLines, "|cFFFFD700" .. (npcData.name or "Unknown") .. "|r") - table.insert(waypointLines, "Entry: " .. (npcData.npcID or "?")) - table.insert(waypointLines, "") - - if serverData and serverData.movement and serverData.movement.waypointPath then - local wp = serverData.movement.waypointPath - local pathId = wp.pathId or 0 - if pathId > 0 then - table.insert(waypointLines, "|cFF00FF00Has Waypoint Path|r") - table.insert(waypointLines, "Path ID: " .. pathId) - table.insert(waypointLines, "Nodes: " .. (wp.nodeCount or 0)) - table.insert(waypointLines, "") - table.insert(waypointLines, "|cFF888888Use the 'Show Waypoints' button|r") - table.insert(waypointLines, "|cFF888888to visualize the path in 3D.|r") - else - table.insert(waypointLines, "|cFFFF8800No Waypoint Path|r") - table.insert(waypointLines, "") - table.insert(waypointLines, "|cFF888888This creature does not have|r") - table.insert(waypointLines, "|cFF888888a defined waypoint path.|r") - end - elseif isLoadingServerData then - table.insert(waypointLines, "|cFF888888Loading waypoint data...|r") - else - table.insert(waypointLines, "|cFF888888Server data not loaded.|r") - table.insert(waypointLines, "|cFF888888Click Refresh to load.|r") - end - - waypointInfoText:SetText(table.concat(waypointLines, "\n")) - else - waypointInfoText:SetText("|cFF888888No creature selected.|r\n\nSelect a creature to view waypoint options.") - end + -- Waypoint tab is now handled by the new list/detail UI + -- No additional update needed here end -- ============================================================================ @@ -1163,12 +1406,14 @@ waypointButton:SetScript("OnClick", function() print("|cFF00FF00[ATA]|r Hiding waypoint markers...") waypointButton:SetText("Working...") waypointButton:Disable() + panelLockedToNPC = false AMS.Send("HIDE_WAYPOINTS", { guid = npcData.guid }) else - -- Show waypoints + -- Show waypoints and lock panel to this NPC print("|cFF00FF00[ATA]|r Spawning waypoint markers in 3D space...") waypointButton:SetText("Working...") waypointButton:Disable() + panelLockedToNPC = true -- Lock panel to prevent target changes from unloading NPC data AMS.Send("SHOW_WAYPOINTS", { guid = npcData.guid }) end end) @@ -1198,6 +1443,11 @@ local function InitWaypointHandler() nodeCount = pendingWaypointRequest.nodeCount } print("|cFF00FF00[ATA]|r Tracking path for: " .. pendingWaypointRequest.name) + + -- Request waypoint details for the Waypoints tab + if data.pathId then + RequestWaypointDetails(data.pathId) + end elseif pendingWaypointRequest.action == "hide" then -- Remove from tracked paths if trackedPaths[pendingWaypointRequest.guid] then @@ -1223,6 +1473,19 @@ local updateFrame = CreateFrame("Frame") updateFrame:RegisterEvent("PLAYER_TARGET_CHANGED") updateFrame:SetScript("OnEvent", function() if npcPanel:IsShown() then + -- Check if targeting a waypoint marker (visual waypoint creature) + local targetGUID = UnitGUID("target") + if targetGUID and panelLockedToNPC then + -- Panel is locked to an NPC, check if we targeted a waypoint marker + local guidLow = tonumber(string.match(targetGUID, "Creature%-0%-0%-0%-(%d+)")) or 0 + if guidLow > 0 and AMS then + -- Request waypoint info for this GUID + AMS.Send("GET_WAYPOINT_FOR_GUID", { guid = guidLow }) + return -- Don't update NPC panel + end + end + + -- Normal NPC targeting behavior serverData = nil isLoadingServerData = false @@ -1240,10 +1503,40 @@ updateFrame:SetScript("OnEvent", function() currentTargetGUID = npcData and npcData.guid or nil end waypointButton:Disable() -- Will be enabled by Update() if creature has waypoints - npcPanel:Update(true) + npcPanel:Update(true, true) -- Request server data and force refresh from cache end end) +-- Handler for waypoint selection response (with visual highlighting) +if AMS then + AMS.RegisterHandler("WAYPOINT_SELECTED_RESPONSE", function(data) + if data and data.success then + print("|cFF00FF00[ATA]|r Waypoint selected: node " .. data.nodeId .. " at " .. data.x .. ", " .. data.y .. ", " .. data.z) + + -- The waypoint is already visualized as a creature marker in the world + -- The highlighting happens through the UI selection (green background on the button) + -- Future: Could add a spell effect or other visual indicator here + end + end) + + AMS.RegisterHandler("WAYPOINT_FOR_GUID_RESPONSE", function(data) + if data and data.success then + -- Select this waypoint in the Waypoints tab + selectedWaypointNodeId = data.nodeId + currentWaypointPathId = data.pathId + + -- Switch to Waypoints tab to show the detail + ShowRightTab("Waypoints") + + -- Update the display + UpdateWaypointList() + UpdateWaypointDetail() + + print("|cFF00FF00[ATA]|r Selected waypoint node " .. data.nodeId) + end + end) +end + -- ============================================================================ -- Initialization -- ============================================================================ diff --git a/araxiaonline/lua_scripts/admin_handlers.lua b/araxiaonline/lua_scripts/admin_handlers.lua index 44617e258b..959919774f 100644 --- a/araxiaonline/lua_scripts/admin_handlers.lua +++ b/araxiaonline/lua_scripts/admin_handlers.lua @@ -13,6 +13,9 @@ if not AMS then return end +-- Load Smallfolk for serialization (used by shared data) +local Smallfolk = require("smallfolk") + -- ============================================================================ -- Helper Functions -- ============================================================================ @@ -371,6 +374,159 @@ AMS.RegisterHandler("CLEAR_ALL_WAYPOINT_MARKERS", function(player, data) AMS.Send(player, "CLEAR_WAYPOINTS_RESPONSE", { success = true }) end) +-- Get detailed waypoint data for a path +AMS.RegisterHandler("GET_WAYPOINT_DETAILS", function(player, data) + if not data or not data.pathId then + print("[Admin Handlers] GET_WAYPOINT_DETAILS: No pathId provided") + AMS.Send(player, "WAYPOINT_DETAILS_RESPONSE", { success = false, error = "No pathId" }) + return + end + + local pathId = data.pathId + print("[Admin Handlers] GET_WAYPOINT_DETAILS: Fetching details for path " .. pathId) + + -- Get the waypoint path using the C++ binding + local path = GetWaypointPath(pathId) + if not path or not path.nodes then + print("[Admin Handlers] GET_WAYPOINT_DETAILS: Path not found") + AMS.Send(player, "WAYPOINT_DETAILS_RESPONSE", { success = false, error = "Path not found" }) + return + end + + print("[Admin Handlers] GET_WAYPOINT_DETAILS: Sending " .. #path.nodes .. " nodes") + AMS.Send(player, "WAYPOINT_DETAILS_RESPONSE", { + success = true, + pathId = pathId, + nodes = path.nodes + }) +end) + +-- Highlight a specific waypoint in the world +AMS.RegisterHandler("SELECT_WAYPOINT", function(player, data) + if not data or not data.pathId or not data.nodeId then + print("[Admin Handlers] SELECT_WAYPOINT: Missing pathId or nodeId") + return + end + + local pathId = data.pathId + local nodeId = data.nodeId + print("[Admin Handlers] SELECT_WAYPOINT: Highlighting path " .. pathId .. " node " .. nodeId) + + -- Get the waypoint path + local path = GetWaypointPath(pathId) + if not path or not path.nodes then + print("[Admin Handlers] SELECT_WAYPOINT: Path not found") + return + end + + -- Find the node + local node = nil + for _, n in ipairs(path.nodes) do + if n.id == nodeId then + node = n + break + end + end + + if not node then + print("[Admin Handlers] SELECT_WAYPOINT: Node not found") + return + end + + -- Notify player of selection + print("[Admin Handlers] SELECT_WAYPOINT: Node " .. nodeId .. " at " .. node.x .. ", " .. node.y .. ", " .. node.z) + + -- Clear previous highlight if any (stored in shared data per player) + local playerKey = "waypoint_highlight_" .. tostring(player:GetGUIDLow()) + local prevData = GetSharedData(playerKey) + if prevData then + local prev = Smallfolk.loads(prevData) + if prev and prev.pathId and prev.nodeId then + ClearWaypointMarkerAuras(player, prev.pathId, prev.nodeId) + end + end + + -- Highlight the new waypoint marker by changing its display + -- Display IDs to try: 17519 (red orb), 17188 (blue orb), 26754 (green flame), 11686 (purple crystal) + local highlightDisplayId = 17519 -- Red glowing orb - very visible + local success = HighlightWaypointMarker(player, pathId, nodeId, highlightDisplayId) + + if success then + print("[Admin Handlers] SELECT_WAYPOINT: Highlighted marker with displayId " .. highlightDisplayId) + -- Store current selection for later cleanup + SetSharedData(playerKey, Smallfolk.dumps({ pathId = pathId, nodeId = nodeId })) + else + print("[Admin Handlers] SELECT_WAYPOINT: Could not find marker to highlight") + end + + player:SendBroadcastMessage("Waypoint " .. nodeId .. " selected") + + -- Send response with node position + AMS.Send(player, "WAYPOINT_SELECTED_RESPONSE", { + success = true, + pathId = pathId, + nodeId = nodeId, + x = node.x, + y = node.y, + z = node.z + }) +end) + +-- Get waypoint node info from a visual waypoint GUID (when targeting a waypoint marker) +AMS.RegisterHandler("GET_WAYPOINT_FOR_GUID", function(player, data) + if not data or not data.guid then + print("[Admin Handlers] GET_WAYPOINT_FOR_GUID: No GUID provided") + return + end + + local pathId, nodeId = GetWaypointNodeForVisualGUID(data.guid) + if pathId == 0 or nodeId == 0 then + print("[Admin Handlers] GET_WAYPOINT_FOR_GUID: GUID not found in waypoint system") + AMS.Send(player, "WAYPOINT_FOR_GUID_RESPONSE", { success = false }) + return + end + + print("[Admin Handlers] GET_WAYPOINT_FOR_GUID: Found path " .. pathId .. " node " .. nodeId) + AMS.Send(player, "WAYPOINT_FOR_GUID_RESPONSE", { + success = true, + pathId = pathId, + nodeId = nodeId + }) +end) + +-- Get player data (GM state, etc.) +AMS.RegisterHandler("GET_PLAYER_DATA", function(player, data) + if not player then + return + end + + -- Send player data including GM state (IsGM is the correct Eluna method) + AMS.Send(player, "PLAYER_DATA_RESPONSE", { + success = true, + isGM = player:IsGM() + }) +end) + +-- Teleport player to a waypoint location +AMS.RegisterHandler("TELEPORT_TO_WAYPOINT", function(player, data) + if not data or not data.x or not data.y or not data.z then + print("[Admin Handlers] TELEPORT_TO_WAYPOINT: Missing coordinates") + return + end + + local x = data.x + local y = data.y + local z = data.z + local orientation = data.orientation or 0 + + print("[Admin Handlers] TELEPORT_TO_WAYPOINT: Teleporting to " .. x .. ", " .. y .. ", " .. z .. " facing " .. orientation) + + -- Teleport player to the waypoint location with correct orientation + player:Teleport(player:GetMapId(), x, y, z, orientation) + + player:SendBroadcastMessage("Teleported to waypoint") +end) + -- ============================================================================ -- Initialization -- ============================================================================ @@ -382,3 +538,8 @@ print(" - SHOW_WAYPOINTS") print(" - HIDE_WAYPOINTS") print(" - HIDE_WAYPOINTS_BY_GUID") print(" - CLEAR_ALL_WAYPOINT_MARKERS") +print(" - GET_WAYPOINT_DETAILS") +print(" - SELECT_WAYPOINT") +print(" - GET_WAYPOINT_FOR_GUID") +print(" - GET_PLAYER_DATA") +print(" - TELEPORT_TO_WAYPOINT") diff --git a/src/server/game/LuaEngine/methods/TrinityCore/CreatureMethods.h b/src/server/game/LuaEngine/methods/TrinityCore/CreatureMethods.h index 34dc41e616..cdbba93066 100644 --- a/src/server/game/LuaEngine/methods/TrinityCore/CreatureMethods.h +++ b/src/server/game/LuaEngine/methods/TrinityCore/CreatureMethods.h @@ -1806,7 +1806,7 @@ namespace LuaCreature // Spells lua_pushstring(L, "spells"); lua_newtable(L); - for (int i = 0; i < MAX_CREATURE_SPELLS; ++i) + for (uint32 i = 0; i < MAX_CREATURE_SPELLS; ++i) { if (cTemplate->spells[i] != 0) { diff --git a/src/server/game/LuaEngine/methods/TrinityCore/GlobalMethods.h b/src/server/game/LuaEngine/methods/TrinityCore/GlobalMethods.h index b69b300c75..64b8b2d072 100644 --- a/src/server/game/LuaEngine/methods/TrinityCore/GlobalMethods.h +++ b/src/server/game/LuaEngine/methods/TrinityCore/GlobalMethods.h @@ -3346,6 +3346,193 @@ namespace LuaGlobalFunctions return 0; } + /** + * Gets waypoint path data by path ID. + * Returns a table with all nodes and their properties. + * + * @param uint32 pathId : the waypoint path ID + * @return table path : table with nodes array, or nil if not found + */ + int GetWaypointPath(Eluna* E) + { + uint32 pathId = E->CHECKVAL(1); + WaypointPath const* path = sWaypointMgr->GetPath(pathId); + + if (!path) + { + E->Push(); // Push nil + return 1; + } + + lua_State* L = E->L; + lua_newtable(L); // Create path table + + // Create nodes array + lua_newtable(L); + int nodeIndex = 1; + for (WaypointNode const& node : path->Nodes) + { + lua_newtable(L); // Create node table + + lua_pushinteger(L, node.Id); + lua_setfield(L, -2, "id"); + + lua_pushnumber(L, node.X); + lua_setfield(L, -2, "x"); + + lua_pushnumber(L, node.Y); + lua_setfield(L, -2, "y"); + + lua_pushnumber(L, node.Z); + lua_setfield(L, -2, "z"); + + // Orientation is Optional + if (node.Orientation) + { + lua_pushnumber(L, *node.Orientation); + lua_setfield(L, -2, "orientation"); + } + else + { + lua_pushnumber(L, 0.0); + lua_setfield(L, -2, "orientation"); + } + + // Delay is Optional + if (node.Delay) + { + lua_pushinteger(L, node.Delay->count()); + lua_setfield(L, -2, "delay"); + } + else + { + lua_pushinteger(L, 0); + lua_setfield(L, -2, "delay"); + } + + // MoveType is WaypointMoveType enum + lua_pushinteger(L, static_cast(node.MoveType)); + lua_setfield(L, -2, "moveType"); + + lua_rawseti(L, -2, nodeIndex++); + } + + lua_setfield(L, -2, "nodes"); + + lua_pushinteger(L, path->Id); + lua_setfield(L, -2, "id"); + + return 1; + } + + /** + * Gets the path ID and node ID for a visual waypoint creature GUID. + * Used to identify which waypoint a creature marker represents. + * + * @param uint64 counter : the counter/low part of the creature GUID + * @return uint32 pathId : the waypoint path ID (0 if not found) + * @return uint32 nodeId : the node ID within the path (0 if not found) + */ + int GetWaypointNodeForVisualGUID(Eluna* E) + { + uint64 counter = E->CHECKVAL(1); + // Create a creature GUID using the factory method + // We use mapId=0, entry=1 (VISUAL_WAYPOINT), and the counter from the client + ObjectGuid guid = ObjectGuid::Create(0, 1, counter); + + uint32 pathId = 0, nodeId = 0; + sWaypointMgr->GetPathAndNodeByVisualGUID(guid, pathId, nodeId); + + E->Push(pathId); + E->Push(nodeId); + return 2; + } + + /** + * Highlights a visual waypoint marker by changing its display ID. + * Used to visually indicate the selected waypoint in the world. + * + * @param Player player : the player (used to find the creature in their map) + * @param uint32 pathId : the waypoint path ID + * @param uint32 nodeId : the node ID within the path + * @param uint32 displayId : the display ID to use for highlighting + * @return bool success : true if the marker was changed + */ + int HighlightWaypointMarker(Eluna* E) + { + Player* player = E->CHECKOBJ(1); + uint32 pathId = E->CHECKVAL(2); + uint32 nodeId = E->CHECKVAL(3); + uint32 displayId = E->CHECKVAL(4); + + ObjectGuid const& markerGuid = sWaypointMgr->GetVisualGUIDByNode(pathId, nodeId); + if (markerGuid.IsEmpty()) + { + E->Push(false); + return 1; + } + + Creature* marker = ObjectAccessor::GetCreature(*player, markerGuid); + if (!marker) + { + E->Push(false); + return 1; + } + + // Change display to highlight model and scale up + marker->SetDisplayId(displayId); + marker->SetObjectScale(3.0f); // Scale up significantly + marker->DestroyForNearbyPlayers(); // Force client update + marker->UpdateObjectVisibilityOnCreate(); // Recreate for players + + E->Push(true); + return 1; + } + + /** + * Resets a visual waypoint marker to its original display. + * Used to remove highlighting from a previously selected waypoint. + * + * @param Player player : the player (used to find the creature in their map) + * @param uint32 pathId : the waypoint path ID + * @param uint32 nodeId : the node ID within the path + * @param uint32 originalDisplayId : the original display ID to restore + * @return bool success : true if marker was reset + */ + int ClearWaypointMarkerAuras(Eluna* E) + { + Player* player = E->CHECKOBJ(1); + uint32 pathId = E->CHECKVAL(2); + uint32 nodeId = E->CHECKVAL(3); + uint32 originalDisplayId = E->CHECKVAL(4, 0); + + ObjectGuid const& markerGuid = sWaypointMgr->GetVisualGUIDByNode(pathId, nodeId); + if (markerGuid.IsEmpty()) + { + E->Push(false); + return 1; + } + + Creature* marker = ObjectAccessor::GetCreature(*player, markerGuid); + if (!marker) + { + E->Push(false); + return 1; + } + + // Reset to original display and scale + if (originalDisplayId > 0) + marker->SetDisplayId(originalDisplayId); + else + marker->SetDisplayId(marker->GetNativeDisplayId()); + marker->SetObjectScale(1.0f); + marker->DestroyForNearbyPlayers(); // Force client update + marker->UpdateObjectVisibilityOnCreate(); // Recreate for players + + E->Push(true); + return 1; + } + /** * Gets all shared data keys. * Useful for debugging cross-state data. @@ -3499,7 +3686,13 @@ namespace LuaGlobalFunctions { "ClearSharedData", &LuaGlobalFunctions::ClearSharedData }, { "HasSharedData", &LuaGlobalFunctions::HasSharedData }, { "ClearAllSharedData", &LuaGlobalFunctions::ClearAllSharedData }, - { "GetSharedDataKeys", &LuaGlobalFunctions::GetSharedDataKeys } + { "GetSharedDataKeys", &LuaGlobalFunctions::GetSharedDataKeys }, + + // Waypoint utilities + { "GetWaypointPath", &LuaGlobalFunctions::GetWaypointPath }, + { "GetWaypointNodeForVisualGUID", &LuaGlobalFunctions::GetWaypointNodeForVisualGUID }, + { "HighlightWaypointMarker", &LuaGlobalFunctions::HighlightWaypointMarker }, + { "ClearWaypointMarkerAuras", &LuaGlobalFunctions::ClearWaypointMarkerAuras } }; } #endif diff --git a/src/server/game/LuaEngine/methods/TrinityCore/PlayerMethods.h b/src/server/game/LuaEngine/methods/TrinityCore/PlayerMethods.h index 9326e0223b..f3804c80d5 100644 --- a/src/server/game/LuaEngine/methods/TrinityCore/PlayerMethods.h +++ b/src/server/game/LuaEngine/methods/TrinityCore/PlayerMethods.h @@ -4265,7 +4265,7 @@ namespace LuaPlayer * This removes stale tracking state and despawns any remaining marker creatures. * Useful for cleaning up after CLEAR_ALL_WAYPOINT_MARKERS or Eluna reload. */ - int ClearAllWaypointVisualizations(Eluna* E, Player* player) + int ClearAllWaypointVisualizations(Eluna* /*E*/, Player* player) { sWaypointMgr->ClearAllVisualizations(player); return 0; diff --git a/src/server/game/Movement/Waypoints/WaypointManager.cpp b/src/server/game/Movement/Waypoints/WaypointManager.cpp index 2e50598ec5..be7f0a4b25 100644 --- a/src/server/game/Movement/Waypoints/WaypointManager.cpp +++ b/src/server/game/Movement/Waypoints/WaypointManager.cpp @@ -398,3 +398,14 @@ void WaypointPath::BuildSegments() ContinuousSegments.emplace_back(i, 1); } } + +bool WaypointMgr::GetPathAndNodeByVisualGUID(ObjectGuid guid, uint32& outPathId, uint32& outNodeId) const +{ + auto itr = _visualWaypointGUIDToNodeMap.find(guid); + if (itr == _visualWaypointGUIDToNodeMap.end()) + return false; + + outPathId = itr->second.first->Id; + outNodeId = itr->second.second->Id; + return true; +} diff --git a/src/server/game/Movement/Waypoints/WaypointManager.h b/src/server/game/Movement/Waypoints/WaypointManager.h index 81c489f3e5..b6b86c4e9f 100644 --- a/src/server/game/Movement/Waypoints/WaypointManager.h +++ b/src/server/game/Movement/Waypoints/WaypointManager.h @@ -65,6 +65,9 @@ class TC_GAME_API WaypointMgr WaypointPath const* GetPathByVisualGUID(ObjectGuid guid) const; WaypointNode const* GetNodeByVisualGUID(ObjectGuid guid) const; ObjectGuid const& GetVisualGUIDByNode(uint32 pathId, uint32 nodeId) const; + + // Get path and node IDs from a visual waypoint GUID + bool GetPathAndNodeByVisualGUID(ObjectGuid guid, uint32& outPathId, uint32& outNodeId) const; private: WaypointMgr();