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:
2025-11-30 18:16:10 -05:00
parent 090f5cbd5f
commit b18b7540e0
7 changed files with 1316 additions and 1 deletions

View File

@@ -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)

View File

@@ -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")

View 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")

View 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

View File

@@ -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

View File

@@ -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})

View File

@@ -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