raids.cpp

main 590 lines · 15.5 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 "raids.h"
23
24#include "pugicast.h"
25
26#include "game.h"
27#include "configmanager.h"
28#include "scheduler.h"
29#include "monster.h"
30
31extern Game g_game;
32extern ConfigManager g_config;
33
34Raids::Raids()
35{
36 scriptInterface.initState();
37}
38
39Raids::~Raids()
40{
41 for (Raid* raid : raidList) {
42 delete raid;
43 }
44}
45
46bool Raids::loadFromXml()
47{
48 if (isLoaded()) {
49 return true;
50 }
51
52 pugi::xml_document doc;
53 pugi::xml_parse_result result = doc.load_file("data/raids/raids.xml");
54 if (!result) {
55 printXMLError("Error - Raids::loadFromXml", "data/raids/raids.xml", result);
56 return false;
57 }
58
59 for (auto raidNode : doc.child("raids").children()) {
60 std::string name, file;
61 uint32_t interval, margin;
62
63 pugi::xml_attribute attr;
64 if ((attr = raidNode.attribute("name"))) {
65 name = attr.as_string();
66 } else {
67 std::cout << "[Error - Raids::loadFromXml] Name tag missing for raid" << std::endl;
68 continue;
69 }
70
71 if ((attr = raidNode.attribute("file"))) {
72 file = attr.as_string();
73 } else {
74 std::ostringstream ss;
75 ss << "raids/" << name << ".xml";
76 file = ss.str();
77 std::cout << "[Warning - Raids::loadFromXml] File tag missing for raid " << name << ". Using default: " << file << std::endl;
78 }
79
80 interval = pugi::cast<uint32_t>(raidNode.attribute("interval2").value()) * 60;
81 if (interval == 0) {
82 std::cout << "[Error - Raids::loadFromXml] interval2 tag missing or zero (would divide by 0) for raid: " << name << std::endl;
83 continue;
84 }
85
86 if ((attr = raidNode.attribute("margin"))) {
87 margin = pugi::cast<uint32_t>(attr.value()) * 60 * 1000;
88 } else {
89 std::cout << "[Warning - Raids::loadFromXml] margin tag missing for raid: " << name << std::endl;
90 margin = 0;
91 }
92
93 bool repeat;
94 if ((attr = raidNode.attribute("repeat"))) {
95 repeat = booleanString(attr.as_string());
96 } else {
97 repeat = false;
98 }
99
100 Raid* newRaid = new Raid(name, interval, margin, repeat);
101 if (newRaid->loadFromXml("data/raids/" + file)) {
102 raidList.push_back(newRaid);
103 } else {
104 std::cout << "[Error - Raids::loadFromXml] Failed to load raid: " << name << std::endl;
105 delete newRaid;
106 }
107 }
108
109 loaded = true;
110 return true;
111}
112
113static constexpr int32_t MAX_RAND_RANGE = 10000000;
114
115bool Raids::startup()
116{
117 if (!isLoaded() || isStarted()) {
118 return false;
119 }
120
121 setLastRaidEnd(OTSYS_TIME());
122
123 checkRaidsEvent = g_scheduler.addEvent(createSchedulerTask(CHECK_RAIDS_INTERVAL * 1000, std::bind(&Raids::checkRaids, this)));
124
125 started = true;
126 return started;
127}
128
129void Raids::checkRaids()
130{
131 if (!getRunning()) {
132 uint64_t now = OTSYS_TIME();
133
134 for (auto it = raidList.begin(), end = raidList.end(); it != end; ++it) {
135 Raid* raid = *it;
136 if (now >= (getLastRaidEnd() + raid->getMargin())) {
137 if (((MAX_RAND_RANGE * CHECK_RAIDS_INTERVAL) / raid->getInterval()) >= static_cast<uint32_t>(uniform_random(0, MAX_RAND_RANGE))) {
138 setRunning(raid);
139 raid->startRaid();
140
141 if (!raid->canBeRepeated()) {
142 raidList.erase(it);
143 }
144 break;
145 }
146 }
147 }
148 }
149
150 checkRaidsEvent = g_scheduler.addEvent(createSchedulerTask(CHECK_RAIDS_INTERVAL * 1000, std::bind(&Raids::checkRaids, this)));
151}
152
153void Raids::clear()
154{
155 g_scheduler.stopEvent(checkRaidsEvent);
156 checkRaidsEvent = 0;
157
158 for (Raid* raid : raidList) {
159 raid->stopEvents();
160 delete raid;
161 }
162 raidList.clear();
163
164 loaded = false;
165 started = false;
166 running = nullptr;
167 lastRaidEnd = 0;
168
169 scriptInterface.reInitState();
170}
171
172bool Raids::reload()
173{
174 clear();
175 return loadFromXml();
176}
177
178Raid* Raids::getRaidByName(const std::string& name)
179{
180 for (Raid* raid : raidList) {
181 if (strcasecmp(raid->getName().c_str(), name.c_str()) == 0) {
182 return raid;
183 }
184 }
185 return nullptr;
186}
187
188Raid::~Raid()
189{
190 for (RaidEvent* raidEvent : raidEvents) {
191 delete raidEvent;
192 }
193}
194
195bool Raid::loadFromXml(const std::string& filename)
196{
197 if (isLoaded()) {
198 return true;
199 }
200
201 pugi::xml_document doc;
202 pugi::xml_parse_result result = doc.load_file(filename.c_str());
203 if (!result) {
204 printXMLError("Error - Raid::loadFromXml", filename, result);
205 return false;
206 }
207
208 for (auto eventNode : doc.child("raid").children()) {
209 RaidEvent* event;
210 if (strcasecmp(eventNode.name(), "announce") == 0) {
211 event = new AnnounceEvent();
212 } else if (strcasecmp(eventNode.name(), "singlespawn") == 0) {
213 event = new SingleSpawnEvent();
214 } else if (strcasecmp(eventNode.name(), "areaspawn") == 0) {
215 event = new AreaSpawnEvent();
216 } else if (strcasecmp(eventNode.name(), "script") == 0) {
217 event = new ScriptEvent(&g_game.raids.getScriptInterface());
218 } else {
219 continue;
220 }
221
222 if (event->configureRaidEvent(eventNode)) {
223 raidEvents.push_back(event);
224 } else {
225 std::cout << "[Error - Raid::loadFromXml] In file (" << filename << "), eventNode: " << eventNode.name() << std::endl;
226 delete event;
227 }
228 }
229
230 //sort by delay time
231 std::sort(raidEvents.begin(), raidEvents.end(), [](const RaidEvent* lhs, const RaidEvent* rhs) {
232 return lhs->getDelay() < rhs->getDelay();
233 });
234
235 loaded = true;
236 return true;
237}
238
239void Raid::startRaid()
240{
241 RaidEvent* raidEvent = getNextRaidEvent();
242 if (raidEvent) {
243 state = RAIDSTATE_EXECUTING;
244 nextEventEvent = g_scheduler.addEvent(createSchedulerTask(raidEvent->getDelay(), std::bind(&Raid::executeRaidEvent, this, raidEvent)));
245 }
246}
247
248void Raid::executeRaidEvent(RaidEvent* raidEvent)
249{
250 if (raidEvent->executeEvent()) {
251 nextEvent++;
252 RaidEvent* newRaidEvent = getNextRaidEvent();
253
254 if (newRaidEvent) {
255 uint32_t ticks = static_cast<uint32_t>(std::max<int32_t>(RAID_MINTICKS, newRaidEvent->getDelay() - raidEvent->getDelay()));
256 nextEventEvent = g_scheduler.addEvent(createSchedulerTask(ticks, std::bind(&Raid::executeRaidEvent, this, newRaidEvent)));
257 } else {
258 resetRaid();
259 }
260 } else {
261 resetRaid();
262 }
263}
264
265void Raid::resetRaid()
266{
267 nextEvent = 0;
268 state = RAIDSTATE_IDLE;
269 g_game.raids.setRunning(nullptr);
270 g_game.raids.setLastRaidEnd(OTSYS_TIME());
271}
272
273void Raid::stopEvents()
274{
275 if (nextEventEvent != 0) {
276 g_scheduler.stopEvent(nextEventEvent);
277 nextEventEvent = 0;
278 }
279}
280
281RaidEvent* Raid::getNextRaidEvent()
282{
283 if (nextEvent < raidEvents.size()) {
284 return raidEvents[nextEvent];
285 } else {
286 return nullptr;
287 }
288}
289
290bool RaidEvent::configureRaidEvent(const pugi::xml_node& eventNode)
291{
292 pugi::xml_attribute delayAttribute = eventNode.attribute("delay");
293 if (!delayAttribute) {
294 std::cout << "[Error] Raid: delay tag missing." << std::endl;
295 return false;
296 }
297
298 delay = std::max<uint32_t>(RAID_MINTICKS, pugi::cast<uint32_t>(delayAttribute.value()));
299 return true;
300}
301
302bool AnnounceEvent::configureRaidEvent(const pugi::xml_node& eventNode)
303{
304 if (!RaidEvent::configureRaidEvent(eventNode)) {
305 return false;
306 }
307
308 pugi::xml_attribute messageAttribute = eventNode.attribute("message");
309 if (!messageAttribute) {
310 std::cout << "[Error] Raid: message tag missing for announce event." << std::endl;
311 return false;
312 }
313 message = messageAttribute.as_string();
314
315 pugi::xml_attribute typeAttribute = eventNode.attribute("type");
316 if (typeAttribute) {
317 std::string tmpStrValue = asLowerCaseString(typeAttribute.as_string());
318 if (tmpStrValue == "warning") {
319 messageType = MESSAGE_STATUS_WARNING;
320 } else if (tmpStrValue == "event") {
321 messageType = MESSAGE_EVENT_ADVANCE;
322 } else if (tmpStrValue == "default") {
323 messageType = MESSAGE_EVENT_DEFAULT;
324 } else if (tmpStrValue == "description") {
325 messageType = MESSAGE_INFO_DESCR;
326 } else if (tmpStrValue == "smallstatus") {
327 messageType = MESSAGE_STATUS_SMALL;
328 } else if (tmpStrValue == "blueconsole") {
329 messageType = MESSAGE_STATUS_CONSOLE_BLUE;
330 } else if (tmpStrValue == "redconsole") {
331 messageType = MESSAGE_STATUS_CONSOLE_RED;
332 } else {
333 std::cout << "[Notice] Raid: Unknown type tag missing for announce event. Using default: " << static_cast<uint32_t>(messageType) << std::endl;
334 }
335 } else {
336 messageType = MESSAGE_EVENT_ADVANCE;
337 std::cout << "[Notice] Raid: type tag missing for announce event. Using default: " << static_cast<uint32_t>(messageType) << std::endl;
338 }
339 return true;
340}
341
342bool AnnounceEvent::executeEvent()
343{
344 g_game.broadcastMessage(message, messageType);
345 return true;
346}
347
348bool SingleSpawnEvent::configureRaidEvent(const pugi::xml_node& eventNode)
349{
350 if (!RaidEvent::configureRaidEvent(eventNode)) {
351 return false;
352 }
353
354 pugi::xml_attribute attr;
355 if ((attr = eventNode.attribute("name"))) {
356 monsterName = attr.as_string();
357 } else {
358 std::cout << "[Error] Raid: name tag missing for singlespawn event." << std::endl;
359 return false;
360 }
361
362 if ((attr = eventNode.attribute("x"))) {
363 position.x = pugi::cast<uint16_t>(attr.value());
364 } else {
365 std::cout << "[Error] Raid: x tag missing for singlespawn event." << std::endl;
366 return false;
367 }
368
369 if ((attr = eventNode.attribute("y"))) {
370 position.y = pugi::cast<uint16_t>(attr.value());
371 } else {
372 std::cout << "[Error] Raid: y tag missing for singlespawn event." << std::endl;
373 return false;
374 }
375
376 if ((attr = eventNode.attribute("z"))) {
377 position.z = pugi::cast<uint16_t>(attr.value());
378 } else {
379 std::cout << "[Error] Raid: z tag missing for singlespawn event." << std::endl;
380 return false;
381 }
382 return true;
383}
384
385bool SingleSpawnEvent::executeEvent()
386{
387 Monster* monster = Monster::createMonster(monsterName);
388 if (!monster) {
389 std::cout << "[Error] Raids: Cant create monster " << monsterName << std::endl;
390 return false;
391 }
392
393 if (!g_game.placeCreature(monster, position, false, true)) {
394 delete monster;
395 std::cout << "[Error] Raids: Cant place monster " << monsterName << std::endl;
396 return false;
397 }
398 return true;
399}
400
401bool AreaSpawnEvent::configureRaidEvent(const pugi::xml_node& eventNode)
402{
403 if (!RaidEvent::configureRaidEvent(eventNode)) {
404 return false;
405 }
406
407 pugi::xml_attribute attr;
408 if ((attr = eventNode.attribute("radius"))) {
409 int32_t radius = pugi::cast<int32_t>(attr.value());
410 Position centerPos;
411
412 if ((attr = eventNode.attribute("centerx"))) {
413 centerPos.x = pugi::cast<uint16_t>(attr.value());
414 } else {
415 std::cout << "[Error] Raid: centerx tag missing for areaspawn event." << std::endl;
416 return false;
417 }
418
419 if ((attr = eventNode.attribute("centery"))) {
420 centerPos.y = pugi::cast<uint16_t>(attr.value());
421 } else {
422 std::cout << "[Error] Raid: centery tag missing for areaspawn event." << std::endl;
423 return false;
424 }
425
426 if ((attr = eventNode.attribute("centerz"))) {
427 centerPos.z = pugi::cast<uint16_t>(attr.value());
428 } else {
429 std::cout << "[Error] Raid: centerz tag missing for areaspawn event." << std::endl;
430 return false;
431 }
432
433 fromPos.x = std::max<int32_t>(0, centerPos.getX() - radius);
434 fromPos.y = std::max<int32_t>(0, centerPos.getY() - radius);
435 fromPos.z = centerPos.z;
436
437 toPos.x = std::min<int32_t>(0xFFFF, centerPos.getX() + radius);
438 toPos.y = std::min<int32_t>(0xFFFF, centerPos.getY() + radius);
439 toPos.z = centerPos.z;
440 } else {
441 if ((attr = eventNode.attribute("fromx"))) {
442 fromPos.x = pugi::cast<uint16_t>(attr.value());
443 } else {
444 std::cout << "[Error] Raid: fromx tag missing for areaspawn event." << std::endl;
445 return false;
446 }
447
448 if ((attr = eventNode.attribute("fromy"))) {
449 fromPos.y = pugi::cast<uint16_t>(attr.value());
450 } else {
451 std::cout << "[Error] Raid: fromy tag missing for areaspawn event." << std::endl;
452 return false;
453 }
454
455 if ((attr = eventNode.attribute("fromz"))) {
456 fromPos.z = pugi::cast<uint16_t>(attr.value());
457 } else {
458 std::cout << "[Error] Raid: fromz tag missing for areaspawn event." << std::endl;
459 return false;
460 }
461
462 if ((attr = eventNode.attribute("tox"))) {
463 toPos.x = pugi::cast<uint16_t>(attr.value());
464 } else {
465 std::cout << "[Error] Raid: tox tag missing for areaspawn event." << std::endl;
466 return false;
467 }
468
469 if ((attr = eventNode.attribute("toy"))) {
470 toPos.y = pugi::cast<uint16_t>(attr.value());
471 } else {
472 std::cout << "[Error] Raid: toy tag missing for areaspawn event." << std::endl;
473 return false;
474 }
475
476 if ((attr = eventNode.attribute("toz"))) {
477 toPos.z = pugi::cast<uint16_t>(attr.value());
478 } else {
479 std::cout << "[Error] Raid: toz tag missing for areaspawn event." << std::endl;
480 return false;
481 }
482 }
483
484 for (auto monsterNode : eventNode.children()) {
485 const char* name;
486
487 if ((attr = monsterNode.attribute("name"))) {
488 name = attr.value();
489 } else {
490 std::cout << "[Error] Raid: name tag missing for monster node." << std::endl;
491 return false;
492 }
493
494 uint32_t minAmount;
495 if ((attr = monsterNode.attribute("minamount"))) {
496 minAmount = pugi::cast<uint32_t>(attr.value());
497 } else {
498 minAmount = 0;
499 }
500
501 uint32_t maxAmount;
502 if ((attr = monsterNode.attribute("maxamount"))) {
503 maxAmount = pugi::cast<uint32_t>(attr.value());
504 } else {
505 maxAmount = 0;
506 }
507
508 if (maxAmount == 0 && minAmount == 0) {
509 if ((attr = monsterNode.attribute("amount"))) {
510 minAmount = pugi::cast<uint32_t>(attr.value());
511 maxAmount = minAmount;
512 } else {
513 std::cout << "[Error] Raid: amount tag missing for monster node." << std::endl;
514 return false;
515 }
516 }
517
518 spawnList.emplace_back(name, minAmount, maxAmount);
519 }
520 return true;
521}
522
523bool AreaSpawnEvent::executeEvent()
524{
525 for (const MonsterSpawn& spawn : spawnList) {
526 uint32_t amount = uniform_random(spawn.minAmount, spawn.maxAmount);
527 for (uint32_t i = 0; i < amount; ++i) {
528 Monster* monster = Monster::createMonster(spawn.name);
529 if (!monster) {
530 std::cout << "[Error - AreaSpawnEvent::executeEvent] Can't create monster " << spawn.name << std::endl;
531 return false;
532 }
533
534 bool success = false;
535 for (int32_t tries = 0; tries < MAXIMUM_TRIES_PER_MONSTER; tries++) {
536 Tile* tile = g_game.map.getTile(uniform_random(fromPos.x, toPos.x), uniform_random(fromPos.y, toPos.y), uniform_random(fromPos.z, toPos.z));
537 if (tile && !tile->isMoveableBlocking() && !tile->hasFlag(TILESTATE_PROTECTIONZONE) && tile->getTopCreature() == nullptr && g_game.placeCreature(monster, tile->getPosition(), false, true)) {
538 success = true;
539 break;
540 }
541 }
542
543 if (!success) {
544 delete monster;
545 }
546 }
547 }
548 return true;
549}
550
551bool ScriptEvent::configureRaidEvent(const pugi::xml_node& eventNode)
552{
553 if (!RaidEvent::configureRaidEvent(eventNode)) {
554 return false;
555 }
556
557 pugi::xml_attribute scriptAttribute = eventNode.attribute("script");
558 if (!scriptAttribute) {
559 std::cout << "Error: [ScriptEvent::configureRaidEvent] No script file found for raid" << std::endl;
560 return false;
561 }
562
563 if (!loadScript("data/raids/scripts/" + std::string(scriptAttribute.as_string()))) {
564 std::cout << "Error: [ScriptEvent::configureRaidEvent] Can not load raid script." << std::endl;
565 return false;
566 }
567 return true;
568}
569
570std::string ScriptEvent::getScriptEventName() const
571{
572 return "onRaid";
573}
574
575bool ScriptEvent::executeEvent()
576{
577 //onRaid()
578 if (!scriptInterface->reserveScriptEnv()) {
579 std::cout << "[Error - ScriptEvent::onRaid] Call stack overflow" << std::endl;
580 return false;
581 }
582
583 ScriptEnvironment* env = scriptInterface->getScriptEnv();
584 env->setScriptId(scriptId, scriptInterface);
585
586 scriptInterface->pushFunction(scriptId);
587
588 return scriptInterface->callFunction(0);
589}
590
Top