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

Solved Old Spawn System

MorganaSacani

Active Member
Joined
Sep 20, 2022
Messages
87
Solutions
1
Reaction score
32
I need to change my script. I need monsters not spawn with players on screen. Exactly as it was in previous versions of Tibia.
What do I need to change?
spawn_monster.cpp:
C++:
/**
 * The Forgotten Server - a free and open-source MMORPG server emulator
 * Copyright (C) 2019  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 "creatures/monsters/spawns/spawn_monster.h"
#include "game/game.h"
#include "creatures/monsters/monster.h"
#include "game/scheduling/scheduler.h"
#include "game/scheduling/events_scheduler.hpp"

#include "utils/pugicast.h"
#include "lua/creature/events.h"

static constexpr int32_t MONSTER_MINSPAWN_INTERVAL = 1000; // 1 second
static constexpr int32_t MONSTER_MAXSPAWN_INTERVAL = 86400000; // 1 day

bool SpawnsMonster::loadFromXML(const std::string& filemonstername)
{
    if (isLoaded()) {
        return true;
    }

    pugi::xml_document doc;
    pugi::xml_parse_result result = doc.load_file(filemonstername.c_str());
    if (!result) {
        printXMLError("SpawnsMonster::loadFromXml", filemonstername, result);
        return false;
    }

    this->filemonstername = filemonstername;
    loaded = true;

    uint32_t eventschedule = g_eventsScheduler().getSpawnMonsterSchedule();
    std::string boostedNameGet = g_game().getBoostedMonsterName();

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

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

        if (!spawnMonsterNode.first_child()) {
            SPDLOG_WARN("[SpawnsMonster::loadFromXml] - Empty spawn at position: {} with radius: {}", centerPos.toString(), radius);
            continue;
        }

        spawnMonsterList.emplace_front(centerPos, radius);
        SpawnMonster& spawnMonster = spawnMonsterList.front();

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

                Direction dir;

                pugi::xml_attribute directionAttribute = childMonsterNode.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>(childMonsterNode.attribute("x").value()),
                    centerPos.y + pugi::cast<uint16_t>(childMonsterNode.attribute("y").value()),
                    centerPos.z
                );

                int32_t boostedrate = 1;
                
                if (nameAttribute.value() == boostedNameGet) {
                    boostedrate = 2;
                }

                uint32_t interval = pugi::cast<uint32_t>(childMonsterNode.attribute("spawntime").value()) * 1000 * 100 / std::max((uint32_t)1, (g_configManager().getNumber(RATE_SPAWN) * boostedrate * eventschedule));
                if (interval >= MONSTER_MINSPAWN_INTERVAL && interval <= MONSTER_MAXSPAWN_INTERVAL) {
                    spawnMonster.addMonster(nameAttribute.as_string(), pos, dir, static_cast<uint32_t>(interval));
                } else {
                    if (interval <= MONSTER_MINSPAWN_INTERVAL) {
                        SPDLOG_WARN("[SpawnsMonster::loadFromXml] - {} {} spawntime cannot be less than {} seconds, set to {} by default.", nameAttribute.as_string(), pos.toString(), MONSTER_MINSPAWN_INTERVAL / 1000, MONSTER_MINSPAWN_INTERVAL / 1000); spawnMonster.addMonster(nameAttribute.as_string(), pos, dir, MONSTER_MINSPAWN_INTERVAL);
                    } else {
                        SPDLOG_WARN("[SpawnsMonster::loadFromXml] - {} {} spawntime can not be more than {} seconds", nameAttribute.as_string(), pos.toString(), MONSTER_MAXSPAWN_INTERVAL / 1000);
                    }
                }
            }
        }
    }
    return true;
}

void SpawnsMonster::startup()
{
    if (!isLoaded() || isStarted()) {
        return;
    }

    for (SpawnMonster& spawnMonster : spawnMonsterList) {
        spawnMonster.startup();
    }

    started = true;
}

void SpawnsMonster::clear()
{
    for (SpawnMonster& spawnMonster : spawnMonsterList) {
        spawnMonster.stopEvent();
    }
    spawnMonsterList.clear();

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

bool SpawnsMonster::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 SpawnMonster::startSpawnMonsterCheck()
{
    if (checkSpawnMonsterEvent == 0) {
        checkSpawnMonsterEvent = g_scheduler().addEvent(createSchedulerTask(getInterval(), std::bind(&SpawnMonster::checkSpawnMonster, this)));
    }
}

SpawnMonster::~SpawnMonster()
{
    for (const auto& it : spawnedMonsterMap) {
        Monster* monster = it.second;
        monster->setSpawnMonster(nullptr);
        monster->decrementReferenceCounter();
    }
}

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

bool SpawnMonster::isInSpawnMonsterZone(const Position& pos)
{
    return SpawnsMonster::isInZone(centerPos, radius, pos);
}

bool SpawnMonster::spawnMonster(uint32_t spawnMonsterId, MonsterType* monsterType, const Position& pos, Direction dir, bool startup /*= false*/)
{
    std::unique_ptr<Monster> monster_ptr(new Monster(monsterType));
    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->setSpawnMonster(this);
    monster->setMasterPos(pos);
    monster->incrementReferenceCounter();

    spawnedMonsterMap.insert(spawned_pair(spawnMonsterId, monster));
    spawnMonsterMap[spawnMonsterId].lastSpawn = OTSYS_TIME();
    g_events().eventMonsterOnSpawn(monster, pos);
    return true;
}

