Spawn mobs without respawn (and without 'role')

Daxter

New Pirate
Registered
LV
0
 
Joined
Jul 24, 2026
Messages
2
Reaction score
0
Points
1
I'm trying to design a custom lua spawning system where spawn coords are randomly picked from a list. Since the idea is that each 'respawn' rerolls for coords, I assume I can't rely on standard respawn mechanics and thus must handle them myself.

My first idea was to look into sand bag mechanics:

Code:
function ItemUse_ShaBao2(role , Item)
local Cha_Boat = 0
      Cha_Boat = GetCtrlBoat ( role )
    if Cha_Boat ~=  nil then
        SystemNotice( role , "Cannot use while sailing" )
        UseItemFailed ( role )
        return
    end
    local reg = 0
          reg =IsChaInRegion( role, 2 )
    if reg == 1 then
        SystemNotice( role , "Training in Safe Zone? Dream on!" )
        UseItemFailed ( role )
                return
    end
            local x, y = GetChaPos(role)
        local x_move=5
        local y_move=5
        x=x_move+x
        y=y_move+y
        local MonsterID = 938
                local Refresh = 1900000                       
        local life = 1800000                       
        local new = CreateChaX( MonsterID , x , y , 145 , Refresh,role )
        SetChaLifeTime( new, life )
end

Which I tried to replicate in garnermonster_conf.lua:

Code:
for index, spawner in ipairs(GlobalRandomSpawns["garner"]) do
    if spawner.Enabled then
        
        -- Create a temporary table to remember which spots this spawner has used
        local used_spots = {}
        
        for i = 1, spawner.quantity do
            -- Pass the used_spots table
            local rx, ry, picked_idx = GetRandomLocation(spawner.locations, used_spots)
            
            -- Mark this index as used so the next in the loop can't pick it
            used_spots[picked_idx] = true
            
            local final_x = rx --+ math.random(-150, 150)
            local final_y = ry 

            local Refresh = 1900000                       
            local life = 1800000                       

            --local new = CreateCha(spawner.MonsterID, final_x, final_y, spawner.angle, Refresh)
            local new = CreateChaX( spawner.MonsterID, final_x, final_y, spawner.angle, Refresh,nil)
            SetChaLifeTime( new, life )
            print("Spawned MonsterID: " .. spawner.MonsterID .. " at (" .. final_x .. ", " .. final_y .. ")")
        end
    end
end

Which doesn't throw any error (and I even the the message on the gamerserver console window "confirming" the spawn), but when I enter the game, no monster is actually spawned at those coordinates. I assume it's because I'm using
Code:
nil
as the 'role' value, but since the spawn occurs at map level, there's no 'role' context for me to use.

Ideas?
 
In case someone wants to test on their side:

(garner) conf.lua

Code:
function map_run_garner( map )
    local current_time = os.time()
   
    -- Safety check in case the queue hasn't been initialized
    if GlobalRespawnQueue == nil then return end
   
    local queue_size = table.getn(GlobalRespawnQueue)
   
    -- Loop backwards through the queue
    for i = queue_size, 1, -1 do
        local task = GlobalRespawnQueue[i]
       
        if task.map == "garner" and current_time >= task.respawn_time then
            local spawner = GlobalRandomSpawns["garner"][task.index]
           
            if spawner and spawner.Enabled then
                local rx, ry = GetRandomLocation(spawner.locations)
                local final_x = rx
                local final_y = ry
               
                local Refresh = 1900000                    
                local life = 1800000                       

                local new = CreateChaX( spawner.MonsterID, final_x, final_y, spawner.angle, Refresh)
                SetChaLifeTime( new, life )
                print("SUCCESS: Respawned MonsterID: " .. spawner.MonsterID .. " at (" .. final_x .. ", " .. final_y .. ")")
            end
           
            table.remove(GlobalRespawnQueue, i)
        end
    end
end

