• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

TFS 1.X+ onSpawn delay

Mjmackan

Mapper ~ Writer
Premium User
Joined
Jul 18, 2009
Messages
1,424
Solutions
15
Reaction score
177
Location
Sweden
I can't for the world figure this stuff out.

I'm trying to make a teleport effect occur 5 times (one each second) and then make the monster spawn(on the fifth teleport effect, so after 5 seconds).

This is what i came up with and well, its shit, but i want to prove i tried at least.
This wont work since its trigger isnt unique to one monster and therefor each and every monster will tick the count + 1.
Lua:
function Monster:onSpawn(position, startup, artificial)
print(count)

addEvent(function() position:sendMagicEffect(11) end, 1000*count)

if count == nil then
    local count = 0
end

if hasEventCallback(EVENT_CALLBACK_ONSPAWN) or count < 6 then
    count = count + 1
    return EventCallback(EVENT_CALLBACK_ONSPAWN, self, position, startup, artificial)
else
    count = 0
    return true
end
end

I also now know that I dont know how to return a function true outside a function, is this possible?
 
Solution
I tried to make a commit to github to show how i solved this but I can't make it work so therefor I'm gonna show partially how I solved it.
First i took help from this old commit: Updating Spawns.. · otland/forgottenserver@c02bafa (https://github.com/otland/forgottenserver/commit/c02bafa02fc63034cdda98bbf58325f6867c0184)
It may differ a bit from newer sources (did for me), but the functions are the same.

Then instead of adding the numbers inside the src, i created two conf.managers and added:
C++:
g_config.getNumber(ConfigManager::RATE_START_EFFECT))
C++:
g_config.getNumber(ConfigManager::RATE_SPAWN))

Which you have to register in configmanager.cpp, configmanager.h & luascript.cpp like this:
configmanager.cpp
C++:
    integer[RATE_BETWEEN_EFFECT]...
I don't think it can be done from within unless you make a full custom build system, however this trick may be enough for you:
data/scripts/spawninterval.lua
Lua:
local interval = 3 -- 3 ticks of base spawntime in spawn.xml
local intervals = {}
local function positionToString(pos)
    return string.format("%d%d%d", pos.x, pos.y, pos.z)
end

local ec = EventCallback

function ec.onSpawn(monster, position, startup, artificial)
    if not startup then
        local index = positionToString(position)
        local count = intervals[index] or 0
        if count >= interval then
            intervals[index] = nil
            return true
        end
        intervals[index] = count + 1
        position:sendMagicEffect(CONST_ME_TELEPORT)
        return false
    end
    return true
end

ec:register(-666)
interval does not mean seconds, you must take into account that the onSpawn event is executed every time the monster is tried to spawn, so everything depends on the respawn time that is defined in spawn.xml, by default it is always 60, and I think the minimum is 10, so if you set it to 10, and the script setting has interval 3 then the monster will spawn in 30 seconds
 
I don't think it can be done from within unless you make a full custom build system, however this trick may be enough for you:
data/scripts/spawninterval.lua
Lua:
local interval = 3 -- 3 ticks of base spawntime in spawn.xml
local intervals = {}
local function positionToString(pos)
    return string.format("%d%d%d", pos.x, pos.y, pos.z)
end

local ec = EventCallback

function ec.onSpawn(monster, position, startup, artificial)
    if not startup then
        local index = positionToString(position)
        local count = intervals[index] or 0
        if count >= interval then
            intervals[index] = nil
            return true
        end
        intervals[index] = count + 1
        position:sendMagicEffect(CONST_ME_TELEPORT)
        return false
    end
    return true
end

ec:register(-666)
interval does not mean seconds, you must take into account that the onSpawn event is executed every time the monster is tried to spawn, so everything depends on the respawn time that is defined in spawn.xml, by default it is always 60, and I think the minimum is 10, so if you set it to 10, and the script setting has interval 3 then the monster will spawn in 30 seconds
Thing is Im using 10 seconds for those creatures that exists in spawn and 60 seconds for monsters in quests. Maybe my only choice would be to redo that in spawn.xml or maybe as you said I need to build the full custom system but I'll study your idea first, thanks for taking ur time helping!
 
Its partially possible, however this should be done source side

Lua:
local interval = 5 -- spawn time (teleport effect)
local intervals = {}
local monsters = {}

local function positionToString(pos)
    return string.format("%d%d%d", pos.x, pos.y, pos.z)
end

local ec = EventCallback
function ec.onSpawn(creature, position, startup, artificial)

    if startup or artificial then
        return true
    end

    local index = positionToString(position)
    if not intervals[index] then
        intervals[index] = 1

        for i = 1, interval do
            addEvent(function(name)
                if i == interval then
                    local monster = Game.createMonster(name, position, true, true)
                    if not monster then
                        intervals[index] = nil
                        return false
                    end

                    monsters[monster:getId()] = index
                    monster:registerEvent("despawnMonster")
                end
                position:sendMagicEffect(CONST_ME_TELEPORT)
            end, i * 1000, creature:getName())
        end 
    end
    return false