void SpawnMonster::startup()
{
    for (const auto& it : spawnMonsterMap) {
        uint32_t spawnMonsterId = it.first;
        const spawnBlock_t& sb = it.second;
        spawnMonster(spawnMonsterId, sb.monsterType, sb.pos, sb.direction, true);
    }
}

void SpawnMonster::checkSpawnMonster()
{
    checkSpawnMonsterEvent = 0;

    cleanup();

    uint32_t spawnMonsterCount = 0;

    for (auto& it : spawnMonsterMap) {
        uint32_t spawnMonsterId = it.first;
        if (spawnedMonsterMap.find(spawnMonsterId) != spawnedMonsterMap.end()) {
            continue;
        }

        spawnBlock_t& sb = it.second;
        if (!sb.monsterType->canSpawn(sb.pos)) {
            sb.lastSpawn = OTSYS_TIME();
            continue;
        }

        if (OTSYS_TIME() >= sb.lastSpawn + sb.interval) {
            if (sb.monsterType->info.isBlockable && findPlayer(sb.pos)) {
                sb.lastSpawn = OTSYS_TIME();
                continue;
            }

            if (sb.monsterType->info.isBlockable) {
                spawnMonster(spawnMonsterId, sb.monsterType, sb.pos, sb.direction);
            } else {
                scheduleSpawn(spawnMonsterId, sb, 3 * NONBLOCKABLE_SPAWN_MONSTER_INTERVAL);
            }

            if (++spawnMonsterCount >= static_cast<uint32_t>(g_configManager().getNumber(RATE_SPAWN))) {
                break;
            }
        }
    }

    if (spawnedMonsterMap.size() < spawnMonsterMap.size()) {
        checkSpawnMonsterEvent = g_scheduler().addEvent(createSchedulerTask(getInterval(), std::bind(&SpawnMonster::checkSpawnMonster, this)));
    }
}

void SpawnMonster::scheduleSpawn(uint32_t spawnMonsterId, spawnBlock_t& sb, uint16_t interval)
{
    if (interval <= 0) {
        spawnMonster(spawnMonsterId, sb.monsterType, sb.pos, sb.direction);
    } else {
        g_game().addMagicEffect(sb.pos, CONST_ME_TELEPORT);
        g_scheduler().addEvent(createSchedulerTask(1400, std::bind(&SpawnMonster::scheduleSpawn, this, spawnMonsterId, sb, interval - NONBLOCKABLE_SPAWN_MONSTER_INTERVAL)));
    }
}

