| @@ -0,0 +1,389 @@ | |||
| 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_TILE_H_96C7EE7CF8CD48E59D5D554A181F0C56 | |
| 21 | + | #define FS_TILE_H_96C7EE7CF8CD48E59D5D554A181F0C56 | |
| 22 | + | ||
| 23 | + | #include "cylinder.h" | |
| 24 | + | #include "item.h" | |
| 25 | + | #include "tools.h" | |
| 26 | + | #include "spectators.h" | |
| 27 | + | ||
| 28 | + | class Creature; | |
| 29 | + | class Teleport; | |
| 30 | + | class TrashHolder; | |
| 31 | + | class Mailbox; | |
| 32 | + | class MagicField; | |
| 33 | + | class QTreeLeafNode; | |
| 34 | + | class BedItem; | |
| 35 | + | ||
| 36 | + | using CreatureVector = std::vector<Creature*>; | |
| 37 | + | using ItemVector = std::vector<Item*>; | |
| 38 | + | ||
| 39 | + | enum tileflags_t : uint32_t { | |
| 40 | + | TILESTATE_NONE = 0, | |
| 41 | + | ||
| 42 | + | TILESTATE_FLOORCHANGE_DOWN = 1 << 0, | |
| 43 | + | TILESTATE_FLOORCHANGE_NORTH = 1 << 1, | |
| 44 | + | TILESTATE_FLOORCHANGE_SOUTH = 1 << 2, | |
| 45 | + | TILESTATE_FLOORCHANGE_EAST = 1 << 3, | |
| 46 | + | TILESTATE_FLOORCHANGE_WEST = 1 << 4, | |
| 47 | + | TILESTATE_FLOORCHANGE_SOUTH_ALT = 1 << 5, | |
| 48 | + | TILESTATE_FLOORCHANGE_EAST_ALT = 1 << 6, | |
| 49 | + | TILESTATE_PROTECTIONZONE = 1 << 7, | |
| 50 | + | TILESTATE_NOPVPZONE = 1 << 8, | |
| 51 | + | TILESTATE_NOLOGOUT = 1 << 9, | |
| 52 | + | TILESTATE_PVPZONE = 1 << 10, | |
| 53 | + | TILESTATE_TELEPORT = 1 << 11, | |
| 54 | + | TILESTATE_MAGICFIELD = 1 << 12, | |
| 55 | + | TILESTATE_MAILBOX = 1 << 13, | |
| 56 | + | TILESTATE_TRASHHOLDER = 1 << 14, | |
| 57 | + | TILESTATE_BED = 1 << 15, | |
| 58 | + | TILESTATE_DEPOT = 1 << 16, | |
| 59 | + | TILESTATE_BLOCKSOLID = 1 << 17, | |
| 60 | + | TILESTATE_BLOCKPATH = 1 << 18, | |
| 61 | + | TILESTATE_IMMOVABLEBLOCKSOLID = 1 << 19, | |
| 62 | + | TILESTATE_IMMOVABLEBLOCKPATH = 1 << 20, | |
| 63 | + | TILESTATE_IMMOVABLENOFIELDBLOCKPATH = 1 << 21, | |
| 64 | + | TILESTATE_NOFIELDBLOCKPATH = 1 << 22, | |
| 65 | + | TILESTATE_SUPPORTS_HANGABLE = 1 << 23, | |
| 66 | + | ||
| 67 | + | TILESTATE_FLOORCHANGE = TILESTATE_FLOORCHANGE_DOWN | TILESTATE_FLOORCHANGE_NORTH | TILESTATE_FLOORCHANGE_SOUTH | TILESTATE_FLOORCHANGE_EAST | TILESTATE_FLOORCHANGE_WEST | TILESTATE_FLOORCHANGE_SOUTH_ALT | TILESTATE_FLOORCHANGE_EAST_ALT, | |
| 68 | + | }; | |
| 69 | + | ||
| 70 | + | enum ZoneType_t { | |
| 71 | + | ZONE_PROTECTION, | |
| 72 | + | ZONE_NOPVP, | |
| 73 | + | ZONE_PVP, | |
| 74 | + | ZONE_NOLOGOUT, | |
| 75 | + | ZONE_NORMAL, | |
| 76 | + | }; | |
| 77 | + | ||
| 78 | + | class TileItemVector : private ItemVector | |
| 79 | + | { | |
| 80 | + | public: | |
| 81 | + | using ItemVector::begin; | |
| 82 | + | using ItemVector::end; | |
| 83 | + | using ItemVector::rbegin; | |
| 84 | + | using ItemVector::rend; | |
| 85 | + | using ItemVector::size; | |
| 86 | + | using ItemVector::clear; | |
| 87 | + | using ItemVector::at; | |
| 88 | + | using ItemVector::insert; | |
| 89 | + | using ItemVector::erase; | |
| 90 | + | using ItemVector::push_back; | |
| 91 | + | using ItemVector::value_type; | |
| 92 | + | using ItemVector::iterator; | |
| 93 | + | using ItemVector::const_iterator; | |
| 94 | + | using ItemVector::reverse_iterator; | |
| 95 | + | using ItemVector::const_reverse_iterator; | |
| 96 | + | using ItemVector::empty; | |
| 97 | + | ||
| 98 | + | iterator getBeginDownItem() { | |
| 99 | + | return begin(); | |
| 100 | + | } | |
| 101 | + | const_iterator getBeginDownItem() const { | |
| 102 | + | return begin(); | |
| 103 | + | } | |
| 104 | + | iterator getEndDownItem() { | |
| 105 | + | return begin() + downItemCount; | |
| 106 | + | } | |
| 107 | + | const_iterator getEndDownItem() const { | |
| 108 | + | return begin() + downItemCount; | |
| 109 | + | } | |
| 110 | + | iterator getBeginTopItem() { | |
| 111 | + | return getEndDownItem(); | |
| 112 | + | } | |
| 113 | + | const_iterator getBeginTopItem() const { | |
| 114 | + | return getEndDownItem(); | |
| 115 | + | } | |
| 116 | + | iterator getEndTopItem() { | |
| 117 | + | return end(); | |
| 118 | + | } | |
| 119 | + | const_iterator getEndTopItem() const { | |
| 120 | + | return end(); | |
| 121 | + | } | |
| 122 | + | ||
| 123 | + | uint32_t getTopItemCount() const { | |
| 124 | + | return size() - downItemCount; | |
| 125 | + | } | |
| 126 | + | uint32_t getDownItemCount() const { | |
| 127 | + | return downItemCount; | |
| 128 | + | } | |
| 129 | + | inline Item* getTopTopItem() const { | |
| 130 | + | if (getTopItemCount() == 0) { | |
| 131 | + | return nullptr; | |
| 132 | + | } | |
| 133 | + | return *(getEndTopItem() - 1); | |
| 134 | + | } | |
| 135 | + | inline Item* getTopDownItem() const { | |
| 136 | + | if (downItemCount == 0) { | |
| 137 | + | return nullptr; | |
| 138 | + | } | |
| 139 | + | return *getBeginDownItem(); | |
| 140 | + | } | |
| 141 | + | void addDownItemCount(int32_t increment) { | |
| 142 | + | downItemCount += increment; | |
| 143 | + | } | |
| 144 | + | ||
| 145 | + | private: | |
| 146 | + | uint16_t downItemCount = 0; | |
| 147 | + | }; | |
| 148 | + | ||
| 149 | + | class Tile : public Cylinder | |
| 150 | + | { | |
| 151 | + | public: | |
| 152 | + | static Tile& nullptr_tile; | |
| 153 | + | Tile(uint16_t x, uint16_t y, uint8_t z) : tilePos(x, y, z) {} | |
| 154 | + | virtual ~Tile() { | |
| 155 | + | delete ground; | |
| 156 | + | }; | |
| 157 | + | ||
| 158 | + | // non-copyable | |
| 159 | + | Tile(const Tile&) = delete; | |
| 160 | + | Tile& operator=(const Tile&) = delete; | |
| 161 | + | ||
| 162 | + | virtual TileItemVector* getItemList() = 0; | |
| 163 | + | virtual const TileItemVector* getItemList() const = 0; | |
| 164 | + | virtual TileItemVector* makeItemList() = 0; | |
| 165 | + | ||
| 166 | + | virtual CreatureVector* getCreatures() = 0; | |
| 167 | + | virtual const CreatureVector* getCreatures() const = 0; | |
| 168 | + | virtual CreatureVector* makeCreatures() = 0; | |
| 169 | + | ||
| 170 | + | int32_t getThrowRange() const override final { | |
| 171 | + | return 0; | |
| 172 | + | } | |
| 173 | + | bool isPushable() const override final { | |
| 174 | + | return false; | |
| 175 | + | } | |
| 176 | + | ||
| 177 | + | MagicField* getFieldItem() const; | |
| 178 | + | Teleport* getTeleportItem() const; | |
| 179 | + | TrashHolder* getTrashHolder() const; | |
| 180 | + | Mailbox* getMailbox() const; | |
| 181 | + | BedItem* getBedItem() const; | |
| 182 | + | ||
| 183 | + | Creature* getTopCreature() const; | |
| 184 | + | const Creature* getBottomCreature() const; | |
| 185 | + | Creature* getTopVisibleCreature(const Creature* creature) const; | |
| 186 | + | const Creature* getBottomVisibleCreature(const Creature* creature) const; | |
| 187 | + | Item* getTopTopItem() const; | |
| 188 | + | Item* getTopDownItem() const; | |
| 189 | + | bool isMoveableBlocking() const; | |
| 190 | + | Thing* getTopVisibleThing(const Creature* creature); | |
| 191 | + | Item* getItemByTopOrder(int32_t topOrder); | |
| 192 | + | ||
| 193 | + | size_t getThingCount() const { | |
| 194 | + | size_t thingCount = getCreatureCount() + getItemCount(); | |
| 195 | + | if (ground) { | |
| 196 | + | thingCount++; | |
| 197 | + | } | |
| 198 | + | return thingCount; | |
| 199 | + | } | |
| 200 | + | // If these return != 0 the associated vectors are guaranteed to exists | |
| 201 | + | size_t getCreatureCount() const; | |
| 202 | + | size_t getItemCount() const; | |
| 203 | + | uint32_t getTopItemCount() const; | |
| 204 | + | uint32_t getDownItemCount() const; | |
| 205 | + | ||
| 206 | + | bool hasProperty(ITEMPROPERTY prop) const; | |
| 207 | + | bool hasProperty(const Item* exclude, ITEMPROPERTY prop) const; | |
| 208 | + | ||
| 209 | + | bool hasFlag(uint32_t flag) const { | |
| 210 | + | return hasBitSet(flag, this->flags); | |
| 211 | + | } | |
| 212 | + | void setFlag(uint32_t flag) { | |
| 213 | + | this->flags |= flag; | |
| 214 | + | } | |
| 215 | + | void resetFlag(uint32_t flag) { | |
| 216 | + | this->flags &= ~flag; | |
| 217 | + | } | |
| 218 | + | ||
| 219 | + | ZoneType_t getZone() const { | |
| 220 | + | if (hasFlag(TILESTATE_PROTECTIONZONE)) { | |
| 221 | + | return ZONE_PROTECTION; | |
| 222 | + | } else if (hasFlag(TILESTATE_NOPVPZONE)) { | |
| 223 | + | return ZONE_NOPVP; | |
| 224 | + | } else if (hasFlag(TILESTATE_PVPZONE)) { | |
| 225 | + | return ZONE_PVP; | |
| 226 | + | } else { | |
| 227 | + | return ZONE_NORMAL; | |
| 228 | + | } | |
| 229 | + | } | |
| 230 | + | ||
| 231 | + | bool hasHeight(uint32_t n) const; | |
| 232 | + | ||
| 233 | + | std::string getDescription(int32_t lookDistance) const override final; | |
| 234 | + | ||
| 235 | + | int32_t getClientIndexOfCreature(const Player* player, const Creature* creature) const; | |
| 236 | + | int32_t getStackposOfCreature(const Player* player, const Creature* creature) const; | |
| 237 | + | int32_t getStackposOfItem(const Player* player, const Item* item) const; | |
| 238 | + | ||
| 239 | + | //cylinder implementations | |
| 240 | + | ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, | |
| 241 | + | uint32_t flags, Creature* actor = nullptr) const override; | |
| 242 | + | ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, | |
| 243 | + | uint32_t& maxQueryCount, uint32_t flags) const override final; | |
| 244 | + | ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const override final; | |
| 245 | + | Tile* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) override; | |
| 246 | + | ||
| 247 | + | void addThing(Thing* thing) override final; | |
| 248 | + | void addThing(int32_t index, Thing* thing) override; | |
| 249 | + | ||
| 250 | + | void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override final; | |
| 251 | + | void replaceThing(uint32_t index, Thing* thing) override final; | |
| 252 | + | ||
| 253 | + | void removeThing(Thing* thing, uint32_t count) override final; | |
| 254 | + | ||
| 255 | + | void removeCreature(Creature* creature); | |
| 256 | + | ||
| 257 | + | int32_t getThingIndex(const Thing* thing) const override final; | |
| 258 | + | size_t getFirstIndex() const override final; | |
| 259 | + | size_t getLastIndex() const override final; | |
| 260 | + | uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const override final; | |
| 261 | + | Thing* getThing(size_t index) const override final; | |
| 262 | + | ||
| 263 | + | void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override final; | |
| 264 | + | void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override final; | |
| 265 | + | ||
| 266 | + | void internalAddThing(Thing* thing) override final; | |
| 267 | + | void internalAddThing(uint32_t index, Thing* thing) override; | |
| 268 | + | ||
| 269 | + | const Position& getPosition() const override final { | |
| 270 | + | return tilePos; | |
| 271 | + | } | |
| 272 | + | ||
| 273 | + | bool isRemoved() const override final { | |
| 274 | + | return false; | |
| 275 | + | } | |
| 276 | + | ||
| 277 | + | Item* getUseItem(int32_t index) const; | |
| 278 | + | ||
| 279 | + | Item* getGround() const { | |
| 280 | + | return ground; | |
| 281 | + | } | |
| 282 | + | void setGround(Item* item) { | |
| 283 | + | ground = item; | |
| 284 | + | } | |
| 285 | + | ||
| 286 | + | private: | |
| 287 | + | void onAddTileItem(Item* item); | |
| 288 | + | void onUpdateTileItem(Item* oldItem, const ItemType& oldType, Item* newItem, const ItemType& newType); | |
| 289 | + | void onRemoveTileItem(const SpectatorVec& spectators, const std::vector<int32_t>& oldStackPosVector, Item* item); | |
| 290 | + | void onUpdateTile(const SpectatorVec& spectators); | |
| 291 | + | ||
| 292 | + | void setTileFlags(const Item* item); | |
| 293 | + | void resetTileFlags(const Item* item); | |
| 294 | + | ||
| 295 | + | Item* ground = nullptr; | |
| 296 | + | Position tilePos; | |
| 297 | + | uint32_t flags = 0; | |
| 298 | + | }; | |
| 299 | + | ||
| 300 | + | // Used for walkable tiles, where there is high likeliness of | |
| 301 | + | // items being added/removed | |
| 302 | + | class DynamicTile : public Tile | |
| 303 | + | { | |
| 304 | + | // By allocating the vectors in-house, we avoid some memory fragmentation | |
| 305 | + | TileItemVector items; | |
| 306 | + | CreatureVector creatures; | |
| 307 | + | ||
| 308 | + | public: | |
| 309 | + | DynamicTile(uint16_t x, uint16_t y, uint8_t z) : Tile(x, y, z) {} | |
| 310 | + | ~DynamicTile() { | |
| 311 | + | for (Item* item : items) { | |
| 312 | + | item->decrementReferenceCounter(); | |
| 313 | + | } | |
| 314 | + | } | |
| 315 | + | ||
| 316 | + | // non-copyable | |
| 317 | + | DynamicTile(const DynamicTile&) = delete; | |
| 318 | + | DynamicTile& operator=(const DynamicTile&) = delete; | |
| 319 | + | ||
| 320 | + | TileItemVector* getItemList() override { | |
| 321 | + | return &items; | |
| 322 | + | } | |
| 323 | + | const TileItemVector* getItemList() const override { | |
| 324 | + | return &items; | |
| 325 | + | } | |
| 326 | + | TileItemVector* makeItemList() override { | |
| 327 | + | return &items; | |
| 328 | + | } | |
| 329 | + | ||
| 330 | + | CreatureVector* getCreatures() override { | |
| 331 | + | return &creatures; | |
| 332 | + | } | |
| 333 | + | const CreatureVector* getCreatures() const override { | |
| 334 | + | return &creatures; | |
| 335 | + | } | |
| 336 | + | CreatureVector* makeCreatures() override { | |
| 337 | + | return &creatures; | |
| 338 | + | } | |
| 339 | + | }; | |
| 340 | + | ||
| 341 | + | // For blocking tiles, where we very rarely actually have items | |
| 342 | + | class StaticTile final : public Tile | |
| 343 | + | { | |
| 344 | + | // We very rarely even need the vectors, so don't keep them in memory | |
| 345 | + | std::unique_ptr<TileItemVector> items; | |
| 346 | + | std::unique_ptr<CreatureVector> creatures; | |
| 347 | + | ||
| 348 | + | public: | |
| 349 | + | StaticTile(uint16_t x, uint16_t y, uint8_t z) : Tile(x, y, z) {} | |
| 350 | + | ~StaticTile() { | |
| 351 | + | if (items) { | |
| 352 | + | for (Item* item : *items) { | |
| 353 | + | item->decrementReferenceCounter(); | |
| 354 | + | } | |
| 355 | + | } | |
| 356 | + | } | |
| 357 | + | ||
| 358 | + | // non-copyable | |
| 359 | + | StaticTile(const StaticTile&) = delete; | |
| 360 | + | StaticTile& operator=(const StaticTile&) = delete; | |
| 361 | + | ||
| 362 | + | TileItemVector* getItemList() override { | |
| 363 | + | return items.get(); | |
| 364 | + | } | |
| 365 | + | const TileItemVector* getItemList() const override { | |
| 366 | + | return items.get(); | |
| 367 | + | } | |
| 368 | + | TileItemVector* makeItemList() override { | |
| 369 | + | if (!items) { | |
| 370 | + | items.reset(new TileItemVector); | |
| 371 | + | } | |
| 372 | + | return items.get(); | |
| 373 | + | } | |
| 374 | + | ||
| 375 | + | CreatureVector* getCreatures() override { | |
| 376 | + | return creatures.get(); | |
| 377 | + | } | |
| 378 | + | const CreatureVector* getCreatures() const override { | |
| 379 | + | return creatures.get(); | |
| 380 | + | } | |
| 381 | + | CreatureVector* makeCreatures() override { | |
| 382 | + | if (!creatures) { | |
| 383 | + | creatures.reset(new CreatureVector); | |
| 384 | + | } | |
| 385 | + | return creatures.get(); | |
| 386 | + | } | |
| 387 | + | }; | |
| 388 | + | ||
| 389 | + | #endif |
| @@ -0,0 +1,1249 @@ | |||
| 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 "tools.h" | |
| 23 | + | #include "configmanager.h" | |
| 24 | + | ||
| 25 | + | extern ConfigManager g_config; | |
| 26 | + | ||
| 27 | + | void printXMLError(const std::string& where, const std::string& fileName, const pugi::xml_parse_result& result) | |
| 28 | + | { | |
| 29 | + | std::cout << '[' << where << "] Failed to load " << fileName << ": " << result.description() << std::endl; | |
| 30 | + | ||
| 31 | + | FILE* file = fopen(fileName.c_str(), "rb"); | |
| 32 | + | if (!file) { | |
| 33 | + | return; | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | char buffer[32768]; | |
| 37 | + | uint32_t currentLine = 1; | |
| 38 | + | std::string line; | |
| 39 | + | ||
| 40 | + | auto offset = static_cast<size_t>(result.offset); | |
| 41 | + | size_t lineOffsetPosition = 0; | |
| 42 | + | size_t index = 0; | |
| 43 | + | size_t bytes; | |
| 44 | + | do { | |
| 45 | + | bytes = fread(buffer, 1, 32768, file); | |
| 46 | + | for (size_t i = 0; i < bytes; ++i) { | |
| 47 | + | char ch = buffer[i]; | |
| 48 | + | if (ch == '\n') { | |
| 49 | + | if ((index + i) >= offset) { | |
| 50 | + | lineOffsetPosition = line.length() - ((index + i) - offset); | |
| 51 | + | bytes = 0; | |
| 52 | + | break; | |
| 53 | + | } | |
| 54 | + | ++currentLine; | |
| 55 | + | line.clear(); | |
| 56 | + | } else { | |
| 57 | + | line.push_back(ch); | |
| 58 | + | } | |
| 59 | + | } | |
| 60 | + | index += bytes; | |
| 61 | + | } while (bytes == 32768); | |
| 62 | + | fclose(file); | |
| 63 | + | ||
| 64 | + | std::cout << "Line " << currentLine << ':' << std::endl; | |
| 65 | + | std::cout << line << std::endl; | |
| 66 | + | for (size_t i = 0; i < lineOffsetPosition; i++) { | |
| 67 | + | if (line[i] == '\t') { | |
| 68 | + | std::cout << '\t'; | |
| 69 | + | } else { | |
| 70 | + | std::cout << ' '; | |
| 71 | + | } | |
| 72 | + | } | |
| 73 | + | std::cout << '^' << std::endl; | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | static uint32_t circularShift(int bits, uint32_t value) | |
| 77 | + | { | |
| 78 | + | return (value << bits) | (value >> (32 - bits)); | |
| 79 | + | } | |
| 80 | + | ||
| 81 | + | static void processSHA1MessageBlock(const uint8_t* messageBlock, uint32_t* H) | |
| 82 | + | { | |
| 83 | + | uint32_t W[80]; | |
| 84 | + | for (int i = 0; i < 16; ++i) { | |
| 85 | + | const size_t offset = i << 2; | |
| 86 | + | W[i] = messageBlock[offset] << 24 | messageBlock[offset + 1] << 16 | messageBlock[offset + 2] << 8 | messageBlock[offset + 3]; | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | for (int i = 16; i < 80; ++i) { | |
| 90 | + | W[i] = circularShift(1, W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]); | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | uint32_t A = H[0], B = H[1], C = H[2], D = H[3], E = H[4]; | |
| 94 | + | ||
| 95 | + | for (int i = 0; i < 20; ++i) { | |
| 96 | + | const uint32_t tmp = circularShift(5, A) + ((B & C) | ((~B) & D)) + E + W[i] + 0x5A827999; | |
| 97 | + | E = D; D = C; C = circularShift(30, B); B = A; A = tmp; | |
| 98 | + | } | |
| 99 | + | ||
| 100 | + | for (int i = 20; i < 40; ++i) { | |
| 101 | + | const uint32_t tmp = circularShift(5, A) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1; | |
| 102 | + | E = D; D = C; C = circularShift(30, B); B = A; A = tmp; | |
| 103 | + | } | |
| 104 | + | ||
| 105 | + | for (int i = 40; i < 60; ++i) { | |
| 106 | + | const uint32_t tmp = circularShift(5, A) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC; | |
| 107 | + | E = D; D = C; C = circularShift(30, B); B = A; A = tmp; | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | for (int i = 60; i < 80; ++i) { | |
| 111 | + | const uint32_t tmp = circularShift(5, A) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6; | |
| 112 | + | E = D; D = C; C = circularShift(30, B); B = A; A = tmp; | |
| 113 | + | } | |
| 114 | + | ||
| 115 | + | H[0] += A; | |
| 116 | + | H[1] += B; | |
| 117 | + | H[2] += C; | |
| 118 | + | H[3] += D; | |
| 119 | + | H[4] += E; | |
| 120 | + | } | |
| 121 | + | ||
| 122 | + | std::string transformToSHA1(const std::string& input) | |
| 123 | + | { | |
| 124 | + | uint32_t H[] = { | |
| 125 | + | 0x67452301, | |
| 126 | + | 0xEFCDAB89, | |
| 127 | + | 0x98BADCFE, | |
| 128 | + | 0x10325476, | |
| 129 | + | 0xC3D2E1F0 | |
| 130 | + | }; | |
| 131 | + | ||
| 132 | + | uint8_t messageBlock[64]; | |
| 133 | + | size_t index = 0; | |
| 134 | + | ||
| 135 | + | uint32_t length_low = 0; | |
| 136 | + | uint32_t length_high = 0; | |
| 137 | + | for (char ch : input) { | |
| 138 | + | messageBlock[index++] = ch; | |
| 139 | + | ||
| 140 | + | length_low += 8; | |
| 141 | + | if (length_low == 0) { | |
| 142 | + | length_high++; | |
| 143 | + | } | |
| 144 | + | ||
| 145 | + | if (index == 64) { | |
| 146 | + | processSHA1MessageBlock(messageBlock, H); | |
| 147 | + | index = 0; | |
| 148 | + | } | |
| 149 | + | } | |
| 150 | + | ||
| 151 | + | messageBlock[index++] = 0x80; | |
| 152 | + | ||
| 153 | + | if (index > 56) { | |
| 154 | + | while (index < 64) { | |
| 155 | + | messageBlock[index++] = 0; | |
| 156 | + | } | |
| 157 | + | ||
| 158 | + | processSHA1MessageBlock(messageBlock, H); | |
| 159 | + | index = 0; | |
| 160 | + | } | |
| 161 | + | ||
| 162 | + | while (index < 56) { | |
| 163 | + | messageBlock[index++] = 0; | |
| 164 | + | } | |
| 165 | + | ||
| 166 | + | messageBlock[56] = length_high >> 24; | |
| 167 | + | messageBlock[57] = length_high >> 16; | |
| 168 | + | messageBlock[58] = length_high >> 8; | |
| 169 | + | messageBlock[59] = length_high; | |
| 170 | + | ||
| 171 | + | messageBlock[60] = length_low >> 24; | |
| 172 | + | messageBlock[61] = length_low >> 16; | |
| 173 | + | messageBlock[62] = length_low >> 8; | |
| 174 | + | messageBlock[63] = length_low; | |
| 175 | + | ||
| 176 | + | processSHA1MessageBlock(messageBlock, H); | |
| 177 | + | ||
| 178 | + | char hexstring[41]; | |
| 179 | + | static const char hexDigits[] = {"0123456789abcdef"}; | |
| 180 | + | for (int hashByte = 20; --hashByte >= 0;) { | |
| 181 | + | const uint8_t byte = H[hashByte >> 2] >> (((3 - hashByte) & 3) << 3); | |
| 182 | + | index = hashByte << 1; | |
| 183 | + | hexstring[index] = hexDigits[byte >> 4]; | |
| 184 | + | hexstring[index + 1] = hexDigits[byte & 15]; | |
| 185 | + | } | |
| 186 | + | return std::string(hexstring, 40); | |
| 187 | + | } | |
| 188 | + | ||
| 189 | + | std::string generateToken(const std::string& key, uint32_t ticks) | |
| 190 | + | { | |
| 191 | + | // generate message from ticks | |
| 192 | + | std::string message(8, 0); | |
| 193 | + | for (uint8_t i = 8; --i; ticks >>= 8) { | |
| 194 | + | message[i] = static_cast<char>(ticks & 0xFF); | |
| 195 | + | } | |
| 196 | + | ||
| 197 | + | // hmac key pad generation | |
| 198 | + | std::string iKeyPad(64, 0x36), oKeyPad(64, 0x5C); | |
| 199 | + | for (uint8_t i = 0; i < key.length(); ++i) { | |
| 200 | + | iKeyPad[i] ^= key[i]; | |
| 201 | + | oKeyPad[i] ^= key[i]; | |
| 202 | + | } | |
| 203 | + | ||
| 204 | + | oKeyPad.reserve(84); | |
| 205 | + | ||
| 206 | + | // hmac concat inner pad with message | |
| 207 | + | iKeyPad.append(message); | |
| 208 | + | ||
| 209 | + | // hmac first pass | |
| 210 | + | message.assign(transformToSHA1(iKeyPad)); | |
| 211 | + | ||
| 212 | + | // hmac concat outer pad with message, conversion from hex to int needed | |
| 213 | + | for (uint8_t i = 0; i < message.length(); i += 2) { | |
| 214 | + | oKeyPad.push_back(static_cast<char>(std::strtoul(message.substr(i, 2).c_str(), nullptr, 16))); | |
| 215 | + | } | |
| 216 | + | ||
| 217 | + | // hmac second pass | |
| 218 | + | message.assign(transformToSHA1(oKeyPad)); | |
| 219 | + | ||
| 220 | + | // calculate hmac offset | |
| 221 | + | uint32_t offset = static_cast<uint32_t>(std::strtoul(message.substr(39, 1).c_str(), nullptr, 16) & 0xF); | |
| 222 | + | ||
| 223 | + | // get truncated hash | |
| 224 | + | uint32_t truncHash = static_cast<uint32_t>(std::strtoul(message.substr(2 * offset, 8).c_str(), nullptr, 16)) & 0x7FFFFFFF; | |
| 225 | + | message.assign(std::to_string(truncHash)); | |
| 226 | + | ||
| 227 | + | // return only last AUTHENTICATOR_DIGITS (default 6) digits, also asserts exactly 6 digits | |
| 228 | + | uint32_t hashLen = message.length(); | |
| 229 | + | message.assign(message.substr(hashLen - std::min(hashLen, AUTHENTICATOR_DIGITS))); | |
| 230 | + | message.insert(0, AUTHENTICATOR_DIGITS - std::min(hashLen, AUTHENTICATOR_DIGITS), '0'); | |
| 231 | + | return message; | |
| 232 | + | } | |
| 233 | + | ||
| 234 | + | void replaceString(std::string& str, const std::string& sought, const std::string& replacement) | |
| 235 | + | { | |
| 236 | + | size_t pos = 0; | |
| 237 | + | size_t start = 0; | |
| 238 | + | size_t soughtLen = sought.length(); | |
| 239 | + | size_t replaceLen = replacement.length(); | |
| 240 | + | ||
| 241 | + | while ((pos = str.find(sought, start)) != std::string::npos) { | |
| 242 | + | str = str.substr(0, pos) + replacement + str.substr(pos + soughtLen); | |
| 243 | + | start = pos + replaceLen; | |
| 244 | + | } | |
| 245 | + | } | |
| 246 | + | ||
| 247 | + | void trim_right(std::string& source, char t) | |
| 248 | + | { | |
| 249 | + | source.erase(source.find_last_not_of(t) + 1); | |
| 250 | + | } | |
| 251 | + | ||
| 252 | + | void trim_left(std::string& source, char t) | |
| 253 | + | { | |
| 254 | + | source.erase(0, source.find_first_not_of(t)); | |
| 255 | + | } | |
| 256 | + | ||
| 257 | + | void toLowerCaseString(std::string& source) | |
| 258 | + | { | |
| 259 | + | std::transform(source.begin(), source.end(), source.begin(), tolower); | |
| 260 | + | } | |
| 261 | + | ||
| 262 | + | std::string asLowerCaseString(std::string source) | |
| 263 | + | { | |
| 264 | + | toLowerCaseString(source); | |
| 265 | + | return source; | |
| 266 | + | } | |
| 267 | + | ||
| 268 | + | std::string asUpperCaseString(std::string source) | |
| 269 | + | { | |
| 270 | + | std::transform(source.begin(), source.end(), source.begin(), toupper); | |
| 271 | + | return source; | |
| 272 | + | } | |
| 273 | + | ||
| 274 | + | StringVector explodeString(const std::string& inString, const std::string& separator, int32_t limit/* = -1*/) | |
| 275 | + | { | |
| 276 | + | StringVector returnVector; | |
| 277 | + | std::string::size_type start = 0, end = 0; | |
| 278 | + | ||
| 279 | + | while (--limit != -1 && (end = inString.find(separator, start)) != std::string::npos) { | |
| 280 | + | returnVector.push_back(inString.substr(start, end - start)); | |
| 281 | + | start = end + separator.size(); | |
| 282 | + | } | |
| 283 | + | ||
| 284 | + | returnVector.push_back(inString.substr(start)); | |
| 285 | + | return returnVector; | |
| 286 | + | } | |
| 287 | + | ||
| 288 | + | IntegerVector vectorAtoi(const StringVector& stringVector) | |
| 289 | + | { | |
| 290 | + | IntegerVector returnVector; | |
| 291 | + | for (const auto& string : stringVector) { | |
| 292 | + | returnVector.push_back(std::stoi(string)); | |
| 293 | + | } | |
| 294 | + | return returnVector; | |
| 295 | + | } | |
| 296 | + | ||
| 297 | + | std::mt19937& getRandomGenerator() | |
| 298 | + | { | |
| 299 | + | static std::random_device rd; | |
| 300 | + | static std::mt19937 generator(rd()); | |
| 301 | + | return generator; | |
| 302 | + | } | |
| 303 | + | ||
| 304 | + | int32_t uniform_random(int32_t minNumber, int32_t maxNumber) | |
| 305 | + | { | |
| 306 | + | static std::uniform_int_distribution<int32_t> uniformRand; | |
| 307 | + | if (minNumber == maxNumber) { | |
| 308 | + | return minNumber; | |
| 309 | + | } else if (minNumber > maxNumber) { | |
| 310 | + | std::swap(minNumber, maxNumber); | |
| 311 | + | } | |
| 312 | + | return uniformRand(getRandomGenerator(), std::uniform_int_distribution<int32_t>::param_type(minNumber, maxNumber)); | |
| 313 | + | } | |
| 314 | + | ||
| 315 | + | int32_t normal_random(int32_t minNumber, int32_t maxNumber) | |
| 316 | + | { | |
| 317 | + | static std::normal_distribution<float> normalRand(0.5f, 0.25f); | |
| 318 | + | if (minNumber == maxNumber) { | |
| 319 | + | return minNumber; | |
| 320 | + | } else if (minNumber > maxNumber) { | |
| 321 | + | std::swap(minNumber, maxNumber); | |
| 322 | + | } | |
| 323 | + | ||
| 324 | + | int32_t increment; | |
| 325 | + | const int32_t diff = maxNumber - minNumber; | |
| 326 | + | const float v = normalRand(getRandomGenerator()); | |
| 327 | + | if (v < 0.0) { | |
| 328 | + | increment = diff / 2; | |
| 329 | + | } else if (v > 1.0) { | |
| 330 | + | increment = (diff + 1) / 2; | |
| 331 | + | } else { | |
| 332 | + | increment = round(v * diff); | |
| 333 | + | } | |
| 334 | + | return minNumber + increment; | |
| 335 | + | } | |
| 336 | + | ||
| 337 | + | bool boolean_random(double probability/* = 0.5*/) | |
| 338 | + | { | |
| 339 | + | static std::bernoulli_distribution booleanRand; | |
| 340 | + | return booleanRand(getRandomGenerator(), std::bernoulli_distribution::param_type(probability)); | |
| 341 | + | } | |
| 342 | + | ||
| 343 | + | void trimString(std::string& str) | |
| 344 | + | { | |
| 345 | + | str.erase(str.find_last_not_of(' ') + 1); | |
| 346 | + | str.erase(0, str.find_first_not_of(' ')); | |
| 347 | + | } | |
| 348 | + | ||
| 349 | + | std::string convertIPToString(uint32_t ip) | |
| 350 | + | { | |
| 351 | + | char buffer[17]; | |
| 352 | + | ||
| 353 | + | int res = sprintf(buffer, "%u.%u.%u.%u", ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (ip >> 24)); | |
| 354 | + | if (res < 0) { | |
| 355 | + | return {}; | |
| 356 | + | } | |
| 357 | + | ||
| 358 | + | return buffer; | |
| 359 | + | } | |
| 360 | + | ||
| 361 | + | std::string formatDate(time_t time) | |
| 362 | + | { | |
| 363 | + | const tm* tms = localtime(&time); | |
| 364 | + | if (!tms) { | |
| 365 | + | return {}; | |
| 366 | + | } | |
| 367 | + | ||
| 368 | + | char buffer[20]; | |
| 369 | + | int res = sprintf(buffer, "%02d/%02d/%04d %02d:%02d:%02d", tms->tm_mday, tms->tm_mon + 1, tms->tm_year + 1900, tms->tm_hour, tms->tm_min, tms->tm_sec); | |
| 370 | + | if (res < 0) { | |
| 371 | + | return {}; | |
| 372 | + | } | |
| 373 | + | return {buffer, 19}; | |
| 374 | + | } | |
| 375 | + | ||
| 376 | + | std::string formatDateShort(time_t time) | |
| 377 | + | { | |
| 378 | + | const tm* tms = localtime(&time); | |
| 379 | + | if (!tms) { | |
| 380 | + | return {}; | |
| 381 | + | } | |
| 382 | + | ||
| 383 | + | char buffer[12]; | |
| 384 | + | size_t res = strftime(buffer, 12, "%d %b %Y", tms); | |
| 385 | + | if (res == 0) { | |
| 386 | + | return {}; | |
| 387 | + | } | |
| 388 | + | return {buffer, 11}; | |
| 389 | + | } | |
| 390 | + | ||
| 391 | + | Direction getDirection(const std::string& string) | |
| 392 | + | { | |
| 393 | + | Direction direction = DIRECTION_NORTH; | |
| 394 | + | ||
| 395 | + | if (string == "north" || string == "n" || string == "0") { | |
| 396 | + | direction = DIRECTION_NORTH; | |
| 397 | + | } else if (string == "east" || string == "e" || string == "1") { | |
| 398 | + | direction = DIRECTION_EAST; | |
| 399 | + | } else if (string == "south" || string == "s" || string == "2") { | |
| 400 | + | direction = DIRECTION_SOUTH; | |
| 401 | + | } else if (string == "west" || string == "w" || string == "3") { | |
| 402 | + | direction = DIRECTION_WEST; | |
| 403 | + | } else if (string == "southwest" || string == "south west" || string == "south-west" || string == "sw" || string == "4") { | |
| 404 | + | direction = DIRECTION_SOUTHWEST; | |
| 405 | + | } else if (string == "southeast" || string == "south east" || string == "south-east" || string == "se" || string == "5") { | |
| 406 | + | direction = DIRECTION_SOUTHEAST; | |
| 407 | + | } else if (string == "northwest" || string == "north west" || string == "north-west" || string == "nw" || string == "6") { | |
| 408 | + | direction = DIRECTION_NORTHWEST; | |
| 409 | + | } else if (string == "northeast" || string == "north east" || string == "north-east" || string == "ne" || string == "7") { | |
| 410 | + | direction = DIRECTION_NORTHEAST; | |
| 411 | + | } | |
| 412 | + | ||
| 413 | + | return direction; | |
| 414 | + | } | |
| 415 | + | ||
| 416 | + | Position getNextPosition(Direction direction, Position pos) | |
| 417 | + | { | |
| 418 | + | switch (direction) { | |
| 419 | + | case DIRECTION_NORTH: | |
| 420 | + | pos.y--; | |
| 421 | + | break; | |
| 422 | + | ||
| 423 | + | case DIRECTION_SOUTH: | |
| 424 | + | pos.y++; | |
| 425 | + | break; | |
| 426 | + | ||
| 427 | + | case DIRECTION_WEST: | |
| 428 | + | pos.x--; | |
| 429 | + | break; | |
| 430 | + | ||
| 431 | + | case DIRECTION_EAST: | |
| 432 | + | pos.x++; | |
| 433 | + | break; | |
| 434 | + | ||
| 435 | + | case DIRECTION_SOUTHWEST: | |
| 436 | + | pos.x--; | |
| 437 | + | pos.y++; | |
| 438 | + | break; | |
| 439 | + | ||
| 440 | + | case DIRECTION_NORTHWEST: | |
| 441 | + | pos.x--; | |
| 442 | + | pos.y--; | |
| 443 | + | break; | |
| 444 | + | ||
| 445 | + | case DIRECTION_NORTHEAST: | |
| 446 | + | pos.x++; | |
| 447 | + | pos.y--; | |
| 448 | + | break; | |
| 449 | + | ||
| 450 | + | case DIRECTION_SOUTHEAST: | |
| 451 | + | pos.x++; | |
| 452 | + | pos.y++; | |
| 453 | + | break; | |
| 454 | + | ||
| 455 | + | default: | |
| 456 | + | break; | |
| 457 | + | } | |
| 458 | + | ||
| 459 | + | return pos; | |
| 460 | + | } | |
| 461 | + | ||
| 462 | + | Direction getDirectionTo(const Position& from, const Position& to) | |
| 463 | + | { | |
| 464 | + | Direction dir; | |
| 465 | + | ||
| 466 | + | int32_t x_offset = Position::getOffsetX(from, to); | |
| 467 | + | if (x_offset < 0) { | |
| 468 | + | dir = DIRECTION_EAST; | |
| 469 | + | x_offset = std::abs(x_offset); | |
| 470 | + | } else { | |
| 471 | + | dir = DIRECTION_WEST; | |
| 472 | + | } | |
| 473 | + | ||
| 474 | + | int32_t y_offset = Position::getOffsetY(from, to); | |
| 475 | + | if (y_offset >= 0) { | |
| 476 | + | if (y_offset > x_offset) { | |
| 477 | + | dir = DIRECTION_NORTH; | |
| 478 | + | } else if (y_offset == x_offset) { | |
| 479 | + | if (dir == DIRECTION_EAST) { | |
| 480 | + | dir = DIRECTION_NORTHEAST; | |
| 481 | + | } else { | |
| 482 | + | dir = DIRECTION_NORTHWEST; | |
| 483 | + | } | |
| 484 | + | } | |
| 485 | + | } else { | |
| 486 | + | y_offset = std::abs(y_offset); | |
| 487 | + | if (y_offset > x_offset) { | |
| 488 | + | dir = DIRECTION_SOUTH; | |
| 489 | + | } else if (y_offset == x_offset) { | |
| 490 | + | if (dir == DIRECTION_EAST) { | |
| 491 | + | dir = DIRECTION_SOUTHEAST; | |
| 492 | + | } else { | |
| 493 | + | dir = DIRECTION_SOUTHWEST; | |
| 494 | + | } | |
| 495 | + | } | |
| 496 | + | } | |
| 497 | + | return dir; | |
| 498 | + | } | |
| 499 | + | ||
| 500 | + | using MagicEffectNames = std::unordered_map<std::string, MagicEffectClasses>; | |
| 501 | + | using ShootTypeNames = std::unordered_map<std::string, ShootType_t>; | |
| 502 | + | using CombatTypeNames = std::unordered_map<CombatType_t, std::string, std::hash<int32_t>>; | |
| 503 | + | using AmmoTypeNames = std::unordered_map<std::string, Ammo_t>; | |
| 504 | + | using WeaponActionNames = std::unordered_map<std::string, WeaponAction_t>; | |
| 505 | + | using SkullNames = std::unordered_map<std::string, Skulls_t>; | |
| 506 | + | ||
| 507 | + | MagicEffectNames magicEffectNames = { | |
| 508 | + | {"redspark", CONST_ME_DRAWBLOOD}, | |
| 509 | + | {"bluebubble", CONST_ME_LOSEENERGY}, | |
| 510 | + | {"poff", CONST_ME_POFF}, | |
| 511 | + | {"yellowspark", CONST_ME_BLOCKHIT}, | |
| 512 | + | {"explosionarea", CONST_ME_EXPLOSIONAREA}, | |
| 513 | + | {"explosion", CONST_ME_EXPLOSIONHIT}, | |
| 514 | + | {"firearea", CONST_ME_FIREAREA}, | |
| 515 | + | {"yellowbubble", CONST_ME_YELLOW_RINGS}, | |
| 516 | + | {"greenbubble", CONST_ME_GREEN_RINGS}, | |
| 517 | + | {"blackspark", CONST_ME_HITAREA}, | |
| 518 | + | {"teleport", CONST_ME_TELEPORT}, | |
| 519 | + | {"energy", CONST_ME_ENERGYHIT}, | |
| 520 | + | {"blueshimmer", CONST_ME_MAGIC_BLUE}, | |
| 521 | + | {"redshimmer", CONST_ME_MAGIC_RED}, | |
| 522 | + | {"greenshimmer", CONST_ME_MAGIC_GREEN}, | |
| 523 | + | {"fire", CONST_ME_HITBYFIRE}, | |
| 524 | + | {"greenspark", CONST_ME_HITBYPOISON}, | |
| 525 | + | {"mortarea", CONST_ME_MORTAREA}, | |
| 526 | + | {"greennote", CONST_ME_SOUND_GREEN}, | |
| 527 | + | {"rednote", CONST_ME_SOUND_RED}, | |
| 528 | + | {"poison", CONST_ME_POISONAREA}, | |
| 529 | + | {"yellownote", CONST_ME_SOUND_YELLOW}, | |
| 530 | + | {"purplenote", CONST_ME_SOUND_PURPLE}, | |
| 531 | + | {"bluenote", CONST_ME_SOUND_BLUE}, | |
| 532 | + | {"whitenote", CONST_ME_SOUND_WHITE}, | |
| 533 | + | {"bubbles", CONST_ME_BUBBLES}, | |
| 534 | + | {"dice", CONST_ME_CRAPS}, | |
| 535 | + | {"giftwraps", CONST_ME_GIFT_WRAPS}, | |
| 536 | + | {"yellowfirework", CONST_ME_FIREWORK_YELLOW}, | |
| 537 | + | {"redfirework", CONST_ME_FIREWORK_RED}, | |
| 538 | + | {"bluefirework", CONST_ME_FIREWORK_BLUE}, | |
| 539 | + | {"stun", CONST_ME_STUN}, | |
| 540 | + | {"sleep", CONST_ME_SLEEP}, | |
| 541 | + | {"watercreature", CONST_ME_WATERCREATURE}, | |
| 542 | + | {"groundshaker", CONST_ME_GROUNDSHAKER}, | |
| 543 | + | {"hearts", CONST_ME_HEARTS}, | |
| 544 | + | {"fireattack", CONST_ME_FIREATTACK}, | |
| 545 | + | {"energyarea", CONST_ME_ENERGYAREA}, | |
| 546 | + | {"smallclouds", CONST_ME_SMALLCLOUDS}, | |
| 547 | + | {"holydamage", CONST_ME_HOLYDAMAGE}, | |
| 548 | + | {"bigclouds", CONST_ME_BIGCLOUDS}, | |
| 549 | + | {"icearea", CONST_ME_ICEAREA}, | |
| 550 | + | {"icetornado", CONST_ME_ICETORNADO}, | |
| 551 | + | {"iceattack", CONST_ME_ICEATTACK}, | |
| 552 | + | {"stones", CONST_ME_STONES}, | |
| 553 | + | {"smallplants", CONST_ME_SMALLPLANTS}, | |
| 554 | + | {"carniphila", CONST_ME_CARNIPHILA}, | |
| 555 | + | {"purpleenergy", CONST_ME_PURPLEENERGY}, | |
| 556 | + | {"yellowenergy", CONST_ME_YELLOWENERGY}, | |
| 557 | + | {"holyarea", CONST_ME_HOLYAREA}, | |
| 558 | + | {"bigplants", CONST_ME_BIGPLANTS}, | |
| 559 | + | {"cake", CONST_ME_CAKE}, | |
| 560 | + | {"giantice", CONST_ME_GIANTICE}, | |
| 561 | + | {"watersplash", CONST_ME_WATERSPLASH}, | |
| 562 | + | {"plantattack", CONST_ME_PLANTATTACK}, | |
| 563 | + | {"tutorialarrow", CONST_ME_TUTORIALARROW}, | |
| 564 | + | {"tutorialsquare", CONST_ME_TUTORIALSQUARE}, | |
| 565 | + | {"mirrorhorizontal", CONST_ME_MIRRORHORIZONTAL}, | |
| 566 | + | {"mirrorvertical", CONST_ME_MIRRORVERTICAL}, | |
| 567 | + | {"skullhorizontal", CONST_ME_SKULLHORIZONTAL}, | |
| 568 | + | {"skullvertical", CONST_ME_SKULLVERTICAL}, | |
| 569 | + | {"assassin", CONST_ME_ASSASSIN}, | |
| 570 | + | {"stepshorizontal", CONST_ME_STEPSHORIZONTAL}, | |
| 571 | + | {"bloodysteps", CONST_ME_BLOODYSTEPS}, | |
| 572 | + | {"stepsvertical", CONST_ME_STEPSVERTICAL}, | |
| 573 | + | {"yalaharighost", CONST_ME_YALAHARIGHOST}, | |
| 574 | + | {"bats", CONST_ME_BATS}, | |
| 575 | + | {"smoke", CONST_ME_SMOKE}, | |
| 576 | + | {"insects", CONST_ME_INSECTS}, | |
| 577 | + | {"dragonhead", CONST_ME_DRAGONHEAD}, | |
| 578 | + | {"orcshaman", CONST_ME_ORCSHAMAN}, | |
| 579 | + | {"orcshamanfire", CONST_ME_ORCSHAMAN_FIRE}, | |
| 580 | + | {"thunder", CONST_ME_THUNDER}, | |
| 581 | + | {"ferumbras", CONST_ME_FERUMBRAS}, | |
| 582 | + | {"confettihorizontal", CONST_ME_CONFETTI_HORIZONTAL}, | |
| 583 | + | {"confettivertical", CONST_ME_CONFETTI_VERTICAL}, | |
| 584 | + | {"blacksmoke", CONST_ME_BLACKSMOKE}, | |
| 585 | + | {"redsmoke", CONST_ME_REDSMOKE}, | |
| 586 | + | {"yellowsmoke", CONST_ME_YELLOWSMOKE}, | |
| 587 | + | {"greensmoke", CONST_ME_GREENSMOKE}, | |
| 588 | + | {"purplesmoke", CONST_ME_PURPLESMOKE}, | |
| 589 | + | }; | |
| 590 | + | ||
| 591 | + | ShootTypeNames shootTypeNames = { | |
| 592 | + | {"spear", CONST_ANI_SPEAR}, | |
| 593 | + | {"bolt", CONST_ANI_BOLT}, | |
| 594 | + | {"arrow", CONST_ANI_ARROW}, | |
| 595 | + | {"fire", CONST_ANI_FIRE}, | |
| 596 | + | {"energy", CONST_ANI_ENERGY}, | |
| 597 | + | {"poisonarrow", CONST_ANI_POISONARROW}, | |
| 598 | + | {"burstarrow", CONST_ANI_BURSTARROW}, | |
| 599 | + | {"throwingstar", CONST_ANI_THROWINGSTAR}, | |
| 600 | + | {"throwingknife", CONST_ANI_THROWINGKNIFE}, | |
| 601 | + | {"smallstone", CONST_ANI_SMALLSTONE}, | |
| 602 | + | {"death", CONST_ANI_DEATH}, | |
| 603 | + | {"largerock", CONST_ANI_LARGEROCK}, | |
| 604 | + | {"snowball", CONST_ANI_SNOWBALL}, | |
| 605 | + | {"powerbolt", CONST_ANI_POWERBOLT}, | |
| 606 | + | {"poison", CONST_ANI_POISON}, | |
| 607 | + | {"infernalbolt", CONST_ANI_INFERNALBOLT}, | |
| 608 | + | {"huntingspear", CONST_ANI_HUNTINGSPEAR}, | |
| 609 | + | {"enchantedspear", CONST_ANI_ENCHANTEDSPEAR}, | |
| 610 | + | {"redstar", CONST_ANI_REDSTAR}, | |
| 611 | + | {"greenstar", CONST_ANI_GREENSTAR}, | |
| 612 | + | {"royalspear", CONST_ANI_ROYALSPEAR}, | |
| 613 | + | {"sniperarrow", CONST_ANI_SNIPERARROW}, | |
| 614 | + | {"onyxarrow", CONST_ANI_ONYXARROW}, | |
| 615 | + | {"piercingbolt", CONST_ANI_PIERCINGBOLT}, | |
| 616 | + | {"whirlwindsword", CONST_ANI_WHIRLWINDSWORD}, | |
| 617 | + | {"whirlwindaxe", CONST_ANI_WHIRLWINDAXE}, | |
| 618 | + | {"whirlwindclub", CONST_ANI_WHIRLWINDCLUB}, | |
| 619 | + | {"etherealspear", CONST_ANI_ETHEREALSPEAR}, | |
| 620 | + | {"ice", CONST_ANI_ICE}, | |
| 621 | + | {"earth", CONST_ANI_EARTH}, | |
| 622 | + | {"holy", CONST_ANI_HOLY}, | |
| 623 | + | {"suddendeath", CONST_ANI_SUDDENDEATH}, | |
| 624 | + | {"flasharrow", CONST_ANI_FLASHARROW}, | |
| 625 | + | {"flammingarrow", CONST_ANI_FLAMMINGARROW}, | |
| 626 | + | {"shiverarrow", CONST_ANI_SHIVERARROW}, | |
| 627 | + | {"energyball", CONST_ANI_ENERGYBALL}, | |
| 628 | + | {"smallice", CONST_ANI_SMALLICE}, | |
| 629 | + | {"smallholy", CONST_ANI_SMALLHOLY}, | |
| 630 | + | {"smallearth", CONST_ANI_SMALLEARTH}, | |
| 631 | + | {"eartharrow", CONST_ANI_EARTHARROW}, | |
| 632 | + | {"explosion", CONST_ANI_EXPLOSION}, | |
| 633 | + | {"cake", CONST_ANI_CAKE}, | |
| 634 | + | {"tarsalarrow", CONST_ANI_TARSALARROW}, | |
| 635 | + | {"vortexbolt", CONST_ANI_VORTEXBOLT}, | |
| 636 | + | {"prismaticbolt", CONST_ANI_PRISMATICBOLT}, | |
| 637 | + | {"crystallinearrow", CONST_ANI_CRYSTALLINEARROW}, | |
| 638 | + | {"drillbolt", CONST_ANI_DRILLBOLT}, | |
| 639 | + | {"envenomedarrow", CONST_ANI_ENVENOMEDARROW}, | |
| 640 | + | {"gloothspear", CONST_ANI_GLOOTHSPEAR}, | |
| 641 | + | {"simplearrow", CONST_ANI_SIMPLEARROW}, | |
| 642 | + | }; | |
| 643 | + | ||
| 644 | + | CombatTypeNames combatTypeNames = { | |
| 645 | + | {COMBAT_PHYSICALDAMAGE, "physical"}, | |
| 646 | + | {COMBAT_ENERGYDAMAGE, "energy"}, | |
| 647 | + | {COMBAT_EARTHDAMAGE, "earth"}, | |
| 648 | + | {COMBAT_FIREDAMAGE, "fire"}, | |
| 649 | + | {COMBAT_UNDEFINEDDAMAGE, "undefined"}, | |
| 650 | + | {COMBAT_LIFEDRAIN, "lifedrain"}, | |
| 651 | + | {COMBAT_MANADRAIN, "manadrain"}, | |
| 652 | + | {COMBAT_HEALING, "healing"}, | |
| 653 | + | {COMBAT_DROWNDAMAGE, "drown"}, | |
| 654 | + | {COMBAT_ICEDAMAGE, "ice"}, | |
| 655 | + | {COMBAT_HOLYDAMAGE, "holy"}, | |
| 656 | + | {COMBAT_DEATHDAMAGE, "death"}, | |
| 657 | + | }; | |
| 658 | + | ||
| 659 | + | AmmoTypeNames ammoTypeNames = { | |
| 660 | + | {"spear", AMMO_SPEAR}, | |
| 661 | + | {"bolt", AMMO_BOLT}, | |
| 662 | + | {"arrow", AMMO_ARROW}, | |
| 663 | + | {"poisonarrow", AMMO_ARROW}, | |
| 664 | + | {"burstarrow", AMMO_ARROW}, | |
| 665 | + | {"throwingstar", AMMO_THROWINGSTAR}, | |
| 666 | + | {"throwingknife", AMMO_THROWINGKNIFE}, | |
| 667 | + | {"smallstone", AMMO_STONE}, | |
| 668 | + | {"largerock", AMMO_STONE}, | |
| 669 | + | {"snowball", AMMO_SNOWBALL}, | |
| 670 | + | {"powerbolt", AMMO_BOLT}, | |
| 671 | + | {"infernalbolt", AMMO_BOLT}, | |
| 672 | + | {"huntingspear", AMMO_SPEAR}, | |
| 673 | + | {"enchantedspear", AMMO_SPEAR}, | |
| 674 | + | {"royalspear", AMMO_SPEAR}, | |
| 675 | + | {"sniperarrow", AMMO_ARROW}, | |
| 676 | + | {"onyxarrow", AMMO_ARROW}, | |
| 677 | + | {"piercingbolt", AMMO_BOLT}, | |
| 678 | + | {"etherealspear", AMMO_SPEAR}, | |
| 679 | + | {"flasharrow", AMMO_ARROW}, | |
| 680 | + | {"flammingarrow", AMMO_ARROW}, | |
| 681 | + | {"shiverarrow", AMMO_ARROW}, | |
| 682 | + | {"eartharrow", AMMO_ARROW}, | |
| 683 | + | }; | |
| 684 | + | ||
| 685 | + | WeaponActionNames weaponActionNames = { | |
| 686 | + | {"move", WEAPONACTION_MOVE}, | |
| 687 | + | {"removecharge", WEAPONACTION_REMOVECHARGE}, | |
| 688 | + | {"removecount", WEAPONACTION_REMOVECOUNT}, | |
| 689 | + | }; | |
| 690 | + | ||
| 691 | + | SkullNames skullNames = { | |
| 692 | + | {"none", SKULL_NONE}, | |
| 693 | + | {"yellow", SKULL_YELLOW}, | |
| 694 | + | {"green", SKULL_GREEN}, | |
| 695 | + | {"white", SKULL_WHITE}, | |
| 696 | + | {"red", SKULL_RED}, | |
| 697 | + | {"black", SKULL_BLACK}, | |
| 698 | + | {"orange", SKULL_ORANGE}, | |
| 699 | + | }; | |
| 700 | + | ||
| 701 | + | MagicEffectClasses getMagicEffect(const std::string& strValue) | |
| 702 | + | { | |
| 703 | + | auto magicEffect = magicEffectNames.find(strValue); | |
| 704 | + | if (magicEffect != magicEffectNames.end()) { | |
| 705 | + | return magicEffect->second; | |
| 706 | + | } | |
| 707 | + | return CONST_ME_NONE; | |
| 708 | + | } | |
| 709 | + | ||
| 710 | + | ShootType_t getShootType(const std::string& strValue) | |
| 711 | + | { | |
| 712 | + | auto shootType = shootTypeNames.find(strValue); | |
| 713 | + | if (shootType != shootTypeNames.end()) { | |
| 714 | + | return shootType->second; | |
| 715 | + | } | |
| 716 | + | return CONST_ANI_NONE; | |
| 717 | + | } | |
| 718 | + | ||
| 719 | + | std::string getCombatName(CombatType_t combatType) | |
| 720 | + | { | |
| 721 | + | auto combatName = combatTypeNames.find(combatType); | |
| 722 | + | if (combatName != combatTypeNames.end()) { | |
| 723 | + | return combatName->second; | |
| 724 | + | } | |
| 725 | + | return "unknown"; | |
| 726 | + | } | |
| 727 | + | ||
| 728 | + | Ammo_t getAmmoType(const std::string& strValue) | |
| 729 | + | { | |
| 730 | + | auto ammoType = ammoTypeNames.find(strValue); | |
| 731 | + | if (ammoType != ammoTypeNames.end()) { | |
| 732 | + | return ammoType->second; | |
| 733 | + | } | |
| 734 | + | return AMMO_NONE; | |
| 735 | + | } | |
| 736 | + | ||
| 737 | + | WeaponAction_t getWeaponAction(const std::string& strValue) | |
| 738 | + | { | |
| 739 | + | auto weaponAction = weaponActionNames.find(strValue); | |
| 740 | + | if (weaponAction != weaponActionNames.end()) { | |
| 741 | + | return weaponAction->second; | |
| 742 | + | } | |
| 743 | + | return WEAPONACTION_NONE; | |
| 744 | + | } | |
| 745 | + | ||
| 746 | + | Skulls_t getSkullType(const std::string& strValue) | |
| 747 | + | { | |
| 748 | + | auto skullType = skullNames.find(strValue); | |
| 749 | + | if (skullType != skullNames.end()) { | |
| 750 | + | return skullType->second; | |
| 751 | + | } | |
| 752 | + | return SKULL_NONE; | |
| 753 | + | } | |
| 754 | + | ||
| 755 | + | std::string getSpecialSkillName(uint8_t skillid) | |
| 756 | + | { | |
| 757 | + | switch (skillid) { | |
| 758 | + | case SPECIALSKILL_CRITICALHITCHANCE: | |
| 759 | + | return "critical hit chance"; | |
| 760 | + | ||
| 761 | + | case SPECIALSKILL_CRITICALHITAMOUNT: | |
| 762 | + | return "critical extra damage"; | |
| 763 | + | ||
| 764 | + | case SPECIALSKILL_LIFELEECHCHANCE: | |
| 765 | + | return "hitpoints leech chance"; | |
| 766 | + | ||
| 767 | + | case SPECIALSKILL_LIFELEECHAMOUNT: | |
| 768 | + | return "hitpoints leech amount"; | |
| 769 | + | ||
| 770 | + | case SPECIALSKILL_MANALEECHCHANCE: | |
| 771 | + | return "manapoints leech chance"; | |
| 772 | + | ||
| 773 | + | case SPECIALSKILL_MANALEECHAMOUNT: | |
| 774 | + | return "mana points leech amount"; | |
| 775 | + | ||
| 776 | + | default: | |
| 777 | + | return "unknown"; | |
| 778 | + | } | |
| 779 | + | } | |
| 780 | + | ||
| 781 | + | std::string getSkillName(uint8_t skillid) | |
| 782 | + | { | |
| 783 | + | switch (skillid) { | |
| 784 | + | case SKILL_FIST: | |
| 785 | + | return "shotgun combat"; | |
| 786 | + | ||
| 787 | + | case SKILL_CLUB: | |
| 788 | + | return "machine-gun combat"; | |
| 789 | + | ||
| 790 | + | case SKILL_SWORD: | |
| 791 | + | return "rifle combat"; | |
| 792 | + | ||
| 793 | + | case SKILL_AXE: | |
| 794 | + | return "pistol combat"; | |
| 795 | + | ||
| 796 | + | case SKILL_DISTANCE: | |
| 797 | + | return "skill distance"; | |
| 798 | + | ||
| 799 | + | case SKILL_SHIELD: | |
| 800 | + | return "shielding"; | |
| 801 | + | ||
| 802 | + | case SKILL_FISHING: | |
| 803 | + | return "fishing"; | |
| 804 | + | ||
| 805 | + | case SKILL_MAGLEVEL: | |
| 806 | + | return "magic level"; | |
| 807 | + | ||
| 808 | + | case SKILL_LEVEL: | |
| 809 | + | return "level"; | |
| 810 | + | ||
| 811 | + | default: | |
| 812 | + | return "unknown"; | |
| 813 | + | } | |
| 814 | + | } | |
| 815 | + | ||
| 816 | + | uint32_t adlerChecksum(const uint8_t* data, size_t length) | |
| 817 | + | { | |
| 818 | + | if (length > NETWORKMESSAGE_MAXSIZE) { | |
| 819 | + | return 0; | |
| 820 | + | } | |
| 821 | + | ||
| 822 | + | const uint16_t adler = 65521; | |
| 823 | + | ||
| 824 | + | uint32_t a = 1, b = 0; | |
| 825 | + | ||
| 826 | + | while (length > 0) { | |
| 827 | + | size_t tmp = length > 5552 ? 5552 : length; | |
| 828 | + | length -= tmp; | |
| 829 | + | ||
| 830 | + | do { | |
| 831 | + | a += *data++; | |
| 832 | + | b += a; | |
| 833 | + | } while (--tmp); | |
| 834 | + | ||
| 835 | + | a %= adler; | |
| 836 | + | b %= adler; | |
| 837 | + | } | |
| 838 | + | ||
| 839 | + | return (b << 16) | a; | |
| 840 | + | } | |
| 841 | + | ||
| 842 | + | std::string ucfirst(std::string str) | |
| 843 | + | { | |
| 844 | + | for (char& i : str) { | |
| 845 | + | if (i != ' ') { | |
| 846 | + | i = toupper(i); | |
| 847 | + | break; | |
| 848 | + | } | |
| 849 | + | } | |
| 850 | + | return str; | |
| 851 | + | } | |
| 852 | + | ||
| 853 | + | std::string ucwords(std::string str) | |
| 854 | + | { | |
| 855 | + | size_t strLength = str.length(); | |
| 856 | + | if (strLength == 0) { | |
| 857 | + | return str; | |
| 858 | + | } | |
| 859 | + | ||
| 860 | + | str[0] = toupper(str.front()); | |
| 861 | + | for (size_t i = 1; i < strLength; ++i) { | |
| 862 | + | if (str[i - 1] == ' ') { | |
| 863 | + | str[i] = toupper(str[i]); | |
| 864 | + | } | |
| 865 | + | } | |
| 866 | + | ||
| 867 | + | return str; | |
| 868 | + | } | |
| 869 | + | ||
| 870 | + | bool booleanString(const std::string& str) | |
| 871 | + | { | |
| 872 | + | if (str.empty()) { | |
| 873 | + | return false; | |
| 874 | + | } | |
| 875 | + | ||
| 876 | + | char ch = tolower(str.front()); | |
| 877 | + | return ch != 'f' && ch != 'n' && ch != '0'; | |
| 878 | + | } | |
| 879 | + | ||
| 880 | + | std::string getWeaponName(WeaponType_t weaponType) | |
| 881 | + | { | |
| 882 | + | switch (weaponType) { | |
| 883 | + | case WEAPON_SWORD: return "sword"; | |
| 884 | + | case WEAPON_CLUB: return "club"; | |
| 885 | + | case WEAPON_AXE: return "axe"; | |
| 886 | + | case WEAPON_DISTANCE: return "distance"; | |
| 887 | + | case WEAPON_WAND: return "wand"; | |
| 888 | + | case WEAPON_AMMO: return "ammunition"; | |
| 889 | + | default: return std::string(); | |
| 890 | + | } | |
| 891 | + | } | |
| 892 | + | ||
| 893 | + | size_t combatTypeToIndex(CombatType_t combatType) | |
| 894 | + | { | |
| 895 | + | switch (combatType) { | |
| 896 | + | case COMBAT_PHYSICALDAMAGE: | |
| 897 | + | return 0; | |
| 898 | + | case COMBAT_ENERGYDAMAGE: | |
| 899 | + | return 1; | |
| 900 | + | case COMBAT_EARTHDAMAGE: | |
| 901 | + | return 2; | |
| 902 | + | case COMBAT_FIREDAMAGE: | |
| 903 | + | return 3; | |
| 904 | + | case COMBAT_UNDEFINEDDAMAGE: | |
| 905 | + | return 4; | |
| 906 | + | case COMBAT_LIFEDRAIN: | |
| 907 | + | return 5; | |
| 908 | + | case COMBAT_MANADRAIN: | |
| 909 | + | return 6; | |
| 910 | + | case COMBAT_HEALING: | |
| 911 | + | return 7; | |
| 912 | + | case COMBAT_DROWNDAMAGE: | |
| 913 | + | return 8; | |
| 914 | + | case COMBAT_ICEDAMAGE: | |
| 915 | + | return 9; | |
| 916 | + | case COMBAT_HOLYDAMAGE: | |
| 917 | + | return 10; | |
| 918 | + | case COMBAT_DEATHDAMAGE: | |
| 919 | + | return 11; | |
| 920 | + | default: | |
| 921 | + | return 0; | |
| 922 | + | } | |
| 923 | + | } | |
| 924 | + | ||
| 925 | + | CombatType_t indexToCombatType(size_t v) | |
| 926 | + | { | |
| 927 | + | return static_cast<CombatType_t>(1 << v); | |
| 928 | + | } | |
| 929 | + | ||
| 930 | + | uint8_t serverFluidToClient(uint8_t serverFluid) | |
| 931 | + | { | |
| 932 | + | uint8_t size = sizeof(clientToServerFluidMap) / sizeof(uint8_t); | |
| 933 | + | for (uint8_t i = 0; i < size; ++i) { | |
| 934 | + | if (clientToServerFluidMap[i] == serverFluid) { | |
| 935 | + | return i; | |
| 936 | + | } | |
| 937 | + | } | |
| 938 | + | return 0; | |
| 939 | + | } | |
| 940 | + | ||
| 941 | + | uint8_t clientFluidToServer(uint8_t clientFluid) | |
| 942 | + | { | |
| 943 | + | uint8_t size = sizeof(clientToServerFluidMap) / sizeof(uint8_t); | |
| 944 | + | if (clientFluid >= size) { | |
| 945 | + | return 0; | |
| 946 | + | } | |
| 947 | + | return clientToServerFluidMap[clientFluid]; | |
| 948 | + | } | |
| 949 | + | ||
| 950 | + | itemAttrTypes stringToItemAttribute(const std::string& str) | |
| 951 | + | { | |
| 952 | + | if (str == "aid") { | |
| 953 | + | return ITEM_ATTRIBUTE_ACTIONID; | |
| 954 | + | } else if (str == "uid") { | |
| 955 | + | return ITEM_ATTRIBUTE_UNIQUEID; | |
| 956 | + | } else if (str == "description") { | |
| 957 | + | return ITEM_ATTRIBUTE_DESCRIPTION; | |
| 958 | + | } else if (str == "text") { | |
| 959 | + | return ITEM_ATTRIBUTE_TEXT; | |
| 960 | + | } else if (str == "date") { | |
| 961 | + | return ITEM_ATTRIBUTE_DATE; | |
| 962 | + | } else if (str == "writer") { | |
| 963 | + | return ITEM_ATTRIBUTE_WRITER; | |
| 964 | + | } else if (str == "name") { | |
| 965 | + | return ITEM_ATTRIBUTE_NAME; | |
| 966 | + | } else if (str == "article") { | |
| 967 | + | return ITEM_ATTRIBUTE_ARTICLE; | |
| 968 | + | } else if (str == "pluralname") { | |
| 969 | + | return ITEM_ATTRIBUTE_PLURALNAME; | |
| 970 | + | } else if (str == "weight") { | |
| 971 | + | return ITEM_ATTRIBUTE_WEIGHT; | |
| 972 | + | } else if (str == "attack") { | |
| 973 | + | return ITEM_ATTRIBUTE_ATTACK; | |
| 974 | + | } else if (str == "defense") { | |
| 975 | + | return ITEM_ATTRIBUTE_DEFENSE; | |
| 976 | + | } else if (str == "extradefense") { | |
| 977 | + | return ITEM_ATTRIBUTE_EXTRADEFENSE; | |
| 978 | + | } else if (str == "armor") { | |
| 979 | + | return ITEM_ATTRIBUTE_ARMOR; | |
| 980 | + | } else if (str == "hitchance") { | |
| 981 | + | return ITEM_ATTRIBUTE_HITCHANCE; | |
| 982 | + | } else if (str == "shootrange") { | |
| 983 | + | return ITEM_ATTRIBUTE_SHOOTRANGE; | |
| 984 | + | } else if (str == "owner") { | |
| 985 | + | return ITEM_ATTRIBUTE_OWNER; | |
| 986 | + | } else if (str == "duration") { | |
| 987 | + | return ITEM_ATTRIBUTE_DURATION; | |
| 988 | + | } else if (str == "decaystate") { | |
| 989 | + | return ITEM_ATTRIBUTE_DECAYSTATE; | |
| 990 | + | } else if (str == "corpseowner") { | |
| 991 | + | return ITEM_ATTRIBUTE_CORPSEOWNER; | |
| 992 | + | } else if (str == "charges") { | |
| 993 | + | return ITEM_ATTRIBUTE_CHARGES; | |
| 994 | + | } else if (str == "fluidtype") { | |
| 995 | + | return ITEM_ATTRIBUTE_FLUIDTYPE; | |
| 996 | + | } else if (str == "doorid") { | |
| 997 | + | return ITEM_ATTRIBUTE_DOORID; | |
| 998 | + | } else if (str == "wrapid") { | |
| 999 | + | return ITEM_ATTRIBUTE_WRAPID; | |
| 1000 | + | } | |
| 1001 | + | return ITEM_ATTRIBUTE_NONE; | |
| 1002 | + | } | |
| 1003 | + | ||
| 1004 | + | std::string getFirstLine(const std::string& str) | |
| 1005 | + | { | |
| 1006 | + | std::string firstLine; | |
| 1007 | + | firstLine.reserve(str.length()); | |
| 1008 | + | for (const char c : str) { | |
| 1009 | + | if (c == '\n') { | |
| 1010 | + | break; | |
| 1011 | + | } | |
| 1012 | + | firstLine.push_back(c); | |
| 1013 | + | } | |
| 1014 | + | return firstLine; | |
| 1015 | + | } | |
| 1016 | + | ||
| 1017 | + | const char* getReturnMessage(ReturnValue value) | |
| 1018 | + | { | |
| 1019 | + | switch (value) { | |
| 1020 | + | case RETURNVALUE_DESTINATIONOUTOFREACH: | |
| 1021 | + | return "Destination is out of range."; | |
| 1022 | + | ||
| 1023 | + | case RETURNVALUE_NOTMOVEABLE: | |
| 1024 | + | return "You cannot move this object."; | |
| 1025 | + | ||
| 1026 | + | case RETURNVALUE_DROPTWOHANDEDITEM: | |
| 1027 | + | return "Drop the double-handed object first."; | |
| 1028 | + | ||
| 1029 | + | case RETURNVALUE_BOTHHANDSNEEDTOBEFREE: | |
| 1030 | + | return "Both hands need to be free."; | |
| 1031 | + | ||
| 1032 | + | case RETURNVALUE_CANNOTBEDRESSED: | |
| 1033 | + | return "You cannot dress this object there."; | |
| 1034 | + | ||
| 1035 | + | case RETURNVALUE_PUTTHISOBJECTINYOURHAND: | |
| 1036 | + | return "Put this object in your hand."; | |
| 1037 | + | ||
| 1038 | + | case RETURNVALUE_PUTTHISOBJECTINBOTHHANDS: | |
| 1039 | + | return "Put this object in both hands."; | |
| 1040 | + | ||
| 1041 | + | case RETURNVALUE_CANONLYUSEONEWEAPON: | |
| 1042 | + | return "You may only use one weapon."; | |
| 1043 | + | ||
| 1044 | + | case RETURNVALUE_TOOFARAWAY: | |
| 1045 | + | return "You are too far away."; | |
| 1046 | + | ||
| 1047 | + | case RETURNVALUE_FIRSTGODOWNSTAIRS: | |
| 1048 | + | return "First go downstairs."; | |
| 1049 | + | ||
| 1050 | + | case RETURNVALUE_FIRSTGOUPSTAIRS: | |
| 1051 | + | return "First go upstairs."; | |
| 1052 | + | ||
| 1053 | + | case RETURNVALUE_NOTENOUGHCAPACITY: | |
| 1054 | + | return "This object is too heavy for you to carry."; | |
| 1055 | + | ||
| 1056 | + | case RETURNVALUE_CONTAINERNOTENOUGHROOM: | |
| 1057 | + | return "You cannot put more objects in this container."; | |
| 1058 | + | ||
| 1059 | + | case RETURNVALUE_NEEDEXCHANGE: | |
| 1060 | + | case RETURNVALUE_NOTENOUGHROOM: | |
| 1061 | + | return "There is not enough room."; | |
| 1062 | + | ||
| 1063 | + | case RETURNVALUE_CANNOTPICKUP: | |
| 1064 | + | return "You cannot take this object."; | |
| 1065 | + | ||
| 1066 | + | case RETURNVALUE_CANNOTTHROW: | |
| 1067 | + | return "You cannot throw there."; | |
| 1068 | + | ||
| 1069 | + | case RETURNVALUE_THEREISNOWAY: | |
| 1070 | + | return "There is no way."; | |
| 1071 | + | ||
| 1072 | + | case RETURNVALUE_THISISIMPOSSIBLE: | |
| 1073 | + | return "This is impossible."; | |
| 1074 | + | ||
| 1075 | + | case RETURNVALUE_PLAYERISPZLOCKED: | |
| 1076 | + | return "You can not enter a protection zone after attacking another player."; | |
| 1077 | + | ||
| 1078 | + | case RETURNVALUE_PLAYERISNOTINVITED: | |
| 1079 | + | return "You are not invited."; | |
| 1080 | + | ||
| 1081 | + | case RETURNVALUE_CREATUREDOESNOTEXIST: | |
| 1082 | + | return "Creature does not exist."; | |
| 1083 | + | ||
| 1084 | + | case RETURNVALUE_DEPOTISFULL: | |
| 1085 | + | return "You cannot put more items in this depot."; | |
| 1086 | + | ||
| 1087 | + | case RETURNVALUE_CANNOTUSETHISOBJECT: | |
| 1088 | + | return "You cannot use this object."; | |
| 1089 | + | ||
| 1090 | + | case RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE: | |
| 1091 | + | return "A player with this name is not online."; | |
| 1092 | + | ||
| 1093 | + | case RETURNVALUE_NOTREQUIREDLEVELTOUSERUNE: | |
| 1094 | + | return "You do not have the required magic level to use this rune."; | |
| 1095 | + | ||
| 1096 | + | case RETURNVALUE_YOUAREALREADYTRADING: | |
| 1097 | + | return "You are already trading."; | |
| 1098 | + | ||
| 1099 | + | case RETURNVALUE_THISPLAYERISALREADYTRADING: | |
| 1100 | + | return "This player is already trading."; | |
| 1101 | + | ||
| 1102 | + | case RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT: | |
| 1103 | + | return "You may not logout during or immediately after a fight!"; | |
| 1104 | + | ||
| 1105 | + | case RETURNVALUE_DIRECTPLAYERSHOOT: | |
| 1106 | + | return "You are not allowed to shoot directly on players."; | |
| 1107 | + | ||
| 1108 | + | case RETURNVALUE_NOTENOUGHLEVEL: | |
| 1109 | + | return "Your level is too low."; | |
| 1110 | + | ||
| 1111 | + | case RETURNVALUE_NOTENOUGHMAGICLEVEL: | |
| 1112 | + | return "You do not have enough magic level."; | |
| 1113 | + | ||
| 1114 | + | case RETURNVALUE_NOTENOUGHMANA: | |
| 1115 | + | return "You do not have enough mana."; | |
| 1116 | + | ||
| 1117 | + | case RETURNVALUE_NOTENOUGHSOUL: | |
| 1118 | + | return "You do not have enough soul."; | |
| 1119 | + | ||
| 1120 | + | case RETURNVALUE_YOUAREEXHAUSTED: | |
| 1121 | + | return "You are exhausted."; | |
| 1122 | + | ||
| 1123 | + | case RETURNVALUE_YOUCANNOTUSEOBJECTSTHATFAST: | |
| 1124 | + | return "You cannot use objects that fast."; | |
| 1125 | + | ||
| 1126 | + | case RETURNVALUE_CANONLYUSETHISRUNEONCREATURES: | |
| 1127 | + | return "You can only use it on creatures."; | |
| 1128 | + | ||
| 1129 | + | case RETURNVALUE_PLAYERISNOTREACHABLE: | |
| 1130 | + | return "Player is not reachable."; | |
| 1131 | + | ||
| 1132 | + | case RETURNVALUE_CREATUREISNOTREACHABLE: | |
| 1133 | + | return "Creature is not reachable."; | |
| 1134 | + | ||
| 1135 | + | case RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE: | |
| 1136 | + | return "This action is not permitted in a protection zone."; | |
| 1137 | + | ||
| 1138 | + | case RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER: | |
| 1139 | + | return "You may not attack this person."; | |
| 1140 | + | ||
| 1141 | + | case RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE: | |
| 1142 | + | return "You may not attack this creature."; | |
| 1143 | + | ||
| 1144 | + | case RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE: | |
| 1145 | + | return "You may not attack a person in a protection zone."; | |
| 1146 | + | ||
| 1147 | + | case RETURNVALUE_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE: | |
| 1148 | + | return "You may not attack a person while you are in a protection zone."; | |
| 1149 | + | ||
| 1150 | + | case RETURNVALUE_YOUCANONLYUSEITONCREATURES: | |
| 1151 | + | return "You can only use it on creatures."; | |
| 1152 | + | ||
| 1153 | + | case RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS: | |
| 1154 | + | return "Turn secure mode off if you really want to attack unmarked players."; | |
| 1155 | + | ||
| 1156 | + | case RETURNVALUE_YOUNEEDPREMIUMACCOUNT: | |
| 1157 | + | return "You need a premium account."; | |
| 1158 | + | ||
| 1159 | + | case RETURNVALUE_YOUNEEDTOLEARNTHISSPELL: | |
| 1160 | + | return "You must learn this spell first."; | |
| 1161 | + | ||
| 1162 | + | case RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL: | |
| 1163 | + | return "You have the wrong vocation to cast this spell."; | |
| 1164 | + | ||
| 1165 | + | case RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL: | |
| 1166 | + | return "You need to equip a weapon to use this spell."; | |
| 1167 | + | ||
| 1168 | + | case RETURNVALUE_PLAYERISPZLOCKEDLEAVEPVPZONE: | |
| 1169 | + | return "You can not leave a pvp zone after attacking another player."; | |
| 1170 | + | ||
| 1171 | + | case RETURNVALUE_PLAYERISPZLOCKEDENTERPVPZONE: | |
| 1172 | + | return "You can not enter a pvp zone after attacking another player."; | |
| 1173 | + | ||
| 1174 | + | case RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE: | |
| 1175 | + | return "This action is not permitted in a non pvp zone."; | |
| 1176 | + | ||
| 1177 | + | case RETURNVALUE_YOUCANNOTLOGOUTHERE: | |
| 1178 | + | return "You can not logout here."; | |
| 1179 | + | ||
| 1180 | + | case RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL: | |
| 1181 | + | return "You need a magic item to cast this spell."; | |
| 1182 | + | ||
| 1183 | + | case RETURNVALUE_CANNOTCONJUREITEMHERE: | |
| 1184 | + | return "You cannot conjure items here."; | |
| 1185 | + | ||
| 1186 | + | case RETURNVALUE_YOUNEEDTOSPLITYOURSPEARS: | |
| 1187 | + | return "You need to split your spears first."; | |
| 1188 | + | ||
| 1189 | + | case RETURNVALUE_NAMEISTOOAMBIGUOUS: | |
| 1190 | + | return "Player name is ambiguous."; | |
| 1191 | + | ||
| 1192 | + | case RETURNVALUE_CANONLYUSEONESHIELD: | |
| 1193 | + | return "You may use only one shield."; | |
| 1194 | + | ||
| 1195 | + | case RETURNVALUE_NOPARTYMEMBERSINRANGE: | |
| 1196 | + | return "No party members in range."; | |
| 1197 | + | ||
| 1198 | + | case RETURNVALUE_YOUARENOTTHEOWNER: | |
| 1199 | + | return "You are not the owner."; | |
| 1200 | + | ||
| 1201 | + | case RETURNVALUE_NOSUCHRAIDEXISTS: | |
| 1202 | + | return "No such raid exists."; | |
| 1203 | + | ||
| 1204 | + | case RETURNVALUE_ANOTHERRAIDISALREADYEXECUTING: | |
| 1205 | + | return "Another raid is already executing."; | |
| 1206 | + | ||
| 1207 | + | case RETURNVALUE_TRADEPLAYERFARAWAY: | |
| 1208 | + | return "Trade player is too far away."; | |
| 1209 | + | ||
| 1210 | + | case RETURNVALUE_YOUDONTOWNTHISHOUSE: | |
| 1211 | + | return "You don't own this house."; | |
| 1212 | + | ||
| 1213 | + | case RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE: | |
| 1214 | + | return "Trade player already owns a house."; | |
| 1215 | + | ||
| 1216 | + | case RETURNVALUE_TRADEPLAYERHIGHESTBIDDER: | |
| 1217 | + | return "Trade player is currently the highest bidder of an auctioned house."; | |
| 1218 | + | ||
| 1219 | + | case RETURNVALUE_YOUCANNOTTRADETHISHOUSE: | |
| 1220 | + | return "You can not trade this house."; | |
| 1221 | + | ||
| 1222 | + | case RETURNVALUE_YOUDONTHAVEREQUIREDPROFESSION: | |
| 1223 | + | return "You don't have the required profession."; | |
| 1224 | + | ||
| 1225 | + | default: // RETURNVALUE_NOTPOSSIBLE, etc | |
| 1226 | + | return "Sorry, not possible."; | |
| 1227 | + | } | |
| 1228 | + | } | |
| 1229 | + | ||
| 1230 | + | int64_t OTSYS_TIME() | |
| 1231 | + | { | |
| 1232 | + | return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count(); | |
| 1233 | + | } | |
| 1234 | + | ||
| 1235 | + | SpellGroup_t stringToSpellGroup(const std::string& value) | |
| 1236 | + | { | |
| 1237 | + | std::string tmpStr = asLowerCaseString(value); | |
| 1238 | + | if (tmpStr == "attack" || tmpStr == "1") { | |
| 1239 | + | return SPELLGROUP_ATTACK; | |
| 1240 | + | } else if (tmpStr == "healing" || tmpStr == "2") { | |
| 1241 | + | return SPELLGROUP_HEALING; | |
| 1242 | + | } else if (tmpStr == "support" || tmpStr == "3") { | |
| 1243 | + | return SPELLGROUP_SUPPORT; | |
| 1244 | + | } else if (tmpStr == "special" || tmpStr == "4") { | |
| 1245 | + | return SPELLGROUP_SPECIAL; | |
| 1246 | + | } | |
| 1247 | + | ||
| 1248 | + | return SPELLGROUP_NONE; | |
| 1249 | + | } |
| @@ -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 | + | #ifndef FS_TOOLS_H_5F9A9742DA194628830AA1C64909AE43 | |
| 21 | + | #define FS_TOOLS_H_5F9A9742DA194628830AA1C64909AE43 | |
| 22 | + | ||
| 23 | + | #include <random> | |
| 24 | + | ||
| 25 | + | #include "position.h" | |
| 26 | + | #include "const.h" | |
| 27 | + | #include "enums.h" | |
| 28 | + | ||
| 29 | + | void printXMLError(const std::string& where, const std::string& fileName, const pugi::xml_parse_result& result); | |
| 30 | + | ||
| 31 | + | std::string transformToSHA1(const std::string& input); | |
| 32 | + | std::string generateToken(const std::string& key, uint32_t ticks); | |
| 33 | + | ||
| 34 | + | void replaceString(std::string& str, const std::string& sought, const std::string& replacement); | |
| 35 | + | void trim_right(std::string& source, char t); | |
| 36 | + | void trim_left(std::string& source, char t); | |
| 37 | + | void toLowerCaseString(std::string& source); | |
| 38 | + | std::string asLowerCaseString(std::string source); | |
| 39 | + | std::string asUpperCaseString(std::string source); | |
| 40 | + | ||
| 41 | + | using StringVector = std::vector<std::string>; | |
| 42 | + | using IntegerVector = std::vector<int32_t>; | |
| 43 | + | ||
| 44 | + | StringVector explodeString(const std::string& inString, const std::string& separator, int32_t limit = -1); | |
| 45 | + | IntegerVector vectorAtoi(const StringVector& stringVector); | |
| 46 | + | constexpr bool hasBitSet(uint32_t flag, uint32_t flags) { | |
| 47 | + | return (flags & flag) != 0; | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | std::mt19937& getRandomGenerator(); | |
| 51 | + | int32_t uniform_random(int32_t minNumber, int32_t maxNumber); | |
| 52 | + | int32_t normal_random(int32_t minNumber, int32_t maxNumber); | |
| 53 | + | bool boolean_random(double probability = 0.5); | |
| 54 | + | ||
| 55 | + | Direction getDirection(const std::string& string); | |
| 56 | + | Position getNextPosition(Direction direction, Position pos); | |
| 57 | + | Direction getDirectionTo(const Position& from, const Position& to); | |
| 58 | + | ||
| 59 | + | std::string getFirstLine(const std::string& str); | |
| 60 | + | ||
| 61 | + | std::string formatDate(time_t time); | |
| 62 | + | std::string formatDateShort(time_t time); | |
| 63 | + | std::string convertIPToString(uint32_t ip); | |
| 64 | + | ||
| 65 | + | void trimString(std::string& str); | |
| 66 | + | ||
| 67 | + | MagicEffectClasses getMagicEffect(const std::string& strValue); | |
| 68 | + | ShootType_t getShootType(const std::string& strValue); | |
| 69 | + | Ammo_t getAmmoType(const std::string& strValue); | |
| 70 | + | WeaponAction_t getWeaponAction(const std::string& strValue); | |
| 71 | + | Skulls_t getSkullType(const std::string& strValue); | |
| 72 | + | std::string getCombatName(CombatType_t combatType); | |
| 73 | + | ||
| 74 | + | std::string getSpecialSkillName(uint8_t skillid); | |
| 75 | + | std::string getSkillName(uint8_t skillid); | |
| 76 | + | ||
| 77 | + | uint32_t adlerChecksum(const uint8_t* data, size_t length); | |
| 78 | + | ||
| 79 | + | std::string ucfirst(std::string str); | |
| 80 | + | std::string ucwords(std::string str); | |
| 81 | + | bool booleanString(const std::string& str); | |
| 82 | + | ||
| 83 | + | std::string getWeaponName(WeaponType_t weaponType); | |
| 84 | + | ||
| 85 | + | size_t combatTypeToIndex(CombatType_t combatType); | |
| 86 | + | CombatType_t indexToCombatType(size_t v); | |
| 87 | + | ||
| 88 | + | uint8_t serverFluidToClient(uint8_t serverFluid); | |
| 89 | + | uint8_t clientFluidToServer(uint8_t clientFluid); | |
| 90 | + | ||
| 91 | + | itemAttrTypes stringToItemAttribute(const std::string& str); | |
| 92 | + | ||
| 93 | + | const char* getReturnMessage(ReturnValue value); | |
| 94 | + | ||
| 95 | + | int64_t OTSYS_TIME(); | |
| 96 | + | ||
| 97 | + | SpellGroup_t stringToSpellGroup(const std::string& value); | |
| 98 | + | ||
| 99 | + | #endif |
| @@ -0,0 +1,98 @@ | |||
| 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_TOWN_H_3BE21D2293B44AA4A3D22D25BE1B9350 | |
| 21 | + | #define FS_TOWN_H_3BE21D2293B44AA4A3D22D25BE1B9350 | |
| 22 | + | ||
| 23 | + | #include "position.h" | |
| 24 | + | ||
| 25 | + | class Town | |
| 26 | + | { | |
| 27 | + | public: | |
| 28 | + | explicit Town(uint32_t id) : id(id) {} | |
| 29 | + | ||
| 30 | + | const Position& getTemplePosition() const { | |
| 31 | + | return templePosition; | |
| 32 | + | } | |
| 33 | + | const std::string& getName() const { | |
| 34 | + | return name; | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | void setTemplePos(Position pos) { | |
| 38 | + | templePosition = pos; | |
| 39 | + | } | |
| 40 | + | void setName(std::string name) { | |
| 41 | + | this->name = std::move(name); | |
| 42 | + | } | |
| 43 | + | uint32_t getID() const { | |
| 44 | + | return id; | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | private: | |
| 48 | + | uint32_t id; | |
| 49 | + | std::string name; | |
| 50 | + | Position templePosition; | |
| 51 | + | }; | |
| 52 | + | ||
| 53 | + | using TownMap = std::map<uint32_t, Town*>; | |
| 54 | + | ||
| 55 | + | class Towns | |
| 56 | + | { | |
| 57 | + | public: | |
| 58 | + | Towns() = default; | |
| 59 | + | ~Towns() { | |
| 60 | + | for (const auto& it : townMap) { | |
| 61 | + | delete it.second; | |
| 62 | + | } | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | // non-copyable | |
| 66 | + | Towns(const Towns&) = delete; | |
| 67 | + | Towns& operator=(const Towns&) = delete; | |
| 68 | + | ||
| 69 | + | bool addTown(uint32_t townId, Town* town) { | |
| 70 | + | return townMap.emplace(townId, town).second; | |
| 71 | + | } | |
| 72 | + | ||
| 73 | + | Town* getTown(const std::string& townName) const { | |
| 74 | + | for (const auto& it : townMap) { | |
| 75 | + | if (strcasecmp(townName.c_str(), it.second->getName().c_str()) == 0) { | |
| 76 | + | return it.second; | |
| 77 | + | } | |
| 78 | + | } | |
| 79 | + | return nullptr; | |
| 80 | + | } | |
| 81 | + | ||
| 82 | + | Town* getTown(uint32_t townId) const { | |
| 83 | + | auto it = townMap.find(townId); | |
| 84 | + | if (it == townMap.end()) { | |
| 85 | + | return nullptr; | |
| 86 | + | } | |
| 87 | + | return it->second; | |
| 88 | + | } | |
| 89 | + | ||
| 90 | + | const TownMap& getTowns() const { | |
| 91 | + | return townMap; | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | private: | |
| 95 | + | TownMap townMap; | |
| 96 | + | }; | |
| 97 | + | ||
| 98 | + | #endif |
| @@ -0,0 +1,102 @@ | |||
| 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 "trashholder.h" | |
| 23 | + | #include "game.h" | |
| 24 | + | ||
| 25 | + | extern Game g_game; | |
| 26 | + | ||
| 27 | + | ReturnValue TrashHolder::queryAdd(int32_t, const Thing&, uint32_t, uint32_t, Creature*) const | |
| 28 | + | { | |
| 29 | + | return RETURNVALUE_NOERROR; | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | ReturnValue TrashHolder::queryMaxCount(int32_t, const Thing&, uint32_t count, uint32_t& maxQueryCount, uint32_t) const | |
| 33 | + | { | |
| 34 | + | maxQueryCount = std::max<uint32_t>(1, count); | |
| 35 | + | return RETURNVALUE_NOERROR; | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | ReturnValue TrashHolder::queryRemove(const Thing&, uint32_t, uint32_t) const | |
| 39 | + | { | |
| 40 | + | return RETURNVALUE_NOTPOSSIBLE; | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | Cylinder* TrashHolder::queryDestination(int32_t&, const Thing&, Item**, uint32_t&) | |
| 44 | + | { | |
| 45 | + | return this; | |
| 46 | + | } | |
| 47 | + | ||
| 48 | + | void TrashHolder::addThing(Thing* thing) | |
| 49 | + | { | |
| 50 | + | return addThing(0, thing); | |
| 51 | + | } | |
| 52 | + | ||
| 53 | + | void TrashHolder::addThing(int32_t, Thing* thing) | |
| 54 | + | { | |
| 55 | + | Item* item = thing->getItem(); | |
| 56 | + | if (!item) { | |
| 57 | + | return; | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | if (item == this || !item->hasProperty(CONST_PROP_MOVEABLE)) { | |
| 61 | + | return; | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | const ItemType& it = Item::items[id]; | |
| 65 | + | if (item->isHangable() && it.isGroundTile()) { | |
| 66 | + | Tile* tile = dynamic_cast<Tile*>(getParent()); | |
| 67 | + | if (tile && tile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) { | |
| 68 | + | return; | |
| 69 | + | } | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | g_game.internalRemoveItem(item); | |
| 73 | + | ||
| 74 | + | if (it.magicEffect != CONST_ME_NONE) { | |
| 75 | + | g_game.addMagicEffect(getPosition(), it.magicEffect); | |
| 76 | + | } | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | void TrashHolder::updateThing(Thing*, uint16_t, uint32_t) | |
| 80 | + | { | |
| 81 | + | // | |
| 82 | + | } | |
| 83 | + | ||
| 84 | + | void TrashHolder::replaceThing(uint32_t, Thing*) | |
| 85 | + | { | |
| 86 | + | // | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | void TrashHolder::removeThing(Thing*, uint32_t) | |
| 90 | + | { | |
| 91 | + | // | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | void TrashHolder::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) | |
| 95 | + | { | |
| 96 | + | getParent()->postAddNotification(thing, oldParent, index, LINK_PARENT); | |
| 97 | + | } | |
| 98 | + | ||
| 99 | + | void TrashHolder::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) | |
| 100 | + | { | |
| 101 | + | getParent()->postRemoveNotification(thing, newParent, index, LINK_PARENT); | |
| 102 | + | } |
| @@ -0,0 +1,57 @@ | |||
| 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_TRASHHOLDER_H_BA162024D67B4D388147F5EE06F33098 | |
| 21 | + | #define FS_TRASHHOLDER_H_BA162024D67B4D388147F5EE06F33098 | |
| 22 | + | ||
| 23 | + | #include "item.h" | |
| 24 | + | #include "cylinder.h" | |
| 25 | + | #include "const.h" | |
| 26 | + | ||
| 27 | + | class TrashHolder final : public Item, public Cylinder | |
| 28 | + | { | |
| 29 | + | public: | |
| 30 | + | explicit TrashHolder(uint16_t itemId) : Item(itemId) {} | |
| 31 | + | ||
| 32 | + | TrashHolder* getTrashHolder() override { | |
| 33 | + | return this; | |
| 34 | + | } | |
| 35 | + | const TrashHolder* getTrashHolder() const override { | |
| 36 | + | return this; | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | //cylinder implementations | |
| 40 | + | ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, Creature* actor = nullptr) const override; | |
| 41 | + | ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, uint32_t flags) const override; | |
| 42 | + | ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const override; | |
| 43 | + | Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) override; | |
| 44 | + | ||
| 45 | + | void addThing(Thing* thing) override; | |
| 46 | + | void addThing(int32_t index, Thing* thing) override; | |
| 47 | + | ||
| 48 | + | void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override; | |
| 49 | + | void replaceThing(uint32_t index, Thing* thing) override; | |
| 50 | + | ||
| 51 | + | void removeThing(Thing* thing, uint32_t count) override; | |
| 52 | + | ||
| 53 | + | void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; | |
| 54 | + | void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; | |
| 55 | + | }; | |
| 56 | + | ||
| 57 | + | #endif |
| @@ -0,0 +1,210 @@ | |||
| 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 "vocation.h" | |
| 23 | + | ||
| 24 | + | #include "pugicast.h" | |
| 25 | + | #include "tools.h" | |
| 26 | + | ||
| 27 | + | bool Vocations::loadFromXml() | |
| 28 | + | { | |
| 29 | + | pugi::xml_document doc; | |
| 30 | + | pugi::xml_parse_result result = doc.load_file("data/XML/vocations.xml"); | |
| 31 | + | if (!result) { | |
| 32 | + | printXMLError("Error - Vocations::loadFromXml", "data/XML/vocations.xml", result); | |
| 33 | + | return false; | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | for (auto vocationNode : doc.child("vocations").children()) { | |
| 37 | + | pugi::xml_attribute attr; | |
| 38 | + | if (!(attr = vocationNode.attribute("id"))) { | |
| 39 | + | std::cout << "[Warning - Vocations::loadFromXml] Missing vocation id" << std::endl; | |
| 40 | + | continue; | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | uint16_t id = pugi::cast<uint16_t>(attr.value()); | |
| 44 | + | ||
| 45 | + | auto res = vocationsMap.emplace(std::piecewise_construct, | |
| 46 | + | std::forward_as_tuple(id), std::forward_as_tuple(id)); | |
| 47 | + | Vocation& voc = res.first->second; | |
| 48 | + | ||
| 49 | + | if ((attr = vocationNode.attribute("name"))) { | |
| 50 | + | voc.name = attr.as_string(); | |
| 51 | + | } | |
| 52 | + | ||
| 53 | + | if ((attr = vocationNode.attribute("clientid"))) { | |
| 54 | + | voc.clientId = pugi::cast<uint16_t>(attr.value()); | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | if ((attr = vocationNode.attribute("description"))) { | |
| 58 | + | voc.description = attr.as_string(); | |
| 59 | + | } | |
| 60 | + | ||
| 61 | + | if ((attr = vocationNode.attribute("gaincap"))) { | |
| 62 | + | voc.gainCap = pugi::cast<uint32_t>(attr.value()) * 100; | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | if ((attr = vocationNode.attribute("gainhp"))) { | |
| 66 | + | voc.gainHP = pugi::cast<uint32_t>(attr.value()); | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | if ((attr = vocationNode.attribute("gainmana"))) { | |
| 70 | + | voc.gainMana = pugi::cast<uint32_t>(attr.value()); | |
| 71 | + | } | |
| 72 | + | ||
| 73 | + | if ((attr = vocationNode.attribute("gainhpticks"))) { | |
| 74 | + | voc.gainHealthTicks = pugi::cast<uint32_t>(attr.value()); | |
| 75 | + | } | |
| 76 | + | ||
| 77 | + | if ((attr = vocationNode.attribute("gainhpamount"))) { | |
| 78 | + | voc.gainHealthAmount = pugi::cast<uint32_t>(attr.value()); | |
| 79 | + | } | |
| 80 | + | ||
| 81 | + | if ((attr = vocationNode.attribute("gainmanaticks"))) { | |
| 82 | + | voc.gainManaTicks = pugi::cast<uint32_t>(attr.value()); | |
| 83 | + | } | |
| 84 | + | ||
| 85 | + | if ((attr = vocationNode.attribute("gainmanaamount"))) { | |
| 86 | + | voc.gainManaAmount = pugi::cast<uint32_t>(attr.value()); | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | if ((attr = vocationNode.attribute("manamultiplier"))) { | |
| 90 | + | voc.manaMultiplier = pugi::cast<float>(attr.value()); | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | if ((attr = vocationNode.attribute("attackspeed"))) { | |
| 94 | + | voc.attackSpeed = pugi::cast<uint32_t>(attr.value()); | |
| 95 | + | } | |
| 96 | + | ||
| 97 | + | if ((attr = vocationNode.attribute("basespeed"))) { | |
| 98 | + | voc.baseSpeed = pugi::cast<uint32_t>(attr.value()); | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | if ((attr = vocationNode.attribute("soulmax"))) { | |
| 102 | + | voc.soulMax = pugi::cast<uint16_t>(attr.value()); | |
| 103 | + | } | |
| 104 | + | ||
| 105 | + | if ((attr = vocationNode.attribute("gainsoulticks"))) { | |
| 106 | + | voc.gainSoulTicks = pugi::cast<uint16_t>(attr.value()); | |
| 107 | + | } | |
| 108 | + | ||
| 109 | + | if ((attr = vocationNode.attribute("fromvoc"))) { | |
| 110 | + | voc.fromVocation = pugi::cast<uint32_t>(attr.value()); | |
| 111 | + | } | |
| 112 | + | ||
| 113 | + | for (auto childNode : vocationNode.children()) { | |
| 114 | + | if (strcasecmp(childNode.name(), "skill") == 0) { | |
| 115 | + | pugi::xml_attribute skillIdAttribute = childNode.attribute("id"); | |
| 116 | + | if (skillIdAttribute) { | |
| 117 | + | uint16_t skill_id = pugi::cast<uint16_t>(skillIdAttribute.value()); | |
| 118 | + | if (skill_id <= SKILL_LAST) { | |
| 119 | + | voc.skillMultipliers[skill_id] = pugi::cast<float>(childNode.attribute("multiplier").value()); | |
| 120 | + | } else { | |
| 121 | + | std::cout << "[Notice - Vocations::loadFromXml] No valid skill id: " << skill_id << " for vocation: " << voc.id << std::endl; | |
| 122 | + | } | |
| 123 | + | } else { | |
| 124 | + | std::cout << "[Notice - Vocations::loadFromXml] Missing skill id for vocation: " << voc.id << std::endl; | |
| 125 | + | } | |
| 126 | + | } else if (strcasecmp(childNode.name(), "formula") == 0) { | |
| 127 | + | pugi::xml_attribute meleeDamageAttribute = childNode.attribute("meleeDamage"); | |
| 128 | + | if (meleeDamageAttribute) { | |
| 129 | + | voc.meleeDamageMultiplier = pugi::cast<float>(meleeDamageAttribute.value()); | |
| 130 | + | } | |
| 131 | + | ||
| 132 | + | pugi::xml_attribute distDamageAttribute = childNode.attribute("distDamage"); | |
| 133 | + | if (distDamageAttribute) { | |
| 134 | + | voc.distDamageMultiplier = pugi::cast<float>(distDamageAttribute.value()); | |
| 135 | + | } | |
| 136 | + | ||
| 137 | + | pugi::xml_attribute defenseAttribute = childNode.attribute("defense"); | |
| 138 | + | if (defenseAttribute) { | |
| 139 | + | voc.defenseMultiplier = pugi::cast<float>(defenseAttribute.value()); | |
| 140 | + | } | |
| 141 | + | ||
| 142 | + | pugi::xml_attribute armorAttribute = childNode.attribute("armor"); | |
| 143 | + | if (armorAttribute) { | |
| 144 | + | voc.armorMultiplier = pugi::cast<float>(armorAttribute.value()); | |
| 145 | + | } | |
| 146 | + | } | |
| 147 | + | } | |
| 148 | + | } | |
| 149 | + | return true; | |
| 150 | + | } | |
| 151 | + | ||
| 152 | + | Vocation* Vocations::getVocation(uint16_t id) | |
| 153 | + | { | |
| 154 | + | auto it = vocationsMap.find(id); | |
| 155 | + | if (it == vocationsMap.end()) { | |
| 156 | + | std::cout << "[Warning - Vocations::getVocation] Vocation " << id << " not found." << std::endl; | |
| 157 | + | return nullptr; | |
| 158 | + | } | |
| 159 | + | return &it->second; | |
| 160 | + | } | |
| 161 | + | ||
| 162 | + | int32_t Vocations::getVocationId(const std::string& name) const | |
| 163 | + | { | |
| 164 | + | for (const auto& it : vocationsMap) { | |
| 165 | + | if (strcasecmp(it.second.name.c_str(), name.c_str()) == 0) { | |
| 166 | + | return it.first; | |
| 167 | + | } | |
| 168 | + | } | |
| 169 | + | return -1; | |
| 170 | + | } | |
| 171 | + | ||
| 172 | + | uint16_t Vocations::getPromotedVocation(uint16_t vocationId) const | |
| 173 | + | { | |
| 174 | + | for (const auto& it : vocationsMap) { | |
| 175 | + | if (it.second.fromVocation == vocationId && it.first != vocationId) { | |
| 176 | + | return it.first; | |
| 177 | + | } | |
| 178 | + | } | |
| 179 | + | return VOCATION_NONE; | |
| 180 | + | } | |
| 181 | + | ||
| 182 | + | uint32_t Vocation::skillBase[SKILL_LAST + 1] = {50, 50, 50, 50, 30, 100, 20}; | |
| 183 | + | ||
| 184 | + | uint64_t Vocation::getReqSkillTries(uint8_t skill, uint16_t level) | |
| 185 | + | { | |
| 186 | + | if (skill > SKILL_LAST) { | |
| 187 | + | return 0; | |
| 188 | + | } | |
| 189 | + | ||
| 190 | + | auto it = cacheSkill[skill].find(level); | |
| 191 | + | if (it != cacheSkill[skill].end()) { | |
| 192 | + | return it->second; | |
| 193 | + | } | |
| 194 | + | ||
| 195 | + | uint64_t tries = static_cast<uint64_t>(skillBase[skill] * std::pow(static_cast<double>(skillMultipliers[skill]), level - 11)); | |
| 196 | + | cacheSkill[skill][level] = tries; | |
| 197 | + | return tries; | |
| 198 | + | } | |
| 199 | + | ||
| 200 | + | uint64_t Vocation::getReqMana(uint32_t magLevel) | |
| 201 | + | { | |
| 202 | + | auto it = cacheMana.find(magLevel); | |
| 203 | + | if (it != cacheMana.end()) { | |
| 204 | + | return it->second; | |
| 205 | + | } | |
| 206 | + | ||
| 207 | + | uint64_t reqMana = std::floor<uint64_t>(1600 * std::pow<double>(manaMultiplier, static_cast<int32_t>(magLevel) - 1)); | |
| 208 | + | cacheMana[magLevel] = reqMana; | |
| 209 | + | return reqMana; | |
| 210 | + | } |
| @@ -0,0 +1,139 @@ | |||
| 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_VOCATION_H_ADCAA356C0DB44CEBA994A0D678EC92D | |
| 21 | + | #define FS_VOCATION_H_ADCAA356C0DB44CEBA994A0D678EC92D | |
| 22 | + | ||
| 23 | + | #include "enums.h" | |
| 24 | + | #include "item.h" | |
| 25 | + | ||
| 26 | + | class Vocation | |
| 27 | + | { | |
| 28 | + | public: | |
| 29 | + | explicit Vocation(uint16_t id) : id(id) {} | |
| 30 | + | ||
| 31 | + | const std::string& getVocName() const { | |
| 32 | + | return name; | |
| 33 | + | } | |
| 34 | + | const std::string& getVocDescription() const { | |
| 35 | + | return description; | |
| 36 | + | } | |
| 37 | + | uint64_t getReqSkillTries(uint8_t skill, uint16_t level); | |
| 38 | + | uint64_t getReqMana(uint32_t magLevel); | |
| 39 | + | ||
| 40 | + | uint16_t getId() const { | |
| 41 | + | return id; | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | uint8_t getClientId() const { | |
| 45 | + | return clientId; | |
| 46 | + | } | |
| 47 | + | ||
| 48 | + | uint32_t getHPGain() const { | |
| 49 | + | return gainHP; | |
| 50 | + | } | |
| 51 | + | uint32_t getManaGain() const { | |
| 52 | + | return gainMana; | |
| 53 | + | } | |
| 54 | + | uint32_t getCapGain() const { | |
| 55 | + | return gainCap; | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | uint32_t getManaGainTicks() const { | |
| 59 | + | return gainManaTicks; | |
| 60 | + | } | |
| 61 | + | uint32_t getManaGainAmount() const { | |
| 62 | + | return gainManaAmount; | |
| 63 | + | } | |
| 64 | + | uint32_t getHealthGainTicks() const { | |
| 65 | + | return gainHealthTicks; | |
| 66 | + | } | |
| 67 | + | uint32_t getHealthGainAmount() const { | |
| 68 | + | return gainHealthAmount; | |
| 69 | + | } | |
| 70 | + | ||
| 71 | + | uint8_t getSoulMax() const { | |
| 72 | + | return soulMax; | |
| 73 | + | } | |
| 74 | + | uint16_t getSoulGainTicks() const { | |
| 75 | + | return gainSoulTicks; | |
| 76 | + | } | |
| 77 | + | ||
| 78 | + | uint32_t getAttackSpeed() const { | |
| 79 | + | return attackSpeed; | |
| 80 | + | } | |
| 81 | + | uint32_t getBaseSpeed() const { | |
| 82 | + | return baseSpeed; | |
| 83 | + | } | |
| 84 | + | ||
| 85 | + | uint32_t getFromVocation() const { | |
| 86 | + | return fromVocation; | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | float meleeDamageMultiplier = 1.0f; | |
| 90 | + | float distDamageMultiplier = 1.0f; | |
| 91 | + | float defenseMultiplier = 1.0f; | |
| 92 | + | float armorMultiplier = 1.0f; | |
| 93 | + | ||
| 94 | + | private: | |
| 95 | + | friend class Vocations; | |
| 96 | + | ||
| 97 | + | std::map<uint32_t, uint64_t> cacheMana; | |
| 98 | + | std::map<uint32_t, uint32_t> cacheSkill[SKILL_LAST + 1]; | |
| 99 | + | ||
| 100 | + | std::string name = "none"; | |
| 101 | + | std::string description; | |
| 102 | + | ||
| 103 | + | float skillMultipliers[SKILL_LAST + 1] = {1.5f, 2.0f, 2.0f, 2.0f, 2.0f, 1.5f, 1.1f}; | |
| 104 | + | float manaMultiplier = 4.0f; | |
| 105 | + | ||
| 106 | + | uint32_t gainHealthTicks = 6; | |
| 107 | + | uint32_t gainHealthAmount = 1; | |
| 108 | + | uint32_t gainManaTicks = 6; | |
| 109 | + | uint32_t gainManaAmount = 1; | |
| 110 | + | uint32_t gainCap = 500; | |
| 111 | + | uint32_t gainMana = 5; | |
| 112 | + | uint32_t gainHP = 5; | |
| 113 | + | uint32_t fromVocation = VOCATION_NONE; | |
| 114 | + | uint32_t attackSpeed = 1500; | |
| 115 | + | uint32_t baseSpeed = 220; | |
| 116 | + | uint16_t id; | |
| 117 | + | ||
| 118 | + | uint16_t gainSoulTicks = 120; | |
| 119 | + | ||
| 120 | + | uint8_t soulMax = 100; | |
| 121 | + | uint8_t clientId = 0; | |
| 122 | + | ||
| 123 | + | static uint32_t skillBase[SKILL_LAST + 1]; | |
| 124 | + | }; | |
| 125 | + | ||
| 126 | + | class Vocations | |
| 127 | + | { | |
| 128 | + | public: | |
| 129 | + | bool loadFromXml(); | |
| 130 | + | ||
| 131 | + | Vocation* getVocation(uint16_t id); | |
| 132 | + | int32_t getVocationId(const std::string& name) const; | |
| 133 | + | uint16_t getPromotedVocation(uint16_t vocationId) const; | |
| 134 | + | ||
| 135 | + | private: | |
| 136 | + | std::map<uint16_t, Vocation> vocationsMap; | |
| 137 | + | }; | |
| 138 | + | ||
| 139 | + | #endif |
| @@ -0,0 +1,149 @@ | |||
| 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 "configmanager.h" | |
| 23 | + | #include "game.h" | |
| 24 | + | #include "waitlist.h" | |
| 25 | + | ||
| 26 | + | #include <tuple> | |
| 27 | + | ||
| 28 | + | extern ConfigManager g_config; | |
| 29 | + | extern Game g_game; | |
| 30 | + | ||
| 31 | + | namespace { | |
| 32 | + | ||
| 33 | + | struct Wait | |
| 34 | + | { | |
| 35 | + | constexpr Wait(int64_t timeout, uint32_t playerGUID) : | |
| 36 | + | timeout(timeout), playerGUID(playerGUID) {} | |
| 37 | + | ||
| 38 | + | int64_t timeout; | |
| 39 | + | uint32_t playerGUID; | |
| 40 | + | }; | |
| 41 | + | ||
| 42 | + | using WaitList = std::list<Wait>; | |
| 43 | + | ||
| 44 | + | void cleanupList(WaitList& list) | |
| 45 | + | { | |
| 46 | + | int64_t time = OTSYS_TIME(); | |
| 47 | + | ||
| 48 | + | auto it = list.begin(), end = list.end(); | |
| 49 | + | while (it != end) { | |
| 50 | + | if ((it->timeout - time) <= 0) { | |
| 51 | + | it = list.erase(it); | |
| 52 | + | } else { | |
| 53 | + | ++it; | |
| 54 | + | } | |
| 55 | + | } | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | int64_t getTimeout(std::size_t slot) | |
| 59 | + | { | |
| 60 | + | //timeout is set to 15 seconds longer than expected retry attempt | |
| 61 | + | return WaitingList::getTime(slot) + 15; | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | } // namespace | |
| 65 | + | ||
| 66 | + | struct WaitListInfo | |
| 67 | + | { | |
| 68 | + | WaitList priorityWaitList; | |
| 69 | + | WaitList waitList; | |
| 70 | + | ||
| 71 | + | std::tuple<WaitList&, WaitList::iterator, WaitList::size_type> findClient(const Player *player) { | |
| 72 | + | std::size_t slot = 1; | |
| 73 | + | for (auto it = priorityWaitList.begin(), end = priorityWaitList.end(); it != end; ++it, ++slot) { | |
| 74 | + | if (it->playerGUID == player->getGUID()) { | |
| 75 | + | return std::make_tuple(std::ref(priorityWaitList), it, slot); | |
| 76 | + | } | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | for (auto it = waitList.begin(), end = waitList.end(); it != end; ++it, ++slot) { | |
| 80 | + | if (it->playerGUID == player->getGUID()) { | |
| 81 | + | return std::make_tuple(std::ref(waitList), it, slot); | |
| 82 | + | } | |
| 83 | + | } | |
| 84 | + | return std::make_tuple(std::ref(waitList), waitList.end(), slot); | |
| 85 | + | } | |
| 86 | + | }; | |
| 87 | + | ||
| 88 | + | WaitingList& WaitingList::getInstance() | |
| 89 | + | { | |
| 90 | + | static WaitingList waitingList; | |
| 91 | + | return waitingList; | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | uint8_t WaitingList::getTime(std::size_t slot) | |
| 95 | + | { | |
| 96 | + | if (slot < 5) { | |
| 97 | + | return 5; | |
| 98 | + | } else if (slot < 10) { | |
| 99 | + | return 10; | |
| 100 | + | } else if (slot < 20) { | |
| 101 | + | return 20; | |
| 102 | + | } else if (slot < 50) { | |
| 103 | + | return 60; | |
| 104 | + | } else { | |
| 105 | + | return 120; | |
| 106 | + | } | |
| 107 | + | } | |
| 108 | + | ||
| 109 | + | std::size_t WaitingList::clientLogin(const Player* player) | |
| 110 | + | { | |
| 111 | + | // Currentslot = position in wait list, 0 for direct access | |
| 112 | + | std::size_t currentSlot = 0; | |
| 113 | + | if (player->hasFlag(PlayerFlag_CanAlwaysLogin) || player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER) { | |
| 114 | + | return currentSlot; | |
| 115 | + | } | |
| 116 | + | ||
| 117 | + | cleanupList(info->priorityWaitList); | |
| 118 | + | cleanupList(info->waitList); | |
| 119 | + | ||
| 120 | + | uint32_t maxPlayers = static_cast<uint32_t>(g_config.getNumber(ConfigManager::MAX_PLAYERS)); | |
| 121 | + | if (maxPlayers == 0 || (info->priorityWaitList.empty() && info->waitList.empty() && g_game.getPlayersOnline() < maxPlayers)) { | |
| 122 | + | return currentSlot; | |
| 123 | + | } | |
| 124 | + | ||
| 125 | + | auto result = info->findClient(player); | |
| 126 | + | if (std::get<1>(result) != std::get<0>(result).end()) { | |
| 127 | + | currentSlot = std::get<2>(result); | |
| 128 | + | // If server has capacity for this client, let him in even though his current slot might be higher than 0. | |
| 129 | + | if ((g_game.getPlayersOnline() + currentSlot) <= maxPlayers) { | |
| 130 | + | std::get<0>(result).erase(std::get<1>(result)); | |
| 131 | + | return 0; | |
| 132 | + | } | |
| 133 | + | ||
| 134 | + | //let them wait a bit longer | |
| 135 | + | std::get<1>(result)->timeout = OTSYS_TIME() + (getTimeout(currentSlot) * 1000); | |
| 136 | + | return currentSlot; | |
| 137 | + | } | |
| 138 | + | ||
| 139 | + | currentSlot = info->priorityWaitList.size(); | |
| 140 | + | if (player->isPremium()) { | |
| 141 | + | info->priorityWaitList.emplace_back(OTSYS_TIME() + (getTimeout(++currentSlot) * 1000), player->getGUID()); | |
| 142 | + | } else { | |
| 143 | + | currentSlot += info->waitList.size(); | |
| 144 | + | info->waitList.emplace_back(OTSYS_TIME() + (getTimeout(++currentSlot) * 1000), player->getGUID()); | |
| 145 | + | } | |
| 146 | + | return currentSlot; | |
| 147 | + | } | |
| 148 | + | ||
| 149 | + | WaitingList::WaitingList() : info(new WaitListInfo) {} |
| @@ -0,0 +1,41 @@ | |||
| 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_WAITLIST_H_7E4299E552E44F10BC4F4E50BF3D7241 | |
| 21 | + | #define FS_WAITLIST_H_7E4299E552E44F10BC4F4E50BF3D7241 | |
| 22 | + | ||
| 23 | + | #include "player.h" | |
| 24 | + | ||
| 25 | + | struct WaitListInfo; | |
| 26 | + | ||
| 27 | + | class WaitingList | |
| 28 | + | { | |
| 29 | + | public: | |
| 30 | + | static WaitingList& getInstance(); | |
| 31 | + | ||
| 32 | + | std::size_t clientLogin(const Player* player); | |
| 33 | + | static uint8_t getTime(std::size_t slot); | |
| 34 | + | ||
| 35 | + | private: | |
| 36 | + | WaitingList(); | |
| 37 | + | ||
| 38 | + | std::unique_ptr<WaitListInfo> info; | |
| 39 | + | }; | |
| 40 | + | ||
| 41 | + | #endif |
| @@ -0,0 +1,140 @@ | |||
| 1 | + | #pragma once | |
| 2 | + | ||
| 3 | + | #include <cstdint> | |
| 4 | + | #include <string> | |
| 5 | + | #include <sstream> | |
| 6 | + | ||
| 7 | + | #include "position.h" | |
| 8 | + | ||
| 9 | + | class WalkMatrix { | |
| 10 | + | public: | |
| 11 | + | ||
| 12 | + | void updatePosition(const Position& newPos) | |
| 13 | + | { | |
| 14 | + | int32_t xdiff = pos.x - newPos.x; | |
| 15 | + | int32_t ydiff = pos.y - newPos.y; | |
| 16 | + | if (pos.z != newPos.z || std::abs(xdiff) > 6 || std::abs(ydiff) > 6) { | |
| 17 | + | pos = newPos; | |
| 18 | + | return clear(); | |
| 19 | + | } | |
| 20 | + | pos = newPos; | |
| 21 | + | ||
| 22 | + | if (xdiff < 0) { | |
| 23 | + | for (int x = 0; x < 7; ++x) { | |
| 24 | + | for (int y = 0; y < 7; ++y) { | |
| 25 | + | if (x - xdiff > 6) { | |
| 26 | + | matrix[x][y] = 0; | |
| 27 | + | } else { | |
| 28 | + | matrix[x][y] = matrix[x - xdiff][y]; | |
| 29 | + | } | |
| 30 | + | } | |
| 31 | + | } | |
| 32 | + | } | |
| 33 | + | if (xdiff > 0) { | |
| 34 | + | for (int x = 6; x >= 0; --x) { | |
| 35 | + | for (int y = 6; y >= 0; --y) { | |
| 36 | + | if (x - xdiff < 0) { | |
| 37 | + | matrix[x][y] = 0; | |
| 38 | + | } else { | |
| 39 | + | matrix[x][y] = matrix[x - xdiff][y]; | |
| 40 | + | } | |
| 41 | + | } | |
| 42 | + | } | |
| 43 | + | } | |
| 44 | + | if (ydiff < 0) { | |
| 45 | + | for (int x = 0; x < 7; ++x) { | |
| 46 | + | for (int y = 0; y < 7; ++y) { | |
| 47 | + | if (y - ydiff > 6) { | |
| 48 | + | matrix[x][y] = 0; | |
| 49 | + | } else { | |
| 50 | + | matrix[x][y] = matrix[x][y - ydiff]; | |
| 51 | + | } | |
| 52 | + | } | |
| 53 | + | } | |
| 54 | + | } | |
| 55 | + | if (ydiff > 0) { | |
| 56 | + | for (int x = 6; x >= 0; --x) { | |
| 57 | + | for (int y = 6; y >= 0; --y) { | |
| 58 | + | if (y - ydiff < 0) { | |
| 59 | + | matrix[x][y] = 0; | |
| 60 | + | } else { | |
| 61 | + | matrix[x][y] = matrix[x][y - ydiff]; | |
| 62 | + | } | |
| 63 | + | } | |
| 64 | + | } | |
| 65 | + | } | |
| 66 | + | } | |
| 67 | + | ||
| 68 | + | bool inRange(const Position& pos2) | |
| 69 | + | { | |
| 70 | + | if (pos.z != pos2.z) return false; | |
| 71 | + | int32_t xdiff = pos.x - pos2.x; | |
| 72 | + | int32_t ydiff = pos.y - pos2.y; | |
| 73 | + | if (std::abs(xdiff) > 3 || std::abs(ydiff) > 3) return false; | |
| 74 | + | return true; | |
| 75 | + | } | |
| 76 | + | ||
| 77 | + | int32_t update(const Position& pos2, int32_t value = 0) | |
| 78 | + | { | |
| 79 | + | if (pos.z != pos2.z) return 0; | |
| 80 | + | int32_t xdiff = pos.x - pos2.x; | |
| 81 | + | int32_t ydiff = pos.y - pos2.y; | |
| 82 | + | if (std::abs(xdiff) > 3 || std::abs(ydiff) > 3) return 0; | |
| 83 | + | if (value != 0) { | |
| 84 | + | matrix[3 - xdiff][3 - ydiff] = value; | |
| 85 | + | return value; | |
| 86 | + | } | |
| 87 | + | counter += 1; | |
| 88 | + | matrix[3 - xdiff][3 - ydiff] = counter; | |
| 89 | + | return counter; | |
| 90 | + | } | |
| 91 | + | ||
| 92 | + | int32_t get(const Position& pos2) | |
| 93 | + | { | |
| 94 | + | if (pos.z != pos2.z) return 0; | |
| 95 | + | int32_t xdiff = pos.x - pos2.x; | |
| 96 | + | int32_t ydiff = pos.y - pos2.y; | |
| 97 | + | if (std::abs(xdiff) > 3 || std::abs(ydiff) > 3) return 0; | |
| 98 | + | return matrix[3 - xdiff][3 - ydiff]; | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | void clear() | |
| 102 | + | { | |
| 103 | + | for (auto& x : matrix) { | |
| 104 | + | for (auto& y : x) { | |
| 105 | + | y = 0; | |
| 106 | + | } | |
| 107 | + | } | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | uint32_t reset(uint32_t value = 0) | |
| 111 | + | { | |
| 112 | + | if (value == 0) { | |
| 113 | + | counter += 1; | |
| 114 | + | value = counter; | |
| 115 | + | } | |
| 116 | + | for (auto& x : matrix) { | |
| 117 | + | for (auto& y : x) { | |
| 118 | + | y = value; | |
| 119 | + | } | |
| 120 | + | } | |
| 121 | + | return value; | |
| 122 | + | } | |
| 123 | + | ||
| 124 | + | std::string dump() | |
| 125 | + | { | |
| 126 | + | std::stringstream ss; | |
| 127 | + | for (int y = 0; y < 7; ++y) { | |
| 128 | + | for (int x = 0; x < 7; ++x) { | |
| 129 | + | ss << matrix[x][y] << "\t"; | |
| 130 | + | } | |
| 131 | + | ss << "\n"; | |
| 132 | + | } | |
| 133 | + | return ss.str(); | |
| 134 | + | } | |
| 135 | + | ||
| 136 | + | private: | |
| 137 | + | Position pos; | |
| 138 | + | int32_t matrix[7][7] = { { 0 } }; | |
| 139 | + | int32_t counter = 0; | |
| 140 | + | }; |