mirror of
https://github.com/araxiaonline/TrinityCore.git
synced 2026-06-13 03:32:28 -04:00
feat(admin): Wander radius visualization and spawn marker tools
- Add SHOW_WANDER_RADIUS / HIDE_WANDER_RADIUS handlers - Add SHOW_SPAWN_MARKER / HIDE_SPAWN_MARKER handlers - Add CLEAR_WANDER_MARKERS and CLEAR_NEARBY_MARKERS (orphan cleanup) - Add UI buttons: Show Spawn Point, Show Radius, Clear All, Clear Orphaned - Button state locking during save operations - Auto-respawn creature after wander distance change - Fix: Use tostring() for uint64 spawnId in Lua table keys - Fix: Update ObjectMgr cache when saving wander_distance/movementType Known issue: Database UPDATE may not be persisting - needs investigation with direct DB access to verify prepared statement execution.
This commit is contained in:
@@ -636,6 +636,382 @@ end
|
||||
-- Deselect button no longer needed in split view layout
|
||||
-- (kept for code compatibility but hidden)
|
||||
|
||||
-- ============================================================================
|
||||
-- Wander Settings Content (shown for Random movement type instead of waypoints)
|
||||
-- ============================================================================
|
||||
|
||||
local wanderSettingsContainer = CreateFrame("Frame", nil, waypointContent, "BackdropTemplate")
|
||||
wanderSettingsContainer:SetAllPoints(waypointContent)
|
||||
wanderSettingsContainer:SetBackdrop({
|
||||
bgFile = "Interface/Tooltips/UI-Tooltip-Background",
|
||||
tile = true, tileSize = 16,
|
||||
insets = { left = 4, right = 4, top = 4, bottom = 4 }
|
||||
})
|
||||
wanderSettingsContainer:SetBackdropColor(0.05, 0.05, 0.05, 0.5)
|
||||
wanderSettingsContainer:Hide()
|
||||
|
||||
local wanderTitle = wanderSettingsContainer:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
|
||||
wanderTitle:SetPoint("TOP", wanderSettingsContainer, "TOP", 0, -15)
|
||||
wanderTitle:SetText("|cFFFFD700Random Movement Settings|r")
|
||||
|
||||
local wanderDesc = wanderSettingsContainer:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
|
||||
wanderDesc:SetPoint("TOP", wanderTitle, "BOTTOM", 0, -10)
|
||||
wanderDesc:SetWidth(280)
|
||||
wanderDesc:SetJustifyH("CENTER")
|
||||
wanderDesc:SetText("This creature uses Random movement.\nAdjust the wander distance below:")
|
||||
|
||||
-- Current wander distance display
|
||||
local wanderCurrentLabel = wanderSettingsContainer:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||
wanderCurrentLabel:SetPoint("TOPLEFT", wanderSettingsContainer, "TOPLEFT", 20, -90)
|
||||
wanderCurrentLabel:SetText("|cFF00FF00Current Wander Distance:|r")
|
||||
|
||||
local wanderCurrentValue = wanderSettingsContainer:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
|
||||
wanderCurrentValue:SetPoint("LEFT", wanderCurrentLabel, "RIGHT", 8, 0)
|
||||
wanderCurrentValue:SetText("0 yards")
|
||||
|
||||
-- Edit section
|
||||
local wanderEditLabel = wanderSettingsContainer:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||
wanderEditLabel:SetPoint("TOPLEFT", wanderCurrentLabel, "BOTTOMLEFT", 0, -20)
|
||||
wanderEditLabel:SetText("|cFF00FF00New Distance (yards):|r")
|
||||
|
||||
local wanderEditBox = CreateFrame("EditBox", nil, wanderSettingsContainer, "InputBoxTemplate")
|
||||
wanderEditBox:SetSize(60, 24)
|
||||
wanderEditBox:SetPoint("LEFT", wanderEditLabel, "RIGHT", 10, 0)
|
||||
wanderEditBox:SetAutoFocus(false)
|
||||
wanderEditBox:SetNumeric(false) -- Allow decimal input
|
||||
wanderEditBox:SetMaxLetters(6)
|
||||
wanderEditBox:SetText("5")
|
||||
|
||||
-- Save button
|
||||
local wanderSaveBtn = CreateFrame("Button", nil, wanderSettingsContainer, "UIPanelButtonTemplate")
|
||||
wanderSaveBtn:SetSize(100, 26)
|
||||
wanderSaveBtn:SetPoint("TOPLEFT", wanderEditLabel, "BOTTOMLEFT", 0, -20)
|
||||
wanderSaveBtn:SetText("Save")
|
||||
-- Helper function to disable all wander setting buttons while waiting for server
|
||||
local function DisableWanderButtons()
|
||||
wanderSaveBtn:Disable()
|
||||
-- Other buttons will be defined below, we'll call this after they exist
|
||||
end
|
||||
|
||||
-- Helper function to re-enable all wander setting buttons
|
||||
local function EnableWanderButtons()
|
||||
wanderSaveBtn:Enable()
|
||||
if spawnMarkerBtn then spawnMarkerBtn:Enable() end
|
||||
if wanderRadiusBtn then wanderRadiusBtn:Enable() end
|
||||
if clearAllBtn then clearAllBtn:Enable() end
|
||||
if clearNearbyBtn then clearNearbyBtn:Enable() end
|
||||
end
|
||||
|
||||
-- Forward declare buttons so DisableWanderButtons can reference them
|
||||
local spawnMarkerBtn, wanderRadiusBtn, clearAllBtn, clearNearbyBtn
|
||||
|
||||
-- Update DisableWanderButtons to use the forward declarations
|
||||
DisableWanderButtons = function()
|
||||
wanderSaveBtn:Disable()
|
||||
if spawnMarkerBtn then spawnMarkerBtn:Disable() end
|
||||
if wanderRadiusBtn then wanderRadiusBtn:Disable() end
|
||||
if clearAllBtn then clearAllBtn:Disable() end
|
||||
if clearNearbyBtn then clearNearbyBtn:Disable() end
|
||||
end
|
||||
|
||||
-- Update EnableWanderButtons to use the forward declarations
|
||||
EnableWanderButtons = function()
|
||||
wanderSaveBtn:Enable()
|
||||
if spawnMarkerBtn then spawnMarkerBtn:Enable() end
|
||||
if wanderRadiusBtn then wanderRadiusBtn:Enable() end
|
||||
if clearAllBtn then clearAllBtn:Enable() end
|
||||
if clearNearbyBtn then clearNearbyBtn:Enable() end
|
||||
end
|
||||
|
||||
wanderSaveBtn:SetScript("OnClick", function()
|
||||
local newDistance = tonumber(wanderEditBox:GetText())
|
||||
if not newDistance then
|
||||
print("|cFFFF0000[ATA]|r Invalid distance value")
|
||||
return
|
||||
end
|
||||
|
||||
if newDistance < 0 or newDistance > 100 then
|
||||
print("|cFFFF0000[ATA]|r Distance must be between 0 and 100 yards")
|
||||
return
|
||||
end
|
||||
|
||||
if AMS then
|
||||
DisableWanderButtons()
|
||||
AMS.Send("SET_WANDER_DISTANCE", { distance = newDistance })
|
||||
print("|cFF00FF00[ATA]|r Saving wander distance: " .. newDistance)
|
||||
end
|
||||
end)
|
||||
|
||||
-- Spawn marker toggle button
|
||||
local spawnMarkerVisible = false
|
||||
spawnMarkerBtn = CreateFrame("Button", nil, wanderSettingsContainer, "UIPanelButtonTemplate")
|
||||
spawnMarkerBtn:SetSize(140, 26)
|
||||
spawnMarkerBtn:SetPoint("LEFT", wanderSaveBtn, "RIGHT", 10, 0)
|
||||
spawnMarkerBtn:SetText("Show Spawn Point")
|
||||
spawnMarkerBtn:SetScript("OnClick", function()
|
||||
if AMS then
|
||||
if spawnMarkerVisible then
|
||||
AMS.Send("HIDE_SPAWN_MARKER", {})
|
||||
spawnMarkerVisible = false
|
||||
spawnMarkerBtn:SetText("Show Spawn Point")
|
||||
else
|
||||
AMS.Send("SHOW_SPAWN_MARKER", {})
|
||||
spawnMarkerVisible = true
|
||||
spawnMarkerBtn:SetText("Hide Spawn Point")
|
||||
end
|
||||
end
|
||||
end)
|
||||
spawnMarkerBtn:SetScript("OnEnter", function(self)
|
||||
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
||||
GameTooltip:AddLine("Spawn Point Marker", 1, 1, 1)
|
||||
GameTooltip:AddLine("Shows a marker at this creature's", 0.7, 0.7, 0.7)
|
||||
GameTooltip:AddLine("spawn/home position.", 0.7, 0.7, 0.7)
|
||||
GameTooltip:Show()
|
||||
end)
|
||||
spawnMarkerBtn:SetScript("OnLeave", function()
|
||||
GameTooltip:Hide()
|
||||
end)
|
||||
|
||||
-- Wander radius visualization button
|
||||
local wanderRadiusVisible = false
|
||||
wanderRadiusBtn = CreateFrame("Button", nil, wanderSettingsContainer, "UIPanelButtonTemplate")
|
||||
wanderRadiusBtn:SetSize(140, 26)
|
||||
wanderRadiusBtn:SetPoint("TOPLEFT", spawnMarkerBtn, "BOTTOMLEFT", 0, -5)
|
||||
wanderRadiusBtn:SetText("Show Radius")
|
||||
wanderRadiusBtn:SetScript("OnClick", function()
|
||||
if AMS then
|
||||
if wanderRadiusVisible then
|
||||
AMS.Send("HIDE_WANDER_RADIUS", {})
|
||||
wanderRadiusVisible = false
|
||||
wanderRadiusBtn:SetText("Show Radius")
|
||||
else
|
||||
AMS.Send("SHOW_WANDER_RADIUS", { segments = 16 })
|
||||
wanderRadiusVisible = true
|
||||
wanderRadiusBtn:SetText("Hide Radius")
|
||||
end
|
||||
end
|
||||
end)
|
||||
wanderRadiusBtn:SetScript("OnEnter", function(self)
|
||||
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
||||
GameTooltip:AddLine("Wander Radius Visualization", 1, 1, 1)
|
||||
GameTooltip:AddLine("Shows markers in a circle around", 0.7, 0.7, 0.7)
|
||||
GameTooltip:AddLine("the spawn point at the wander", 0.7, 0.7, 0.7)
|
||||
GameTooltip:AddLine("distance to visualize the area.", 0.7, 0.7, 0.7)
|
||||
GameTooltip:Show()
|
||||
end)
|
||||
wanderRadiusBtn:SetScript("OnLeave", function()
|
||||
GameTooltip:Hide()
|
||||
end)
|
||||
|
||||
-- Clear All button - removes spawn marker and wander radius markers
|
||||
clearAllBtn = CreateFrame("Button", nil, wanderSettingsContainer, "UIPanelButtonTemplate")
|
||||
clearAllBtn:SetSize(140, 26)
|
||||
clearAllBtn:SetPoint("TOPLEFT", wanderRadiusBtn, "BOTTOMLEFT", 0, -5)
|
||||
clearAllBtn:SetText("Clear All Markers")
|
||||
clearAllBtn:SetScript("OnClick", function()
|
||||
if AMS then
|
||||
AMS.Send("CLEAR_WANDER_MARKERS", {})
|
||||
-- Reset button states
|
||||
spawnMarkerVisible = false
|
||||
spawnMarkerBtn:SetText("Show Spawn Point")
|
||||
wanderRadiusVisible = false
|
||||
wanderRadiusBtn:SetText("Show Radius")
|
||||
end
|
||||
end)
|
||||
clearAllBtn:SetScript("OnEnter", function(self)
|
||||
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
||||
GameTooltip:AddLine("Clear All Markers", 1, 1, 1)
|
||||
GameTooltip:AddLine("Removes both spawn point marker", 0.7, 0.7, 0.7)
|
||||
GameTooltip:AddLine("and wander radius markers.", 0.7, 0.7, 0.7)
|
||||
GameTooltip:Show()
|
||||
end)
|
||||
clearAllBtn:SetScript("OnLeave", function()
|
||||
GameTooltip:Hide()
|
||||
end)
|
||||
|
||||
-- Clear Nearby (orphaned) markers button
|
||||
clearNearbyBtn = CreateFrame("Button", nil, wanderSettingsContainer, "UIPanelButtonTemplate")
|
||||
clearNearbyBtn:SetSize(140, 26)
|
||||
clearNearbyBtn:SetPoint("TOPLEFT", clearAllBtn, "BOTTOMLEFT", 0, -5)
|
||||
clearNearbyBtn:SetText("Clear Orphaned")
|
||||
clearNearbyBtn:SetScript("OnClick", function()
|
||||
if AMS then
|
||||
AMS.Send("CLEAR_NEARBY_MARKERS", { range = 100 })
|
||||
end
|
||||
end)
|
||||
clearNearbyBtn:SetScript("OnEnter", function(self)
|
||||
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
||||
GameTooltip:AddLine("Clear Orphaned Markers", 1, 0.5, 0)
|
||||
GameTooltip:AddLine("Removes ALL waypoint markers within", 0.7, 0.7, 0.7)
|
||||
GameTooltip:AddLine("100 yards of your character.", 0.7, 0.7, 0.7)
|
||||
GameTooltip:AddLine(" ", 0.7, 0.7, 0.7)
|
||||
GameTooltip:AddLine("Use this to clean up markers that", 1, 1, 0)
|
||||
GameTooltip:AddLine("were lost after a script reload.", 1, 1, 0)
|
||||
GameTooltip:Show()
|
||||
end)
|
||||
clearNearbyBtn:SetScript("OnLeave", function()
|
||||
GameTooltip:Hide()
|
||||
end)
|
||||
|
||||
-- Help text
|
||||
local wanderHelp = wanderSettingsContainer:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
|
||||
wanderHelp:SetPoint("BOTTOMLEFT", wanderSettingsContainer, "BOTTOMLEFT", 20, 20)
|
||||
wanderHelp:SetPoint("RIGHT", wanderSettingsContainer, "RIGHT", -20, 0)
|
||||
wanderHelp:SetJustifyH("LEFT")
|
||||
wanderHelp:SetText("|cFF888888Wander distance is the radius (in yards) the creature will\nrandomly move within from its spawn point.\nSet to 0 for no movement (idle).|r")
|
||||
|
||||
-- Handler for spawn marker response
|
||||
if AMS then
|
||||
AMS.RegisterHandler("SPAWN_MARKER_RESPONSE", function(data)
|
||||
if data and data.success then
|
||||
if data.message and data.message:find("shown") then
|
||||
spawnMarkerVisible = true
|
||||
spawnMarkerBtn:SetText("Hide Spawn Point")
|
||||
print("|cFF00FF00[ATA]|r Spawn marker shown at " .. string.format("%.1f, %.1f, %.1f", data.x or 0, data.y or 0, data.z or 0))
|
||||
else
|
||||
spawnMarkerVisible = false
|
||||
spawnMarkerBtn:SetText("Show Spawn Point")
|
||||
print("|cFF00FF00[ATA]|r Spawn marker hidden")
|
||||
end
|
||||
else
|
||||
print("|cFFFF0000[ATA]|r Spawn marker error: " .. (data.error or "Unknown error"))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Handler for wander distance save response
|
||||
if AMS then
|
||||
AMS.RegisterHandler("SET_WANDER_DISTANCE_RESPONSE", function(data)
|
||||
EnableWanderButtons()
|
||||
if data and data.success then
|
||||
print("|cFF00FF00[ATA]|r Wander distance updated to " .. (data.newDistance or "?") .. " yards")
|
||||
wanderCurrentValue:SetText(string.format("%.1f yards", data.newDistance or 0))
|
||||
|
||||
-- Remember what markers were visible BEFORE we cleared them
|
||||
local wasSpawnVisible = spawnMarkerVisible
|
||||
local wasRadiusVisible = wanderRadiusVisible
|
||||
|
||||
-- Reset button states since markers were hidden by the save
|
||||
spawnMarkerVisible = false
|
||||
spawnMarkerBtn:SetText("Show Spawn Point")
|
||||
wanderRadiusVisible = false
|
||||
wanderRadiusBtn:SetText("Show Radius")
|
||||
|
||||
-- Schedule re-showing markers after creature respawns (2 seconds)
|
||||
-- Only if they were visible before AND we still have same target
|
||||
if wasSpawnVisible or wasRadiusVisible then
|
||||
C_Timer.After(2, function()
|
||||
if AMS and UnitExists("target") then
|
||||
if wasSpawnVisible then
|
||||
AMS.Send("SHOW_SPAWN_MARKER", {})
|
||||
spawnMarkerVisible = true
|
||||
spawnMarkerBtn:SetText("Hide Spawn Point")
|
||||
end
|
||||
if wasRadiusVisible then
|
||||
AMS.Send("SHOW_WANDER_RADIUS", { segments = 16 })
|
||||
wanderRadiusVisible = true
|
||||
wanderRadiusBtn:SetText("Hide Radius")
|
||||
end
|
||||
print("|cFF00FF00[ATA]|r Markers refreshed at new location")
|
||||
else
|
||||
print("|cFFFFFF00[ATA]|r Target lost - markers not refreshed")
|
||||
end
|
||||
end)
|
||||
end
|
||||
else
|
||||
print("|cFFFF0000[ATA]|r Failed to update wander distance: " .. (data.message or "Unknown error"))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Handler for wander radius visualization response
|
||||
if AMS then
|
||||
AMS.RegisterHandler("WANDER_RADIUS_RESPONSE", function(data)
|
||||
if data and data.success then
|
||||
if data.message and data.message:find("shown") then
|
||||
wanderRadiusVisible = true
|
||||
wanderRadiusBtn:SetText("Hide Radius")
|
||||
print("|cFF00FF00[ATA]|r " .. data.message .. " (" .. (data.markerCount or "?") .. " markers)")
|
||||
else
|
||||
wanderRadiusVisible = false
|
||||
wanderRadiusBtn:SetText("Show Radius")
|
||||
print("|cFF00FF00[ATA]|r Wander radius hidden")
|
||||
end
|
||||
else
|
||||
print("|cFFFF0000[ATA]|r Wander radius error: " .. (data.error or "Unknown error"))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Handler for clear all markers response
|
||||
if AMS then
|
||||
AMS.RegisterHandler("CLEAR_WANDER_MARKERS_RESPONSE", function(data)
|
||||
if data and data.success then
|
||||
if data.cleared and data.cleared > 0 then
|
||||
print("|cFF00FF00[ATA]|r " .. (data.message or "Markers cleared"))
|
||||
else
|
||||
print("|cFFFFFF00[ATA]|r " .. (data.message or "No markers found to clear"))
|
||||
end
|
||||
else
|
||||
print("|cFFFF0000[ATA]|r Clear markers error: " .. (data.error or "Unknown error"))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Handler for clear nearby orphaned markers response
|
||||
if AMS then
|
||||
AMS.RegisterHandler("CLEAR_NEARBY_MARKERS_RESPONSE", function(data)
|
||||
if data and data.success then
|
||||
if data.cleared and data.cleared > 0 then
|
||||
print("|cFF00FF00[ATA]|r " .. data.message)
|
||||
else
|
||||
print("|cFFFFFF00[ATA]|r No orphaned markers found nearby")
|
||||
end
|
||||
else
|
||||
print("|cFFFF0000[ATA]|r Clear orphaned error: " .. (data.error or "Unknown error"))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Function to update wander settings display
|
||||
local function UpdateWanderSettings(wanderRadius)
|
||||
wanderCurrentValue:SetText(string.format("%.1f yards", wanderRadius or 0))
|
||||
wanderEditBox:SetText(string.format("%.1f", wanderRadius or 5))
|
||||
end
|
||||
|
||||
-- Function to show waypoint or wander content based on movement type
|
||||
local function UpdateMovementContent(movementType, wanderRadius, hasWaypointPath)
|
||||
if movementType == 1 and not hasWaypointPath then
|
||||
-- Random movement without waypoints - show wander settings
|
||||
waypointListContainer:Hide()
|
||||
waypointDivider:Hide()
|
||||
waypointDetailPanel:Hide()
|
||||
wanderSettingsContainer:Show()
|
||||
UpdateWanderSettings(wanderRadius)
|
||||
waypointButton:Disable()
|
||||
else
|
||||
-- Waypoint movement or has waypoint path - show waypoint list
|
||||
wanderSettingsContainer:Hide()
|
||||
waypointListContainer:Show()
|
||||
waypointDivider:Show()
|
||||
waypointDetailPanel:Show()
|
||||
if hasWaypointPath then
|
||||
waypointButton:Enable()
|
||||
else
|
||||
waypointButton:Disable()
|
||||
end
|
||||
end
|
||||
|
||||
-- Reset button states when switching targets
|
||||
spawnMarkerVisible = false
|
||||
spawnMarkerBtn:SetText("Show Spawn Point")
|
||||
wanderRadiusVisible = false
|
||||
wanderRadiusBtn:SetText("Show Radius")
|
||||
|
||||
-- Re-enable all buttons (in case they were disabled waiting for a response)
|
||||
EnableWanderButtons()
|
||||
end
|
||||
|
||||
-- Request waypoint details from server
|
||||
local function RequestWaypointDetails(pathId)
|
||||
if not AMS then return end
|
||||
@@ -1331,6 +1707,14 @@ function npcPanel:Update(requestServerData, forceRefresh)
|
||||
else
|
||||
waypointButton:Disable()
|
||||
end
|
||||
|
||||
-- Update Waypoints tab to show either waypoint list or wander settings
|
||||
if data and data.movement then
|
||||
local movementType = data.movement.defaultType or 0
|
||||
local wanderRadius = data.behavior and data.behavior.wanderRadius or 0
|
||||
local hasWaypointPath = data.movement.waypointPath ~= nil
|
||||
UpdateMovementContent(movementType, wanderRadius, hasWaypointPath)
|
||||
end
|
||||
end, forceRefresh) -- Pass forceRefresh to bypass cache
|
||||
end
|
||||
|
||||
@@ -1356,6 +1740,10 @@ end
|
||||
-- ============================================================================
|
||||
|
||||
refreshButton:SetScript("OnClick", function()
|
||||
-- Re-enable buttons in case they got stuck disabled from a failed request
|
||||
pcall(function()
|
||||
if EnableWanderButtons then EnableWanderButtons() end
|
||||
end)
|
||||
npcPanel:Update(true, true) -- Always force refresh from server, bypass cache
|
||||
end)
|
||||
|
||||
|
||||
@@ -374,6 +374,314 @@ AMS.RegisterHandler("CLEAR_ALL_WAYPOINT_MARKERS", function(player, data)
|
||||
AMS.Send(player, "CLEAR_WAYPOINTS_RESPONSE", { success = true })
|
||||
end)
|
||||
|
||||
-- ============================================================================
|
||||
-- Spawn Point Marker Operations
|
||||
-- ============================================================================
|
||||
|
||||
--[[
|
||||
SHOW_SPAWN_MARKER - Show a marker at the selected creature's spawn point
|
||||
Request: { displayId = optional number }
|
||||
Response: { success = bool, x, y, z, o }
|
||||
]]
|
||||
AMS.RegisterHandler("SHOW_SPAWN_MARKER", function(player, data)
|
||||
print("[Admin Handlers] SHOW_SPAWN_MARKER request received")
|
||||
|
||||
local creature = player:GetSelection()
|
||||
if not creature then
|
||||
AMS.Send(player, "SPAWN_MARKER_RESPONSE", { success = false, error = "No target selected" })
|
||||
return
|
||||
end
|
||||
|
||||
creature = creature:ToCreature()
|
||||
if not creature then
|
||||
AMS.Send(player, "SPAWN_MARKER_RESPONSE", { success = false, error = "Target is not a creature" })
|
||||
return
|
||||
end
|
||||
|
||||
-- Get home position for response
|
||||
local x, y, z, o = creature:GetHomePosition()
|
||||
|
||||
-- Show spawn marker (pass player for phase, optional displayId)
|
||||
local displayId = data and data.displayId or nil
|
||||
local success, marker = pcall(function()
|
||||
if displayId then
|
||||
return creature:ShowSpawnPointMarker(player, displayId)
|
||||
else
|
||||
return creature:ShowSpawnPointMarker(player)
|
||||
end
|
||||
end)
|
||||
|
||||
if success and marker then
|
||||
print("[Admin Handlers] SHOW_SPAWN_MARKER: Marker created at " .. x .. ", " .. y .. ", " .. z)
|
||||
AMS.Send(player, "SPAWN_MARKER_RESPONSE", {
|
||||
success = true,
|
||||
x = x, y = y, z = z, o = o,
|
||||
message = "Spawn marker shown"
|
||||
})
|
||||
else
|
||||
print("[Admin Handlers] SHOW_SPAWN_MARKER: Failed to create marker")
|
||||
AMS.Send(player, "SPAWN_MARKER_RESPONSE", {
|
||||
success = false,
|
||||
error = "Failed to show marker (rebuild server required)"
|
||||
})
|
||||
end
|
||||
end)
|
||||
|
||||
--[[
|
||||
HIDE_SPAWN_MARKER - Hide the spawn point marker for the selected creature
|
||||
Request: {}
|
||||
Response: { success = bool }
|
||||
]]
|
||||
AMS.RegisterHandler("HIDE_SPAWN_MARKER", function(player, data)
|
||||
print("[Admin Handlers] HIDE_SPAWN_MARKER request received")
|
||||
|
||||
local creature = player:GetSelection()
|
||||
if not creature then
|
||||
AMS.Send(player, "SPAWN_MARKER_RESPONSE", { success = false, error = "No target selected" })
|
||||
return
|
||||
end
|
||||
|
||||
creature = creature:ToCreature()
|
||||
if not creature then
|
||||
AMS.Send(player, "SPAWN_MARKER_RESPONSE", { success = false, error = "Target is not a creature" })
|
||||
return
|
||||
end
|
||||
|
||||
local success = pcall(function() return creature:HideSpawnPointMarker() end)
|
||||
|
||||
if success then
|
||||
print("[Admin Handlers] HIDE_SPAWN_MARKER: Marker hidden")
|
||||
AMS.Send(player, "SPAWN_MARKER_RESPONSE", { success = true, message = "Spawn marker hidden" })
|
||||
else
|
||||
print("[Admin Handlers] HIDE_SPAWN_MARKER: Failed to hide marker")
|
||||
AMS.Send(player, "SPAWN_MARKER_RESPONSE", { success = false, error = "Failed to hide marker" })
|
||||
end
|
||||
end)
|
||||
|
||||
-- Local cache for wander radius markers (for same-session cleanup)
|
||||
-- Also stored in ElunaSharedData for visibility, but Lua object refs only work same-session
|
||||
local wanderRadiusMarkers = {}
|
||||
|
||||
--[[
|
||||
SHOW_WANDER_RADIUS - Show visual markers in a circle at the creature's wander radius
|
||||
Request: { segments = optional number (default 12) }
|
||||
Response: { success = bool, radius = number, x, y, z }
|
||||
]]
|
||||
AMS.RegisterHandler("SHOW_WANDER_RADIUS", function(player, data)
|
||||
print("[Admin Handlers] SHOW_WANDER_RADIUS request received")
|
||||
|
||||
local creature = player:GetSelection()
|
||||
if not creature then
|
||||
AMS.Send(player, "WANDER_RADIUS_RESPONSE", { success = false, error = "No target selected" })
|
||||
return
|
||||
end
|
||||
|
||||
creature = creature:ToCreature()
|
||||
if not creature then
|
||||
AMS.Send(player, "WANDER_RADIUS_RESPONSE", { success = false, error = "Target is not a creature" })
|
||||
return
|
||||
end
|
||||
|
||||
local spawnId = creature:GetDBTableGUIDLow()
|
||||
local spawnIdStr = tostring(spawnId) -- Convert to string for table key
|
||||
local wanderRadius = creature:GetWanderRadius()
|
||||
|
||||
if wanderRadius <= 0 then
|
||||
AMS.Send(player, "WANDER_RADIUS_RESPONSE", { success = false, error = "Creature has no wander radius (0)" })
|
||||
return
|
||||
end
|
||||
|
||||
-- Get spawn position
|
||||
local homeX, homeY, homeZ, homeO = creature:GetHomePosition()
|
||||
local markerKey = "wander_markers_" .. spawnIdStr
|
||||
|
||||
-- Clear any existing markers for this creature (from local cache)
|
||||
if wanderRadiusMarkers[spawnIdStr] then
|
||||
for _, marker in ipairs(wanderRadiusMarkers[spawnIdStr]) do
|
||||
if marker and marker:IsInWorld() then
|
||||
marker:DespawnOrUnsummon()
|
||||
end
|
||||
end
|
||||
wanderRadiusMarkers[spawnIdStr] = nil
|
||||
end
|
||||
ClearSharedData(markerKey) -- Also clear shared data
|
||||
|
||||
-- Number of markers around the circle (default 12 = every 30 degrees)
|
||||
local segments = (data and data.segments) or 12
|
||||
if segments < 4 then segments = 4 end
|
||||
if segments > 36 then segments = 36 end
|
||||
|
||||
local markers = {}
|
||||
local markerGuids = {}
|
||||
local angleStep = (2 * math.pi) / segments
|
||||
|
||||
-- Spawn markers around the circle perimeter
|
||||
for i = 0, segments - 1 do
|
||||
local angle = i * angleStep
|
||||
local markerX = homeX + wanderRadius * math.cos(angle)
|
||||
local markerY = homeY + wanderRadius * math.sin(angle)
|
||||
local markerZ = homeZ -- Keep at spawn height
|
||||
|
||||
-- Spawn a visual marker at this position
|
||||
-- Using entry 1 (VISUAL_WAYPOINT) with a distinct display
|
||||
local marker = creature:SpawnCreature(1, markerX, markerY, markerZ, 0, 8) -- 8 = TEMPSUMMON_MANUAL_DESPAWN
|
||||
if marker then
|
||||
marker:SetDisplayId(31366) -- Green circle like spawn marker
|
||||
marker:SetScale(0.3) -- Smaller for radius markers
|
||||
table.insert(markers, marker)
|
||||
table.insert(markerGuids, tostring(marker:GetGUIDLow()))
|
||||
end
|
||||
end
|
||||
|
||||
-- Also spawn a marker at the center (spawn point)
|
||||
local centerMarker = creature:SpawnCreature(1, homeX, homeY, homeZ, homeO, 8)
|
||||
if centerMarker then
|
||||
centerMarker:SetDisplayId(31366) -- Green circle
|
||||
centerMarker:SetScale(1.0) -- Larger for center
|
||||
table.insert(markers, centerMarker)
|
||||
table.insert(markerGuids, tostring(centerMarker:GetGUIDLow()))
|
||||
end
|
||||
|
||||
-- Store in local cache for same-session cleanup (use string key)
|
||||
wanderRadiusMarkers[spawnIdStr] = markers
|
||||
|
||||
-- Also store GUIDs in shared data (for debugging/tracking)
|
||||
SetSharedData(markerKey, Smallfolk.dumps(markerGuids))
|
||||
|
||||
print("[Admin Handlers] SHOW_WANDER_RADIUS: Created " .. #markers .. " markers for radius " .. wanderRadius)
|
||||
AMS.Send(player, "WANDER_RADIUS_RESPONSE", {
|
||||
success = true,
|
||||
radius = wanderRadius,
|
||||
segments = segments,
|
||||
markerCount = #markers,
|
||||
x = homeX, y = homeY, z = homeZ,
|
||||
message = "Wander radius shown (" .. wanderRadius .. " yards)"
|
||||
})
|
||||
end)
|
||||
|
||||
--[[
|
||||
HIDE_WANDER_RADIUS - Hide the wander radius markers for the selected creature
|
||||
Request: {}
|
||||
Response: { success = bool }
|
||||
]]
|
||||
AMS.RegisterHandler("HIDE_WANDER_RADIUS", function(player, data)
|
||||
print("[Admin Handlers] HIDE_WANDER_RADIUS request received")
|
||||
|
||||
local creature = player:GetSelection()
|
||||
if not creature then
|
||||
AMS.Send(player, "WANDER_RADIUS_RESPONSE", { success = false, error = "No target selected" })
|
||||
return
|
||||
end
|
||||
|
||||
creature = creature:ToCreature()
|
||||
if not creature then
|
||||
AMS.Send(player, "WANDER_RADIUS_RESPONSE", { success = false, error = "Target is not a creature" })
|
||||
return
|
||||
end
|
||||
|
||||
local spawnId = creature:GetDBTableGUIDLow()
|
||||
local spawnIdStr = tostring(spawnId)
|
||||
local markerKey = "wander_markers_" .. spawnIdStr
|
||||
|
||||
if wanderRadiusMarkers[spawnIdStr] then
|
||||
local count = #wanderRadiusMarkers[spawnIdStr]
|
||||
for _, marker in ipairs(wanderRadiusMarkers[spawnIdStr]) do
|
||||
if marker and marker:IsInWorld() then
|
||||
marker:DespawnOrUnsummon()
|
||||
end
|
||||
end
|
||||
wanderRadiusMarkers[spawnIdStr] = nil
|
||||
ClearSharedData(markerKey)
|
||||
print("[Admin Handlers] HIDE_WANDER_RADIUS: Removed " .. count .. " markers")
|
||||
AMS.Send(player, "WANDER_RADIUS_RESPONSE", { success = true, message = "Wander radius hidden" })
|
||||
else
|
||||
-- No local cache - markers may have been lost on reload
|
||||
-- They'll despawn naturally, just clear any tracking data
|
||||
ClearSharedData(markerKey)
|
||||
AMS.Send(player, "WANDER_RADIUS_RESPONSE", { success = true, message = "Wander radius markers cleared (may already be despawned)" })
|
||||
end
|
||||
end)
|
||||
|
||||
--[[
|
||||
CLEAR_WANDER_MARKERS - Clear both spawn marker and wander radius markers
|
||||
Request: {}
|
||||
Response: { success = bool }
|
||||
]]
|
||||
AMS.RegisterHandler("CLEAR_WANDER_MARKERS", function(player, data)
|
||||
print("[Admin Handlers] CLEAR_WANDER_MARKERS request received")
|
||||
|
||||
local creature = player:GetSelection()
|
||||
if not creature then
|
||||
AMS.Send(player, "CLEAR_WANDER_MARKERS_RESPONSE", { success = false, error = "No target selected" })
|
||||
return
|
||||
end
|
||||
|
||||
creature = creature:ToCreature()
|
||||
if not creature then
|
||||
AMS.Send(player, "CLEAR_WANDER_MARKERS_RESPONSE", { success = false, error = "Target is not a creature" })
|
||||
return
|
||||
end
|
||||
|
||||
local spawnId = creature:GetDBTableGUIDLow()
|
||||
local spawnIdStr = tostring(spawnId)
|
||||
local cleared = 0
|
||||
local messages = {}
|
||||
|
||||
-- Clear spawn marker (via C++ method if available)
|
||||
local spawnSuccess, spawnResult = pcall(function() return creature:HideSpawnPointMarker() end)
|
||||
if spawnSuccess and spawnResult then
|
||||
cleared = cleared + 1
|
||||
table.insert(messages, "spawn marker")
|
||||
end
|
||||
|
||||
-- Clear wander radius markers (from local cache, use string key)
|
||||
if wanderRadiusMarkers[spawnIdStr] then
|
||||
local count = #wanderRadiusMarkers[spawnIdStr]
|
||||
for _, marker in ipairs(wanderRadiusMarkers[spawnIdStr]) do
|
||||
if marker and marker:IsInWorld() then
|
||||
marker:DespawnOrUnsummon()
|
||||
cleared = cleared + 1
|
||||
end
|
||||
end
|
||||
wanderRadiusMarkers[spawnIdStr] = nil
|
||||
table.insert(messages, count .. " radius markers")
|
||||
end
|
||||
|
||||
-- Clear shared data
|
||||
ClearSharedData("wander_markers_" .. spawnIdStr)
|
||||
|
||||
local msg = cleared > 0 and ("Cleared: " .. table.concat(messages, ", ")) or "No active markers in cache (may have been lost on reload)"
|
||||
print("[Admin Handlers] CLEAR_WANDER_MARKERS: " .. msg .. " for creature " .. tostring(spawnId))
|
||||
AMS.Send(player, "CLEAR_WANDER_MARKERS_RESPONSE", { success = true, cleared = cleared, message = msg })
|
||||
end)
|
||||
|
||||
--[[
|
||||
CLEAR_NEARBY_MARKERS - Emergency clear of all VISUAL_WAYPOINT creatures near player
|
||||
This clears orphaned markers from before tracking was implemented
|
||||
Request: { range = optional number (default 100) }
|
||||
Response: { success = bool, cleared = number }
|
||||
]]
|
||||
AMS.RegisterHandler("CLEAR_NEARBY_MARKERS", function(player, data)
|
||||
print("[Admin Handlers] CLEAR_NEARBY_MARKERS request received")
|
||||
|
||||
local range = (data and data.range) or 100
|
||||
local cleared = 0
|
||||
|
||||
-- Get all creatures near player with entry 1 (VISUAL_WAYPOINT)
|
||||
local creatures = player:GetCreaturesInRange(range, 1) -- entry 1 = VISUAL_WAYPOINT
|
||||
if creatures then
|
||||
for _, creature in ipairs(creatures) do
|
||||
if creature and creature:IsInWorld() then
|
||||
creature:DespawnOrUnsummon()
|
||||
cleared = cleared + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print("[Admin Handlers] CLEAR_NEARBY_MARKERS: Cleared " .. cleared .. " markers within " .. range .. " yards")
|
||||
AMS.Send(player, "CLEAR_NEARBY_MARKERS_RESPONSE", { success = true, cleared = cleared, message = "Cleared " .. cleared .. " orphaned markers" })
|
||||
end)
|
||||
|
||||
-- Get detailed waypoint data for a path
|
||||
AMS.RegisterHandler("GET_WAYPOINT_DETAILS", function(player, data)
|
||||
if not data or not data.pathId then
|
||||
@@ -527,6 +835,151 @@ AMS.RegisterHandler("TELEPORT_TO_WAYPOINT", function(player, data)
|
||||
player:SendBroadcastMessage("Teleported to waypoint")
|
||||
end)
|
||||
|
||||
-- ============================================================================
|
||||
-- Creature Edit Operations (Araxia Write Operations)
|
||||
-- ============================================================================
|
||||
|
||||
--[[
|
||||
SET_WANDER_DISTANCE - Set the wander distance for the selected creature
|
||||
Request: { distance = 10.0 }
|
||||
Response: { success = bool, message = string, newDistance = number }
|
||||
Note: Creature must be selected (same as GET_NPC_DATA)
|
||||
]]
|
||||
AMS.RegisterHandler("SET_WANDER_DISTANCE", function(player, data)
|
||||
if not data or data.distance == nil then
|
||||
print("[Admin Handlers] SET_WANDER_DISTANCE: Missing distance")
|
||||
AMS.Send(player, "SET_WANDER_DISTANCE_RESPONSE", {
|
||||
success = false,
|
||||
message = "Missing distance parameter"
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
print("[Admin Handlers] SET_WANDER_DISTANCE: Setting distance to " .. data.distance)
|
||||
|
||||
-- Use player's selection (same pattern as GET_NPC_DATA)
|
||||
local creature = player:GetSelection()
|
||||
if not creature then
|
||||
AMS.Send(player, "SET_WANDER_DISTANCE_RESPONSE", {
|
||||
success = false,
|
||||
message = "No creature selected"
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
creature = creature:ToCreature()
|
||||
if not creature then
|
||||
AMS.Send(player, "SET_WANDER_DISTANCE_RESPONSE", {
|
||||
success = false,
|
||||
message = "Selected object is not a creature"
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
local spawnId = creature:GetDBTableGUIDLow()
|
||||
local spawnIdStr = tostring(spawnId) -- Convert to string for table key
|
||||
local homeX, homeY, homeZ, homeO = creature:GetHomePosition()
|
||||
|
||||
-- Check if markers were visible and hide them
|
||||
local hadSpawnMarker = false
|
||||
local hadRadiusMarkers = false
|
||||
|
||||
-- Try to hide spawn marker (via C++) - check if it returns true
|
||||
local spawnHideSuccess, spawnHideResult = pcall(function() return creature:HideSpawnPointMarker() end)
|
||||
if spawnHideSuccess and spawnHideResult then
|
||||
hadSpawnMarker = true
|
||||
print("[Admin Handlers] SET_WANDER_DISTANCE: Hid spawn marker")
|
||||
end
|
||||
|
||||
-- Hide wander radius markers if they exist (use string key)
|
||||
if wanderRadiusMarkers[spawnIdStr] then
|
||||
hadRadiusMarkers = true
|
||||
for _, marker in ipairs(wanderRadiusMarkers[spawnIdStr]) do
|
||||
if marker and marker:IsInWorld() then
|
||||
marker:DespawnOrUnsummon()
|
||||
end
|
||||
end
|
||||
wanderRadiusMarkers[spawnIdStr] = nil
|
||||
ClearSharedData("wander_markers_" .. spawnIdStr)
|
||||
print("[Admin Handlers] SET_WANDER_DISTANCE: Hid radius markers")
|
||||
end
|
||||
|
||||
-- Use the Araxia writer method to save to database
|
||||
local success, message = creature:SaveWanderDistance(data.distance, player)
|
||||
|
||||
print("[Admin Handlers] SET_WANDER_DISTANCE: Result - " .. tostring(success) .. ", " .. message)
|
||||
|
||||
-- Despawn and respawn the creature so the new settings take effect immediately
|
||||
if success then
|
||||
-- Set a very short respawn delay (1 second), then despawn
|
||||
creature:SetRespawnDelay(1)
|
||||
creature:Respawn() -- Mark for respawn
|
||||
creature:DespawnOrUnsummon(0) -- Remove from world
|
||||
print("[Admin Handlers] SET_WANDER_DISTANCE: Creature despawned, respawning in 1 second")
|
||||
|
||||
-- Tell client markers need to be re-shown (client will handle the delay)
|
||||
if hadSpawnMarker or hadRadiusMarkers then
|
||||
print("[Admin Handlers] SET_WANDER_DISTANCE: Markers will need to be re-shown after respawn")
|
||||
end
|
||||
end
|
||||
|
||||
AMS.Send(player, "SET_WANDER_DISTANCE_RESPONSE", {
|
||||
success = success,
|
||||
message = message,
|
||||
newDistance = data.distance,
|
||||
markersCleared = hadSpawnMarker or hadRadiusMarkers,
|
||||
hadSpawnMarker = hadSpawnMarker,
|
||||
hadRadiusMarkers = hadRadiusMarkers
|
||||
})
|
||||
end)
|
||||
|
||||
--[[
|
||||
SET_MOVEMENT_TYPE - Set the movement type for the selected creature
|
||||
Request: { movementType = 0|1|2 }
|
||||
Response: { success = bool, message = string, newMovementType = number }
|
||||
Note: Creature must be selected
|
||||
]]
|
||||
AMS.RegisterHandler("SET_MOVEMENT_TYPE", function(player, data)
|
||||
if not data or data.movementType == nil then
|
||||
print("[Admin Handlers] SET_MOVEMENT_TYPE: Missing movementType")
|
||||
AMS.Send(player, "SET_MOVEMENT_TYPE_RESPONSE", {
|
||||
success = false,
|
||||
message = "Missing movementType parameter"
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
print("[Admin Handlers] SET_MOVEMENT_TYPE: Setting type to " .. data.movementType)
|
||||
|
||||
local creature = player:GetSelection()
|
||||
if not creature then
|
||||
AMS.Send(player, "SET_MOVEMENT_TYPE_RESPONSE", {
|
||||
success = false,
|
||||
message = "No creature selected"
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
creature = creature:ToCreature()
|
||||
if not creature then
|
||||
AMS.Send(player, "SET_MOVEMENT_TYPE_RESPONSE", {
|
||||
success = false,
|
||||
message = "Selected object is not a creature"
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
local success, message = creature:SaveMovementType(data.movementType, player)
|
||||
|
||||
print("[Admin Handlers] SET_MOVEMENT_TYPE: Result - " .. tostring(success) .. ", " .. message)
|
||||
|
||||
AMS.Send(player, "SET_MOVEMENT_TYPE_RESPONSE", {
|
||||
success = success,
|
||||
message = message,
|
||||
newMovementType = data.movementType
|
||||
})
|
||||
end)
|
||||
|
||||
-- ============================================================================
|
||||
-- Initialization
|
||||
-- ============================================================================
|
||||
@@ -538,8 +991,16 @@ print(" - SHOW_WAYPOINTS")
|
||||
print(" - HIDE_WAYPOINTS")
|
||||
print(" - HIDE_WAYPOINTS_BY_GUID")
|
||||
print(" - CLEAR_ALL_WAYPOINT_MARKERS")
|
||||
print(" - SHOW_SPAWN_MARKER")
|
||||
print(" - HIDE_SPAWN_MARKER")
|
||||
print(" - SHOW_WANDER_RADIUS")
|
||||
print(" - HIDE_WANDER_RADIUS")
|
||||
print(" - CLEAR_WANDER_MARKERS")
|
||||
print(" - CLEAR_NEARBY_MARKERS")
|
||||
print(" - GET_WAYPOINT_DETAILS")
|
||||
print(" - SELECT_WAYPOINT")
|
||||
print(" - GET_WAYPOINT_FOR_GUID")
|
||||
print(" - GET_PLAYER_DATA")
|
||||
print(" - TELEPORT_TO_WAYPOINT")
|
||||
print(" - SET_WANDER_DISTANCE")
|
||||
print(" - SET_MOVEMENT_TYPE")
|
||||
|
||||
25
src/araxiaonline/CMakeLists.txt
Normal file
25
src/araxiaonline/CMakeLists.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
# Araxia Online - Custom Code
|
||||
# This folder contains all Araxia-specific customizations
|
||||
|
||||
CollectSourceFiles(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
PRIVATE_SOURCES)
|
||||
|
||||
GroupSources(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# Add include directories for araxiaonline code
|
||||
target_include_directories(game
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/game
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/game/Entities
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/game/Entities/Creature
|
||||
)
|
||||
|
||||
# Add sources to the game target
|
||||
target_sources(game
|
||||
PRIVATE
|
||||
${PRIVATE_SOURCES}
|
||||
)
|
||||
|
||||
message(STATUS "[Araxia] Custom code sources added")
|
||||
159
src/araxiaonline/game/Entities/Creature/AraxiaCreatureWriter.cpp
Normal file
159
src/araxiaonline/game/Entities/Creature/AraxiaCreatureWriter.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* AraxiaCreatureWriter.cpp
|
||||
*
|
||||
* Araxia Online - Custom Write Operations for Creatures
|
||||
*/
|
||||
|
||||
#include "AraxiaCreatureWriter.h"
|
||||
#include "Creature.h"
|
||||
#include "Player.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Log.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Map.h"
|
||||
|
||||
namespace Araxia
|
||||
{
|
||||
|
||||
CreatureWriter& CreatureWriter::Instance()
|
||||
{
|
||||
static CreatureWriter instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
CreatureWriter::CreatureWriter()
|
||||
{
|
||||
TC_LOG_INFO("server.loading", "[Araxia] CreatureWriter initialized");
|
||||
}
|
||||
|
||||
void CreatureWriter::LogChange(std::string const& table, std::string const& field,
|
||||
std::string const& oldValue, std::string const& newValue,
|
||||
ObjectGuid::LowType affectedGuid, Player* changedBy)
|
||||
{
|
||||
// TODO: Store changes in araxia_change_log table for export
|
||||
// For now, just log to the server log
|
||||
std::string changerName = changedBy ? changedBy->GetName() : "SYSTEM";
|
||||
|
||||
TC_LOG_INFO("araxia.changes", "[Araxia Change] Table: %s, Field: %s, GUID: " UI64FMTD ", "
|
||||
"Old: %s, New: %s, ChangedBy: %s",
|
||||
table.c_str(), field.c_str(), affectedGuid,
|
||||
oldValue.c_str(), newValue.c_str(), changerName.c_str());
|
||||
}
|
||||
|
||||
WriteResult CreatureWriter::SetWanderDistance(ObjectGuid::LowType spawnGuid, float distance, Player* changedBy)
|
||||
{
|
||||
WriteResult result;
|
||||
result.success = false;
|
||||
result.changeId = 0;
|
||||
|
||||
// Validate distance
|
||||
if (distance < 0.0f)
|
||||
{
|
||||
result.message = "Wander distance cannot be negative";
|
||||
return result;
|
||||
}
|
||||
|
||||
if (distance > 100.0f)
|
||||
{
|
||||
result.message = "Wander distance too large (max 100 yards)";
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get current value for logging
|
||||
float oldDistance = GetWanderDistance(spawnGuid);
|
||||
|
||||
// Update database
|
||||
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_CREATURE_WANDER_DISTANCE);
|
||||
stmt->setFloat(0, distance);
|
||||
stmt->setUInt64(1, spawnGuid);
|
||||
WorldDatabase.Execute(stmt);
|
||||
|
||||
// Log the change
|
||||
LogChange("creature", "wander_distance",
|
||||
std::to_string(oldDistance), std::to_string(distance),
|
||||
spawnGuid, changedBy);
|
||||
|
||||
// CRITICAL: Also update the ObjectMgr cache so respawned creatures get the new value
|
||||
CreatureData const* data = sObjectMgr->GetCreatureData(spawnGuid);
|
||||
if (data)
|
||||
{
|
||||
// Cast away const to update the cached value
|
||||
// This is safe because we own the data and are updating it consistently with DB
|
||||
CreatureData* mutableData = const_cast<CreatureData*>(data);
|
||||
mutableData->wander_distance = distance;
|
||||
TC_LOG_DEBUG("araxia.changes", "[Araxia] Updated ObjectMgr cache for spawn " UI64FMTD " wander_distance to %.2f", spawnGuid, distance);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
result.message = "Wander distance updated successfully";
|
||||
|
||||
TC_LOG_INFO("araxia.changes", "[Araxia] SetWanderDistance: GUID " UI64FMTD " set to %.2f yards by %s",
|
||||
spawnGuid, distance, changedBy ? changedBy->GetName().c_str() : "SYSTEM");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
float CreatureWriter::GetWanderDistance(ObjectGuid::LowType spawnGuid)
|
||||
{
|
||||
CreatureData const* data = sObjectMgr->GetCreatureData(spawnGuid);
|
||||
if (data)
|
||||
return data->wander_distance;
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
WriteResult CreatureWriter::SetMovementType(ObjectGuid::LowType spawnGuid, uint8 movementType, Player* changedBy)
|
||||
{
|
||||
WriteResult result;
|
||||
result.success = false;
|
||||
result.changeId = 0;
|
||||
|
||||
// Validate movement type
|
||||
if (movementType > 2)
|
||||
{
|
||||
result.message = "Invalid movement type (must be 0=Idle, 1=Random, 2=Waypoint)";
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get current value for logging
|
||||
uint8 oldType = GetMovementType(spawnGuid);
|
||||
|
||||
// Update database
|
||||
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_CREATURE_MOVEMENT_TYPE);
|
||||
stmt->setUInt8(0, movementType);
|
||||
stmt->setUInt64(1, spawnGuid);
|
||||
WorldDatabase.Execute(stmt);
|
||||
|
||||
// Log the change
|
||||
LogChange("creature", "MovementType",
|
||||
std::to_string(oldType), std::to_string(movementType),
|
||||
spawnGuid, changedBy);
|
||||
|
||||
// CRITICAL: Also update the ObjectMgr cache so respawned creatures get the new value
|
||||
CreatureData const* data = sObjectMgr->GetCreatureData(spawnGuid);
|
||||
if (data)
|
||||
{
|
||||
CreatureData* mutableData = const_cast<CreatureData*>(data);
|
||||
mutableData->movementType = movementType;
|
||||
TC_LOG_DEBUG("araxia.changes", "[Araxia] Updated ObjectMgr cache for spawn " UI64FMTD " movementType to %u", spawnGuid, movementType);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
result.message = "Movement type updated successfully";
|
||||
|
||||
TC_LOG_INFO("araxia.changes", "[Araxia] SetMovementType: GUID " UI64FMTD " set to %u by %s",
|
||||
spawnGuid, movementType, changedBy ? changedBy->GetName().c_str() : "SYSTEM");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint8 CreatureWriter::GetMovementType(ObjectGuid::LowType spawnGuid)
|
||||
{
|
||||
CreatureData const* data = sObjectMgr->GetCreatureData(spawnGuid);
|
||||
if (data)
|
||||
return data->movementType;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace Araxia
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* AraxiaCreatureWriter.h
|
||||
*
|
||||
* Araxia Online - Custom Write Operations for Creatures
|
||||
*
|
||||
* This class wraps all database write operations for creatures.
|
||||
* All modifications to creature data should go through this class to enable:
|
||||
* - Centralized logging of changes
|
||||
* - Easy export of modifications
|
||||
* - Audit trail for content changes
|
||||
* - Future rollback capabilities
|
||||
*/
|
||||
|
||||
#ifndef ARAXIA_CREATURE_WRITER_H
|
||||
#define ARAXIA_CREATURE_WRITER_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include <string>
|
||||
|
||||
class Creature;
|
||||
class Player;
|
||||
|
||||
namespace Araxia
|
||||
{
|
||||
|
||||
// Result structure for write operations
|
||||
struct WriteResult
|
||||
{
|
||||
bool success;
|
||||
std::string message;
|
||||
uint64 changeId; // Future: unique ID for tracking changes
|
||||
};
|
||||
|
||||
class CreatureWriter
|
||||
{
|
||||
public:
|
||||
static CreatureWriter& Instance();
|
||||
|
||||
// Prevent copying
|
||||
CreatureWriter(CreatureWriter const&) = delete;
|
||||
CreatureWriter& operator=(CreatureWriter const&) = delete;
|
||||
|
||||
// ========================================================================
|
||||
// Movement Settings
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Set the wander distance for a creature spawn.
|
||||
* Only applies to creatures with MovementType = 1 (Random).
|
||||
*
|
||||
* @param spawnGuid The creature spawn GUID (from creature table)
|
||||
* @param distance The wander radius in yards
|
||||
* @param changedBy Optional player who made the change (for logging)
|
||||
* @return WriteResult with success status and any error message
|
||||
*/
|
||||
WriteResult SetWanderDistance(ObjectGuid::LowType spawnGuid, float distance, Player* changedBy = nullptr);
|
||||
|
||||
/**
|
||||
* Get the current wander distance for a creature spawn.
|
||||
*
|
||||
* @param spawnGuid The creature spawn GUID
|
||||
* @return The wander distance, or 0 if not found
|
||||
*/
|
||||
float GetWanderDistance(ObjectGuid::LowType spawnGuid);
|
||||
|
||||
/**
|
||||
* Set the movement type for a creature spawn.
|
||||
*
|
||||
* @param spawnGuid The creature spawn GUID
|
||||
* @param movementType 0=Idle, 1=Random, 2=Waypoint
|
||||
* @param changedBy Optional player who made the change
|
||||
* @return WriteResult with success status
|
||||
*/
|
||||
WriteResult SetMovementType(ObjectGuid::LowType spawnGuid, uint8 movementType, Player* changedBy = nullptr);
|
||||
|
||||
/**
|
||||
* Get the movement type for a creature spawn.
|
||||
*
|
||||
* @param spawnGuid The creature spawn GUID
|
||||
* @return Movement type (0=Idle, 1=Random, 2=Waypoint), or 0 if not found
|
||||
*/
|
||||
uint8 GetMovementType(ObjectGuid::LowType spawnGuid);
|
||||
|
||||
private:
|
||||
CreatureWriter();
|
||||
~CreatureWriter() = default;
|
||||
|
||||
// Log a change for future export capabilities
|
||||
void LogChange(std::string const& table, std::string const& field,
|
||||
std::string const& oldValue, std::string const& newValue,
|
||||
ObjectGuid::LowType affectedGuid, Player* changedBy);
|
||||
};
|
||||
|
||||
#define sAraxiaCreatureWriter Araxia::CreatureWriter::Instance()
|
||||
|
||||
} // namespace Araxia
|
||||
|
||||
#endif // ARAXIA_CREATURE_WRITER_H
|
||||
@@ -81,6 +81,9 @@ if(BUILD_SHARED_LIBS)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Add Araxia Online custom code
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/src/araxiaonline ${CMAKE_BINARY_DIR}/araxiaonline)
|
||||
|
||||
# Generate precompiled header
|
||||
if(USE_COREPCH)
|
||||
add_cxx_pch(game ${PRIVATE_PCH_HEADER})
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
#ifndef CREATUREMETHODS_H
|
||||
#define CREATUREMETHODS_H
|
||||
|
||||
// Araxia Online - Custom write operations
|
||||
#include "AraxiaCreatureWriter.h"
|
||||
#include "ElunaSharedData.h"
|
||||
|
||||
/***
|
||||
* Non-[Player] controlled [Unit]s (i.e. NPCs).
|
||||
*
|
||||
@@ -1627,6 +1631,108 @@ namespace LuaCreature
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a visual marker at the [Creature]'s spawn/home position.
|
||||
* The marker is a creature with the specified display ID (default: spawn point marker).
|
||||
*
|
||||
* @param [Player] phaseSource = nil : optional player to inherit phase from
|
||||
* @param uint32 displayId = 31366 : display ID for the marker (default is green circle)
|
||||
* @return Creature marker : the spawned marker creature, or nil on failure
|
||||
*/
|
||||
int ShowSpawnPointMarker(Eluna* E, Creature* creature)
|
||||
{
|
||||
float x, y, z, o;
|
||||
creature->GetHomePosition(x, y, z, o);
|
||||
|
||||
// Optional player parameter for phase inheritance (arg 2)
|
||||
Player* phaseSource = E->CHECKOBJ<Player>(2, false);
|
||||
|
||||
// Display ID - default to green circle (31366) or specified
|
||||
uint32 displayId = 31366; // Green targeting circle
|
||||
int displayIdArg = phaseSource ? 3 : 2;
|
||||
if (!lua_isnoneornil(E->L, displayIdArg))
|
||||
{
|
||||
displayId = E->CHECKVAL<uint32>(displayIdArg);
|
||||
}
|
||||
|
||||
// First, hide any existing spawn marker for this creature
|
||||
ObjectGuid::LowType spawnId = creature->GetSpawnId();
|
||||
std::string markerKey = "spawn_marker_" + std::to_string(spawnId);
|
||||
std::string existingCounter;
|
||||
if (sElunaSharedData->Get(markerKey, existingCounter))
|
||||
{
|
||||
try {
|
||||
ObjectGuid::LowType counter = std::stoull(existingCounter);
|
||||
ObjectGuid oldGuid = ObjectGuid::Create<HighGuid::Creature>(creature->GetMapId(), VISUAL_WAYPOINT, counter);
|
||||
if (Creature* oldMarker = ObjectAccessor::GetCreature(*creature, oldGuid))
|
||||
{
|
||||
oldMarker->DespawnOrUnsummon();
|
||||
}
|
||||
} catch (...) {
|
||||
// Ignore invalid stored GUID
|
||||
}
|
||||
sElunaSharedData->Clear(markerKey);
|
||||
}
|
||||
|
||||
// Use SummonCreature like waypoint visualization does (VISUAL_WAYPOINT = 1)
|
||||
TempSummon* marker = creature->SummonCreature(VISUAL_WAYPOINT, x, y, z, o);
|
||||
if (!marker)
|
||||
{
|
||||
E->Push();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Set custom display
|
||||
marker->SetDisplayId(displayId, true);
|
||||
marker->SetObjectScale(1.5f); // Make it visible
|
||||
|
||||
// Inherit phase from player if provided
|
||||
if (phaseSource)
|
||||
{
|
||||
PhasingHandler::InheritPhaseShift(marker, phaseSource);
|
||||
}
|
||||
|
||||
// Store marker's counter for later removal
|
||||
sElunaSharedData->Set(markerKey, std::to_string(marker->GetGUID().GetCounter()));
|
||||
|
||||
E->Push(marker);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides/removes the spawn point marker for this [Creature].
|
||||
*
|
||||
* @return bool success : true if marker was removed
|
||||
*/
|
||||
int HideSpawnPointMarker(Eluna* E, Creature* creature)
|
||||
{
|
||||
ObjectGuid::LowType spawnId = creature->GetSpawnId();
|
||||
std::string markerKey = "spawn_marker_" + std::to_string(spawnId);
|
||||
std::string counterStr;
|
||||
|
||||
if (!sElunaSharedData->Get(markerKey, counterStr))
|
||||
{
|
||||
E->Push(false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
ObjectGuid::LowType counter = std::stoull(counterStr);
|
||||
ObjectGuid markerGuid = ObjectGuid::Create<HighGuid::Creature>(creature->GetMapId(), VISUAL_WAYPOINT, counter);
|
||||
|
||||
if (Creature* marker = ObjectAccessor::GetCreature(*creature, markerGuid))
|
||||
{
|
||||
marker->DespawnOrUnsummon();
|
||||
}
|
||||
} catch (...) {
|
||||
// Ignore invalid stored GUID
|
||||
}
|
||||
|
||||
sElunaSharedData->Clear(markerKey);
|
||||
E->Push(true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a table containing the [Creature]'s waypoint path data.
|
||||
* Includes path info and all waypoint nodes.
|
||||
@@ -1820,6 +1926,74 @@ namespace LuaCreature
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Araxia Online - Database Write Operations
|
||||
// These methods persist changes to the database using AraxiaCreatureWriter
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* [Araxia] Saves the wander distance to the database for this creature spawn.
|
||||
* This persists the change across server restarts.
|
||||
* Only applies to creatures with MovementType = 1 (Random).
|
||||
*
|
||||
* @param float distance : the wander radius in yards (0-100)
|
||||
* @param [Player] changedBy : optional player who made the change (for logging)
|
||||
* @return bool success : true if the change was saved
|
||||
* @return string message : result message
|
||||
*/
|
||||
int SaveWanderDistance(Eluna* E, Creature* creature)
|
||||
{
|
||||
float distance = E->CHECKVAL<float>(2);
|
||||
Player* changedBy = E->CHECKOBJ<Player>(3, false);
|
||||
|
||||
ObjectGuid::LowType spawnId = creature->GetSpawnId();
|
||||
if (spawnId == 0)
|
||||
{
|
||||
E->Push(false);
|
||||
E->Push("Creature has no spawn ID (not a database spawn)");
|
||||
return 2;
|
||||
}
|
||||
|
||||
Araxia::WriteResult result = sAraxiaCreatureWriter.SetWanderDistance(spawnId, distance, changedBy);
|
||||
|
||||
// Also update the live creature's in-memory value
|
||||
if (result.success)
|
||||
creature->SetWanderDistance(distance);
|
||||
|
||||
E->Push(result.success);
|
||||
E->Push(result.message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Araxia] Saves the movement type to the database for this creature spawn.
|
||||
* This persists the change across server restarts.
|
||||
*
|
||||
* @param uint8 movementType : 0=Idle, 1=Random, 2=Waypoint
|
||||
* @param [Player] changedBy : optional player who made the change (for logging)
|
||||
* @return bool success : true if the change was saved
|
||||
* @return string message : result message
|
||||
*/
|
||||
int SaveMovementType(Eluna* E, Creature* creature)
|
||||
{
|
||||
uint8 movementType = E->CHECKVAL<uint8>(2);
|
||||
Player* changedBy = E->CHECKOBJ<Player>(3, false);
|
||||
|
||||
ObjectGuid::LowType spawnId = creature->GetSpawnId();
|
||||
if (spawnId == 0)
|
||||
{
|
||||
E->Push(false);
|
||||
E->Push("Creature has no spawn ID (not a database spawn)");
|
||||
return 2;
|
||||
}
|
||||
|
||||
Araxia::WriteResult result = sAraxiaCreatureWriter.SetMovementType(spawnId, movementType, changedBy);
|
||||
|
||||
E->Push(result.success);
|
||||
E->Push(result.message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
ElunaRegister<Creature> CreatureMethods[] =
|
||||
{
|
||||
// Getters
|
||||
@@ -1865,6 +2039,8 @@ namespace LuaCreature
|
||||
{ "GetWaypointPathData", &LuaCreature::GetWaypointPathData },
|
||||
{ "VisualizeWaypointPath", &LuaCreature::VisualizeWaypointPath },
|
||||
{ "DevisualizeWaypointPath", &LuaCreature::DevisualizeWaypointPath },
|
||||
{ "ShowSpawnPointMarker", &LuaCreature::ShowSpawnPointMarker },
|
||||
{ "HideSpawnPointMarker", &LuaCreature::HideSpawnPointMarker },
|
||||
|
||||
// Setters
|
||||
{ "SetRegeneratingHealth", &LuaCreature::SetRegeneratingHealth },
|
||||
@@ -1940,7 +2116,11 @@ namespace LuaCreature
|
||||
{ "ResetAllThreat", &LuaCreature::ResetAllThreat },
|
||||
{ "FixateTarget", &LuaCreature::FixateTarget },
|
||||
{ "ClearFixate", &LuaCreature::ClearFixate },
|
||||
{ "RemoveFromWorld", &LuaCreature::RemoveFromWorld }
|
||||
{ "RemoveFromWorld", &LuaCreature::RemoveFromWorld },
|
||||
|
||||
// Araxia Online - Database Write Operations
|
||||
{ "SaveWanderDistance", &LuaCreature::SaveWanderDistance },
|
||||
{ "SaveMovementType", &LuaCreature::SaveMovementType }
|
||||
};
|
||||
};
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user