void SpawnMonster::cleanup()
{
    auto it = spawnedMonsterMap.begin();
    while (it != spawnedMonsterMap.end()) {
            uint32_t spawnMonsterId = it->first;
        Monster* monster = it->second;
            if (monster->isRemoved()) {
               spawnMonsterMap[spawnMonsterId].lastSpawn = OTSYS_TIME();
            monster->decrementReferenceCounter();
            it = spawnedMonsterMap.erase(it);
        } else {
            ++it;
        }
    }
}

bool SpawnMonster::addMonster(const std::string& name, const Position& pos, Direction dir, uint32_t scheduleInterval)
{
    MonsterType* monsterType = g_monsters().getMonsterType(name);
    if (!monsterType) {
        SPDLOG_ERROR("Can not find {}", name);
        return false;
    }

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

    spawnBlock_t sb;
    sb.monsterType = monsterType;
    sb.pos = pos;
    sb.direction = dir;
    sb.interval = scheduleInterval;
    sb.lastSpawn = 0;

    uint32_t spawnMonsterId = spawnMonsterMap.size() + 1;
    spawnMonsterMap[spawnMonsterId] = sb;
    return true;
}

void SpawnMonster::removeMonster(Monster* monster)
{
    for (auto it = spawnedMonsterMap.begin(), end = spawnedMonsterMap.end(); it != end; ++it) {
        if (it->second == monster) {
            monster->decrementReferenceCounter();
            spawnedMonsterMap.erase(it);
            break;
        }
    }
}

void SpawnMonster::stopEvent()
{
    if (checkSpawnMonsterEvent != 0) {
        g_scheduler().stopEvent(checkSpawnMonsterEvent);
        checkSpawnMonsterEvent = 0;
    }
}

spawn_monster.h:
C++:
/**
 * The Forgotten Server - a free and open-source MMORPG server emulator
 * Copyright (C) 2019  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 SRC_CREATURES_MONSTERS_SPAWNS_SPAWN_MONSTER_H_
#define SRC_CREATURES_MONSTERS_SPAWNS_SPAWN_MONSTER_H_

#include "items/tile.h"
#include "game/movement/position.h"

class Monster;
class MonsterType;

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

class SpawnMonster
{
    public:
        SpawnMonster(Position initPos, int32_t initRadius) : centerPos(std::move(initPos)), radius(initRadius) {}
        ~SpawnMonster();

        // non-copyable
        SpawnMonster(const SpawnMonster&) = delete;
        SpawnMonster& operator=(const SpawnMonster&) = 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 startSpawnMonsterCheck();
        void stopEvent();

        bool isInSpawnMonsterZone(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 spawnedMonsterMap;

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

        Position centerPos;
        int32_t radius;

        uint32_t interval = 30000;
        uint32_t checkSpawnMonsterEvent = 0;

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

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

        bool loadFromXML(const std::string& filemonstername);
        void startup();
        void clear();

        bool isStarted() const {
            return started;
        }
        bool isLoaded() const {
            return loaded;
        }
        std::forward_list<SpawnMonster>& getspawnMonsterList() {
            return spawnMonsterList;
        }

    private:
        std::forward_list<SpawnMonster> spawnMonsterList;
        std::string filemonstername;
        bool loaded = false;
        bool started = false;
};

static constexpr int32_t NONBLOCKABLE_SPAWN_MONSTER_INTERVAL = 1400;

#endif  // SRC_CREATURES_MONSTERS_SPAWNS_SPAWN_MONSTER_H_
 
TFS version?

if useing 0.x+

in config : monsterSpawnWalkback = true / change it to monsterSpawnWalkback = false
allowBlockSpawn = false // to allowBlockSpawn = true
 
TFS version?

if useing 0.x+

in config : monsterSpawnWalkback = true / change it to monsterSpawnWalkback = false
allowBlockSpawn = false // to allowBlockSpawn = true
Do you know how to change the monster walking idle, for exemple, player is pos.z+1 from monster, so, monster stay still until player is in same pos.z

Tfs 1.5 nekiro
 
just add this flag to "rat.xml" file or whatever monster you which to be "spawnblocked" by player or not

XML:
<flags>
        <flag ignorespawnblock="0" />
</flags>

A simple solution if you want some monsters to be blocked or not, change 0->1 also does not require any c++ knowledge to do this.
 
Back
Top