Server upload

RD Core / Commit #2

Commit Server upload

Alex Alex committed 18/10/2026 23:38 main Patch
1,106 files +223,404 -0
A src/protocolgame.h +405-0 View file
@@ -0,0 +1,405 @@
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+#ifndef FS_PROTOCOLGAME_H_FACA2A2D1A9348B78E8FD7E8003EBB87
21+#define FS_PROTOCOLGAME_H_FACA2A2D1A9348B78E8FD7E8003EBB87
22+
23+#include "chat.h"
24+#include "creature.h"
25+#include "protocol.h"
26+#include "tasks.h"
27+#include "walkmatrix.h"
28+
29+class NetworkMessage;
30+class Player;
31+class Game;
32+class House;
33+class Container;
34+class Tile;
35+class Connection;
36+class Quest;
37+class ProtocolGame;
38+using ProtocolGame_ptr = std::shared_ptr<ProtocolGame>;
39+
40+extern Game g_game;
41+
42+struct TextMessage {
43+ MessageClasses type = MESSAGE_STATUS_DEFAULT;
44+ std::string text;
45+ Position position;
46+ uint16_t channelId;
47+ struct {
48+ int32_t value = 0;
49+ TextColor_t color;
50+ } primary, secondary;
51+
52+ TextMessage() = default;
53+ TextMessage(MessageClasses type, std::string text)
54+ : type(type), text(std::move(text)) {}
55+};
56+
57+class InputMessage {
58+public:
59+ // Define los miembros y métodos de InputMessage aquí
60+ // ...
61+};
62+
63+class ProtocolGame final : public Protocol {
64+public:
65+ // static protocol information
66+ enum { server_sends_first = true };
67+ enum { protocol_identifier = 0 }; // Not required as we send first
68+ enum { use_checksum = true };
69+ static const char *protocol_name() { return "gameworld protocol"; }
70+
71+ explicit ProtocolGame(Connection_ptr connection) : Protocol(connection) {}
72+
73+ void login(const std::string &name, uint32_t accountId,
74+ OperatingSystem_t operatingSystem);
75+ void logout(bool displayEffect, bool forced);
76+
77+ uint16_t getVersion() const { return version; }
78+
79+private:
80+ ProtocolGame_ptr getThis() {
81+ return std::static_pointer_cast<ProtocolGame>(shared_from_this());
82+ }
83+ void connect(uint32_t playerId, OperatingSystem_t operatingSystem);
84+ void disconnectClient(const std::string &message) const;
85+ void writeToOutputBuffer(const NetworkMessage &msg);
86+
87+ void release() override;
88+
89+ void checkCreatureAsKnown(uint32_t id, bool &known, uint32_t &removedKnown);
90+
91+ bool canSee(int32_t x, int32_t y, int32_t z) const;
92+ bool canSee(const Creature *) const;
93+ bool canSee(const Position &pos) const;
94+
95+ // we have all the parse methods
96+ void parsePacket(NetworkMessage &msg) override;
97+ void onRecvFirstMessage(NetworkMessage &msg) override;
98+ void onConnect() override;
99+
100+ // Parse methods
101+ void parseAutoWalk(NetworkMessage &msg);
102+ void parseSetOutfit(NetworkMessage &msg);
103+ void parseSay(NetworkMessage &msg);
104+ void parseLookAt(NetworkMessage &msg);
105+ void parseLookInBattleList(NetworkMessage &msg);
106+ void parseFightModes(NetworkMessage &msg);
107+ void parseAttack(NetworkMessage &msg);
108+ void parseFollow(NetworkMessage &msg);
109+ void parseEquipObject(NetworkMessage &msg);
110+
111+ void parseBugReport(NetworkMessage &msg);
112+ void parseDebugAssert(NetworkMessage &msg);
113+ void parseRuleViolationReport(NetworkMessage &msg);
114+
115+ void parseThrow(NetworkMessage &msg);
116+ void parseUseItemEx(NetworkMessage &msg);
117+ void parseUseWithCreature(NetworkMessage &msg);
118+ void parseUseItem(NetworkMessage &msg);
119+ void parseCloseContainer(NetworkMessage &msg);
120+ void parseUpArrowContainer(NetworkMessage &msg);
121+ void parseUpdateContainer(NetworkMessage &msg);
122+ void parseTextWindow(NetworkMessage &msg);
123+ void parseHouseWindow(NetworkMessage &msg);
124+ void parseWrapItem(NetworkMessage &msg);
125+
126+ void parseLookInShop(NetworkMessage &msg);
127+ void parsePlayerPurchase(NetworkMessage &msg);
128+ void parsePlayerSale(NetworkMessage &msg);
129+
130+ void parseQuestLine(NetworkMessage &msg);
131+
132+ void parseInviteToParty(NetworkMessage &msg);
133+ void parseJoinParty(NetworkMessage &msg);
134+ void parseRevokePartyInvite(NetworkMessage &msg);
135+ void parsePassPartyLeadership(NetworkMessage &msg);
136+ void parseEnableSharedPartyExperience(NetworkMessage &msg);
137+
138+ void parseToggleMount(NetworkMessage &msg);
139+
140+ void parseModalWindowAnswer(NetworkMessage &msg);
141+
142+ void parseBrowseField(NetworkMessage &msg);
143+ void parseSeekInContainer(NetworkMessage &msg);
144+
145+ // trade methods
146+ void parseRequestTrade(NetworkMessage &msg);
147+ void parseLookInTrade(NetworkMessage &msg);
148+
149+ // market methods
150+ void parseMarketLeave();
151+ void parseMarketBrowse(NetworkMessage &msg);
152+ void parseMarketCreateOffer(NetworkMessage &msg);
153+ void parseMarketCancelOffer(NetworkMessage &msg);
154+ void parseMarketAcceptOffer(NetworkMessage &msg);
155+
156+ // VIP methods
157+ void parseAddVip(NetworkMessage &msg);
158+ void parseRemoveVip(NetworkMessage &msg);
159+ void parseEditVip(NetworkMessage &msg);
160+
161+ void parseRotateItem(NetworkMessage &msg);
162+
163+ // Channel tabs
164+ void parseChannelInvite(NetworkMessage &msg);
165+ void parseChannelExclude(NetworkMessage &msg);
166+ void parseOpenChannel(NetworkMessage &msg);
167+ void parseOpenPrivateChannel(NetworkMessage &msg);
168+ void parseCloseChannel(NetworkMessage &msg);
169+
170+ // Send functions
171+ void sendChannelMessage(const std::string &author, const std::string &text,
172+ SpeakClasses type, uint16_t channel);
173+ void sendChannelEvent(uint16_t channelId, const std::string &playerName,
174+ ChannelEvent_t channelEvent);
175+ void sendClosePrivate(uint16_t channelId);
176+ void sendCreatePrivateChannel(uint16_t channelId,
177+ const std::string &channelName);
178+ void sendChannelsDialog();
179+ void sendChannel(uint16_t channelId, const std::string &channelName,
180+ const UsersMap *channelUsers,
181+ const InvitedMap *invitedUsers);
182+ void sendOpenPrivateChannel(const std::string &receiver);
183+ void sendToChannel(const Creature *creature, SpeakClasses type,
184+ const std::string &text, uint16_t channelId);
185+ void sendPrivateMessage(const Player *speaker, SpeakClasses type,
186+ const std::string &text);
187+ void sendIcons(uint16_t icons);
188+ void sendFYIBox(const std::string &message);
189+
190+ void sendDistanceShoot(const Position &from, const Position &to,
191+ uint8_t type);
192+ void sendMagicEffect(const Position &pos, uint16_t type);
193+ void sendCreatureHealth(const Creature *creature);
194+ void sendSkills();
195+ void sendPing();
196+ void sendPingBack();
197+ void sendCreatureTurn(const Creature *creature, uint32_t stackPos);
198+ void sendCreatureSay(const Creature *creature, SpeakClasses type,
199+ const std::string &text, const Position *pos = nullptr);
200+
201+ void sendQuestLog();
202+ void sendQuestLine(const Quest *quest);
203+
204+ void sendCancelWalk();
205+ void sendChangeSpeed(const Creature *creature, uint32_t speed);
206+ void sendCancelTarget();
207+ void sendCreatureOutfit(const Creature *creature, const Outfit_t &outfit);
208+ void sendCreatureAttackPulse(const Creature *creature, const Outfit_t &outfit,
209+ uint32_t duration);
210+ void sendStats();
211+ void sendBasicData();
212+ void sendTextMessage(const TextMessage &message);
213+ void sendReLoginWindow(uint8_t unfairFightReduction);
214+
215+ void sendTutorial(uint8_t tutorialId);
216+ void sendAddMarker(const Position &pos, uint8_t markType,
217+ const std::string &desc);
218+
219+ void sendCreatureWalkthrough(const Creature *creature, bool walkthrough);
220+ void sendCreatureShield(const Creature *creature);
221+ void sendCreatureSkull(const Creature *creature);
222+ void sendCreatureType(uint32_t creatureId, uint8_t creatureType);
223+ void sendCreatureHelpers(uint32_t creatureId, uint16_t helpers);
224+
225+ void sendShop(Npc *npc, const ShopInfoList &itemList);
226+ void sendCloseShop();
227+ void sendSaleItemList(const std::list<ShopInfo> &shop);
228+ void sendMarketEnter(uint32_t depotId);
229+ void sendMarketLeave();
230+ void sendMarketBrowseItem(uint16_t itemId, const MarketOfferList &buyOffers,
231+ const MarketOfferList &sellOffers);
232+ void sendMarketAcceptOffer(const MarketOfferEx &offer);
233+ void sendMarketBrowseOwnOffers(const MarketOfferList &buyOffers,
234+ const MarketOfferList &sellOffers);
235+ void sendMarketCancelOffer(const MarketOfferEx &offer);
236+ void sendMarketBrowseOwnHistory(const HistoryMarketOfferList &buyOffers,
237+ const HistoryMarketOfferList &sellOffers);
238+ void sendMarketDetail(uint16_t itemId);
239+ void sendTradeItemRequest(const std::string &traderName, const Item *item,
240+ bool ack);
241+ void sendCloseTrade();
242+
243+ void sendTextWindow(uint32_t windowTextId, Item *item, uint16_t maxlen,
244+ bool canWrite);
245+ void sendTextWindow(uint32_t windowTextId, uint32_t itemId,
246+ const std::string &text);
247+ void sendHouseWindow(uint32_t windowTextId, const std::string &text);
248+ void sendOutfitWindow();
249+
250+ void sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus);
251+ void sendVIP(uint32_t guid, const std::string &name,
252+ const std::string &description, uint32_t icon, bool notify,
253+ VipStatus_t status);
254+ void sendVIPEntries();
255+
256+ void sendPendingStateEntered();
257+ void sendEnterWorld();
258+
259+ void sendFightModes();
260+
261+ void sendCreatureLight(const Creature *creature);
262+ void sendWorldLight(LightInfo lightInfo);
263+
264+ void sendCreatureSquare(const Creature *creature, SquareColor_t color);
265+
266+ void sendSpellCooldown(uint8_t spellId, uint32_t time);
267+ void sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time);
268+
269+ // tiles
270+ void sendMapDescription(const Position &pos);
271+
272+ void sendAddTileItem(const Position &pos, uint32_t stackpos,
273+ const Item *item);
274+ void sendUpdateTileItem(const Position &pos, uint32_t stackpos,
275+ const Item *item);
276+ void sendRemoveTileThing(const Position &pos, uint32_t stackpos);
277+ void sendUpdateTile(const Tile *tile, const Position &pos);
278+
279+ void sendAddCreature(const Creature *creature, const Position &pos,
280+ int32_t stackpos, bool isLogin);
281+ void sendMoveCreature(const Creature *creature, const Position &newPos,
282+ int32_t newStackPos, const Position &oldPos,
283+ int32_t oldStackPos, bool teleport);
284+
285+ // containers
286+ void sendAddContainerItem(uint8_t cid, uint16_t slot, const Item *item);
287+ void sendUpdateContainerItem(uint8_t cid, uint16_t slot, const Item *item);
288+ void sendRemoveContainerItem(uint8_t cid, uint16_t slot,
289+ const Item *lastItem);
290+
291+ void sendContainer(uint8_t cid, const Container *container, bool hasParent,
292+ uint16_t firstIndex);
293+ void sendCloseContainer(uint8_t cid);
294+
295+ // inventory
296+ void sendInventoryItem(slots_t slot, const Item *item);
297+ void sendItems();
298+
299+ // messages
300+ void sendModalWindow(const ModalWindow &modalWindow);
301+
302+ // Help functions
303+
304+ // translate a tile to clientreadable format
305+ void GetTileDescription(const Tile *tile, NetworkMessage &msg);
306+
307+ // translate a floor to clientreadable format
308+ void GetFloorDescription(NetworkMessage &msg, int32_t x, int32_t y, int32_t z,
309+ int32_t width, int32_t height, int32_t offset,
310+ int32_t &skip);
311+
312+ // translate a map area to clientreadable format
313+ void GetMapDescription(int32_t x, int32_t y, int32_t z, int32_t width,
314+ int32_t height, NetworkMessage &msg);
315+
316+ void AddCreature(NetworkMessage &msg, const Creature *creature, bool known,
317+ uint32_t remove);
318+ void AddPlayerStats(NetworkMessage &msg);
319+ void AddOutfit(NetworkMessage &msg, const Outfit_t &outfit,
320+ uint16_t lookAttack = 0);
321+ void AddPlayerSkills(NetworkMessage &msg);
322+ void AddWorldLight(NetworkMessage &msg, LightInfo lightInfo);
323+ void AddCreatureLight(NetworkMessage &msg, const Creature *creature);
324+
325+ // tiles
326+ static void RemoveTileThing(NetworkMessage &msg, const Position &pos,
327+ uint32_t stackpos);
328+
329+ void MoveUpCreature(NetworkMessage &msg, const Creature *creature,
330+ const Position &newPos, const Position &oldPos);
331+ void MoveDownCreature(NetworkMessage &msg, const Creature *creature,
332+ const Position &newPos, const Position &oldPos);
333+
334+ // shop
335+ void AddShopItem(NetworkMessage &msg, const ShopInfo &item);
336+
337+ // otclient
338+ void parseExtendedOpcode(NetworkMessage &msg);
339+
340+ // OTCv8
341+ void sendFeatures();
342+
343+ void sendFloorDescription(const Position &pos, int floor);
344+ void parseChangeAwareRange(NetworkMessage &msg);
345+ void updateAwareRange(int width, int height);
346+ void sendAwareRange();
347+
348+ void parseNewWalking(NetworkMessage &msg);
349+ void checkPredictiveWalking(const Position &pos);
350+ void sendPredictiveCancel(const Position &pos, int value);
351+ void sendWalkId();
352+ void sendNewCancelWalk();
353+
354+ void parseNewPing(NetworkMessage &msg);
355+ void sendNewPing(uint32_t pingId);
356+
357+ void sendEffect(uint16_t effectId);
358+
359+ void parseMagicEffect(const std::shared_ptr<InputMessage> &msg);
360+
361+ friend class Player;
362+
363+ // Helpers so we don't need to bind every time
364+ template <typename Callable, typename... Args>
365+ void addGameTask(Callable function, Args &&...args) {
366+ g_dispatcher.addTask(
367+ createTask(std::bind(function, &g_game, std::forward<Args>(args)...)));
368+ }
369+
370+ template <typename Callable, typename... Args>
371+ void addGameTaskTimed(uint32_t delay, Callable function, Args &&...args) {
372+ g_dispatcher.addTask(createTask(
373+ delay, std::bind(function, &g_game, std::forward<Args>(args)...)));
374+ }
375+
376+ std::unordered_set<uint32_t> knownCreatureSet;
377+ Player *player = nullptr;
378+
379+ uint32_t eventConnect = 0;
380+ uint32_t challengeTimestamp = 0;
381+ uint16_t version = CLIENT_VERSION_MIN;
382+
383+ uint8_t challengeRandom = 0;
384+
385+ bool debugAssertSent = false;
386+ bool acceptPackets = false;
387+
388+ uint16_t otclientV8 = 0;
389+ struct AwareRange {
390+ int width = 17;
391+ int height = 13;
392+
393+ int left() const { return width / 2; }
394+ int right() const { return 1 + width / 2; }
395+ int top() const { return height / 2; }
396+ int bottom() const { return 1 + height / 2; }
397+ int horizontal() const { return width + 1; }
398+ int vertical() const { return height + 1; }
399+ } awareRange;
400+
401+ uint32_t walkId = 0;
402+ WalkMatrix walkMatrix;
403+};
404+
405+#endif
A src/protocollogin.cpp +252-0 View file
@@ -0,0 +1,252 @@
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 "protocollogin.h"
23+
24+#include "outputmessage.h"
25+#include "tasks.h"
26+
27+#include "configmanager.h"
28+#include "iologindata.h"
29+#include "ban.h"
30+#include "game.h"
31+
32+extern ConfigManager g_config;
33+extern Game g_game;
34+
35+void ProtocolLogin::disconnectClient(const std::string& message, uint16_t version)
36+{
37+ auto output = OutputMessagePool::getOutputMessage();
38+
39+ output->addByte(version >= 1076 ? 0x0B : 0x0A);
40+ output->addString(message);
41+ send(output);
42+
43+ disconnect();
44+}
45+
46+void ProtocolLogin::getCharacterList(const std::string& accountName, const std::string& password, const std::string& token, uint16_t version)
47+{
48+ Account account;
49+ if (!IOLoginData::loginserverAuthentication(accountName, password, account)) {
50+ disconnectClient("Account name or password is not correct.", version);
51+ return;
52+ }
53+
54+ uint32_t ticks = time(nullptr) / AUTHENTICATOR_PERIOD;
55+
56+ auto output = OutputMessagePool::getOutputMessage();
57+ if (!account.key.empty()) {
58+ if (token.empty() || !(token == generateToken(account.key, ticks) || token == generateToken(account.key, ticks - 1) || token == generateToken(account.key, ticks + 1))) {
59+ output->addByte(0x0D);
60+ output->addByte(0);
61+ send(output);
62+ disconnect();
63+ return;
64+ }
65+ output->addByte(0x0C);
66+ output->addByte(0);
67+ }
68+
69+ //Update premium days
70+ Game::updatePremium(account);
71+
72+ const std::string& motd = g_config.getString(ConfigManager::MOTD);
73+ if (!motd.empty()) {
74+ //Add MOTD
75+ output->addByte(0x14);
76+
77+ std::ostringstream ss;
78+ ss << g_game.getMotdNum() << "\n" << motd;
79+ output->addString(ss.str());
80+ }
81+
82+ //Add session key
83+ output->addByte(0x28);
84+ output->addString(accountName + "\n" + password + "\n" + token + "\n" + std::to_string(ticks));
85+
86+ //Add char list
87+ output->addByte(0x64);
88+
89+/* uint8_t size = std::min<size_t>(std::numeric_limits<uint8_t>::max(), account.characters.size()); */
90+ uint8_t characters = std::min<size_t>(std::numeric_limits<uint8_t>::max() - 1, account.characters.size());
91+
92+ if (g_config.getBoolean(ConfigManager::ONLINE_OFFLINE_CHARLIST)) {
93+ output->addByte(2); // number of worlds
94+
95+ for (uint8_t i = 0; i < 2; i++) {
96+ output->addByte(i); // world id
97+ output->addString(i == 0 ? "Offline" : "Online");
98+ output->addString(g_config.getString(ConfigManager::IP));
99+ output->add<uint16_t>(g_config.getNumber(ConfigManager::GAME_PORT));
100+ output->addByte(0);
101+ }
102+ } else {
103+ output->addByte(1); // number of worlds
104+ output->addByte(0); // world id
105+ output->addString(g_config.getString(ConfigManager::SERVER_NAME));
106+ output->addString(g_config.getString(ConfigManager::IP));
107+ output->add<uint16_t>(g_config.getNumber(ConfigManager::GAME_PORT));
108+ output->addByte(0);
109+ }
110+
111+/* output->addByte(size);
112+ for (uint8_t i = 0; i < size; i++) {
113+ const std::string& character = account.characters[i];
114+ if (g_config.getBoolean(ConfigManager::ONLINE_OFFLINE_CHARLIST)) {
115+ output->addByte(g_game.getPlayerByName(character) ? 1 : 0);
116+ } else {
117+ output->addByte(0);
118+ }
119+ output->addString(character);
120+ } */
121+
122+ output->addByte(characters);
123+ for (std::vector<Character>::iterator it = account.characters.begin(); it != account.characters.end(); it++) {
124+ output->addByte(0x00);
125+ output->addString(it->name);
126+ output->add<uint32_t>(it->level);
127+ output->add<uint16_t>(it->vocation);
128+ output->addOutfit(it->outfit);
129+ }
130+
131+ //Add premium days
132+ output->addByte(0);
133+ if (g_config.getBoolean(ConfigManager::FREE_PREMIUM)) {
134+ output->addByte(1);
135+ output->add<uint32_t>(0);
136+ } else {
137+ output->addByte(account.premiumDays > 0 ? 1 : 0);
138+ output->add<uint32_t>(time(nullptr) + (account.premiumDays * 86400));
139+ }
140+
141+ send(output);
142+
143+ disconnect();
144+}
145+
146+void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg)
147+{
148+ if (g_game.getGameState() == GAME_STATE_SHUTDOWN) {
149+ disconnect();
150+ return;
151+ }
152+
153+ msg.skipBytes(2); // client OS
154+
155+ uint16_t version = msg.get<uint16_t>();
156+ if (version >= 971) {
157+ msg.skipBytes(17);
158+ } else {
159+ msg.skipBytes(12);
160+ }
161+ /*
162+ * Skipped bytes:
163+ * 4 bytes: protocolVersion
164+ * 12 bytes: dat, spr, pic signatures (4 bytes each)
165+ * 1 byte: 0
166+ */
167+
168+ if (version <= 760) {
169+ std::ostringstream ss;
170+ ss << "Only clients with protocol " << CLIENT_VERSION_STR << " allowed!";
171+ disconnectClient(ss.str(), version);
172+ return;
173+ }
174+
175+ if (!Protocol::RSA_decrypt(msg)) {
176+ disconnect();
177+ return;
178+ }
179+
180+ xtea::key key;
181+ key[0] = msg.get<uint32_t>();
182+ key[1] = msg.get<uint32_t>();
183+ key[2] = msg.get<uint32_t>();
184+ key[3] = msg.get<uint32_t>();
185+ enableXTEAEncryption();
186+ setXTEAKey(std::move(key));
187+
188+ if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) {
189+ std::ostringstream ss;
190+ ss << "Only clients with protocol " << CLIENT_VERSION_STR << " allowed!";
191+ disconnectClient(ss.str(), version);
192+ return;
193+ }
194+
195+ if (g_game.getGameState() == GAME_STATE_STARTUP) {
196+ disconnectClient("Gameworld is starting up. Please wait.", version);
197+ return;
198+ }
199+
200+ if (g_game.getGameState() == GAME_STATE_MAINTAIN) {
201+ disconnectClient("Gameworld is under maintenance.\nPlease re-connect in a while.", version);
202+ return;
203+ }
204+
205+ BanInfo banInfo;
206+ auto connection = getConnection();
207+ if (!connection) {
208+ return;
209+ }
210+
211+ if (IOBan::isIpBanned(connection->getIP(), banInfo)) {
212+ if (banInfo.reason.empty()) {
213+ banInfo.reason = "(none)";
214+ }
215+
216+ std::ostringstream ss;
217+ ss << "Your IP has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason;
218+ disconnectClient(ss.str(), version);
219+ return;
220+ }
221+
222+ std::string accountName = msg.getString();
223+ if (accountName.empty()) {
224+ disconnectClient("Invalid account name.", version);
225+ return;
226+ }
227+
228+ std::string password = msg.getString();
229+ if (password.empty()) {
230+ disconnectClient("Invalid password.", version);
231+ return;
232+ }
233+
234+ // OTCv8 version detection
235+ uint16_t otclientV8 = 0;
236+ uint16_t otcV8StringLength = msg.get<uint16_t>();
237+ if(otcV8StringLength == 5 && msg.getString(5) == "OTCv8") {
238+ otclientV8 = msg.get<uint16_t>(); // 253, 260, 261, ...
239+ }
240+
241+ // read authenticator token and stay logged in flag from last 128 bytes
242+ msg.skipBytes((msg.getLength() - 128) - msg.getBufferPosition());
243+ if (!Protocol::RSA_decrypt(msg)) {
244+ disconnectClient("Invalid authentification token.", version);
245+ return;
246+ }
247+
248+ std::string authToken = msg.getString();
249+
250+ auto thisPtr = std::static_pointer_cast<ProtocolLogin>(shared_from_this());
251+ g_dispatcher.addTask(createTask(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountName, password, authToken, version)));
252+}
A src/protocollogin.h +49-0 View file
@@ -0,0 +1,49 @@
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+#ifndef FS_PROTOCOLLOGIN_H_1238F4B473074DF2ABC595C29E81C46D
21+#define FS_PROTOCOLLOGIN_H_1238F4B473074DF2ABC595C29E81C46D
22+
23+#include "protocol.h"
24+
25+class NetworkMessage;
26+class OutputMessage;
27+
28+class ProtocolLogin : public Protocol
29+{
30+ public:
31+ // static protocol information
32+ enum {server_sends_first = false};
33+ enum {protocol_identifier = 0x01};
34+ enum {use_checksum = true};
35+ static const char* protocol_name() {
36+ return "login protocol";
37+ }
38+
39+ explicit ProtocolLogin(Connection_ptr connection) : Protocol(connection) {}
40+
41+ void onRecvFirstMessage(NetworkMessage& msg) override;
42+
43+ private:
44+ void disconnectClient(const std::string& message, uint16_t version);
45+
46+ void getCharacterList(const std::string& accountName, const std::string& password, const std::string& token, uint16_t version);
47+};
48+
49+#endif
A src/protocolold.cpp +77-0 View file
@@ -0,0 +1,77 @@
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 "protocolold.h"
23+#include "outputmessage.h"
24+
25+#include "game.h"
26+
27+extern Game g_game;
28+
29+void ProtocolOld::disconnectClient(const std::string& message)
30+{
31+ auto output = OutputMessagePool::getOutputMessage();
32+ output->addByte(0x0A);
33+ output->addString(message);
34+ send(output);
35+
36+ disconnect();
37+}
38+
39+void ProtocolOld::onRecvFirstMessage(NetworkMessage& msg)
40+{
41+ if (g_game.getGameState() == GAME_STATE_SHUTDOWN) {
42+ disconnect();
43+ return;
44+ }
45+
46+ /*uint16_t clientOS =*/ msg.get<uint16_t>();
47+ uint16_t version = msg.get<uint16_t>();
48+ msg.skipBytes(12);
49+
50+ if (version <= 760) {
51+ std::ostringstream ss;
52+ ss << "Only clients with protocol " << CLIENT_VERSION_STR << " allowed!";
53+ disconnectClient(ss.str());
54+ return;
55+ }
56+
57+ if (!Protocol::RSA_decrypt(msg)) {
58+ disconnect();
59+ return;
60+ }
61+
62+ xtea::key key;
63+ key[0] = msg.get<uint32_t>();
64+ key[1] = msg.get<uint32_t>();
65+ key[2] = msg.get<uint32_t>();
66+ key[3] = msg.get<uint32_t>();
67+ enableXTEAEncryption();
68+ setXTEAKey(std::move(key));
69+
70+ if (version <= 822) {
71+ disableChecksum();
72+ }
73+
74+ std::ostringstream ss;
75+ ss << "Only clients with protocol " << CLIENT_VERSION_STR << " allowed!";
76+ disconnectClient(ss.str());
77+}
A src/protocolold.h +46-0 View file
@@ -0,0 +1,46 @@
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+#ifndef FS_PROTOCOLOLD_H_5487B862FE144AE0904D098A3238E161
21+#define FS_PROTOCOLOLD_H_5487B862FE144AE0904D098A3238E161
22+
23+#include "protocol.h"
24+
25+class NetworkMessage;
26+
27+class ProtocolOld final : public Protocol
28+{
29+ public:
30+ // static protocol information
31+ enum {server_sends_first = false};
32+ enum {protocol_identifier = 0x01};
33+ enum {use_checksum = false};
34+ static const char* protocol_name() {
35+ return "old login protocol";
36+ }
37+
38+ explicit ProtocolOld(Connection_ptr connection) : Protocol(connection) {}
39+
40+ void onRecvFirstMessage(NetworkMessage& msg) override;
41+
42+ private:
43+ void disconnectClient(const std::string& message);
44+};
45+
46+#endif
A src/protocolstatus.cpp +228-0 View file
@@ -0,0 +1,228 @@
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 "protocolstatus.h"
23+#include "configmanager.h"
24+#include "game.h"
25+#include "outputmessage.h"
26+
27+extern ConfigManager g_config;
28+extern Game g_game;
29+
30+std::map<uint32_t, int64_t> ProtocolStatus::ipConnectMap;
31+const uint64_t ProtocolStatus::start = OTSYS_TIME();
32+
33+enum RequestedInfo_t : uint16_t {
34+ REQUEST_BASIC_SERVER_INFO = 1 << 0,
35+ REQUEST_OWNER_SERVER_INFO = 1 << 1,
36+ REQUEST_MISC_SERVER_INFO = 1 << 2,
37+ REQUEST_PLAYERS_INFO = 1 << 3,
38+ REQUEST_MAP_INFO = 1 << 4,
39+ REQUEST_EXT_PLAYERS_INFO = 1 << 5,
40+ REQUEST_PLAYER_STATUS_INFO = 1 << 6,
41+ REQUEST_SERVER_SOFTWARE_INFO = 1 << 7,
42+};
43+
44+void ProtocolStatus::onRecvFirstMessage(NetworkMessage& msg)
45+{
46+ uint32_t ip = getIP();
47+ if (ip != 0x0100007F) {
48+ std::string ipStr = convertIPToString(ip);
49+ if (ipStr != g_config.getString(ConfigManager::IP)) {
50+ std::map<uint32_t, int64_t>::const_iterator it = ipConnectMap.find(ip);
51+ if (it != ipConnectMap.end() && (OTSYS_TIME() < (it->second + g_config.getNumber(ConfigManager::STATUSQUERY_TIMEOUT)))) {
52+ disconnect();
53+ return;
54+ }
55+ }
56+ }
57+
58+ ipConnectMap[ip] = OTSYS_TIME();
59+
60+ switch (msg.getByte()) {
61+ //XML info protocol
62+ case 0xFF: {
63+ if (msg.getString(4) == "info") {
64+ g_dispatcher.addTask(createTask(std::bind(&ProtocolStatus::sendStatusString,
65+ std::static_pointer_cast<ProtocolStatus>(shared_from_this()))));
66+ return;
67+ }
68+ break;
69+ }
70+
71+ //Another ServerInfo protocol
72+ case 0x01: {
73+ uint16_t requestedInfo = msg.get<uint16_t>(); // only a Byte is necessary, though we could add new info here
74+ std::string characterName;
75+ if (requestedInfo & REQUEST_PLAYER_STATUS_INFO) {
76+ characterName = msg.getString();
77+ }
78+ g_dispatcher.addTask(createTask(std::bind(&ProtocolStatus::sendInfo, std::static_pointer_cast<ProtocolStatus>(shared_from_this()),
79+ requestedInfo, characterName)));
80+ return;
81+ }
82+
83+ default:
84+ break;
85+ }
86+ disconnect();
87+}
88+
89+void ProtocolStatus::sendStatusString()
90+{
91+ auto output = OutputMessagePool::getOutputMessage();
92+
93+ setRawMessages(true);
94+
95+ pugi::xml_document doc;
96+
97+ pugi::xml_node decl = doc.prepend_child(pugi::node_declaration);
98+ decl.append_attribute("version") = "1.0";
99+
100+ pugi::xml_node tsqp = doc.append_child("tsqp");
101+ tsqp.append_attribute("version") = "1.0";
102+
103+ pugi::xml_node serverinfo = tsqp.append_child("serverinfo");
104+ uint64_t uptime = (OTSYS_TIME() - ProtocolStatus::start) / 1000;
105+ serverinfo.append_attribute("uptime") = std::to_string(uptime).c_str();
106+ serverinfo.append_attribute("ip") = g_config.getString(ConfigManager::IP).c_str();
107+ serverinfo.append_attribute("servername") = g_config.getString(ConfigManager::SERVER_NAME).c_str();
108+ serverinfo.append_attribute("port") = std::to_string(g_config.getNumber(ConfigManager::LOGIN_PORT)).c_str();
109+ serverinfo.append_attribute("location") = g_config.getString(ConfigManager::LOCATION).c_str();
110+ serverinfo.append_attribute("url") = g_config.getString(ConfigManager::URL).c_str();
111+ serverinfo.append_attribute("server") = STATUS_SERVER_NAME;
112+ serverinfo.append_attribute("version") = STATUS_SERVER_VERSION;
113+ serverinfo.append_attribute("client") = CLIENT_VERSION_STR;
114+
115+ pugi::xml_node owner = tsqp.append_child("owner");
116+ owner.append_attribute("name") = g_config.getString(ConfigManager::OWNER_NAME).c_str();
117+ owner.append_attribute("email") = g_config.getString(ConfigManager::OWNER_EMAIL).c_str();
118+
119+ pugi::xml_node players = tsqp.append_child("players");
120+ players.append_attribute("online") = std::to_string(g_game.getPlayersOnline()).c_str();
121+ players.append_attribute("max") = std::to_string(g_config.getNumber(ConfigManager::MAX_PLAYERS)).c_str();
122+ players.append_attribute("peak") = std::to_string(g_game.getPlayersRecord()).c_str();
123+
124+ pugi::xml_node monsters = tsqp.append_child("monsters");
125+ monsters.append_attribute("total") = std::to_string(g_game.getMonstersOnline()).c_str();
126+
127+ pugi::xml_node npcs = tsqp.append_child("npcs");
128+ npcs.append_attribute("total") = std::to_string(g_game.getNpcsOnline()).c_str();
129+
130+ pugi::xml_node rates = tsqp.append_child("rates");
131+ rates.append_attribute("experience") = std::to_string(g_config.getNumber(ConfigManager::RATE_EXPERIENCE)).c_str();
132+ rates.append_attribute("skill") = std::to_string(g_config.getNumber(ConfigManager::RATE_SKILL)).c_str();
133+ rates.append_attribute("loot") = std::to_string(g_config.getNumber(ConfigManager::RATE_LOOT)).c_str();
134+ rates.append_attribute("magic") = std::to_string(g_config.getNumber(ConfigManager::RATE_MAGIC)).c_str();
135+ rates.append_attribute("spawn") = std::to_string(g_config.getNumber(ConfigManager::RATE_SPAWN)).c_str();
136+
137+ pugi::xml_node map = tsqp.append_child("map");
138+ map.append_attribute("name") = g_config.getString(ConfigManager::MAP_NAME).c_str();
139+ map.append_attribute("author") = g_config.getString(ConfigManager::MAP_AUTHOR).c_str();
140+
141+ uint32_t mapWidth, mapHeight;
142+ g_game.getMapDimensions(mapWidth, mapHeight);
143+ map.append_attribute("width") = std::to_string(mapWidth).c_str();
144+ map.append_attribute("height") = std::to_string(mapHeight).c_str();
145+
146+ pugi::xml_node motd = tsqp.append_child("motd");
147+ motd.text() = g_config.getString(ConfigManager::MOTD).c_str();
148+
149+ std::ostringstream ss;
150+ doc.save(ss, "", pugi::format_raw);
151+
152+ std::string data = ss.str();
153+ output->addBytes(data.c_str(), data.size());
154+ send(output);
155+ disconnect();
156+}
157+
158+void ProtocolStatus::sendInfo(uint16_t requestedInfo, const std::string& characterName)
159+{
160+ auto output = OutputMessagePool::getOutputMessage();
161+
162+ if (requestedInfo & REQUEST_BASIC_SERVER_INFO) {
163+ output->addByte(0x10);
164+ output->addString(g_config.getString(ConfigManager::SERVER_NAME));
165+ output->addString(g_config.getString(ConfigManager::IP));
166+ output->addString(std::to_string(g_config.getNumber(ConfigManager::LOGIN_PORT)));
167+ }
168+
169+ if (requestedInfo & REQUEST_OWNER_SERVER_INFO) {
170+ output->addByte(0x11);
171+ output->addString(g_config.getString(ConfigManager::OWNER_NAME));
172+ output->addString(g_config.getString(ConfigManager::OWNER_EMAIL));
173+ }
174+
175+ if (requestedInfo & REQUEST_MISC_SERVER_INFO) {
176+ output->addByte(0x12);
177+ output->addString(g_config.getString(ConfigManager::MOTD));
178+ output->addString(g_config.getString(ConfigManager::LOCATION));
179+ output->addString(g_config.getString(ConfigManager::URL));
180+ output->add<uint64_t>((OTSYS_TIME() - ProtocolStatus::start) / 1000);
181+ }
182+
183+ if (requestedInfo & REQUEST_PLAYERS_INFO) {
184+ output->addByte(0x20);
185+ output->add<uint32_t>(g_game.getPlayersOnline());
186+ output->add<uint32_t>(g_config.getNumber(ConfigManager::MAX_PLAYERS));
187+ output->add<uint32_t>(g_game.getPlayersRecord());
188+ }
189+
190+ if (requestedInfo & REQUEST_MAP_INFO) {
191+ output->addByte(0x30);
192+ output->addString(g_config.getString(ConfigManager::MAP_NAME));
193+ output->addString(g_config.getString(ConfigManager::MAP_AUTHOR));
194+ uint32_t mapWidth, mapHeight;
195+ g_game.getMapDimensions(mapWidth, mapHeight);
196+ output->add<uint16_t>(mapWidth);
197+ output->add<uint16_t>(mapHeight);
198+ }
199+
200+ if (requestedInfo & REQUEST_EXT_PLAYERS_INFO) {
201+ output->addByte(0x21); // players info - online players list
202+
203+ const auto& players = g_game.getPlayers();
204+ output->add<uint32_t>(players.size());
205+ for (const auto& it : players) {
206+ output->addString(it.second->getName());
207+ output->add<uint32_t>(it.second->getLevel());
208+ }
209+ }
210+
211+ if (requestedInfo & REQUEST_PLAYER_STATUS_INFO) {
212+ output->addByte(0x22); // players info - online status info of a player
213+ if (g_game.getPlayerByName(characterName) != nullptr) {
214+ output->addByte(0x01);
215+ } else {
216+ output->addByte(0x00);
217+ }
218+ }
219+
220+ if (requestedInfo & REQUEST_SERVER_SOFTWARE_INFO) {
221+ output->addByte(0x23); // server software info
222+ output->addString(STATUS_SERVER_NAME);
223+ output->addString(STATUS_SERVER_VERSION);
224+ output->addString(CLIENT_VERSION_STR);
225+ }
226+ send(output);
227+ disconnect();
228+}
A src/protocolstatus.h +50-0 View file
@@ -0,0 +1,50 @@
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+#ifndef FS_STATUS_H_8B28B354D65B4C0483E37AD1CA316EB4
21+#define FS_STATUS_H_8B28B354D65B4C0483E37AD1CA316EB4
22+
23+#include "networkmessage.h"
24+#include "protocol.h"
25+
26+class ProtocolStatus final : public Protocol
27+{
28+ public:
29+ // static protocol information
30+ enum {server_sends_first = false};
31+ enum {protocol_identifier = 0xFF};
32+ enum {use_checksum = false};
33+ static const char* protocol_name() {
34+ return "status protocol";
35+ }
36+
37+ explicit ProtocolStatus(Connection_ptr connection) : Protocol(connection) {}
38+
39+ void onRecvFirstMessage(NetworkMessage& msg) override;
40+
41+ void sendStatusString();
42+ void sendInfo(uint16_t requestedInfo, const std::string& characterName);
43+
44+ static const uint64_t start;
45+
46+ private:
47+ static std::map<uint32_t, int64_t> ipConnectMap;
48+};
49+
50+#endif
A src/pugicast.h +39-0 View file
@@ -0,0 +1,39 @@
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+#ifndef FS_PUGICAST_H_07810DF7954D411EB14A16C3ED2A7548
21+#define FS_PUGICAST_H_07810DF7954D411EB14A16C3ED2A7548
22+
23+#include <boost/lexical_cast.hpp>
24+
25+namespace pugi {
26+ template<typename T>
27+ T cast(const pugi::char_t* str)
28+ {
29+ T value;
30+ try {
31+ value = boost::lexical_cast<T>(str);
32+ } catch (boost::bad_lexical_cast&) {
33+ value = T();
34+ }
35+ return value;
36+ }
37+}
38+
39+#endif
A src/quests.cpp +228-0 View file
@@ -0,0 +1,228 @@
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 "quests.h"
23+
24+#include "pugicast.h"
25+
26+std::string Mission::getDescription(Player* player) const
27+{
28+ int32_t value;
29+ player->getStorageValue(storageID, value);
30+
31+ if (!mainDescription.empty()) {
32+ std::string desc = mainDescription;
33+ replaceString(desc, "|STATE|", std::to_string(value));
34+ replaceString(desc, "\\n", "\n");
35+ return desc;
36+ }
37+
38+ if (ignoreEndValue) {
39+ for (int32_t current = endValue; current >= startValue; current--) {
40+ if (value >= current) {
41+ auto sit = descriptions.find(current);
42+ if (sit != descriptions.end()) {
43+ return sit->second;
44+ }
45+ }
46+ }
47+ } else {
48+ for (int32_t current = endValue; current >= startValue; current--) {
49+ if (value == current) {
50+ auto sit = descriptions.find(current);
51+ if (sit != descriptions.end()) {
52+ return sit->second;
53+ }
54+ }
55+ }
56+ }
57+ return "An error has occurred, please contact a gamemaster.";
58+}
59+
60+bool Mission::isStarted(Player* player) const
61+{
62+ if (!player) {
63+ return false;
64+ }
65+
66+ int32_t value;
67+ if (!player->getStorageValue(storageID, value)) {
68+ return false;
69+ }
70+
71+ if (value < startValue) {
72+ return false;
73+ }
74+
75+ if (!ignoreEndValue && value > endValue) {
76+ return false;
77+ }
78+
79+ return true;
80+}
81+
82+bool Mission::isCompleted(Player* player) const
83+{
84+ if (!player) {
85+ return false;
86+ }
87+
88+ int32_t value;
89+ if (!player->getStorageValue(storageID, value)) {
90+ return false;
91+ }
92+
93+ if (ignoreEndValue) {
94+ return value >= endValue;
95+ }
96+
97+ return value == endValue;
98+}
99+
100+std::string Mission::getName(Player* player) const
101+{
102+ if (isCompleted(player)) {
103+ return name + " (completed)";
104+ }
105+ return name;
106+}
107+
108+uint16_t Quest::getMissionsCount(Player* player) const
109+{
110+ uint16_t count = 0;
111+ for (const Mission& mission : missions) {
112+ if (mission.isStarted(player)) {
113+ count++;
114+ }
115+ }
116+ return count;
117+}
118+
119+bool Quest::isCompleted(Player* player) const
120+{
121+ for (const Mission& mission : missions) {
122+ if (!mission.isCompleted(player)) {
123+ return false;
124+ }
125+ }
126+ return true;
127+}
128+
129+bool Quest::isStarted(Player* player) const
130+{
131+ if (!player) {
132+ return false;
133+ }
134+
135+ int32_t value;
136+ if (!player->getStorageValue(startStorageID, value) || value < startStorageValue) {
137+ return false;
138+ }
139+
140+ return true;
141+}
142+
143+bool Quests::reload()
144+{
145+ quests.clear();
146+ return loadFromXml();
147+}
148+
149+bool Quests::loadFromXml()
150+{
151+ pugi::xml_document doc;
152+ pugi::xml_parse_result result = doc.load_file("data/XML/quests.xml");
153+ if (!result) {
154+ printXMLError("Error - Quests::loadFromXml", "data/XML/quests.xml", result);
155+ return false;
156+ }
157+
158+ uint16_t id = 0;
159+ for (auto questNode : doc.child("quests").children()) {
160+ quests.emplace_back(
161+ questNode.attribute("name").as_string(),
162+ ++id,
163+ pugi::cast<int32_t>(questNode.attribute("startstorageid").value()),
164+ pugi::cast<int32_t>(questNode.attribute("startstoragevalue").value())
165+ );
166+ Quest& quest = quests.back();
167+
168+ for (auto missionNode : questNode.children()) {
169+ std::string mainDescription = missionNode.attribute("description").as_string();
170+
171+ quest.missions.emplace_back(
172+ missionNode.attribute("name").as_string(),
173+ pugi::cast<int32_t>(missionNode.attribute("storageid").value()),
174+ pugi::cast<int32_t>(missionNode.attribute("startvalue").value()),
175+ pugi::cast<int32_t>(missionNode.attribute("endvalue").value()),
176+ missionNode.attribute("ignoreendvalue").as_bool()
177+ );
178+ Mission& mission = quest.missions.back();
179+
180+ if (mainDescription.empty()) {
181+ for (auto missionStateNode : missionNode.children()) {
182+ int32_t missionId = pugi::cast<int32_t>(missionStateNode.attribute("id").value());
183+ mission.descriptions.emplace(missionId, missionStateNode.attribute("description").as_string());
184+ }
185+ } else {
186+ mission.mainDescription = mainDescription;
187+ }
188+ }
189+ }
190+ return true;
191+}
192+
193+Quest* Quests::getQuestByID(uint16_t id)
194+{
195+ for (Quest& quest : quests) {
196+ if (quest.id == id) {
197+ return &quest;
198+ }
199+ }
200+ return nullptr;
201+}
202+
203+uint16_t Quests::getQuestsCount(Player* player) const
204+{
205+ uint16_t count = 0;
206+ for (const Quest& quest : quests) {
207+ if (quest.isStarted(player)) {
208+ count++;
209+ }
210+ }
211+ return count;
212+}
213+
214+bool Quests::isQuestStorage(const uint32_t key, const int32_t value, const int32_t oldValue) const
215+{
216+ for (const Quest& quest : quests) {
217+ if (quest.getStartStorageId() == key && quest.getStartStorageValue() == value) {
218+ return true;
219+ }
220+
221+ for (const Mission& mission : quest.getMissions()) {
222+ if (mission.getStorageId() == key && value >= mission.getStartStorageValue() && value <= mission.getEndStorageValue()) {
223+ return mission.mainDescription.empty() || oldValue < mission.getStartStorageValue() || oldValue > mission.getEndStorageValue();
224+ }
225+ }
226+ }
227+ return false;
228+}
A src/quests.h +119-0 View file
@@ -0,0 +1,119 @@
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+#ifndef FS_QUESTS_H_16E44051F23547BE8097F8EA9FCAACA0
21+#define FS_QUESTS_H_16E44051F23547BE8097F8EA9FCAACA0
22+
23+#include "player.h"
24+#include "networkmessage.h"
25+
26+class Mission;
27+class Quest;
28+
29+using MissionsList = std::list<Mission>;
30+using QuestsList = std::list<Quest>;
31+
32+class Mission
33+{
34+ public:
35+ Mission(std::string name, int32_t storageID, int32_t startValue, int32_t endValue, bool ignoreEndValue) :
36+ name(std::move(name)), storageID(storageID), startValue(startValue), endValue(endValue), ignoreEndValue(ignoreEndValue) {}
37+
38+ bool isCompleted(Player* player) const;
39+ bool isStarted(Player* player) const;
40+ std::string getName(Player* player) const;
41+ std::string getDescription(Player* player) const;
42+
43+ uint32_t getStorageId() const {
44+ return storageID;
45+ }
46+ int32_t getStartStorageValue() const {
47+ return startValue;
48+ }
49+ int32_t getEndStorageValue() const {
50+ return endValue;
51+ }
52+
53+ std::map<int32_t, std::string> descriptions;
54+ std::string mainDescription;
55+
56+ private:
57+ std::string name;
58+ uint32_t storageID;
59+ int32_t startValue, endValue;
60+ bool ignoreEndValue;
61+};
62+
63+class Quest
64+{
65+ public:
66+ Quest(std::string name, uint16_t id, int32_t startStorageID, int32_t startStorageValue) :
67+ name(std::move(name)), startStorageID(startStorageID), startStorageValue(startStorageValue), id(id) {}
68+
69+ bool isCompleted(Player* player) const;
70+ bool isStarted(Player* player) const;
71+ uint16_t getID() const {
72+ return id;
73+ }
74+ std::string getName() const {
75+ return name;
76+ }
77+ uint16_t getMissionsCount(Player* player) const;
78+
79+ uint32_t getStartStorageId() const {
80+ return startStorageID;
81+ }
82+ int32_t getStartStorageValue() const {
83+ return startStorageValue;
84+ }
85+
86+ const MissionsList& getMissions() const {
87+ return missions;
88+ }
89+
90+ private:
91+ std::string name;
92+
93+ uint32_t startStorageID;
94+ int32_t startStorageValue;
95+ uint16_t id;
96+
97+ MissionsList missions;
98+
99+ friend class Quests;
100+};
101+
102+class Quests
103+{
104+ public:
105+ const QuestsList& getQuests() const {
106+ return quests;
107+ }
108+
109+ bool loadFromXml();
110+ Quest* getQuestByID(uint16_t id);
111+ bool isQuestStorage(const uint32_t key, const int32_t value, const int32_t oldValue) const;
112+ uint16_t getQuestsCount(Player* player) const;
113+ bool reload();
114+
115+ private:
116+ QuestsList quests;
117+};
118+
119+#endif
A src/raids.cpp +589-0 View file
@@ -0,0 +1,589 @@
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+
31+extern Game g_game;
32+extern ConfigManager g_config;
33+
34+Raids::Raids()
35+{
36+ scriptInterface.initState();
37+}
38+
39+Raids::~Raids()
40+{
41+ for (Raid* raid : raidList) {
42+ delete raid;
43+ }
44+}
45+
46+bool 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+
113+static constexpr int32_t MAX_RAND_RANGE = 10000000;
114+
115+bool 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+
129+void 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+
153+void 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+
172+bool Raids::reload()
173+{
174+ clear();
175+ return loadFromXml();
176+}
177+
178+Raid* 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+
188+Raid::~Raid()
189+{
190+ for (RaidEvent* raidEvent : raidEvents) {
191+ delete raidEvent;
192+ }
193+}
194+
195+bool 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+
239+void 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+
248+void 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+
265+void 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+
273+void Raid::stopEvents()
274+{
275+ if (nextEventEvent != 0) {
276+ g_scheduler.stopEvent(nextEventEvent);
277+ nextEventEvent = 0;
278+ }
279+}
280+
281+RaidEvent* Raid::getNextRaidEvent()
282+{
283+ if (nextEvent < raidEvents.size()) {
284+ return raidEvents[nextEvent];
285+ } else {
286+ return nullptr;
287+ }
288+}
289+
290+bool 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+
302+bool 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+
342+bool AnnounceEvent::executeEvent()
343+{
344+ g_game.broadcastMessage(message, messageType);
345+ return true;
346+}
347+
348+bool 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+
385+bool 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+
401+bool 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+
523+bool 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+
551+bool 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+
570+std::string ScriptEvent::getScriptEventName() const
571+{
572+ return "onRaid";
573+}
574+
575+bool 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+}
A src/raids.h +228-0 View file
@@ -0,0 +1,228 @@
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+#ifndef FS_RAIDS_H_3583C7C054584881856D55765DEDAFA9
21+#define FS_RAIDS_H_3583C7C054584881856D55765DEDAFA9
22+
23+#include "const.h"
24+#include "position.h"
25+#include "baseevents.h"
26+
27+enum RaidState_t {
28+ RAIDSTATE_IDLE,
29+ RAIDSTATE_EXECUTING,
30+};
31+
32+struct MonsterSpawn {
33+ MonsterSpawn(std::string name, uint32_t minAmount, uint32_t maxAmount) :
34+ name(std::move(name)), minAmount(minAmount), maxAmount(maxAmount) {}
35+
36+ std::string name;
37+ uint32_t minAmount;
38+ uint32_t maxAmount;
39+};
40+
41+//How many times it will try to find a tile to add the monster to before giving up
42+static constexpr int32_t MAXIMUM_TRIES_PER_MONSTER = 10;
43+static constexpr int32_t CHECK_RAIDS_INTERVAL = 60;
44+static constexpr int32_t RAID_MINTICKS = 1000;
45+
46+class Raid;
47+class RaidEvent;
48+
49+class Raids
50+{
51+ public:
52+ Raids();
53+ ~Raids();
54+
55+ // non-copyable
56+ Raids(const Raids&) = delete;
57+ Raids& operator=(const Raids&) = delete;
58+
59+ bool loadFromXml();
60+ bool startup();
61+
62+ void clear();
63+ bool reload();
64+
65+ bool isLoaded() const {
66+ return loaded;
67+ }
68+ bool isStarted() const {
69+ return started;
70+ }
71+
72+ Raid* getRunning() {
73+ return running;
74+ }
75+ void setRunning(Raid* newRunning) {
76+ running = newRunning;
77+ }
78+
79+ Raid* getRaidByName(const std::string& name);
80+
81+ uint64_t getLastRaidEnd() const {
82+ return lastRaidEnd;
83+ }
84+ void setLastRaidEnd(uint64_t newLastRaidEnd) {
85+ lastRaidEnd = newLastRaidEnd;
86+ }
87+
88+ void checkRaids();
89+
90+ LuaScriptInterface& getScriptInterface() {
91+ return scriptInterface;
92+ }
93+
94+ private:
95+ LuaScriptInterface scriptInterface{"Raid Interface"};
96+
97+ std::list<Raid*> raidList;
98+ Raid* running = nullptr;
99+ uint64_t lastRaidEnd = 0;
100+ uint32_t checkRaidsEvent = 0;
101+ bool loaded = false;
102+ bool started = false;
103+};
104+
105+class Raid
106+{
107+ public:
108+ Raid(std::string name, uint32_t interval, uint32_t marginTime, bool repeat) :
109+ name(std::move(name)), interval(interval), margin(marginTime), repeat(repeat) {}
110+ ~Raid();
111+
112+ // non-copyable
113+ Raid(const Raid&) = delete;
114+ Raid& operator=(const Raid&) = delete;
115+
116+ bool loadFromXml(const std::string& filename);
117+
118+ void startRaid();
119+
120+ void executeRaidEvent(RaidEvent* raidEvent);
121+ void resetRaid();
122+
123+ RaidEvent* getNextRaidEvent();
124+ void setState(RaidState_t newState) {
125+ state = newState;
126+ }
127+ const std::string& getName() const {
128+ return name;
129+ }
130+
131+ bool isLoaded() const {
132+ return loaded;
133+ }
134+ uint64_t getMargin() const {
135+ return margin;
136+ }
137+ uint32_t getInterval() const {
138+ return interval;
139+ }
140+ bool canBeRepeated() const {
141+ return repeat;
142+ }
143+
144+ void stopEvents();
145+
146+ private:
147+ std::vector<RaidEvent*> raidEvents;
148+ std::string name;
149+ uint32_t interval;
150+ uint32_t nextEvent = 0;
151+ uint64_t margin;
152+ RaidState_t state = RAIDSTATE_IDLE;
153+ uint32_t nextEventEvent = 0;
154+ bool loaded = false;
155+ bool repeat;
156+};
157+
158+class RaidEvent
159+{
160+ public:
161+ virtual ~RaidEvent() = default;
162+
163+ virtual bool configureRaidEvent(const pugi::xml_node& eventNode);
164+
165+ virtual bool executeEvent() = 0;
166+ uint32_t getDelay() const {
167+ return delay;
168+ }
169+
170+ private:
171+ uint32_t delay;
172+};
173+
174+class AnnounceEvent final : public RaidEvent
175+{
176+ public:
177+ AnnounceEvent() = default;
178+
179+ bool configureRaidEvent(const pugi::xml_node& eventNode) override;
180+
181+ bool executeEvent() override;
182+
183+ private:
184+ std::string message;
185+ MessageClasses messageType = MESSAGE_EVENT_ADVANCE;
186+};
187+
188+class SingleSpawnEvent final : public RaidEvent
189+{
190+ public:
191+ bool configureRaidEvent(const pugi::xml_node& eventNode) override;
192+
193+ bool executeEvent() override;
194+
195+ private:
196+ std::string monsterName;
197+ Position position;
198+};
199+
200+class AreaSpawnEvent final : public RaidEvent
201+{
202+ public:
203+ bool configureRaidEvent(const pugi::xml_node& eventNode) override;
204+
205+ bool executeEvent() override;
206+
207+ private:
208+ std::list<MonsterSpawn> spawnList;
209+ Position fromPos, toPos;
210+};
211+
212+class ScriptEvent final : public RaidEvent, public Event
213+{
214+ public:
215+ explicit ScriptEvent(LuaScriptInterface* interface) : Event(interface) {}
216+
217+ bool configureRaidEvent(const pugi::xml_node& eventNode) override;
218+ bool configureEvent(const pugi::xml_node&) override {
219+ return false;
220+ }
221+
222+ bool executeEvent() override;
223+
224+ private:
225+ std::string getScriptEventName() const override;
226+};
227+
228+#endif
A src/rsa.cpp +87-0 View file
@@ -0,0 +1,87 @@
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 "rsa.h"
23+
24+#include <cryptopp/base64.h>
25+#include <cryptopp/osrng.h>
26+
27+#include <fstream>
28+#include <sstream>
29+
30+static CryptoPP::AutoSeededRandomPool prng;
31+
32+void RSA::decrypt(char* msg) const
33+{
34+ try {
35+ CryptoPP::Integer m{reinterpret_cast<uint8_t*>(msg), 128};
36+ auto c = pk.CalculateInverse(prng, m);
37+ c.Encode(reinterpret_cast<uint8_t*>(msg), 128);
38+ } catch (const CryptoPP::Exception& e) {
39+ std::cout << e.what() << '\n';
40+ }
41+}
42+
43+static const std::string header = "-----BEGIN RSA PRIVATE KEY-----";
44+static const std::string footer = "-----END RSA PRIVATE KEY-----";
45+
46+void RSA::loadPEM(const std::string& filename)
47+{
48+ std::ifstream file{filename};
49+
50+ if (!file.is_open()) {
51+ throw std::runtime_error("Missing file " + filename + ".");
52+ }
53+
54+ std::ostringstream oss;
55+ for (std::string line; std::getline(file, line); oss << line);
56+ std::string key = oss.str();
57+
58+ // fixes "Missing RSA private key footer." error
59+ key.erase(0, key.find_first_not_of(" \t\f\v\n\r"));
60+ key.erase(key.find_last_not_of(" \t\f\v\n\r") + 1);
61+
62+ if (key.substr(0, header.size()) != header) {
63+ throw std::runtime_error("Missing RSA private key header.");
64+ }
65+
66+ if (key.substr(key.size() - footer.size(), footer.size()) != footer) {
67+ throw std::runtime_error("Missing RSA private key footer.");
68+ }
69+
70+ key = key.substr(header.size(), key.size() - footer.size());
71+
72+ CryptoPP::ByteQueue queue;
73+ CryptoPP::Base64Decoder decoder;
74+ decoder.Attach(new CryptoPP::Redirector(queue));
75+ decoder.Put(reinterpret_cast<const uint8_t*>(key.c_str()), key.size());
76+ decoder.MessageEnd();
77+
78+ try {
79+ pk.BERDecodePrivateKey(queue, false, queue.MaxRetrievable());
80+
81+ if (!pk.Validate(prng, 3)) {
82+ throw std::runtime_error("RSA private key is not valid.");
83+ }
84+ } catch (const CryptoPP::Exception& e) {
85+ std::cout << e.what() << '\n';
86+ }
87+}
A src/rsa.h +43-0 View file
@@ -0,0 +1,43 @@
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+#ifndef FS_RSA_H_C4E277DA8E884B578DDBF0566F504E91
21+#define FS_RSA_H_C4E277DA8E884B578DDBF0566F504E91
22+
23+#include <cryptopp/rsa.h>
24+
25+#include <string>
26+
27+class RSA
28+{
29+ public:
30+ RSA() = default;
31+
32+ // non-copyable
33+ RSA(const RSA&) = delete;
34+ RSA& operator=(const RSA&) = delete;
35+
36+ void loadPEM(const std::string& filename);
37+ void decrypt(char* msg) const;
38+
39+ private:
40+ CryptoPP::RSA::PrivateKey pk;
41+};
42+
43+#endif
A src/runeeffects.cpp +112-0 View file
@@ -0,0 +1,112 @@
1+#include "otpch.h"
2+
3+#include "runeeffects.h"
4+
5+#include "pugicast.h"
6+#include "tools.h"
7+
8+bool RuneEffects::reload()
9+{
10+ runeEffects.clear();
11+ return loadFromXml();
12+}
13+
14+bool RuneEffects::loadFromXml()
15+{
16+ pugi::xml_document doc;
17+ pugi::xml_parse_result result = doc.load_file("data/XML/runeffects.xml");
18+ if (!result) {
19+ printXMLError("Error - RuneEffects::loadFromXml", "data/XML/runeffects.xml", result);
20+ return false;
21+ }
22+
23+ for (auto node : doc.child("runeffects").children()) {
24+ RuneEffect re(
25+ static_cast<uint8_t>(pugi::cast<uint16_t>(node.attribute("id").value())),
26+ node.attribute("name").as_string(),
27+ node.attribute("image").as_string(),
28+ static_cast<uint16_t>(pugi::cast<uint16_t>(node.attribute("effectId").value())),
29+ node.attribute("speed").as_float(0.5f),
30+ node.attribute("scale").as_float(1.0f),
31+ node.attribute("opacity").as_float(0.8f),
32+ node.attribute("color").as_string("#ffffff"),
33+ node.attribute("layer").as_string("back"),
34+ node.attribute("glow").as_float(1.0f),
35+ node.attribute("colorShift").as_float(0.0f),
36+ node.attribute("noise").as_float(0.0f),
37+ node.attribute("scanlines").as_float(0.0f),
38+ node.attribute("flicker").as_float(0.0f),
39+ pugi::cast<int32_t>(node.attribute("bonusLoot").value()),
40+ pugi::cast<int32_t>(node.attribute("bonusDamage").value()),
41+ pugi::cast<int32_t>(node.attribute("bonusSpeed").value()),
42+ pugi::cast<int32_t>(node.attribute("bonusXp").value()),
43+ node.attribute("damageInterval").as_int(2000),
44+ node.attribute("damageDelay").as_int(200),
45+ node.attribute("premium").as_bool()
46+ );
47+
48+ // Parse <layer> children for multi-layer support
49+ for (auto layerNode : node.children("layer")) {
50+ std::string texture = layerNode.attribute("texture").as_string("");
51+ uint16_t layerEffectId = static_cast<uint16_t>(pugi::cast<uint16_t>(layerNode.attribute("effectId").value()));
52+
53+ // If no texture and no effectId, try legacy single-image fallback
54+ if (texture.empty() && layerEffectId == 0) {
55+ continue;
56+ }
57+
58+ re.layers.emplace_back(
59+ texture,
60+ layerEffectId,
61+ layerNode.attribute("offsetX").as_float(0.0f),
62+ layerNode.attribute("offsetY").as_float(0.0f),
63+ layerNode.attribute("speed").as_float(re.speed),
64+ layerNode.attribute("scale").as_float(re.scale),
65+ layerNode.attribute("opacity").as_float(re.opacity),
66+ layerNode.attribute("color").as_string(re.color.c_str()),
67+ layerNode.attribute("flicker").as_float(re.flicker),
68+ layerNode.attribute("colorShift").as_float(re.colorShift),
69+ layerNode.attribute("drawOrder").as_int(0),
70+ layerNode.attribute("behind").as_bool(true)
71+ );
72+ }
73+
74+ // Backward compat: if no <layer> children but has image/effectId, create implicit single layer
75+ if (re.layers.empty() && (!re.image.empty() || re.effectId > 0)) {
76+ re.layers.emplace_back(
77+ re.image,
78+ re.effectId,
79+ 0.0f, 0.0f,
80+ re.speed, re.scale, re.opacity, re.color,
81+ re.flicker, re.colorShift,
82+ 0,
83+ true
84+ );
85+ }
86+
87+ runeEffects.emplace_back(std::move(re));
88+ }
89+ runeEffects.shrink_to_fit();
90+ return true;
91+}
92+
93+RuneEffect* RuneEffects::getRuneEffectByID(uint8_t id)
94+{
95+ auto it = std::find_if(runeEffects.begin(), runeEffects.end(), [id](const RuneEffect& re) {
96+ return re.id == id;
97+ });
98+
99+ return it != runeEffects.end() ? &*it : nullptr;
100+}
101+
102+RuneEffect* RuneEffects::getRuneEffectByName(const std::string& name)
103+{
104+ auto effectName = name.c_str();
105+ for (auto& it : runeEffects) {
106+ if (strcasecmp(effectName, it.name.c_str()) == 0) {
107+ return &it;
108+ }
109+ }
110+
111+ return nullptr;
112+}
A src/runeeffects.h +96-0 View file
@@ -0,0 +1,96 @@
1+#ifndef FS_RUNEEFFECTS_H
2+#define FS_RUNEEFFECTS_H
3+
4+struct RuneEffectLayer
5+{
6+ RuneEffectLayer() = default;
7+
8+ RuneEffectLayer(std::string texture, uint16_t effectId,
9+ float offsetX, float offsetY,
10+ float speed, float scale, float opacity, std::string color,
11+ float flicker, float colorShift, int32_t drawOrder, bool behind) :
12+ texture(std::move(texture)), effectId(effectId),
13+ offsetX(offsetX), offsetY(offsetY),
14+ speed(speed), scale(scale), opacity(opacity), color(std::move(color)),
15+ flicker(flicker), colorShift(colorShift), drawOrder(drawOrder), behind(behind) {}
16+
17+ std::string texture;
18+ uint16_t effectId = 0;
19+ float offsetX = 0.0f;
20+ float offsetY = 0.0f;
21+ float speed = 0.5f;
22+ float scale = 1.0f;
23+ float opacity = 0.8f;
24+ std::string color = "#ffffff";
25+ float flicker = 0.0f;
26+ float colorShift = 0.0f;
27+ int32_t drawOrder = 0;
28+ bool behind = true;
29+};
30+
31+struct RuneEffect
32+{
33+ RuneEffect(uint8_t id, std::string name, std::string image,
34+ uint16_t effectId,
35+ float speed, float scale, float opacity, std::string color,
36+ std::string layer, float glow, float colorShift, float noise,
37+ float scanlines, float flicker,
38+ int32_t bonusLoot, int32_t bonusDamage, int32_t bonusSpeed, int32_t bonusXp,
39+ int32_t damageInterval, int32_t damageDelay,
40+ bool premium) :
41+ name(std::move(name)), image(std::move(image)), color(std::move(color)),
42+ layer(std::move(layer)),
43+ speed(speed), scale(scale), opacity(opacity),
44+ glow(glow), colorShift(colorShift), noise(noise),
45+ scanlines(scanlines), flicker(flicker),
46+ bonusLoot(bonusLoot), bonusDamage(bonusDamage),
47+ bonusSpeed(bonusSpeed), bonusXp(bonusXp),
48+ damageInterval(damageInterval > 0 ? damageInterval : 2000),
49+ damageDelay(damageDelay > 0 ? damageDelay : 200),
50+ id(id), effectId(effectId), premium(premium) {}
51+
52+ std::string name;
53+ std::string image;
54+ std::string color;
55+ std::string layer;
56+ float speed;
57+ float scale;
58+ float opacity;
59+ float glow;
60+ float colorShift;
61+ float noise;
62+ float scanlines;
63+ float flicker;
64+ int32_t bonusLoot;
65+ int32_t bonusDamage;
66+ int32_t bonusSpeed;
67+ int32_t bonusXp;
68+ int32_t damageInterval;
69+ int32_t damageDelay;
70+ uint8_t id;
71+ uint16_t effectId;
72+ bool premium;
73+
74+ // Multi-layer support
75+ std::vector<RuneEffectLayer> layers;
76+
77+ bool hasLayers() const { return !layers.empty(); }
78+};
79+
80+class RuneEffects
81+{
82+ public:
83+ bool reload();
84+ bool loadFromXml();
85+ RuneEffect* getRuneEffectByID(uint8_t id);
86+ RuneEffect* getRuneEffectByName(const std::string& name);
87+
88+ const std::vector<RuneEffect>& getRuneEffects() const {
89+ return runeEffects;
90+ }
91+
92+ private:
93+ std::vector<RuneEffect> runeEffects;
94+};
95+
96+#endif
A src/scheduler.cpp +138-0 View file
@@ -0,0 +1,138 @@
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 "scheduler.h"
23+
24+void Scheduler::threadMain()
25+{
26+ std::unique_lock<std::mutex> eventLockUnique(eventLock, std::defer_lock);
27+ while (getState() != THREAD_STATE_TERMINATED) {
28+ std::cv_status ret = std::cv_status::no_timeout;
29+
30+ eventLockUnique.lock();
31+ if (eventList.empty()) {
32+ eventSignal.wait(eventLockUnique);
33+ } else {
34+ ret = eventSignal.wait_until(eventLockUnique, eventList.top()->getCycle());
35+ }
36+
37+ // the mutex is locked again now...
38+ if (ret == std::cv_status::timeout && !eventList.empty()) {
39+ // ok we had a timeout, so there has to be an event we have to execute...
40+ SchedulerTask* task = eventList.top();
41+ eventList.pop();
42+
43+ // check if the event was stopped
44+ auto it = eventIds.find(task->getEventId());
45+ if (it == eventIds.end()) {
46+ eventLockUnique.unlock();
47+ delete task;
48+ continue;
49+ }
50+ eventIds.erase(it);
51+ eventLockUnique.unlock();
52+
53+ task->setDontExpire();
54+ g_dispatcher.addTask(task, true);
55+ } else {
56+ eventLockUnique.unlock();
57+ }
58+ }
59+}
60+
61+uint32_t Scheduler::addEvent(SchedulerTask* task)
62+{
63+ eventLock.lock();
64+
65+ if (getState() != THREAD_STATE_RUNNING) {
66+ eventLock.unlock();
67+ delete task;
68+ return 0;
69+ }
70+
71+ // check if the event has a valid id
72+ if (task->getEventId() == 0) {
73+ // if not generate one
74+ if (++lastEventId == 0) {
75+ lastEventId = 1;
76+ }
77+
78+ task->setEventId(lastEventId);
79+ }
80+
81+ // insert the event id in the list of active events
82+ uint32_t eventId = task->getEventId();
83+ eventIds.insert(eventId);
84+
85+ // add the event to the queue
86+ eventList.push(task);
87+
88+ // if the list was empty or this event is the top in the list
89+ // we have to signal it
90+ bool do_signal = (task == eventList.top());
91+
92+ eventLock.unlock();
93+
94+ if (do_signal) {
95+ eventSignal.notify_one();
96+ }
97+
98+ return eventId;
99+}
100+
101+bool Scheduler::stopEvent(uint32_t eventId)
102+{
103+ if (eventId == 0) {
104+ return false;
105+ }
106+
107+ std::lock_guard<std::mutex> lockClass(eventLock);
108+
109+ // search the event id..
110+ auto it = eventIds.find(eventId);
111+ if (it == eventIds.end()) {
112+ return false;
113+ }
114+
115+ eventIds.erase(it);
116+ return true;
117+}
118+
119+void Scheduler::shutdown()
120+{
121+ setState(THREAD_STATE_TERMINATED);
122+ eventLock.lock();
123+
124+ //this list should already be empty
125+ while (!eventList.empty()) {
126+ delete eventList.top();
127+ eventList.pop();
128+ }
129+
130+ eventIds.clear();
131+ eventLock.unlock();
132+ eventSignal.notify_one();
133+}
134+
135+SchedulerTask* createSchedulerTask(uint32_t delay, std::function<void (void)> f)
136+{
137+ return new SchedulerTask(delay, std::move(f));
138+}
A src/scheduler.h +83-0 View file
@@ -0,0 +1,83 @@
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+#ifndef FS_SCHEDULER_H_2905B3D5EAB34B4BA8830167262D2DC1
21+#define FS_SCHEDULER_H_2905B3D5EAB34B4BA8830167262D2DC1
22+
23+#include "tasks.h"
24+#include <unordered_set>
25+#include <queue>
26+
27+#include "thread_holder_base.h"
28+
29+static constexpr int32_t SCHEDULER_MINTICKS = 50;
30+
31+class SchedulerTask : public Task
32+{
33+ public:
34+ void setEventId(uint32_t id) {
35+ eventId = id;
36+ }
37+ uint32_t getEventId() const {
38+ return eventId;
39+ }
40+
41+ std::chrono::system_clock::time_point getCycle() const {
42+ return expiration;
43+ }
44+
45+ private:
46+ SchedulerTask(uint32_t delay, std::function<void (void)>&& f) : Task(delay, std::move(f)) {}
47+
48+ uint32_t eventId = 0;
49+
50+ friend SchedulerTask* createSchedulerTask(uint32_t, std::function<void (void)>);
51+};
52+
53+SchedulerTask* createSchedulerTask(uint32_t delay, std::function<void (void)> f);
54+
55+struct TaskComparator {
56+ bool operator()(const SchedulerTask* lhs, const SchedulerTask* rhs) const {
57+ return lhs->getCycle() > rhs->getCycle();
58+ }
59+};
60+
61+class Scheduler : public ThreadHolder<Scheduler>
62+{
63+ public:
64+ uint32_t addEvent(SchedulerTask* task);
65+ bool stopEvent(uint32_t eventId);
66+
67+ void shutdown();
68+
69+ void threadMain();
70+
71+ private:
72+ std::thread thread;
73+ std::mutex eventLock;
74+ std::condition_variable eventSignal;
75+
76+ uint32_t lastEventId {0};
77+ std::priority_queue<SchedulerTask*, std::deque<SchedulerTask*>, TaskComparator> eventList;
78+ std::unordered_set<uint32_t> eventIds;
79+};
80+
81+extern Scheduler g_scheduler;
82+
83+#endif
A src/script.cpp +99-0 View file
@@ -0,0 +1,99 @@
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 "script.h"
23+#include <boost/filesystem.hpp>
24+#include "configmanager.h"
25+
26+extern LuaEnvironment g_luaEnvironment;
27+extern ConfigManager g_config;
28+
29+Scripts::Scripts() :
30+ scriptInterface("Scripts Interface")
31+{
32+ scriptInterface.initState();
33+}
34+
35+Scripts::~Scripts()
36+{
37+ scriptInterface.reInitState();
38+}
39+
40+bool Scripts::loadScripts(std::string folderName, bool isLib, bool reload)
41+{
42+ namespace fs = boost::filesystem;
43+
44+ const auto dir = fs::current_path() / "data" / folderName;
45+ if(!fs::exists(dir) || !fs::is_directory(dir)) {
46+ std::cout << "[Warning - Scripts::loadScripts] Can not load folder '" << folderName << "'." << std::endl;
47+ return false;
48+ }
49+
50+ fs::recursive_directory_iterator endit;
51+ std::vector<fs::path> v;
52+ std::string disable = ("#");
53+ for(fs::recursive_directory_iterator it(dir); it != endit; ++it) {
54+ auto fn = it->path().parent_path().filename();
55+ if ((fn == "lib" && !isLib) || fn == "events") {
56+ continue;
57+ }
58+ if(fs::is_regular_file(*it) && it->path().extension() == ".lua") {
59+ size_t found = it->path().filename().string().find(disable);
60+ if (found != std::string::npos) {
61+ if (g_config.getBoolean(ConfigManager::SCRIPTS_CONSOLE_LOGS)) {
62+ std::cout << "> " << it->path().filename().string() << " [disabled]" << std::endl;
63+ }
64+ continue;
65+ }
66+ v.push_back(it->path());
67+ }
68+ }
69+ sort(v.begin(), v.end());
70+ std::string redir;
71+ for (auto it = v.begin(); it != v.end(); ++it) {
72+ const std::string scriptFile = it->string();
73+ if (!isLib) {
74+ if (redir.empty() || redir != it->parent_path().string()) {
75+ auto p = fs::path(it->relative_path());
76+ if (g_config.getBoolean(ConfigManager::SCRIPTS_CONSOLE_LOGS)) {
77+ std::cout << ">> [" << p.parent_path().filename() << "]" << std::endl;
78+ }
79+ redir = it->parent_path().string();
80+ }
81+ }
82+
83+ if(scriptInterface.loadFile(scriptFile) == -1) {
84+ std::cout << "> " << it->filename().string() << " [error]" << std::endl;
85+ std::cout << "^ " << scriptInterface.getLastLuaError() << std::endl;
86+ continue;
87+ }
88+
89+ if (g_config.getBoolean(ConfigManager::SCRIPTS_CONSOLE_LOGS)) {
90+ if (!reload) {
91+ std::cout << "> " << it->filename().string() << " [loaded]" << std::endl;
92+ } else {
93+ std::cout << "> " << it->filename().string() << " [reloaded]" << std::endl;
94+ }
95+ }
96+ }
97+
98+ return true;
99+}
Top