Server upload

RD Core / Commit #2

Commit Server upload

Alex Alex committed 18/10/2026 23:38 main Patch
1,106 files +223,404 -0
A src/iologindata.h +70-0 View file
@@ -0,0 +1,70 @@
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_IOLOGINDATA_H_28B0440BEC594654AC0F4E1A5E42B2EF
21+#define FS_IOLOGINDATA_H_28B0440BEC594654AC0F4E1A5E42B2EF
22+
23+#include "account.h"
24+#include "player.h"
25+#include "database.h"
26+
27+using ItemBlockList = std::list<std::pair<int32_t, Item*>>;
28+
29+class IOLoginData
30+{
31+ public:
32+ static Account loadAccount(uint32_t accno);
33+ static bool saveAccount(const Account& acc);
34+
35+ static bool loginserverAuthentication(const std::string& name, const std::string& password, Account& account);
36+ static uint32_t gameworldAuthentication(const std::string& accountName, const std::string& password, std::string& characterName, std::string& token, uint32_t tokenTime);
37+ static uint32_t getAccountIdByPlayerName(const std::string& playerName);
38+
39+ static AccountType_t getAccountType(uint32_t accountId);
40+ static void setAccountType(uint32_t accountId, AccountType_t accountType);
41+ static void updateOnlineStatus(uint32_t guid, bool login);
42+ static bool preloadPlayer(Player* player, const std::string& name);
43+
44+ static bool loadPlayerById(Player* player, uint32_t id);
45+ static bool loadPlayerByName(Player* player, const std::string& name);
46+ static bool loadPlayer(Player* player, DBResult_ptr result);
47+ static bool savePlayer(Player* player);
48+ static uint32_t getGuidByName(const std::string& name);
49+ static bool getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& name);
50+ static std::string getNameByGuid(uint32_t guid);
51+ static bool formatPlayerName(std::string& name);
52+ static void increaseBankBalance(uint32_t guid, uint64_t bankBalance);
53+ static bool hasBiddedOnHouse(uint32_t guid);
54+
55+ static std::forward_list<VIPEntry> getVIPEntries(uint32_t accountId);
56+ static void addVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, bool notify);
57+ static void editVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, bool notify);
58+ static void removeVIPEntry(uint32_t accountId, uint32_t guid);
59+
60+ static void addPremiumDays(uint32_t accountId, int32_t addDays);
61+ static void removePremiumDays(uint32_t accountId, int32_t removeDays);
62+
63+ private:
64+ using ItemMap = std::map<uint32_t, std::pair<Item*, uint32_t>>;
65+
66+ static void loadItems(ItemMap& itemMap, DBResult_ptr result);
67+ static bool saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream);
68+};
69+
70+#endif
A src/iomap.cpp +482-0 View file
@@ -0,0 +1,482 @@
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 "iomap.h"
23+
24+#include "bed.h"
25+
26+/*
27+ OTBM_ROOTV1
28+ |
29+ |--- OTBM_MAP_DATA
30+ | |
31+ | |--- OTBM_TILE_AREA
32+ | | |--- OTBM_TILE
33+ | | |--- OTBM_TILE_SQUARE (not implemented)
34+ | | |--- OTBM_TILE_REF (not implemented)
35+ | | |--- OTBM_HOUSETILE
36+ | |
37+ | |--- OTBM_SPAWNS (not implemented)
38+ | | |--- OTBM_SPAWN_AREA (not implemented)
39+ | | |--- OTBM_MONSTER (not implemented)
40+ | |
41+ | |--- OTBM_TOWNS
42+ | | |--- OTBM_TOWN
43+ | |
44+ | |--- OTBM_WAYPOINTS
45+ | |--- OTBM_WAYPOINT
46+ |
47+ |--- OTBM_ITEM_DEF (not implemented)
48+*/
49+
50+Tile* IOMap::createTile(Item*& ground, Item* item, uint16_t x, uint16_t y, uint8_t z)
51+{
52+ if (!ground) {
53+ return new StaticTile(x, y, z);
54+ }
55+
56+ Tile* tile;
57+ if ((item && item->isBlocking()) || ground->isBlocking()) {
58+ tile = new StaticTile(x, y, z);
59+ } else {
60+ tile = new DynamicTile(x, y, z);
61+ }
62+
63+ tile->internalAddThing(ground);
64+ ground->startDecaying();
65+ ground = nullptr;
66+ return tile;
67+}
68+
69+bool IOMap::loadMap(Map* map, const std::string& fileName)
70+{
71+ int64_t start = OTSYS_TIME();
72+ OTB::Loader loader{fileName, OTB::Identifier{{'O', 'T', 'B', 'M'}}};
73+ auto& root = loader.parseTree();
74+
75+ PropStream propStream;
76+ if (!loader.getProps(root, propStream)) {
77+ setLastErrorString("Could not read root property.");
78+ return false;
79+ }
80+
81+ OTBM_root_header root_header;
82+ if (!propStream.read(root_header)) {
83+ setLastErrorString("Could not read header.");
84+ return false;
85+ }
86+
87+ uint32_t headerVersion = root_header.version;
88+ if (headerVersion == 0) {
89+ //In otbm version 1 the count variable after splashes/fluidcontainers and stackables
90+ //are saved as attributes instead, this solves alot of problems with items
91+ //that is changed (stackable/charges/fluidcontainer/splash) during an update.
92+ setLastErrorString("This map need to be upgraded by using the latest map editor version to be able to load correctly.");
93+ return false;
94+ }
95+
96+ if (headerVersion > 2) {
97+ setLastErrorString("Unknown OTBM version detected.");
98+ return false;
99+ }
100+
101+ if (root_header.majorVersionItems < 3) {
102+ setLastErrorString("This map need to be upgraded by using the latest map editor version to be able to load correctly.");
103+ return false;
104+ }
105+
106+ if (root_header.majorVersionItems > Item::items.majorVersion) {
107+ setLastErrorString("The map was saved with a different items.otb version, an upgraded items.otb is required.");
108+ return false;
109+ }
110+
111+ if (root_header.minorVersionItems < CLIENT_VERSION_810) {
112+ setLastErrorString("This map needs to be updated.");
113+ return false;
114+ }
115+
116+ if (root_header.minorVersionItems > Item::items.minorVersion) {
117+ std::cout << "[Warning - IOMap::loadMap] This map needs an updated items.otb." << std::endl;
118+ }
119+
120+ std::cout << "> Map size: " << root_header.width << "x" << root_header.height << '.' << std::endl;
121+ map->width = root_header.width;
122+ map->height = root_header.height;
123+
124+ if (root.children.size() != 1 || root.children[0].type != OTBM_MAP_DATA) {
125+ setLastErrorString("Could not read data node.");
126+ return false;
127+ }
128+
129+ auto& mapNode = root.children[0];
130+ if (!parseMapDataAttributes(loader, mapNode, *map, fileName)) {
131+ return false;
132+ }
133+
134+ for (auto& mapDataNode : mapNode.children) {
135+ if (mapDataNode.type == OTBM_TILE_AREA) {
136+ if (!parseTileArea(loader, mapDataNode, *map)) {
137+ return false;
138+ }
139+ } else if (mapDataNode.type == OTBM_TOWNS) {
140+ if (!parseTowns(loader, mapDataNode, *map)) {
141+ return false;
142+ }
143+ } else if (mapDataNode.type == OTBM_WAYPOINTS && headerVersion > 1) {
144+ if (!parseWaypoints(loader, mapDataNode, *map)) {
145+ return false;
146+ }
147+ } else {
148+ setLastErrorString("Unknown map node.");
149+ return false;
150+ }
151+ }
152+
153+ std::cout << "> Map loading time: " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl;
154+ return true;
155+}
156+
157+bool IOMap::parseMapDataAttributes(OTB::Loader& loader, const OTB::Node& mapNode, Map& map, const std::string& fileName)
158+{
159+ PropStream propStream;
160+ if (!loader.getProps(mapNode, propStream)) {
161+ setLastErrorString("Could not read map data attributes.");
162+ return false;
163+ }
164+
165+ std::string mapDescription;
166+ std::string tmp;
167+
168+ uint8_t attribute;
169+ while (propStream.read<uint8_t>(attribute)) {
170+ switch (attribute) {
171+ case OTBM_ATTR_DESCRIPTION:
172+ if (!propStream.readString(mapDescription)) {
173+ setLastErrorString("Invalid description tag.");
174+ return false;
175+ }
176+ break;
177+
178+ case OTBM_ATTR_EXT_SPAWN_FILE:
179+ if (!propStream.readString(tmp)) {
180+ setLastErrorString("Invalid spawn tag.");
181+ return false;
182+ }
183+
184+ map.spawnfile = fileName.substr(0, fileName.rfind('/') + 1);
185+ map.spawnfile += tmp;
186+ break;
187+
188+ case OTBM_ATTR_EXT_HOUSE_FILE:
189+ if (!propStream.readString(tmp)) {
190+ setLastErrorString("Invalid house tag.");
191+ return false;
192+ }
193+
194+ map.housefile = fileName.substr(0, fileName.rfind('/') + 1);
195+ map.housefile += tmp;
196+ break;
197+
198+ default:
199+ setLastErrorString("Unknown header node.");
200+ return false;
201+ }
202+ }
203+ return true;
204+}
205+
206+bool IOMap::parseTileArea(OTB::Loader& loader, const OTB::Node& tileAreaNode, Map& map)
207+{
208+ PropStream propStream;
209+ if (!loader.getProps(tileAreaNode, propStream)) {
210+ setLastErrorString("Invalid map node.");
211+ return false;
212+ }
213+
214+ OTBM_Destination_coords area_coord;
215+ if (!propStream.read(area_coord)) {
216+ setLastErrorString("Invalid map node.");
217+ return false;
218+ }
219+
220+ uint16_t base_x = area_coord.x;
221+ uint16_t base_y = area_coord.y;
222+ uint16_t z = area_coord.z;
223+
224+ for (auto& tileNode : tileAreaNode.children) {
225+ if (tileNode.type != OTBM_TILE && tileNode.type != OTBM_HOUSETILE) {
226+ setLastErrorString("Unknown tile node.");
227+ return false;
228+ }
229+
230+ if (!loader.getProps(tileNode, propStream)) {
231+ setLastErrorString("Could not read node data.");
232+ return false;
233+ }
234+
235+ OTBM_Tile_coords tile_coord;
236+ if (!propStream.read(tile_coord)) {
237+ setLastErrorString("Could not read tile position.");
238+ return false;
239+ }
240+
241+ uint16_t x = base_x + tile_coord.x;
242+ uint16_t y = base_y + tile_coord.y;
243+
244+ bool isHouseTile = false;
245+ House* house = nullptr;
246+ Tile* tile = nullptr;
247+ Item* ground_item = nullptr;
248+ uint32_t tileflags = TILESTATE_NONE;
249+
250+ if (tileNode.type == OTBM_HOUSETILE) {
251+ uint32_t houseId;
252+ if (!propStream.read<uint32_t>(houseId)) {
253+ std::ostringstream ss;
254+ ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Could not read house id.";
255+ setLastErrorString(ss.str());
256+ return false;
257+ }
258+
259+ house = map.houses.addHouse(houseId);
260+ if (!house) {
261+ std::ostringstream ss;
262+ ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Could not create house id: " << houseId;
263+ setLastErrorString(ss.str());
264+ return false;
265+ }
266+
267+ tile = new HouseTile(x, y, z, house);
268+ house->addTile(static_cast<HouseTile*>(tile));
269+ isHouseTile = true;
270+ }
271+
272+ uint8_t attribute;
273+ //read tile attributes
274+ while (propStream.read<uint8_t>(attribute)) {
275+ switch (attribute) {
276+ case OTBM_ATTR_TILE_FLAGS: {
277+ uint32_t flags;
278+ if (!propStream.read<uint32_t>(flags)) {
279+ std::ostringstream ss;
280+ ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to read tile flags.";
281+ setLastErrorString(ss.str());
282+ return false;
283+ }
284+
285+ if ((flags & OTBM_TILEFLAG_PROTECTIONZONE) != 0) {
286+ tileflags |= TILESTATE_PROTECTIONZONE;
287+ } else if ((flags & OTBM_TILEFLAG_NOPVPZONE) != 0) {
288+ tileflags |= TILESTATE_NOPVPZONE;
289+ } else if ((flags & OTBM_TILEFLAG_PVPZONE) != 0) {
290+ tileflags |= TILESTATE_PVPZONE;
291+ }
292+
293+ if ((flags & OTBM_TILEFLAG_NOLOGOUT) != 0) {
294+ tileflags |= TILESTATE_NOLOGOUT;
295+ }
296+ break;
297+ }
298+
299+ case OTBM_ATTR_ITEM: {
300+ Item* item = Item::CreateItem(propStream);
301+ if (!item) {
302+ std::ostringstream ss;
303+ ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to create item.";
304+ setLastErrorString(ss.str());
305+ return false;
306+ }
307+
308+ if (isHouseTile && item->isMoveable()) {
309+ std::cout << "[Warning - IOMap::loadMap] Moveable item with ID: " << item->getID() << ", in house: " << house->getId() << ", at position [x: " << x << ", y: " << y << ", z: " << z << "]." << std::endl;
310+ delete item;
311+ } else {
312+ if (item->getItemCount() == 0) {
313+ item->setItemCount(1);
314+ }
315+
316+ if (tile) {
317+ tile->internalAddThing(item);
318+ item->startDecaying();
319+ item->setLoadedFromMap(true);
320+ } else if (item->isGroundTile()) {
321+ delete ground_item;
322+ ground_item = item;
323+ } else {
324+ tile = createTile(ground_item, item, x, y, z);
325+ tile->internalAddThing(item);
326+ item->startDecaying();
327+ item->setLoadedFromMap(true);
328+ }
329+ }
330+ break;
331+ }
332+
333+ default:
334+ std::ostringstream ss;
335+ ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Unknown tile attribute.";
336+ setLastErrorString(ss.str());
337+ return false;
338+ }
339+ }
340+
341+ for (auto& itemNode : tileNode.children) {
342+ if (itemNode.type != OTBM_ITEM) {
343+ std::ostringstream ss;
344+ ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Unknown node type.";
345+ setLastErrorString(ss.str());
346+ return false;
347+ }
348+
349+ PropStream stream;
350+ if (!loader.getProps(itemNode, stream)) {
351+ setLastErrorString("Invalid item node.");
352+ return false;
353+ }
354+
355+ Item* item = Item::CreateItem(stream);
356+ if (!item) {
357+ std::ostringstream ss;
358+ ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to create item.";
359+ setLastErrorString(ss.str());
360+ return false;
361+ }
362+
363+ if (!item->unserializeItemNode(loader, itemNode, stream)) {
364+ std::ostringstream ss;
365+ ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to load item " << item->getID() << '.';
366+ setLastErrorString(ss.str());
367+ delete item;
368+ return false;
369+ }
370+
371+ if (isHouseTile && item->isMoveable()) {
372+ std::cout << "[Warning - IOMap::loadMap] Moveable item with ID: " << item->getID() << ", in house: " << house->getId() << ", at position [x: " << x << ", y: " << y << ", z: " << z << "]." << std::endl;
373+ delete item;
374+ } else {
375+ if (item->getItemCount() == 0) {
376+ item->setItemCount(1);
377+ }
378+
379+ if (tile) {
380+ tile->internalAddThing(item);
381+ item->startDecaying();
382+ item->setLoadedFromMap(true);
383+ } else if (item->isGroundTile()) {
384+ delete ground_item;
385+ ground_item = item;
386+ } else {
387+ tile = createTile(ground_item, item, x, y, z);
388+ tile->internalAddThing(item);
389+ item->startDecaying();
390+ item->setLoadedFromMap(true);
391+ }
392+ }
393+ }
394+
395+ if (!tile) {
396+ tile = createTile(ground_item, nullptr, x, y, z);
397+ }
398+
399+ tile->setFlag(static_cast<tileflags_t>(tileflags));
400+
401+ map.setTile(x, y, z, tile);
402+ }
403+ return true;
404+}
405+
406+bool IOMap::parseTowns(OTB::Loader& loader, const OTB::Node& townsNode, Map& map)
407+{
408+ for (auto& townNode : townsNode.children) {
409+ PropStream propStream;
410+ if (townNode.type != OTBM_TOWN) {
411+ setLastErrorString("Unknown town node.");
412+ return false;
413+ }
414+
415+ if (!loader.getProps(townNode, propStream)) {
416+ setLastErrorString("Could not read town data.");
417+ return false;
418+ }
419+
420+ uint32_t townId;
421+ if (!propStream.read<uint32_t>(townId)) {
422+ setLastErrorString("Could not read town id.");
423+ return false;
424+ }
425+
426+ Town* town = map.towns.getTown(townId);
427+ if (!town) {
428+ town = new Town(townId);
429+ map.towns.addTown(townId, town);
430+ }
431+
432+ std::string townName;
433+ if (!propStream.readString(townName)) {
434+ setLastErrorString("Could not read town name.");
435+ return false;
436+ }
437+
438+ town->setName(townName);
439+
440+ OTBM_Destination_coords town_coords;
441+ if (!propStream.read(town_coords)) {
442+ setLastErrorString("Could not read town coordinates.");
443+ return false;
444+ }
445+
446+ town->setTemplePos(Position(town_coords.x, town_coords.y, town_coords.z));
447+ }
448+ return true;
449+}
450+
451+
452+bool IOMap::parseWaypoints(OTB::Loader& loader, const OTB::Node& waypointsNode, Map& map)
453+{
454+ PropStream propStream;
455+ for (auto& node : waypointsNode.children) {
456+ if (node.type != OTBM_WAYPOINT) {
457+ setLastErrorString("Unknown waypoint node.");
458+ return false;
459+ }
460+
461+ if (!loader.getProps(node, propStream)) {
462+ setLastErrorString("Could not read waypoint data.");
463+ return false;
464+ }
465+
466+ std::string name;
467+ if (!propStream.readString(name)) {
468+ setLastErrorString("Could not read waypoint name.");
469+ return false;
470+ }
471+
472+ OTBM_Destination_coords waypoint_coords;
473+ if (!propStream.read(waypoint_coords)) {
474+ setLastErrorString("Could not read waypoint coordinates.");
475+ return false;
476+ }
477+
478+ map.waypoints[name] = Position(waypoint_coords.x, waypoint_coords.y, waypoint_coords.z);
479+ }
480+ return true;
481+}
482+
A src/iomap.h +158-0 View file
@@ -0,0 +1,158 @@
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_IOMAP_H_8085D4B1037A44288494A52FDBB775E4
21+#define FS_IOMAP_H_8085D4B1037A44288494A52FDBB775E4
22+
23+#include "item.h"
24+#include "map.h"
25+#include "house.h"
26+#include "spawn.h"
27+#include "configmanager.h"
28+
29+extern ConfigManager g_config;
30+
31+enum OTBM_AttrTypes_t {
32+ OTBM_ATTR_DESCRIPTION = 1,
33+ OTBM_ATTR_EXT_FILE = 2,
34+ OTBM_ATTR_TILE_FLAGS = 3,
35+ OTBM_ATTR_ACTION_ID = 4,
36+ OTBM_ATTR_UNIQUE_ID = 5,
37+ OTBM_ATTR_TEXT = 6,
38+ OTBM_ATTR_DESC = 7,
39+ OTBM_ATTR_TELE_DEST = 8,
40+ OTBM_ATTR_ITEM = 9,
41+ OTBM_ATTR_DEPOT_ID = 10,
42+ OTBM_ATTR_EXT_SPAWN_FILE = 11,
43+ OTBM_ATTR_RUNE_CHARGES = 12,
44+ OTBM_ATTR_EXT_HOUSE_FILE = 13,
45+ OTBM_ATTR_HOUSEDOORID = 14,
46+ OTBM_ATTR_COUNT = 15,
47+ OTBM_ATTR_DURATION = 16,
48+ OTBM_ATTR_DECAYING_STATE = 17,
49+ OTBM_ATTR_WRITTENDATE = 18,
50+ OTBM_ATTR_WRITTENBY = 19,
51+ OTBM_ATTR_SLEEPERGUID = 20,
52+ OTBM_ATTR_SLEEPSTART = 21,
53+ OTBM_ATTR_CHARGES = 22,
54+};
55+
56+enum OTBM_NodeTypes_t {
57+ OTBM_ROOTV1 = 1,
58+ OTBM_MAP_DATA = 2,
59+ OTBM_ITEM_DEF = 3,
60+ OTBM_TILE_AREA = 4,
61+ OTBM_TILE = 5,
62+ OTBM_ITEM = 6,
63+ OTBM_TILE_SQUARE = 7,
64+ OTBM_TILE_REF = 8,
65+ OTBM_SPAWNS = 9,
66+ OTBM_SPAWN_AREA = 10,
67+ OTBM_MONSTER = 11,
68+ OTBM_TOWNS = 12,
69+ OTBM_TOWN = 13,
70+ OTBM_HOUSETILE = 14,
71+ OTBM_WAYPOINTS = 15,
72+ OTBM_WAYPOINT = 16,
73+};
74+
75+enum OTBM_TileFlag_t : uint32_t {
76+ OTBM_TILEFLAG_PROTECTIONZONE = 1 << 0,
77+ OTBM_TILEFLAG_NOPVPZONE = 1 << 2,
78+ OTBM_TILEFLAG_NOLOGOUT = 1 << 3,
79+ OTBM_TILEFLAG_PVPZONE = 1 << 4
80+};
81+
82+#pragma pack(1)
83+
84+struct OTBM_root_header {
85+ uint32_t version;
86+ uint16_t width;
87+ uint16_t height;
88+ uint32_t majorVersionItems;
89+ uint32_t minorVersionItems;
90+};
91+
92+struct OTBM_Destination_coords {
93+ uint16_t x;
94+ uint16_t y;
95+ uint8_t z;
96+};
97+
98+struct OTBM_Tile_coords {
99+ uint8_t x;
100+ uint8_t y;
101+};
102+
103+#pragma pack()
104+
105+class IOMap
106+{
107+ static Tile* createTile(Item*& ground, Item* item, uint16_t x, uint16_t y, uint8_t z);
108+
109+ public:
110+ bool loadMap(Map* map, const std::string& fileName);
111+
112+ /* Load the spawns
113+ * \param map pointer to the Map class
114+ * \returns Returns true if the spawns were loaded successfully
115+ */
116+ static bool loadSpawns(Map* map) {
117+ if (map->spawnfile.empty()) {
118+ //OTBM file doesn't tell us about the spawnfile,
119+ //lets guess it is mapname-spawn.xml.
120+ map->spawnfile = g_config.getString(ConfigManager::MAP_NAME);
121+ map->spawnfile += "-spawn.xml";
122+ }
123+
124+ return map->spawns.loadFromXml(map->spawnfile);
125+ }
126+
127+ /* Load the houses (not house tile-data)
128+ * \param map pointer to the Map class
129+ * \returns Returns true if the houses were loaded successfully
130+ */
131+ static bool loadHouses(Map* map) {
132+ if (map->housefile.empty()) {
133+ //OTBM file doesn't tell us about the housefile,
134+ //lets guess it is mapname-house.xml.
135+ map->housefile = g_config.getString(ConfigManager::MAP_NAME);
136+ map->housefile += "-house.xml";
137+ }
138+
139+ return map->houses.loadHousesXML(map->housefile);
140+ }
141+
142+ const std::string& getLastErrorString() const {
143+ return errorString;
144+ }
145+
146+ void setLastErrorString(std::string error) {
147+ errorString = error;
148+ }
149+
150+ private:
151+ bool parseMapDataAttributes(OTB::Loader& loader, const OTB::Node& mapNode, Map& map, const std::string& fileName);
152+ bool parseWaypoints(OTB::Loader& loader, const OTB::Node& waypointsNode, Map& map);
153+ bool parseTowns(OTB::Loader& loader, const OTB::Node& townsNode, Map& map);
154+ bool parseTileArea(OTB::Loader& loader, const OTB::Node& tileAreaNode, Map& map);
155+ std::string errorString;
156+};
157+
158+#endif
A src/iomapserialize.cpp +372-0 View file
@@ -0,0 +1,372 @@
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 "iomapserialize.h"
23+#include "game.h"
24+#include "bed.h"
25+
26+extern Game g_game;
27+
28+void IOMapSerialize::loadHouseItems(Map* map)
29+{
30+ int64_t start = OTSYS_TIME();
31+
32+ DBResult_ptr result = Database::getInstance().storeQuery("SELECT `data` FROM `tile_store`");
33+ if (!result) {
34+ return;
35+ }
36+
37+ do {
38+ unsigned long attrSize;
39+ const char* attr = result->getStream("data", attrSize);
40+
41+ PropStream propStream;
42+ propStream.init(attr, attrSize);
43+
44+ uint16_t x, y;
45+ uint8_t z;
46+ if (!propStream.read<uint16_t>(x) || !propStream.read<uint16_t>(y) || !propStream.read<uint8_t>(z)) {
47+ continue;
48+ }
49+
50+ Tile* tile = map->getTile(x, y, z);
51+ if (!tile) {
52+ continue;
53+ }
54+
55+ uint32_t item_count;
56+ if (!propStream.read<uint32_t>(item_count)) {
57+ continue;
58+ }
59+
60+ while (item_count--) {
61+ loadItem(propStream, tile);
62+ }
63+ } while (result->next());
64+ std::cout << "> Loaded house items in: " << (OTSYS_TIME() - start) / (1000.) << " s" << std::endl;
65+}
66+
67+bool IOMapSerialize::saveHouseItems()
68+{
69+ int64_t start = OTSYS_TIME();
70+ Database& db = Database::getInstance();
71+ std::ostringstream query;
72+
73+ //Start the transaction
74+ DBTransaction transaction;
75+ if (!transaction.begin()) {
76+ return false;
77+ }
78+
79+ //clear old tile data
80+ if (!db.executeQuery("DELETE FROM `tile_store`")) {
81+ return false;
82+ }
83+
84+ DBInsert stmt("INSERT INTO `tile_store` (`house_id`, `data`) VALUES ");
85+
86+ PropWriteStream stream;
87+ for (const auto& it : g_game.map.houses.getHouses()) {
88+ //save house items
89+ House* house = it.second;
90+ for (HouseTile* tile : house->getTiles()) {
91+ saveTile(stream, tile);
92+
93+ size_t attributesSize;
94+ const char* attributes = stream.getStream(attributesSize);
95+ if (attributesSize > 0) {
96+ query << house->getId() << ',' << db.escapeBlob(attributes, attributesSize);
97+ if (!stmt.addRow(query)) {
98+ return false;
99+ }
100+ stream.clear();
101+ }
102+ }
103+ }
104+
105+ if (!stmt.execute()) {
106+ return false;
107+ }
108+
109+ //End the transaction
110+ bool success = transaction.commit();
111+ std::cout << "> Saved house items in: " <<
112+ (OTSYS_TIME() - start) / (1000.) << " s" << std::endl;
113+ return success;
114+}
115+
116+bool IOMapSerialize::loadContainer(PropStream& propStream, Container* container)
117+{
118+ while (container->serializationCount > 0) {
119+ if (!loadItem(propStream, container)) {
120+ std::cout << "[Warning - IOMapSerialize::loadContainer] Unserialization error for container item: " << container->getID() << std::endl;
121+ return false;
122+ }
123+ container->serializationCount--;
124+ }
125+
126+ uint8_t endAttr;
127+ if (!propStream.read<uint8_t>(endAttr) || endAttr != 0) {
128+ std::cout << "[Warning - IOMapSerialize::loadContainer] Unserialization error for container item: " << container->getID() << std::endl;
129+ return false;
130+ }
131+ return true;
132+}
133+
134+bool IOMapSerialize::loadItem(PropStream& propStream, Cylinder* parent)
135+{
136+ uint16_t id;
137+ if (!propStream.read<uint16_t>(id)) {
138+ return false;
139+ }
140+
141+ Tile* tile = nullptr;
142+ if (parent->getParent() == nullptr) {
143+ tile = parent->getTile();
144+ }
145+
146+ const ItemType& iType = Item::items[id];
147+ if (iType.moveable || iType.forceSerialize || !tile) {
148+ //create a new item
149+ Item* item = Item::CreateItem(id);
150+ if (item) {
151+ if (item->unserializeAttr(propStream)) {
152+ Container* container = item->getContainer();
153+ if (container && !loadContainer(propStream, container)) {
154+ delete item;
155+ return false;
156+ }
157+
158+ parent->internalAddThing(item);
159+ item->startDecaying();
160+ } else {
161+ std::cout << "WARNING: Unserialization error in IOMapSerialize::loadItem()" << id << std::endl;
162+ delete item;
163+ return false;
164+ }
165+ }
166+ } else {
167+ // Stationary items like doors/beds/blackboards/bookcases
168+ Item* item = nullptr;
169+ if (const TileItemVector* items = tile->getItemList()) {
170+ for (Item* findItem : *items) {
171+ if (findItem->getID() == id) {
172+ item = findItem;
173+ break;
174+ } else if (iType.isDoor() && findItem->getDoor()) {
175+ item = findItem;
176+ break;
177+ } else if (iType.isBed() && findItem->getBed()) {
178+ item = findItem;
179+ break;
180+ }
181+ }
182+ }
183+
184+ if (item) {
185+ if (item->unserializeAttr(propStream)) {
186+ Container* container = item->getContainer();
187+ if (container && !loadContainer(propStream, container)) {
188+ return false;
189+ }
190+
191+ g_game.transformItem(item, id);
192+ } else {
193+ std::cout << "WARNING: Unserialization error in IOMapSerialize::loadItem()" << id << std::endl;
194+ }
195+ } else {
196+ //The map changed since the last save, just read the attributes
197+ std::unique_ptr<Item> dummy(Item::CreateItem(id));
198+ if (dummy) {
199+ dummy->unserializeAttr(propStream);
200+ Container* container = dummy->getContainer();
201+ if (container) {
202+ if (!loadContainer(propStream, container)) {
203+ return false;
204+ }
205+ } else if (BedItem* bedItem = dynamic_cast<BedItem*>(dummy.get())) {
206+ uint32_t sleeperGUID = bedItem->getSleeper();
207+ if (sleeperGUID != 0) {
208+ g_game.removeBedSleeper(sleeperGUID);
209+ }
210+ }
211+ }
212+ }
213+ }
214+ return true;
215+}
216+
217+void IOMapSerialize::saveItem(PropWriteStream& stream, const Item* item)
218+{
219+ const Container* container = item->getContainer();
220+
221+ // Write ID & props
222+ stream.write<uint16_t>(item->getID());
223+ item->serializeAttr(stream);
224+
225+ if (container) {
226+ // Hack our way into the attributes
227+ stream.write<uint8_t>(ATTR_CONTAINER_ITEMS);
228+ stream.write<uint32_t>(container->size());
229+ for (auto it = container->getReversedItems(), end = container->getReversedEnd(); it != end; ++it) {
230+ saveItem(stream, *it);
231+ }
232+ }
233+
234+ stream.write<uint8_t>(0x00); // attr end
235+}
236+
237+void IOMapSerialize::saveTile(PropWriteStream& stream, const Tile* tile)
238+{
239+ const TileItemVector* tileItems = tile->getItemList();
240+ if (!tileItems) {
241+ return;
242+ }
243+
244+ std::forward_list<Item*> items;
245+ uint16_t count = 0;
246+ for (Item* item : *tileItems) {
247+ const ItemType& it = Item::items[item->getID()];
248+
249+ // Note that these are NEGATED, ie. these are the items that will be saved.
250+ if (!(it.moveable || it.forceSerialize || item->getDoor() || (item->getContainer() && !item->getContainer()->empty()) || it.canWriteText || item->getBed())) {
251+ continue;
252+ }
253+
254+ items.push_front(item);
255+ ++count;
256+ }
257+
258+ if (!items.empty()) {
259+ const Position& tilePosition = tile->getPosition();
260+ stream.write<uint16_t>(tilePosition.x);
261+ stream.write<uint16_t>(tilePosition.y);
262+ stream.write<uint8_t>(tilePosition.z);
263+
264+ stream.write<uint32_t>(count);
265+ for (const Item* item : items) {
266+ saveItem(stream, item);
267+ }
268+ }
269+}
270+
271+bool IOMapSerialize::loadHouseInfo()
272+{
273+ Database& db = Database::getInstance();
274+
275+ DBResult_ptr result = db.storeQuery("SELECT `id`, `owner`, `paid`, `warnings` FROM `houses`");
276+ if (!result) {
277+ return false;
278+ }
279+
280+ do {
281+ House* house = g_game.map.houses.getHouse(result->getNumber<uint32_t>("id"));
282+ if (house) {
283+ house->setOwner(result->getNumber<uint32_t>("owner"), false);
284+ house->setPaidUntil(result->getNumber<time_t>("paid"));
285+ house->setPayRentWarnings(result->getNumber<uint32_t>("warnings"));
286+ }
287+ } while (result->next());
288+
289+ result = db.storeQuery("SELECT `house_id`, `listid`, `list` FROM `house_lists`");
290+ if (result) {
291+ do {
292+ House* house = g_game.map.houses.getHouse(result->getNumber<uint32_t>("house_id"));
293+ if (house) {
294+ house->setAccessList(result->getNumber<uint32_t>("listid"), result->getString("list"));
295+ }
296+ } while (result->next());
297+ }
298+ return true;
299+}
300+
301+bool IOMapSerialize::saveHouseInfo()
302+{
303+ Database& db = Database::getInstance();
304+
305+ DBTransaction transaction;
306+ if (!transaction.begin()) {
307+ return false;
308+ }
309+
310+ if (!db.executeQuery("DELETE FROM `house_lists`")) {
311+ return false;
312+ }
313+
314+ std::ostringstream query;
315+ for (const auto& it : g_game.map.houses.getHouses()) {
316+ House* house = it.second;
317+ query << "SELECT `id` FROM `houses` WHERE `id` = " << house->getId();
318+ DBResult_ptr result = db.storeQuery(query.str());
319+ if (result) {
320+ query.str(std::string());
321+ query << "UPDATE `houses` SET `owner` = " << house->getOwner() << ", `paid` = " << house->getPaidUntil() << ", `warnings` = " << house->getPayRentWarnings() << ", `name` = " << db.escapeString(house->getName()) << ", `town_id` = " << house->getTownId() << ", `rent` = " << house->getRent() << ", `size` = " << house->getTiles().size() << ", `beds` = " << house->getBedCount() << " WHERE `id` = " << house->getId();
322+ } else {
323+ query.str(std::string());
324+ query << "INSERT INTO `houses` (`id`, `owner`, `paid`, `warnings`, `name`, `town_id`, `rent`, `size`, `beds`) VALUES (" << house->getId() << ',' << house->getOwner() << ',' << house->getPaidUntil() << ',' << house->getPayRentWarnings() << ',' << db.escapeString(house->getName()) << ',' << house->getTownId() << ',' << house->getRent() << ',' << house->getTiles().size() << ',' << house->getBedCount() << ')';
325+ }
326+
327+ db.executeQuery(query.str());
328+ query.str(std::string());
329+ }
330+
331+ DBInsert stmt("INSERT INTO `house_lists` (`house_id` , `listid` , `list`) VALUES ");
332+
333+ for (const auto& it : g_game.map.houses.getHouses()) {
334+ House* house = it.second;
335+
336+ std::string listText;
337+ if (house->getAccessList(GUEST_LIST, listText) && !listText.empty()) {
338+ query << house->getId() << ',' << GUEST_LIST << ',' << db.escapeString(listText);
339+ if (!stmt.addRow(query)) {
340+ return false;
341+ }
342+
343+ listText.clear();
344+ }
345+
346+ if (house->getAccessList(SUBOWNER_LIST, listText) && !listText.empty()) {
347+ query << house->getId() << ',' << SUBOWNER_LIST << ',' << db.escapeString(listText);
348+ if (!stmt.addRow(query)) {
349+ return false;
350+ }
351+
352+ listText.clear();
353+ }
354+
355+ for (Door* door : house->getDoors()) {
356+ if (door->getAccessList(listText) && !listText.empty()) {
357+ query << house->getId() << ',' << door->getDoorId() << ',' << db.escapeString(listText);
358+ if (!stmt.addRow(query)) {
359+ return false;
360+ }
361+
362+ listText.clear();
363+ }
364+ }
365+ }
366+
367+ if (!stmt.execute()) {
368+ return false;
369+ }
370+
371+ return transaction.commit();
372+}
A src/iomapserialize.h +42-0 View file
@@ -0,0 +1,42 @@
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_IOMAPSERIALIZE_H_7E903658F34E44F9BE03A713B55A3D6D
21+#define FS_IOMAPSERIALIZE_H_7E903658F34E44F9BE03A713B55A3D6D
22+
23+#include "database.h"
24+#include "map.h"
25+
26+class IOMapSerialize
27+{
28+ public:
29+ static void loadHouseItems(Map* map);
30+ static bool saveHouseItems();
31+ static bool loadHouseInfo();
32+ static bool saveHouseInfo();
33+
34+ private:
35+ static void saveItem(PropWriteStream& stream, const Item* item);
36+ static void saveTile(PropWriteStream& stream, const Tile* tile);
37+
38+ static bool loadContainer(PropStream& propStream, Container* container);
39+ static bool loadItem(PropStream& propStream, Cylinder* parent);
40+};
41+
42+#endif
A src/iomarket.cpp +348-0 View file
@@ -0,0 +1,348 @@
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 "iomarket.h"
23+
24+#include "configmanager.h"
25+#include "databasetasks.h"
26+#include "iologindata.h"
27+#include "game.h"
28+#include "scheduler.h"
29+
30+extern ConfigManager g_config;
31+extern Game g_game;
32+
33+MarketOfferList IOMarket::getActiveOffers(MarketAction_t action, uint16_t itemId)
34+{
35+ MarketOfferList offerList;
36+
37+ std::ostringstream query;
38+ query << "SELECT `id`, `amount`, `price`, `created`, `anonymous`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `player_name` FROM `market_offers` WHERE `sale` = " << action << " AND `itemtype` = " << itemId;
39+
40+ DBResult_ptr result = Database::getInstance().storeQuery(query.str());
41+ if (!result) {
42+ return offerList;
43+ }
44+
45+ const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION);
46+
47+ do {
48+ MarketOffer offer;
49+ offer.amount = result->getNumber<uint16_t>("amount");
50+ offer.price = result->getNumber<uint32_t>("price");
51+ offer.timestamp = result->getNumber<uint32_t>("created") + marketOfferDuration;
52+ offer.counter = result->getNumber<uint32_t>("id") & 0xFFFF;
53+ if (result->getNumber<uint16_t>("anonymous") == 0) {
54+ offer.playerName = result->getString("player_name");
55+ } else {
56+ offer.playerName = "Anonymous";
57+ }
58+ offerList.push_back(offer);
59+ } while (result->next());
60+ return offerList;
61+}
62+
63+MarketOfferList IOMarket::getOwnOffers(MarketAction_t action, uint32_t playerId)
64+{
65+ MarketOfferList offerList;
66+
67+ const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION);
68+
69+ std::ostringstream query;
70+ query << "SELECT `id`, `amount`, `price`, `created`, `itemtype` FROM `market_offers` WHERE `player_id` = " << playerId << " AND `sale` = " << action;
71+
72+ DBResult_ptr result = Database::getInstance().storeQuery(query.str());
73+ if (!result) {
74+ return offerList;
75+ }
76+
77+ do {
78+ MarketOffer offer;
79+ offer.amount = result->getNumber<uint16_t>("amount");
80+ offer.price = result->getNumber<uint32_t>("price");
81+ offer.timestamp = result->getNumber<uint32_t>("created") + marketOfferDuration;
82+ offer.counter = result->getNumber<uint32_t>("id") & 0xFFFF;
83+ offer.itemId = result->getNumber<uint16_t>("itemtype");
84+ offerList.push_back(offer);
85+ } while (result->next());
86+ return offerList;
87+}
88+
89+HistoryMarketOfferList IOMarket::getOwnHistory(MarketAction_t action, uint32_t playerId)
90+{
91+ HistoryMarketOfferList offerList;
92+
93+ std::ostringstream query;
94+ query << "SELECT `itemtype`, `amount`, `price`, `expires_at`, `state` FROM `market_history` WHERE `player_id` = " << playerId << " AND `sale` = " << action;
95+
96+ DBResult_ptr result = Database::getInstance().storeQuery(query.str());
97+ if (!result) {
98+ return offerList;
99+ }
100+
101+ do {
102+ HistoryMarketOffer offer;
103+ offer.itemId = result->getNumber<uint16_t>("itemtype");
104+ offer.amount = result->getNumber<uint16_t>("amount");
105+ offer.price = result->getNumber<uint32_t>("price");
106+ offer.timestamp = result->getNumber<uint32_t>("expires_at");
107+
108+ MarketOfferState_t offerState = static_cast<MarketOfferState_t>(result->getNumber<uint16_t>("state"));
109+ if (offerState == OFFERSTATE_ACCEPTEDEX) {
110+ offerState = OFFERSTATE_ACCEPTED;
111+ }
112+
113+ offer.state = offerState;
114+
115+ offerList.push_back(offer);
116+ } while (result->next());
117+ return offerList;
118+}
119+
120+void IOMarket::processExpiredOffers(DBResult_ptr result, bool)
121+{
122+ if (!result) {
123+ return;
124+ }
125+
126+ do {
127+ if (!IOMarket::moveOfferToHistory(result->getNumber<uint32_t>("id"), OFFERSTATE_EXPIRED)) {
128+ continue;
129+ }
130+
131+ const uint32_t playerId = result->getNumber<uint32_t>("player_id");
132+ const uint16_t amount = result->getNumber<uint16_t>("amount");
133+ if (result->getNumber<uint16_t>("sale") == 1) {
134+ const ItemType& itemType = Item::items[result->getNumber<uint16_t>("itemtype")];
135+ if (itemType.id == 0) {
136+ continue;
137+ }
138+
139+ Player* player = g_game.getPlayerByGUID(playerId);
140+ if (!player) {
141+ player = new Player(nullptr);
142+ if (!IOLoginData::loadPlayerById(player, playerId)) {
143+ delete player;
144+ continue;
145+ }
146+ }
147+
148+ if (itemType.stackable) {
149+ uint16_t tmpAmount = amount;
150+ while (tmpAmount > 0) {
151+ uint16_t stackCount = std::min<uint16_t>(100, tmpAmount);
152+ Item* item = Item::CreateItem(itemType.id, stackCount);
153+ if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
154+ delete item;
155+ break;
156+ }
157+
158+ tmpAmount -= stackCount;
159+ }
160+ } else {
161+ int32_t subType;
162+ if (itemType.charges != 0) {
163+ subType = itemType.charges;
164+ } else {
165+ subType = -1;
166+ }
167+
168+ for (uint16_t i = 0; i < amount; ++i) {
169+ Item* item = Item::CreateItem(itemType.id, subType);
170+ if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
171+ delete item;
172+ break;
173+ }
174+ }
175+ }
176+
177+ if (player->isOffline()) {
178+ IOLoginData::savePlayer(player);
179+ delete player;
180+ }
181+ } else {
182+ uint64_t totalPrice = result->getNumber<uint64_t>("price") * amount;
183+
184+ Player* player = g_game.getPlayerByGUID(playerId);
185+ if (player) {
186+ player->setBankBalance(player->getBankBalance() + totalPrice);
187+ } else {
188+ IOLoginData::increaseBankBalance(playerId, totalPrice);
189+ }
190+ }
191+ } while (result->next());
192+}
193+
194+void IOMarket::checkExpiredOffers()
195+{
196+ const time_t lastExpireDate = time(nullptr) - g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION);
197+
198+ std::ostringstream query;
199+ query << "SELECT `id`, `amount`, `price`, `itemtype`, `player_id`, `sale` FROM `market_offers` WHERE `created` <= " << lastExpireDate;
200+ g_databaseTasks.addTask(query.str(), IOMarket::processExpiredOffers, true);
201+
202+ int32_t checkExpiredMarketOffersEachMinutes = g_config.getNumber(ConfigManager::CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES);
203+ if (checkExpiredMarketOffersEachMinutes <= 0) {
204+ return;
205+ }
206+
207+ g_scheduler.addEvent(createSchedulerTask(checkExpiredMarketOffersEachMinutes * 60 * 1000, IOMarket::checkExpiredOffers));
208+}
209+
210+uint32_t IOMarket::getPlayerOfferCount(uint32_t playerId)
211+{
212+ std::ostringstream query;
213+ query << "SELECT COUNT(*) AS `count` FROM `market_offers` WHERE `player_id` = " << playerId;
214+
215+ DBResult_ptr result = Database::getInstance().storeQuery(query.str());
216+ if (!result) {
217+ return 0;
218+ }
219+ return result->getNumber<int32_t>("count");
220+}
221+
222+MarketOfferEx IOMarket::getOfferByCounter(uint32_t timestamp, uint16_t counter)
223+{
224+ MarketOfferEx offer;
225+
226+ const int32_t created = timestamp - g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION);
227+
228+ std::ostringstream query;
229+ query << "SELECT `id`, `sale`, `itemtype`, `amount`, `created`, `price`, `player_id`, `anonymous`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `player_name` FROM `market_offers` WHERE `created` = " << created << " AND (`id` & 65535) = " << counter << " LIMIT 1";
230+
231+ DBResult_ptr result = Database::getInstance().storeQuery(query.str());
232+ if (!result) {
233+ offer.id = 0;
234+ offer.playerId = 0;
235+ return offer;
236+ }
237+
238+ offer.id = result->getNumber<uint32_t>("id");
239+ offer.type = static_cast<MarketAction_t>(result->getNumber<uint16_t>("sale"));
240+ offer.amount = result->getNumber<uint16_t>("amount");
241+ offer.counter = result->getNumber<uint32_t>("id") & 0xFFFF;
242+ offer.timestamp = result->getNumber<uint32_t>("created");
243+ offer.price = result->getNumber<uint32_t>("price");
244+ offer.itemId = result->getNumber<uint16_t>("itemtype");
245+ offer.playerId = result->getNumber<uint32_t>("player_id");
246+ if (result->getNumber<uint16_t>("anonymous") == 0) {
247+ offer.playerName = result->getString("player_name");
248+ } else {
249+ offer.playerName = "Anonymous";
250+ }
251+ return offer;
252+}
253+
254+void IOMarket::createOffer(uint32_t playerId, MarketAction_t action, uint32_t itemId, uint16_t amount, uint32_t price, bool anonymous)
255+{
256+ std::ostringstream query;
257+ query << "INSERT INTO `market_offers` (`player_id`, `sale`, `itemtype`, `amount`, `price`, `created`, `anonymous`) VALUES (" << playerId << ',' << action << ',' << itemId << ',' << amount << ',' << price << ',' << time(nullptr) << ',' << anonymous << ')';
258+ Database::getInstance().executeQuery(query.str());
259+}
260+
261+void IOMarket::acceptOffer(uint32_t offerId, uint16_t amount)
262+{
263+ std::ostringstream query;
264+ query << "UPDATE `market_offers` SET `amount` = `amount` - " << amount << " WHERE `id` = " << offerId;
265+ Database::getInstance().executeQuery(query.str());
266+}
267+
268+void IOMarket::deleteOffer(uint32_t offerId)
269+{
270+ std::ostringstream query;
271+ query << "DELETE FROM `market_offers` WHERE `id` = " << offerId;
272+ Database::getInstance().executeQuery(query.str());
273+}
274+
275+void IOMarket::appendHistory(uint32_t playerId, MarketAction_t type, uint16_t itemId, uint16_t amount, uint32_t price, time_t timestamp, MarketOfferState_t state)
276+{
277+ std::ostringstream query;
278+ query << "INSERT INTO `market_history` (`player_id`, `sale`, `itemtype`, `amount`, `price`, `expires_at`, `inserted`, `state`) VALUES ("
279+ << playerId << ',' << type << ',' << itemId << ',' << amount << ',' << price << ','
280+ << timestamp << ',' << time(nullptr) << ',' << state << ')';
281+ g_databaseTasks.addTask(query.str());
282+}
283+
284+bool IOMarket::moveOfferToHistory(uint32_t offerId, MarketOfferState_t state)
285+{
286+ const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION);
287+
288+ Database& db = Database::getInstance();
289+
290+ std::ostringstream query;
291+ query << "SELECT `player_id`, `sale`, `itemtype`, `amount`, `price`, `created` FROM `market_offers` WHERE `id` = " << offerId;
292+
293+ DBResult_ptr result = db.storeQuery(query.str());
294+ if (!result) {
295+ return false;
296+ }
297+
298+ query.str(std::string());
299+ query << "DELETE FROM `market_offers` WHERE `id` = " << offerId;
300+ if (!db.executeQuery(query.str())) {
301+ return false;
302+ }
303+
304+ appendHistory(result->getNumber<uint32_t>("player_id"), static_cast<MarketAction_t>(result->getNumber<uint16_t>("sale")), result->getNumber<uint16_t>("itemtype"), result->getNumber<uint16_t>("amount"), result->getNumber<uint32_t>("price"), result->getNumber<uint32_t>("created") + marketOfferDuration, state);
305+ return true;
306+}
307+
308+void IOMarket::updateStatistics()
309+{
310+ std::ostringstream query;
311+ query << "SELECT `sale` AS `sale`, `itemtype` AS `itemtype`, COUNT(`price`) AS `num`, MIN(`price`) AS `min`, MAX(`price`) AS `max`, SUM(`price`) AS `sum` FROM `market_history` WHERE `state` = " << OFFERSTATE_ACCEPTED << " GROUP BY `itemtype`, `sale`";
312+ DBResult_ptr result = Database::getInstance().storeQuery(query.str());
313+ if (!result) {
314+ return;
315+ }
316+
317+ do {
318+ MarketStatistics* statistics;
319+ if (result->getNumber<uint16_t>("sale") == MARKETACTION_BUY) {
320+ statistics = &purchaseStatistics[result->getNumber<uint16_t>("itemtype")];
321+ } else {
322+ statistics = &saleStatistics[result->getNumber<uint16_t>("itemtype")];
323+ }
324+
325+ statistics->numTransactions = result->getNumber<uint32_t>("num");
326+ statistics->lowestPrice = result->getNumber<uint32_t>("min");
327+ statistics->totalPrice = result->getNumber<uint64_t>("sum");
328+ statistics->highestPrice = result->getNumber<uint32_t>("max");
329+ } while (result->next());
330+}
331+
332+MarketStatistics* IOMarket::getPurchaseStatistics(uint16_t itemId)
333+{
334+ auto it = purchaseStatistics.find(itemId);
335+ if (it == purchaseStatistics.end()) {
336+ return nullptr;
337+ }
338+ return &it->second;
339+}
340+
341+MarketStatistics* IOMarket::getSaleStatistics(uint16_t itemId)
342+{
343+ auto it = saleStatistics.find(itemId);
344+ if (it == saleStatistics.end()) {
345+ return nullptr;
346+ }
347+ return &it->second;
348+}
A src/iomarket.h +63-0 View file
@@ -0,0 +1,63 @@
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_IOMARKET_H_B981E52C218C42D3B9EF726EBF0E92C9
21+#define FS_IOMARKET_H_B981E52C218C42D3B9EF726EBF0E92C9
22+
23+#include "enums.h"
24+#include "database.h"
25+
26+class IOMarket
27+{
28+ public:
29+ static IOMarket& getInstance() {
30+ static IOMarket instance;
31+ return instance;
32+ }
33+
34+ static MarketOfferList getActiveOffers(MarketAction_t action, uint16_t itemId);
35+ static MarketOfferList getOwnOffers(MarketAction_t action, uint32_t playerId);
36+ static HistoryMarketOfferList getOwnHistory(MarketAction_t action, uint32_t playerId);
37+
38+ static void processExpiredOffers(DBResult_ptr result, bool);
39+ static void checkExpiredOffers();
40+
41+ static uint32_t getPlayerOfferCount(uint32_t playerId);
42+ static MarketOfferEx getOfferByCounter(uint32_t timestamp, uint16_t counter);
43+
44+ static void createOffer(uint32_t playerId, MarketAction_t action, uint32_t itemId, uint16_t amount, uint32_t price, bool anonymous);
45+ static void acceptOffer(uint32_t offerId, uint16_t amount);
46+ static void deleteOffer(uint32_t offerId);
47+
48+ static void appendHistory(uint32_t playerId, MarketAction_t type, uint16_t itemId, uint16_t amount, uint32_t price, time_t timestamp, MarketOfferState_t state);
49+ static bool moveOfferToHistory(uint32_t offerId, MarketOfferState_t state);
50+
51+ void updateStatistics();
52+
53+ MarketStatistics* getPurchaseStatistics(uint16_t itemId);
54+ MarketStatistics* getSaleStatistics(uint16_t itemId);
55+
56+ private:
57+ IOMarket() = default;
58+
59+ std::map<uint16_t, MarketStatistics> purchaseStatistics;
60+ std::map<uint16_t, MarketStatistics> saleStatistics;
61+};
62+
63+#endif
Top