garnermonster_conf.lua
Code:
if GlobalRandomSpawns == nil then GlobalRandomSpawns = {} end
if GlobalRespawnQueue == nil then GlobalRespawnQueue = {} end
GlobalRandomSpawns["garner"] = {
    {
        MonsterID = 694, -- replace with duplicated ID!
        Enabled = true,
        quantity = 1,
        RespawnTime = 30,
        angle = 275,
        locations = {
            {x = 189500, y = 277600, weight = 1}, -- weight can be used for relative chance vs other locations
            {x = 190600, y = 277300, weight = 1},
            {x = 190000, y = 276600, weight = 1},
        }
    }
}
math.randomseed(os.time())
function GetRandomLocation(locations, used_indices)
    -- safety line! If used_indices is nil, make it an empty table.
    used_indices = used_indices or {}
   
    local totalWeight = 0
    local available_locs = {}
    -- Step 1: Filter out locations we've already spawned a monster at
    for i, loc in ipairs(locations) do
        if not used_indices[i] then
            totalWeight = totalWeight + (loc.weight or 1)
            -- Store both the location data and its original index
            table.insert(available_locs, {index = i, data = loc})
        end
    end
    -- Failsafe: If we want to spawn 4 monsters but only gave 3 coordinates,
    -- it will reset and allow overlapping rather than crashing.
    if totalWeight == 0 then
        return locations[1].x, locations[1].y, 1
    end
    -- Step 2: Roll a random number from the remaining available spots
    local randomVal = math.random(1, totalWeight)
    local currentWeight = 0
    -- Step 3: Find which available location "won"
    for _, item in ipairs(available_locs) do
        currentWeight = currentWeight + (item.data.weight or 1)
        if randomVal <= currentWeight then
            -- Return the X, Y, and the Index so we can mark it as used
            return item.data.x, item.data.y, item.index
        end
    end
   
    return available_locs[1].data.x, available_locs[1].data.y, available_locs[1].index
end
-- Initial spawn loop when the map boots up
for index, spawner in ipairs(GlobalRandomSpawns["garner"]) do
    if spawner.Enabled then
       
        -- Create a temporary table to remember which spots this spawner has used
        local used_spots = {}
       
        for i = 1, spawner.quantity do
            -- Pass the used_spots table into our function
            local rx, ry, picked_idx = GetRandomLocation(spawner.locations, used_spots)
           
            -- Mark this index as used so the next monster in the loop can't pick it
            used_spots[picked_idx] = true
           
            -- MMO Trick: Add a tiny random scatter (-150 to +150) so they don't look robotic
            -- (Remove the math.random addition if you want them on the EXACT pixel)
            local final_x = rx --+ math.random(-150, 150)
            local final_y = ry --+ math.random(-150, 150)
            local Refresh = 1900000                    
            local life = 1800000                       
            --local new = CreateCha(spawner.MonsterID, final_x, final_y, spawner.angle, Refresh)
            local new = CreateChaX( spawner.MonsterID, final_x, final_y, spawner.angle, Refresh)
            SetChaLifeTime( new, life )
            print("Spawned MonsterID: " .. spawner.MonsterID .. " at (" .. final_x .. ", " .. final_y .. ")")
        end
    end
end

exp_and_level.lua
Code:
GlobalRandomSpawns = {}
GlobalRespawnQueue = {}
-- The math helper for coordinates
function GetRandomLocation(locations)
    local totalWeight = 0
    for _, loc in ipairs(locations) do totalWeight = totalWeight + (loc.weight or 1) end
    local randomVal = math.random(1, totalWeight)
    local currentWeight = 0
    for _, loc in ipairs(locations) do
        currentWeight = currentWeight + (loc.weight or 1)
        if randomVal <= currentWeight then return loc.x, loc.y end
    end
    return locations[1].x, locations[1].y
end
function GetExp_PKM( dead , atk  )
    local MonsterID = GetChaID(dead)
    local map_name = GetChaMapName(dead)
    local map_spawns = GlobalRandomSpawns[map_name]
  
    -- If this map has random spawns configured, check if the dead monster matches one
    if map_spawns then
        for index, spawner in ipairs(map_spawns) do
            if spawner.MonsterID == MonsterID and spawner.Enabled then
                -- Add to the global waiting list with a future timestamp
                table.insert(GlobalRespawnQueue, {
                    map = map_name,
                    index = index,
                    respawn_time = os.time() + spawner.RespawnTime
                })
                print("MonsterID: " .. MonsterID .. " defeated. Scheduled respawn in " .. spawner.RespawnTime .. " seconds.")
                break -- We found it, stop looping
            end
        end
    end
(... remaining function)