| @@ -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 |
| @@ -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 | + | } |
| @@ -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 |
| @@ -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 | + | } |
| @@ -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 |
| @@ -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 | + | } |
| @@ -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 |
| @@ -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 |
| @@ -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 ? | |
| 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 | + | } |
| @@ -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 |
| @@ -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 | + | } |
| @@ -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 |
| @@ -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 | + | } |
| @@ -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 |
| @@ -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 ⁢ | |
| 108 | + | } | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | return nullptr; | |
| 112 | + | } |
| @@ -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 |
| @@ -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 | + | } |
| @@ -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 |
| @@ -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 | + | } |