• 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!

Monster instant respawn - not desirable

Feleron

New Member
Joined
Aug 19, 2025
Messages
4
Reaction score
1
Hello

Monsters instantly re spawn if I kill them and go up/down the level. same thing is if I go bit out of their spawn location and come back. have no idea how to fix that. I was playing with config.lua, global-spawn.xml, spawn.h & spawn.cpp files. I have a feeling that I'm looking at wrong place. Could someone direct me what could be the problem? Thanks
 
Hello, we are not psychics...

You need to provide either the server engine, or post here anything you've modified, i.e spawn.cpp, world spawns xml file.
 
Hello
sure. I though it could be something obvious.

changes for global-spawn.xml, spawn.h & spawn.cpp I have reverted back as they seems to have no effect on spawning monsters.
I presume there is different trigger that spawn monsters. I'm thinking it could be something like monsters are spawned as player approach location so they are not there to save resources.
I'm using server engine from [8.60] [TFS 1.2] -[Real Map full] with mount & market system. Haven't modify anything except exp rates and MySQL details in config.lua
 
Last edited:
Config.lua
-- Combat settings
-- NOTE: valid values for worldType are: "pvp", "no-pvp" and "pvp-enforced"
worldType = "pvp"
hotkeyAimbotEnabled = true
protectionLevel = 1
killsToRedSkull = 15
pzLocked = 60000
removeChargesFromRunes = true
removeWeaponAmmunition = true
timeToDecreaseFrags = 24 * 60 * 60 * 1000
whiteSkullTime = 15 * 60 * 1000
stairJumpExhaustion = 800
experienceByKillingPlayers = true
expFromPlayersLevelRange = 75
allowFightBack = "false"

accountManager = true
namelockManager = false
newPlayerChooseVoc = true
newPlayerSpawnPosX = 331
newPlayerSpawnPosY = 46
newPlayerSpawnPosZ = 6
newPlayerTownId = 1
newPlayerLevel = 1
newPlayerMagicLevel = 0
generateAccountNumber = false
generateAccountSalt = false

-- Connection Config
-- NOTE: maxPlayers set to 0 means no limit
ip = "127.0.0.1"
bindOnlyGlobalAddress = false
loginProtocolPort = 7171
gameProtocolPort = 7172
statusProtocolPort = 7171
maxPlayers = 100
motd = "Welcome to OTS."
onePlayerOnlinePerAccount = true
allowClones = false
serverName = "Feleron"
statusTimeout = 50000
replaceKickOnLogin = true
maxPacketsPerSecond = 100
enableLiveCasting = false
liveCastPort = 7173

-- Deaths
-- NOTE: Leave deathLosePercent as -1 if you want to use the default
-- death penalty formula. For the old formula, set it to 10. For
-- no skill/experience loss, set it to 0.
deathLosePercent = -1

-- Houses
-- NOTE: set housePriceEachSQM to -1 to disable the ingame buy house functionality
housePriceEachSQM = 1000
houseRentPeriod = "never"

-- Item Usage
timeBetweenActions = 150
timeBetweenExActions = 800

-- Map
-- NOTE: set mapName WITHOUT .otbm at the end
mapName = "global"
mapAuthor = "Bruno"

-- Misc.
-- NOTE: classicAttackSpeed set to true makes players constantly attack at regular
-- intervals regardless of other actions such as item (potion) use. This setting
-- may cause high CPU usage with many players and potentially affect performance!

allowChangeOutfit = true
freePremium = true
kickIdlePlayerAfterMinutes = 15
maxMessageBuffer = 4
emoteSpells = false
classicEquipmentSlots = true
classicAttackSpeed = true
autoStackItems = true
displayLootMessage = true

-- Rates
-- NOTE: rateExp is not used if you have enabled stages in data/XML/stages.xml
rateExp = 2
rateSkill = 30
rateLoot = 10
rateMagic = 4
rateSpawn = 1

-- Monsters
deSpawnRange = 2
deSpawnRadius = 50

-- Stamina
staminaSystem = false

-- Scripts
warnUnsafeScripts = true
convertUnsafeScripts = false

-- Startup
-- NOTE: defaultPriority only works on Windows and sets process
-- priority, valid values are: "normal", "above-normal", "high"
defaultPriority = "high"
startupDatabaseOptimization = true

-- Status server information
ownerName = ""
ownerEmail = ""
url = "OTLand (https://otland.net/)"
location = "Brazil"
Spawn.h

/**
* The Forgotten Server D - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Mark Samman <[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.
*/

#ifndef FS_SPAWN_H_1A86089E080846A9AE53ED12E7AE863B
#define FS_SPAWN_H_1A86089E080846A9AE53ED12E7AE863B

#include "tile.h"
#include "position.h"

class Monster;
class MonsterType;
class Npc;

struct spawnBlock_t {
Position pos;
MonsterType* mType;
int64_t lastSpawn;
uint32_t interval;
Direction direction;
};

