• 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!
  • 2026 staff recruitment is open! Check it out and consider applying!

Lua [TFS 1.4.2] Spawn Issue

vpshost99

Banned User
Joined
Nov 29, 2021
Messages
30
Reaction score
5
I Have Problem In Respawn Monster its take alot time not 50 seconds and sometime respawn not working in cyclops etc someone have idea?
 
Spawnrate = spawn interval + amount of creatures in spawn area if I remember correctly. So if you do a 30x30 area and you have 20 monsters inside the area with 60s interval for spawn time. A single monster spawns in the 60 seconds and it makes all others wait again for 60s. They don't all spawn at the same time after 60s if they are all dead. I may be wrong, but I think I saw that before. I always make spawns smaller and keep only a couple monsters per spawn.
 
Spawnrate = spawn interval + amount of creatures in spawn area if I remember correctly. So if you do a 30x30 area and you have 20 monsters inside the area with 60s interval for spawn time. A single monster spawns in the 60 seconds and it makes all others wait again for 60s. They don't all spawn at the same time after 60s if they are all dead. I may be wrong, but I think I saw that before. I always make spawns smaller and keep only a couple monsters per spawn.
so how to fix ? xd
Post automatically merged:

so how to fix ? xd
i mean fix respawn monsters in src time to respawn like 5 second or 10 second etc
 
so how to fix ? xd
Post automatically merged:


i mean fix respawn monsters in src
If you have access to source code you can change it so it will spawn them all if they are dead instead of just 1.. Otherwise decrease the size of spawns and only add a few monsters. You could also set spawn time to 1s which will give you behavior you don't really want but at least the monsters will spawn faster.

If you would like I can take a look at the source code if you are not comfortable doing so.
 
