spawn.cpp

main 333 lines · 8.4 KB Raw
Alex Alex Commit Server upload 18/10/2026 23:38
1/**
2 * The Forgotten Server - a free and open-source MMORPG server emulator
3 * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "otpch.h"
21
22#include "spawn.h"
23#include "game.h"
24#include "monster.h"
25#include "configmanager.h"
26#include "scheduler.h"
27
28#include "pugicast.h"
29#include "events.h"
30
31extern ConfigManager g_config;
32extern Monsters g_monsters;
33extern Game g_game;
34extern Events* g_events;
35
36static constexpr int32_t MINSPAWN_INTERVAL = 1000;
37
38bool Spawns::loadFromXml(const std::string& filename)
39{
40 if (loaded) {
41 return true;
42 }
43
44 pugi::xml_document doc;
45 pugi::xml_parse_result result = doc.load_file(filename.c_str());
46 if (!result) {
47 printXMLError("Error - Spawns::loadFromXml", filename, result);
48 return false;
49 }
50
51 this->filename = filename;
52 loaded = true;
53
54 for (auto spawnNode : doc.child("spawns").children()) {
55 Position centerPos(
56 pugi::cast<uint16_t>(spawnNode.attribute("centerx").value()),
57 pugi::cast<uint16_t>(spawnNode.attribute("centery").value()),
58 pugi::cast<uint16_t>(spawnNode.attribute("centerz").value())
59 );
60
61 int32_t radius;
62 pugi::xml_attribute radiusAttribute = spawnNode.attribute("radius");
63 if (radiusAttribute) {
64 radius = pugi::cast<int32_t>(radiusAttribute.value());
65 } else {
66 radius = -1;
67 }
68
69 spawnList.emplace_front(centerPos, radius);
70 Spawn& spawn = spawnList.front();
71
72 for (auto childNode : spawnNode.children()) {
73 if (strcasecmp(childNode.name(), "monster") == 0) {
74 pugi::xml_attribute nameAttribute = childNode.attribute("name");
75 if (!nameAttribute) {
76 continue;
77 }
78
79 Direction dir;
80
81 pugi::xml_attribute directionAttribute = childNode.attribute("direction");
82 if (directionAttribute) {
83 dir = static_cast<Direction>(pugi::cast<uint16_t>(directionAttribute.value()));
84 } else {
85 dir = DIRECTION_NORTH;
86 }
87
88 Position pos(
89 centerPos.x + pugi::cast<uint16_t>(childNode.attribute("x").value()),
90 centerPos.y + pugi::cast<uint16_t>(childNode.attribute("y").value()),
91 centerPos.z
92 );
93 uint32_t interval = pugi::cast<uint32_t>(childNode.attribute("spawntime").value()) * 1000;
94 if (interval > MINSPAWN_INTERVAL) {
95 spawn.addMonster(nameAttribute.as_string(), pos, dir, interval);
96 } else {
97 std::cout << "[Warning - Spawns::loadFromXml] " << nameAttribute.as_string() << ' ' << pos << " spawntime can not be less than " << MINSPAWN_INTERVAL / 1000 << " seconds." << std::endl;
98 }
99 } else if (strcasecmp(childNode.name(), "npc") == 0) {
100 pugi::xml_attribute nameAttribute = childNode.attribute("name");
101 if (!nameAttribute) {
102 continue;
103 }
104
105 Npc* npc = Npc::createNpc(nameAttribute.as_string());
106 if (!npc) {
107 continue;
108 }
109
110 pugi::xml_attribute directionAttribute = childNode.attribute("direction");
111 if (directionAttribute) {
112 npc->setDirection(static_cast<Direction>(pugi::cast<uint16_t>(directionAttribute.value())));
113 }
114
115 npc->setMasterPos(Position(
116 centerPos.x + pugi::cast<uint16_t>(childNode.attribute("x").value()),
117 centerPos.y + pugi::cast<uint16_t>(childNode.attribute("y").value()),
118 centerPos.z
119 ), radius);
120 npcList.push_front(npc);
121 }
122 }
123 }
124 return true;
125}
126
127void Spawns::startup()
128{
129 if (!loaded || isStarted()) {
130 return;
131 }
132
133 for (Npc* npc : npcList) {
134 g_game.placeCreature(npc, npc->getMasterPos(), false, true);
135 }
136 npcList.clear();
137
138 for (Spawn& spawn : spawnList) {
139 spawn.startup();
140 }
141
142 started = true;
143}
144
145void Spawns::clear()
146{
147 for (Spawn& spawn : spawnList) {
148 spawn.stopEvent();
149 }
150 spawnList.clear();
151
152 loaded = false;
153 started = false;
154 filename.clear();
155}
156
157bool Spawns::isInZone(const Position& centerPos, int32_t radius, const Position& pos)
158{
159 if (radius == -1) {
160 return true;
161 }
162
163 return ((pos.getX() >= centerPos.getX() - radius) && (pos.getX() <= centerPos.getX() + radius) &&
164 (pos.getY() >= centerPos.getY() - radius) && (pos.getY() <= centerPos.getY() + radius));
165}
166
167void Spawn::startSpawnCheck()
168{
169 if (checkSpawnEvent == 0) {
170 checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(getInterval(), std::bind(&Spawn::checkSpawn, this)));
171 }
172}
173
174Spawn::~Spawn()
175{
176 for (const auto& it : spawnedMap) {
177 Monster* monster = it.second;
178 monster->setSpawn(nullptr);
179 monster->decrementReferenceCounter();
180 }
181}
182
183bool Spawn::findPlayer(const Position& pos)
184{
185 SpectatorVec spectators;
186 g_game.map.getSpectators(spectators, pos, false, true);
187 for (Creature* spectator : spectators) {
188 if (!spectator->getPlayer()->hasFlag(PlayerFlag_IgnoredByMonsters)) {
189 return true;
190 }
191 }
192 return false;
193}
194
195bool Spawn::isInSpawnZone(const Position& pos)
196{
197 return Spawns::isInZone(centerPos, radius, pos);
198}
199
200bool Spawn::spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& pos, Direction dir, bool startup /*= false*/)
201{
202 std::unique_ptr<Monster> monster_ptr(new Monster(mType));
203 if (!g_events->eventMonsterOnSpawn(monster_ptr.get(), pos, startup, false)) {
204 return false;
205 }
206
207 if (startup) {
208 //No need to send out events to the surrounding since there is no one out there to listen!
209 if (!g_game.internalPlaceCreature(monster_ptr.get(), pos, true)) {
210 return false;
211 }
212 } else {
213 if (!g_game.placeCreature(monster_ptr.get(), pos, false, true)) {
214 return false;
215 }
216 }
217
218 Monster* monster = monster_ptr.release();
219 monster->setDirection(dir);
220 monster->setSpawn(this);
221 monster->setMasterPos(pos);
222 monster->incrementReferenceCounter();
223
224 spawnedMap.insert(spawned_pair(spawnId, monster));
225 spawnMap[spawnId].lastSpawn = OTSYS_TIME();
226 return true;
227}
228
229void Spawn::startup()
230{
231 for (const auto& it : spawnMap) {
232 uint32_t spawnId = it.first;
233 const spawnBlock_t& sb = it.second;
234 spawnMonster(spawnId, sb.mType, sb.pos, sb.direction, true);
235 }
236}
237
238void Spawn::checkSpawn()
239{
240 checkSpawnEvent = 0;
241
242 cleanup();
243
244 uint32_t spawnCount = 0;
245
246 for (auto& it : spawnMap) {
247 uint32_t spawnId = it.first;
248 if (spawnedMap.find(spawnId) != spawnedMap.end()) {
249 continue;
250 }
251
252 spawnBlock_t& sb = it.second;
253 if (OTSYS_TIME() >= sb.lastSpawn + sb.interval) {
254 if (findPlayer(sb.pos)) {
255 sb.lastSpawn = OTSYS_TIME();
256 continue;
257 }
258
259 spawnMonster(spawnId, sb.mType, sb.pos, sb.direction);
260 if (++spawnCount >= static_cast<uint32_t>(g_config.getNumber(ConfigManager::RATE_SPAWN))) {
261 break;
262 }
263 }
264 }
265
266 if (spawnedMap.size() < spawnMap.size()) {
267 checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(getInterval(), std::bind(&Spawn::checkSpawn, this)));
268 }
269}
270
271void Spawn::cleanup()
272{
273 auto it = spawnedMap.begin();
274 while (it != spawnedMap.end()) {
275 uint32_t spawnId = it->first;
276 Monster* monster = it->second;
277 if (monster->isRemoved()) {
278 if (spawnId != 0) {
279 spawnMap[spawnId].lastSpawn = OTSYS_TIME();
280 }
281
282 monster->decrementReferenceCounter();
283 it = spawnedMap.erase(it);
284 } else if (!isInSpawnZone(monster->getPosition()) && spawnId != 0) {
285 spawnedMap.insert(spawned_pair(0, monster));
286 it = spawnedMap.erase(it);
287 } else {
288 ++it;
289 }
290 }
291}
292
293bool Spawn::addMonster(const std::string& name, const Position& pos, Direction dir, uint32_t interval)
294{
295 MonsterType* mType = g_monsters.getMonsterType(name);
296 if (!mType) {
297 std::cout << "[Spawn::addMonster] Can not find " << name << std::endl;
298 return false;
299 }
300
301 this->interval = std::min(this->interval, interval);
302
303 spawnBlock_t sb;
304 sb.mType = mType;
305 sb.pos = pos;
306 sb.direction = dir;
307 sb.interval = interval;
308 sb.lastSpawn = 0;
309
310 uint32_t spawnId = spawnMap.size() + 1;
311 spawnMap[spawnId] = sb;
312 return true;
313}
314
315void Spawn::removeMonster(Monster* monster)
316{
317 for (auto it = spawnedMap.begin(), end = spawnedMap.end(); it != end; ++it) {
318 if (it->second == monster) {
319 monster->decrementReferenceCounter();
320 spawnedMap.erase(it);
321 break;
322 }
323 }
324}
325
326void Spawn::stopEvent()
327{
328 if (checkSpawnEvent != 0) {
329 g_scheduler.stopEvent(checkSpawnEvent);
330 checkSpawnEvent = 0;
331 }
332}
333
Top