class Spawn
{
public:
Spawn(Position pos, int32_t radius) : centerPos(std::move(pos)), radius(radius) {}
~Spawn();

// non-copyable
Spawn(const Spawn&) = delete;
Spawn& operator=(const Spawn&) = delete;

bool addMonster(const std::string& name, const Position& pos, Direction dir, uint32_t interval);
void removeMonster(Monster* monster);

uint32_t getInterval() const {
return interval;
}
void startup();

void startSpawnCheck();
void stopEvent();

bool isInSpawnZone(const Position& pos);
void cleanup();

private:
//map of the spawned creatures
using SpawnedMap = std::multimap<uint32_t, Monster*>;
using spawned_pair = SpawnedMap::value_type;
SpawnedMap spawnedMap;

//map of creatures in the spawn
std::map<uint32_t, spawnBlock_t> spawnMap;

Position centerPos;
int32_t radius;

uint32_t interval = 60000;
uint32_t checkSpawnEvent = 0;

static bool findPlayer(const Position& pos);
bool spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& pos, Direction dir, bool startup = false);
void checkSpawn();
void scheduleSpawn(uint32_t spawnId, spawnBlock_t& sb, uint16_t interval);
};

class Spawns
{
public:
static bool isInZone(const Position& centerPos, int32_t radius, const Position& pos);

bool loadFromXml(const std::string& filename);
void startup();
void clear();

bool isStarted() const {
return started;
}

private:
std::forward_list<Npc*> npcList;
std::forward_list<Spawn> spawnList;
std::string filename;
bool loaded = false;
bool started = false;
};

static constexpr int32_t NONBLOCKABLE_SPAWN_INTERVAL = 1400;

#endif