end
ec:register(-666)

local despawnEvent = CreatureEvent("despawnMonster")
function despawnEvent.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified)
    intervals[monsters[creature:getId()]] = nil
    return true
end
despawnEvent:register()

If you use /r on a spawned monster, you will face memory leak and the spawn position will never work again
Solution: set intervals and monsters tables as global and do proper checks on /r command
 
Its partially possible, however this should be done source side

Lua:
local interval = 5 -- spawn time (teleport effect)
local intervals = {}
local monsters = {}

local function positionToString(pos)
    return string.format("%d%d%d", pos.x, pos.y, pos.z)
end

local ec = EventCallback
function ec.onSpawn(creature, position, startup, artificial)

    if startup or artificial then
        return true
    end

    local index = positionToString(position)
    if not intervals[index] then
        intervals[index] = 1

        for i = 1, interval do
            addEvent(function(name)
                if i == interval then
                    local monster = Game.createMonster(name, position, true, true)
                    if not monster then
                        intervals[index] = nil
                        return false
                    end

                    monsters[monster:getId()] = index
                    monster:registerEvent("despawnMonster")
                end
                position:sendMagicEffect(CONST_ME_TELEPORT)
            end, i * 1000, creature:getName())
        end
    end
    return false
end
ec:register(-666)

local despawnEvent = CreatureEvent("despawnMonster")
function despawnEvent.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified)
    intervals[monsters[creature:getId()]] = nil
    return true
end
despawnEvent:register()

If you use /r on a spawned monster, you will face memory leak and the spawn position will never work again
Solution: set intervals and monsters tables as global and do proper checks on /r command
I came up with something similar but as you said, server crashed when '/r' the mob.
I'm now trying to do it through sources instread and make it able to adjust the spawn delay through config.lua, but my c++ knowledge is lacking I've found out.
 
I tried to make a commit to github to show how i solved this but I can't make it work so therefor I'm gonna show partially how I solved it.
First i took help from this old commit: Updating Spawns.. · otland/forgottenserver@c02bafa (https://github.com/otland/forgottenserver/commit/c02bafa02fc63034cdda98bbf58325f6867c0184)
It may differ a bit from newer sources (did for me), but the functions are the same.

Then instead of adding the numbers inside the src, i created two conf.managers and added:
C++:
g_config.getNumber(ConfigManager::RATE_START_EFFECT))
C++:
g_config.getNumber(ConfigManager::RATE_SPAWN))

Which you have to register in configmanager.cpp, configmanager.h & luascript.cpp like this:
configmanager.cpp
C++:
    integer[RATE_BETWEEN_EFFECT] = getGlobalNumber(L, "timeBetweenTeleportEffects", 1400);
    integer[RATE_START_EFFECT] = getGlobalNumber(L, "timeStartEffect", 4200);
configmanager.h
C++:
            RATE_BETWEEN_EFFECT,
            RATE_START_EFFECT,
luascript.cpp
C++:
    registerEnumIn("configKeys", ConfigManager::RATE_BETWEEN_EFFECT)
    registerEnumIn("configKeys", ConfigManager::RATE_START_EFFECT)


You can then add these lines in config and reload config to adjust:
Lua:
timeBetweenTeleportEffects = 2000
timeStartEffect = 8000



Also this is how my spawn.cpp turned out incase it helps anyone:
C++:
void Spawn::checkSpawn()
{
    checkSpawnEvent = 0;

    cleanup();

    uint32_t spawnCount = 0;

    for (auto& it : spawnMap) {
        uint32_t spawnId = it.first;
        if (spawnedMap.find(spawnId) != spawnedMap.end()) {
            continue;
        }

        spawnBlock_t& sb = it.second;
        if (OTSYS_TIME() >= sb.lastSpawn + sb.interval) {

            scheduleSpawn(spawnId, sb, static_cast<uint32_t>(g_config.getNumber(ConfigManager::RATE_START_EFFECT)));

            if (++spawnCount >= static_cast<uint32_t>(g_config.getNumber(ConfigManager::RATE_SPAWN))) {
                break;
            }
        }
    }

    if (spawnedMap.size() < spawnMap.size()) {
        checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(getInterval(), std::bind(&Spawn::checkSpawn, this)));
    }
}

void Spawn::scheduleSpawn(uint32_t spawnId, spawnBlock_t sb, uint32_t interval)
{
    if (interval <= 0) {
        spawnMonster(spawnId, sb);
    } else {
        g_game.addMagicEffect(sb.pos, CONST_ME_TELEPORT);
        g_scheduler.addEvent(createSchedulerTask(static_cast<uint32_t>(g_config.getNumber(ConfigManager::RATE_BETWEEN_EFFECT)), std::bind(&Spawn::scheduleSpawn, this, spawnId, sb, interval - static_cast<uint32_t>(g_config.getNumber(ConfigManager::RATE_BETWEEN_EFFECT)))));
    }
}
 
Solution
Back
Top