LUA:
/**
 * The Violet Project - a free and open-source MMORPG server emulator
 * Copyright (C) 2021 - Ezzz <[email protected]>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "otpch.h"

#include "spawn.h"
#include "game.h"
#include "monster.h"
#include "configmanager.h"
#include "scheduler.h"

#include "pugicast.h"
#include "events.h"

extern ConfigManager g_config;
extern Monsters g_monsters;
extern Game g_game;
extern Events* g_events;

static constexpr int32_t MINSPAWN_INTERVAL = 10 * 1000; // 10 seconds to match RME
static constexpr int32_t MAXSPAWN_INTERVAL = 24 * 60 * 60 * 1000; // 1 day

int32_t Spawns::calculateSpawnDelay(int32_t delay)
{
    int32_t newDelay = delay;
    int32_t onlineCount = g_game.getPlayersOnline();
    if (onlineCount <= 800) {
        if (onlineCount > 200) {
            newDelay = 200 * newDelay / (onlineCount / 2 + 100);
        }
    } else {
        newDelay = 2 * newDelay / 5;
    }

    int32_t spawnRate = g_config.getNumber(ConfigManager::RATE_SPAWN);
    if (spawnRate != 0) {
        // minimum of 40 s respawn time
        newDelay = std::max<int32_t>(40000, newDelay * 100 / spawnRate);
    }

    return uniform_random(newDelay / 2, newDelay);
}

bool Spawns::loadFromXml(const std::string& filename)
{
    if (loaded) {
        return true;
    }

    pugi::xml_document doc;
    pugi::xml_parse_result result = doc.load_file(filename.c_str());
    if (!result) {
        printXMLError("Error - Spawns::loadFromXml", filename, result);
        return false;
    }

    this->filename = filename;
    loaded = true;

    for (auto spawnNode : doc.child("spawns").children()) {
        Position centerPos(
            pugi::cast<uint16_t>(spawnNode.attribute("centerx").value()),
            pugi::cast<uint16_t>(spawnNode.attribute("centery").value()),
            pugi::cast<uint16_t>(spawnNode.attribute("centerz").value())
        );

        int32_t radius;
        pugi::xml_attribute radiusAttribute = spawnNode.attribute("radius");
        if (radiusAttribute) {
            radius = pugi::cast<int32_t>(radiusAttribute.value());
        } else {
            radius = -1;
        }

        Direction dir;

        pugi::xml_attribute directionAttribute = spawnNode.attribute("direction");
        if (directionAttribute) {
            dir = static_cast<Direction>(pugi::cast<uint16_t>(directionAttribute.value()));
        } else {
            dir = DIRECTION_NORTH;
        }

        pugi::xml_attribute amountAttribute = spawnNode.attribute("amount");
        if (amountAttribute) { // TVP Spawn system
            TvpSpawn* spawn = new TvpSpawn(centerPos, radius);
            tvpSpawnList.push_front(spawn);

            bool isNpc = false;
            std::string name;

            if (pugi::xml_attribute monsterName = spawnNode.attribute("monstername")) {
                name = monsterName.as_string();
            } else if (pugi::xml_attribute npcName = spawnNode.attribute("npcname")) {
                name = npcName.as_string();
                isNpc = true;
            }

            // tvp spawn system only supports one monsterhome per spawn
            if (!isNpc) {
                spawn->addMonster(name, centerPos, dir,
                    static_cast<uint32_t>(spawnNode.attribute("spawntime").as_int() * 1000),
                    static_cast<uint32_t>(spawnNode.attribute("amount").as_int()));
            } else {
                Npc* npc = Npc::createNpc(name);
                if (!npc) {
                    continue;
                }

                if (directionAttribute) {
                    npc->setDirection(static_cast<Direction>(pugi::cast<uint16_t>(directionAttribute.value())));
                }

                npc->setMasterPos(centerPos, radius);
                npcList.push_front(npc);
            }
        } else { // TFS Spawn system

            if (!spawnNode.first_child()) {
                std::cout << "[Warning - Spawns::loadFromXml] Empty spawn at position: " << centerPos << " with radius: " << radius << '.' << std::endl;
                continue;
            }

            Spawn* spawn = new Spawn(centerPos, radius);
            spawnList.push_front(spawn);

            for (auto childNode : spawnNode.children()) {
                if (strcasecmp(childNode.name(), "monster") == 0) {
                    pugi::xml_attribute nameAttribute = childNode.attribute("name");
                    if (!nameAttribute) {
                        continue;
                    }

                    Position pos(
                        centerPos.x + pugi::cast<uint16_t>(childNode.attribute("x").value()),
                        centerPos.y + pugi::cast<uint16_t>(childNode.attribute("y").value()),
                        centerPos.z
                    );
                    int32_t interval = pugi::cast<int32_t>(childNode.attribute("spawntime").value()) * 1000;
                    if (interval >= MINSPAWN_INTERVAL && interval <= MAXSPAWN_INTERVAL) {
                        spawn->addMonster(nameAttribute.as_string(), pos, dir, static_cast<uint32_t>(interval), 0);
                    } else {
                        if (interval < MINSPAWN_INTERVAL) {
                            std::cout << "[Warning - Spawns::loadFromXml] " << nameAttribute.as_string() << ' ' << pos << " spawntime can not be less than " << MINSPAWN_INTERVAL / 1000 << " seconds." << std::endl;
                        } else {
                            std::cout << "[Warning - Spawns::loadFromXml] " << nameAttribute.as_string() << ' ' << pos << " spawntime can not be more than " << MAXSPAWN_INTERVAL / 1000 << " seconds." << std::endl;
                        }
                    }
                } else if (strcasecmp(childNode.name(), "npc") == 0) {
                    pugi::xml_attribute nameAttribute = childNode.attribute("name");
                    if (!nameAttribute) {
                        continue;
                    }

                    Npc* npc = Npc::createNpc(nameAttribute.as_string());
                    if (!npc) {
                        continue;
                    }

                    if (directionAttribute) {
                        npc->setDirection(static_cast<Direction>(pugi::cast<uint16_t>(directionAttribute.value())));
                    }

                    npc->setMasterPos(Position(
                        centerPos.x + pugi::cast<uint16_t>(childNode.attribute("x").value()),
                        centerPos.y + pugi::cast<uint16_t>(childNode.attribute("y").value()),
                        centerPos.z
                    ), radius);
                    npcList.push_front(npc);
                }
            }
        }
    }

    return true;
}

void Spawns::startup()
{
    if (!loaded || isStarted()) {
        return;
    }

    for (Npc* npc : npcList) {
        if (!g_game.placeCreature(npc, npc->getMasterPos(), true)) {
            std::cout << "[Warning - Spawns::startup] Couldn't spawn npc \"" << npc->getName() << "\" on position: " << npc->getMasterPos() << '.' << std::endl;
            delete npc;
        }
    }
    npcList.clear();

    if (g_config.getBoolean(ConfigManager::DISABLE_MONSTER_SPAWNS)) {
        return;
    }

    for (BaseSpawn* spawn : spawnList) {
        spawn->startup();
    }

    for (BaseSpawn* spawn : tvpSpawnList) {
        spawn->startup();
    }

    started = true;
}

void Spawns::clear()
{
    for (BaseSpawn* spawn : spawnList) {
        spawn->stopSpawnCheck();
    }
    spawnList.clear();

    for (BaseSpawn* spawn : tvpSpawnList) {
        spawn->stopSpawnCheck();
    }
    tvpSpawnList.clear();

    loaded = false;
    started = false;
    filename.clear();
}

bool Spawns::isInZone(const Position& centerPos, int32_t radius, const Position& pos)
{
    if (radius == -1) {
        return true;
    }

    return ((pos.getX() >= centerPos.getX() - radius) && (pos.getX() <= centerPos.getX() + radius) &&
            (pos.getY() >= centerPos.getY() - radius) && (pos.getY() <= centerPos.getY() + radius));
}

void Spawn::startup()
{
    for (auto& it : spawnMap) {
        spawnBlock_t& sb = it;
        spawnMonster(sb, true);
    }
}

void Spawn::addMonster(const std::string& name, const Position& pos, Direction& dir, uint32_t interval, uint32_t)
{
    spawnBlock_t sb;
    sb.mType = g_monsters.getMonsterType(name);
    if (!sb.mType) {
        std::cout << "Warning - [Spawn::addMonster] Could not find monster with name " << name << std::endl;
        return;
    }

    sb.direction = dir;
    sb.pos = pos;
    sb.interval = interval;
    spawnMap.push_back(sb);
}

void Spawn::checkSpawn()
{
    checkSpawnEvent = 0;

    if (activeMonsters >= spawnMap.size()) {
        // no need to respawn anymore monsters
        return;
    }

    for (auto& it : spawnMap) {
        spawnBlock_t& sb = it;
        if (OTSYS_TIME() >= sb.nextSpawnTime) {
            if (!isPlayerAround(sb.pos)) {
                // we do not care if we successfully spawned the monster or not
                spawnMonster(sb);

                sb.nextSpawnTime = OTSYS_TIME() + Spawns::calculateSpawnDelay(sb.interval);

                if (activeMonsters >= spawnMap.size()) {
                    break;
                }
            }
        }
    }

    checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(SPAWN_CHECK_INTERVAL, std::bind(&Spawn::checkSpawn, this)));
}

bool searchSpawnPosition(const Position& pos, Position& spawnPos)
{
    int32_t d = 0;
    int32_t c = 0;
    int32_t s = 1;
    uint16_t x = pos.x;
    uint16_t y = pos.y;
    int32_t totalNonSpawnableTiles = 0;

    for (int k = 1; k <= (64 - 1) && totalNonSpawnableTiles < 64; k++) {
        for (int j = 0; j < (k < (64 - 1) ? 2 : 3) && totalNonSpawnableTiles < 64; j++) {
            for (int i = 0; i < s && totalNonSpawnableTiles < 64; i++) {
                Tile* tile = g_game.map.getTile(x, y, pos.z);
                if (tile && tile->getGround()) {
                    if (tile->getCreatureCount() > 0 || tile->hasFlag(TILESTATE_PROTECTIONZONE) || tile->hasFlag(TILESTATE_BLOCKSOLID) || tile->hasFlag(TILESTATE_BLOCKPATH)) {
                        // cannot spawn in this tile
                        c++;
                        switch (d) {
                        case 3: y = y + 1; break;
                        case 0: x = x + 1; break;
                        case 1: y = y - 1; break;
                        case 2: x = x - 1; break;
                        }

                        if (tile->getCreatureCount() == 0 && !tile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID) && !tile->hasFlag(TILESTATE_PROTECTIONZONE)) {
                            spawnPos.x = x;
                            spawnPos.y = y;
                            spawnPos.z = pos.z;
                        }
                        totalNonSpawnableTiles++;
                        continue;
                    }

                    spawnPos.x = x;
                    spawnPos.y = y;
                    spawnPos.z = pos.z;
                    return true;
                }

                c++;
                switch (d) {
                    case 3: y = y + 1; break;
                    case 0: x = x + 1; break;
                    case 1: y = y - 1; break;
                    case 2: x = x - 1; break;
                }
            }

            d = (d + 1) % 4;
        }

        s = s + 1;
    }

    return false;
}

void TvpSpawn::startup()
{
    if (monsterSpawn.amount == 1) {
        spawnMonster(monsterSpawn.mType, monsterSpawn.pos, monsterSpawn.direction, monsterSpawn.interval, true);
    } else {
        for (uint32_t i = 1; i <= monsterSpawn.amount; i++) {
            Position pos = monsterSpawn.pos;
            if (g_game.searchSpawnField(pos.x, pos.y, pos.z, radius)) {
                g_game.searchFreeField(nullptr, pos.x, pos.y, pos.z, 2, false, false);
                spawnMonster(monsterSpawn.mType, pos, monsterSpawn.direction, monsterSpawn.interval, true);
            }
        }
    }
}

void TvpSpawn::addMonster(const std::string& name, const Position& pos, Direction& dir, uint32_t interval, uint32_t amount)
{
    spawnBlock_t sb;
    sb.mType = g_monsters.getMonsterType(name);
    if (!sb.mType) {
        std::cout << "Warning - [Spawn::addMonster] Could not find monster with name " << name << std::endl;
        return;
    }

    sb.direction = dir;
    sb.pos = pos;
    sb.interval = interval;
    sb.amount = amount;
    monsterSpawn = sb;
}

void TvpSpawn::checkSpawn()
{
    checkSpawnEvent = 0;

    if (activeMonsters >= monsterSpawn.amount) {
        // no need to respawn anymore monsters
        return;
    }

    if (OTSYS_TIME() >= monsterSpawn.nextSpawnTime) {
        if (!isPlayerAround(monsterSpawn.pos)) {
            if (monsterSpawn.amount == 1) {
                spawnMonster(monsterSpawn.mType, monsterSpawn.pos, monsterSpawn.direction, monsterSpawn.interval, true);
            } else {
                Position pos = monsterSpawn.pos;
                if (searchSpawnPosition(pos, pos)) {
                    spawnMonster(monsterSpawn.mType, pos, monsterSpawn.direction, monsterSpawn.interval, true);
                } else {
                    Position urgentPos = pos;
                    if (g_game.searchLoginField(nullptr, urgentPos.x, urgentPos.y, urgentPos.z, 1, true, false)) {
                        spawnMonster(monsterSpawn.mType, urgentPos, monsterSpawn.direction, monsterSpawn.interval, true);
                    }
                }
            }
            monsterSpawn.nextSpawnTime = OTSYS_TIME() + Spawns::calculateSpawnDelay(monsterSpawn.interval);
        }
    }

    checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(SPAWN_CHECK_INTERVAL, std::bind(&TvpSpawn::checkSpawn, this)));
}

void BaseSpawn::startSpawnCheck(uint32_t interval)
{
    if (checkSpawnEvent == 0) {
        checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(Spawns::calculateSpawnDelay(interval), std::bind(&BaseSpawn::checkSpawn, this)));
    }
}

void BaseSpawn::stopSpawnCheck()
{
    if (checkSpawnEvent != 0) {
        g_scheduler.stopEvent(checkSpawnEvent);
        checkSpawnEvent = 0;
    }
}

bool BaseSpawn::isPlayerAround(const Position& pos)
{
    SpectatorVec spectators;
    g_game.map.getSpectators(spectators, pos, true, true, Map::maxSpawnViewportX, Map::maxSpawnViewportX, Map::maxSpawnViewportY, Map::maxSpawnViewportY);
    for (Creature* spectator : spectators) {
        if (spectator->canSee(pos) && !spectator->getPlayer()->hasFlag(PlayerFlag_IgnoredByMonsters)) {
            return true;
        }
    }
    return false;
}

bool BaseSpawn::spawnMonster(spawnBlock_t& sb, bool startup)
{
    bool isBlocked = !startup && isPlayerAround(sb.pos);
    if (isBlocked && !sb.mType->info.isIgnoringSpawnBlock) {
        return false;
    }

    return spawnMonster(sb.mType, sb.pos, sb.direction, sb.interval, startup);
}

bool BaseSpawn::spawnMonster(MonsterType* mType, const Position& pos, Direction dir, uint32_t interval, bool forceSpawn)
{
    std::unique_ptr<Monster> monster_ptr(new Monster(mType, nullptr, forceSpawn, pos));

    if (forceSpawn) {
        if (g_game.getGameState() <= GAME_STATE_CLOSED) {
            if (!g_game.placeCreature(monster_ptr.get(), pos, true)) {
                std::cout << "[Warning - BaseSpawn::spawnMonster] Couldn't spawn monster \"" << monster_ptr->getName() << "\" on position: " << pos << '.' << std::endl;
                return false;
            }
        } else {
            if (!g_game.internalPlaceCreature(monster_ptr.get(), pos, true)) {
                std::cout << "[Warning - BaseSpawn::spawnMonster] Couldn't spawn monster \"" << monster_ptr->getName() << "\" on position: " << pos << '.' << std::endl;
                return false;
            }
        }
    } else {
        if (!g_game.placeCreature(monster_ptr.get(), pos, forceSpawn)) {
            return false;
        }
    }

    Monster* monster = monster_ptr.release();

    monster->setDirection(dir);
    monster->setSpawn(this);
    monster->setMasterPos(pos);
    monster->setSpawnInterval(interval);
    increaseMonsterCount();
    return true;
}
 
Did you modify this?

C++:
int32_t Spawns::calculateSpawnDelay(int32_t delay)
no !
Post automatically merged:

LUA:
extern ConfigManager g_config;
extern Monsters g_monsters;
extern Game g_game;
extern Events* g_events;

static constexpr int32_t MINSPAWN_INTERVAL = 10 * 1000; // 10 seconds to match RME
static constexpr int32_t MAXSPAWN_INTERVAL = 24 * 60 * 60 * 1000; // 1 day

int32_t Spawns::calculateSpawnDelay(int32_t delay)
{
    int32_t newDelay = delay;
    int32_t onlineCount = g_game.getPlayersOnline();
    if (onlineCount <= 800) {
        if (onlineCount > 200) {
            newDelay = 200 * newDelay / (onlineCount / 2 + 100);
        }
    } else {
        newDelay = 2 * newDelay / 5;
    }
Post automatically merged:

Did you modify this?

C++:
int32_t Spawns::calculateSpawnDelay(int32_t delay)
delay seconds monsters from here?
 
You should try just changing it to this. See how it changes. If it is still too slow

C++:
int32_t Spawns::calculateSpawnDelay(int32_t delay)
{
    int32_t spawnRate = g_config.getNumber(ConfigManager::RATE_SPAWN);
    int32_t newDelay = std::round(delay / spawnRate);
    if (newDelay <= 0) {
        newDelay = 40000;
    }
   
    // This line will show you the default number for the spawn. Post this on Otland
    std::cout << "Old Delay: " << delay << "New Delay: " << newDelay << std::endl;
    return newDelay;
}

The code will print things to your console. Post it here. I am not exactly sure what the numbers are so if I can see them I can get it set up in a better way.
 
You should try just changing it to this. See how it changes. If it is still too slow

C++:
int32_t Spawns::calculateSpawnDelay(int32_t delay)[/LEFT]
{
[LEFT]    int32_t spawnRate = g_config.getNumber(ConfigManager::RATE_SPAWN);
    int32_t newDelay = std::round(delay / spawnRate);
    if (newDelay <= 0) {
        newDelay = 40000;[/LEFT]
    }
  
[LEFT]    // This line will show you the default number for the spawn. Post this on Otland
    std::cout << "Old Delay: " << delay << "New Delay: " << newDelay << std::endl;
    return newDelay;[/LEFT]
}
[LEFT]

The code will print things to your console. Post it here. I am not exactly sure what the numbers are so if I can see them I can get it set up in a better way.​
do
You should try just changing it to this. See how it changes. If it is still too slow

C++:
int32_t Spawns::calculateSpawnDelay(int32_t delay)[/LEFT]
{
[LEFT]    int32_t spawnRate = g_config.getNumber(ConfigManager::RATE_SPAWN);
    int32_t newDelay = std::round(delay / spawnRate);
    if (newDelay <= 0) {
        newDelay = 40000;[/LEFT]
    }
  
[LEFT]    // This line will show you the default number for the spawn. Post this on Otland
    std::cout << "Old Delay: " << delay << "New Delay: " << newDelay << std::endl;
    return newDelay;[/LEFT]
}
[LEFT]

The code will print things to your console. Post it here. I am not exactly sure what the numbers are so if I can see them I can get it set up in a better way.​
i added i will re compile then test - do u have discord ?
 
I always make spawns smaller and keep only a couple monsters per spawn.
Yes. If you want fast spawns, make all spawns smaller (the best: 1 monster per spawn).
You can set spawnRate to some super high value like 50 - it won't spawn more monsters than there are defined in map file (spawns.xml / RME). Then it will be able to spawn most of killed monsters at once, but it will be still worse than small spawns.
 
Fix code

C++:
int32_t Spawns::calculateSpawnDelay(int32_t delay) {
    int32_t newDelay = delay;
    int32_t onlineCount = g_game.getPlayersOnline();

    if (onlineCount <= 800) {
        if (onlineCount > 200) {
            newDelay = 200 * newDelay / (onlineCount / 2 + 100);
        }
    } else {
        newDelay = 2 * newDelay / 5;
    }

    int32_t spawnRate = g_config.getNumber(ConfigManager::RATE_SPAWN);
    if (spawnRate != 0) {
        // mínimo de 40 s respawn por spawnRate
        newDelay = std::max<int32_t>(40000, newDelay * 100 / spawnRate);
    }

    // Forzar límites absolutos definidos
    newDelay = std::max<int32_t>(MINSPAWN_INTERVAL, std::min<int32_t>(newDelay, MAXSPAWN_INTERVAL));

    return uniform_random(newDelay / 2, newDelay);
}
 
Yes. If you want fast spawns, make all spawns smaller (the best: 1 monster per spawn).
You can set spawnRate to some super high value like 50 - it won't spawn more monsters than there are defined in map file (spawns.xml / RME). Then it will be able to spawn most of killed monsters at once, but it will be still worse than small spawns.
spawnrate 50 from config ?
Post automatically merged:

Yes. If you want fast spawns, make all spawns smaller (the best: 1 monster per spawn).
You can set spawnRate to some super high value like 50 - it won't spawn more monsters than there are defined in map file (spawns.xml / RME). Then it will be able to spawn most of killed monsters at once, but it will be still worse than small spawns.
i do it in config.lua spawnrate = 50 and in RME 60S
Post automatically merged:

Screenshot_2.webp
 
Back
Top