Spawn.cpp
/**
* The Forgotten Server D - a free and open-source MMORPG server emulator
* Copyright (C) 2017 Mark Samman <[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 "events.h"

#include "pugicast.h"

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

static constexpr int32_t MINSPAWN_INTERVAL = 1000;

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;

#pragma omp parallel for
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;
}

spawnList.emplace_front(centerPos, radius);
Spawn& spawn = spawnList.front();

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

Direction dir;

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

Position pos(
centerPos.x + pugi::cast<uint16_t>(childNode.attribute("x").value()),
centerPos.y + pugi::cast<uint16_t>(childNode.attribute("y").value()),
centerPos.z
);
float interval = pugi::cast<float>(childNode.attribute("spawntime").value()) * 1000 / 2;
interval = interval/g_config.getNumber(ConfigManager::RATE_SPAWN);

if (interval > MINSPAWN_INTERVAL) {
spawn.addMonster(nameAttribute.as_string(), pos, dir, interval);
} else {
std::cout << "[Warning - Spawns::loadFromXml] " << nameAttribute.as_string() << ' ' << pos << " spawntime can not be less than " << MINSPAWN_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;
}

pugi::xml_attribute directionAttribute = childNode.attribute("direction");
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;
}

#pragma omp parallel for
for (Npc* npc : npcList) {
g_game.placeCreature(npc, npc->getMasterPos(), false, true);
}
npcList.clear();

for (Spawn& spawn : spawnList) {
spawn.startup();
}

started = true;
}

void Spawns::clear()
{
#pragma omp parallel for
for (Spawn& spawn : spawnList) {
spawn.stopEvent();
}
spawnList.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::startSpawnCheck()
{
if (checkSpawnEvent == 0) {
checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(getInterval(), std::bind(&Spawn::checkSpawn, this)));
}
}

Spawn::~Spawn()
{
#pragma omp parallel for
for (const auto& it : spawnedMap) {
Monster* monster = it.second;
monster->setSpawn(nullptr);
monster->decrementReferenceCounter();
}
}

bool Spawn::findPlayer(const Position& pos)
{
SpectatorHashSet spectators;
g_game.map.getSpectators(spectators, pos, false, true);
#pragma omp parallel for
for (Creature* spectator : spectators) {
if (!spectator->getPlayer()->hasFlag(PlayerFlag_IgnoredByMonsters)) {
return true;
}
}
return false;
}

bool Spawn::isInSpawnZone(const Position& pos)
{
return Spawns::isInZone(centerPos, radius, pos);
}

bool Spawn::spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& pos, Direction dir, bool startup /= false/)
{
std::unique_ptr<Monster> monster_ptr(new Monster(mType));
bool result = g_events->eventMonsterOnSpawn(monster_ptr.get(), pos, startup, false);
if (result) {
if (startup) {
//No need to send out events to the surrounding since there is no one out there to listen!
if (!g_game.internalPlaceCreature(monster_ptr.get(), pos, true)) {
return false;
}
} else {
if (!g_game.placeCreature(monster_ptr.get(), pos, false, true)) {
return false;
}
}
}

Monster* monster = monster_ptr.release();
monster->setDirection(dir);
monster->setSpawn(this);
monster->setMasterPos(pos);
monster->incrementReferenceCounter();

if (result) {
spawnedMap.insert(spawned_pair(spawnId, monster));
}
spawnMap[spawnId].lastSpawn = OTSYS_TIME();
return result;
}

void Spawn::startup()
{
#pragma omp parallel for
for (const auto& it : spawnMap) {
uint32_t spawnId = it.first;
const spawnBlock_t& sb = it.second;
spawnMonster(spawnId, sb.mType, sb.pos, sb.direction, true);
}
}

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

cleanup();

uint32_t spawnCount = 0;

#pragma omp parallel for
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) {
if (sb.mType->info.isBlockable && findPlayer(sb.pos)) {
sb.lastSpawn = OTSYS_TIME();
continue;
}

if (sb.mType->info.isBlockable) {
spawnMonster(spawnId, sb.mType, sb.pos, sb.direction);
} else {
scheduleSpawn(spawnId, sb, 3 * NONBLOCKABLE_SPAWN_INTERVAL);
}

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, uint16_t interval)
{
if (interval <= 0) {
spawnMonster(spawnId, sb.mType, sb.pos, sb.direction);
} else {
g_game.addMagicEffect(sb.pos, CONST_ME_TELEPORT);
g_scheduler.addEvent(createSchedulerTask(1400, std::bind(&Spawn::scheduleSpawn, this, spawnId, sb, interval - NONBLOCKABLE_SPAWN_INTERVAL)));
}
}

void Spawn::cleanup()
{
auto it = spawnedMap.begin();
while (it != spawnedMap.end()) {
uint32_t spawnId = it->first;
Monster* monster = it->second;
if (monster->isRemoved()) {
if (spawnId != 0) {
spawnMap[spawnId].lastSpawn = OTSYS_TIME();
}

monster->decrementReferenceCounter();
it = spawnedMap.erase(it);
} else if (!isInSpawnZone(monster->getPosition()) && spawnId != 0) {
spawnedMap.insert(spawned_pair(0, monster));
it = spawnedMap.erase(it);
} else {
++it;
}
}
}

bool Spawn::addMonster(const std::string& name, const Position& pos, Direction dir, uint32_t interval)
{
MonsterType* mType = g_monsters.getMonsterType(name);
if (!mType) {
std::cout << "[Spawn::addMonster] Can not find " << name << std::endl;
return false;
}

this->interval = std::min(this->interval, interval);

spawnBlock_t sb;
sb.mType = mType;
sb.pos = pos;
sb.direction = dir;
sb.interval = interval;
sb.lastSpawn = 0;

uint32_t spawnId = spawnMap.size() + 1;
spawnMap[spawnId] = sb;
return true;
}

void Spawn::removeMonster(Monster* monster)
{
#pragma omp parallel for
for (auto it = spawnedMap.begin(), end = spawnedMap.end(); it != end; ++it) {
if (it->second == monster) {
monster->decrementReferenceCounter();
spawnedMap.erase(it);
break;
}
}
}

void Spawn::stopEvent()
{
if (checkSpawnEvent != 0) {
g_scheduler.stopEvent(checkSpawnEvent);
checkSpawnEvent = 0;
}
}
 
There is some custom system to make spawns 'unblockable' with spawn time 1.4 second. Maybe that's a problem, maybe all/most of monsters use that system:

I've seen multiple custom 'spawn systems' and 90% of them were bugged: crashed server or worked not like author intended.
These fast spawns are popular on high rate evo servers, so maybe it's not bad and you can keep it.

To disable that system, you can try to replace it with official TFS 1.2 spawn system:
 
There is some custom system to make spawns 'unblockable' with spawn time 1.4 second. Maybe that's a problem, maybe all/most of monsters use that system:

I've seen multiple custom 'spawn systems' and 90% of them were bugged: crashed server or worked not like author intended.
These fast spawns are popular on high rate evo servers, so maybe it's not bad and you can keep it.

To disable that system, you can try to replace it with official TFS 1.2 spawn system:
I got different server engine and it work normally. Unfortunately that server engine has some weird attack speed and paladin shoots arrows like he has ak47.
Will have a look at what you have posted. Thanks
 
I got different server engine and it work normally. Unfortunately that server engine has some weird attack speed and paladin shoots arrows like he has ak47.
Will have a look at what you have posted. Thanks
Put FAST_ATTACK_SPEED = 2000 in config.lua

or change it in config.cpp to 2000 and recompile.

Late to the party but maybe it helps someone.

Credits to the user: Shiris Undrin

Edit: Also if you have monster spawn rate below 1, like 0.5 it will respawn instantly.
Edit2: Also, if editing the spawns.xml with notepad++* you can edit all instances with the same number at once using CTRL+H and then replace all with desired spawn time.
 
Last edited:
Back
Top