diff --git a/config.lua.dist b/config.lua.dist index 113c6b2a4c9..60c4e22928c 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -22,10 +22,8 @@ toggleMaintainMode = false maintainModeMessage = "" -- Combat settings --- NOTE: valid values for worldType are: "pvp", "no-pvp" and "pvp-enforced" -- NOTE: removeBeginningWeaponAmmunition: spears, arrows, bolt have endless ammo (allows training for paladins) -- NOTE: refundManaOnBeginningWeapons: wand of vortex and snakebite refund mana used (allows training for mages) -worldType = "pvp" hotkeyAimbotEnabled = true protectionLevel = 7 pzLocked = 60 * 1000 @@ -52,14 +50,17 @@ cleanProtectionZones = false -- Connection Config -- NOTE: allowOldProtocol can allow login on 10x protocol. (11.00) -- NOTE: maxPlayers set to 0 means no limit --- NOTE: MaxPacketsPerSeconds if you change you will be subject to bugs by WPE, keep the default value of 25, +-- NOTE: gameProtocolPort and statusProtocolPort configs are deprecated, now will be loaded from database, statusProtocolPort will be ever as 9PORT, example: 97172 +-- NOTE: serverName is different from world name, but for better usage, use the same name as world name +-- NOTE: serverMotd is deprecated, now will be loaded from database +-- NOTE: MaxPacketsPerSeconds if you change you will be subject to bugs by WPE, keep the default value of 25, -- It's recommended to use a range like min 50 in this function, otherwise you will be disconnected after equipping two-handed distance weapons. ip = "127.0.0.1" allowOldProtocol = false bindOnlyGlobalAddress = false loginProtocolPort = 7171 gameProtocolPort = 7172 -statusProtocolPort = 7171 +statusProtocolPort = 97172 maxPlayers = 0 serverName = "OTServBR-Global" serverMotd = "Welcome to the OTServBR-Global!" @@ -71,6 +72,14 @@ maxContainer = 100 maxPlayersOnlinePerAccount = 1 maxPlayersOutsidePZPerAccount = 1 +-- World settings (This information will be loaded from the 'worlds' database table as well) +-- NOTE: World id is 1 as default, new worlds must have different ids always greater than 1 +-- NOTE: valid values for worldType are: "pvp", "no-pvp", "pvp-enforced", "retro-pvp" and "retro-pvp-enforced" +-- NOTE: valid values for worldLocation are: "Europe", "North America", "South America" and "Oceania" +worldId = 1 +worldType = "pvp" +worldLocation = "South America" + -- Packet Compression -- Minimize network bandwith and reduce ping -- Levels: 0 = disabled, 1 = best speed, 9 = best compression @@ -244,7 +253,6 @@ onlyPremiumAccount = false -- NOTE: autoBank = true, the dropped coins from monsters will be automatically deposited to your bank account. -- NOTE: toggleGoldPouchAllowAnything will allow players to move items or gold to gold pouch -- NOTE: toggleGoldPouchQuickLootOnly will ONLY allow quickloot to move items to gold pouch --- NOTE: toggleServerIsRetroPVP will make this server as retro, setting PARTY_PROTECTION and ADVANCED_SECURE_MODE to 0 -- NOTE: toggleTravelsFree will make all travels from boat free -- NOTE: buyAolCommandFee will add fee when player buy aol by command (!aol), active changing value more than 0 (fee value. ex: 1 = 1gp aol will be 50001) -- NOTE: buyBlessCommandFee will add fee when player buy bless by command (!bless), active changing value between 1 and 100 (fee percent. ex: 3 = 3%, 30 = 30%) @@ -263,7 +271,6 @@ autoLoot = false autoBank = false toggleGoldPouchAllowAnything = false toggleGoldPouchQuickLootOnly = false -toggleServerIsRetroPVP = false toggleTravelsFree = false buyAolCommandFee = 0 buyBlessCommandFee = 0 @@ -539,7 +546,6 @@ startupDatabaseOptimization = true ownerName = "OpenTibiaBR" ownerEmail = "opentibiabr@outlook.com" url = "http://docs.opentibiabr.com/" -location = "South America" -- Sends Discord webhook notifications on startup, raids and shutdown. -- The URL layout is https://discord.com/api/webhooks/:id/:token diff --git a/data-otservbr-global/migrations/46.lua b/data-otservbr-global/migrations/46.lua index 86a6d8ffec1..1b63e202bdc 100644 --- a/data-otservbr-global/migrations/46.lua +++ b/data-otservbr-global/migrations/46.lua @@ -1,3 +1,73 @@ function onUpdateDatabase() - return false -- true = There are others migrations file | false = this is the last migration file + logger.info("Updating database to version 47 (multiworld system)") + + db.query("SET FOREIGN_KEY_CHECKS=0;") + + db.query([[ + CREATE TABLE IF NOT EXISTS `worlds` ( + `id` int(3) UNSIGNED NOT NULL AUTO_INCREMENT, + `name` varchar(80) NOT NULL, + `type` enum('no-pvp','pvp','retro-pvp','pvp-enforced','retro-pvp-enforced') NOT NULL, + `motd` varchar(255) NOT NULL DEFAULT '', + `location` enum('Europe','North America','South America','Oceania') NOT NULL, + `ip` varchar(15) NOT NULL, + `port` int(5) UNSIGNED NOT NULL, + `port_status` int(6) UNSIGNED NOT NULL, + `creation` int(11) NOT NULL DEFAULT 0, + CONSTRAINT `worlds_pk` PRIMARY KEY (`id`), + CONSTRAINT `worlds_unique` UNIQUE (`name`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + ]]) + + db.query("ALTER TABLE `server_config` ADD `world_id` int(3) UNSIGNED NOT NULL DEFAULT 1;") + db.query("ALTER TABLE `server_config` DROP PRIMARY KEY;") + db.query("ALTER TABLE `server_config` ADD PRIMARY KEY (`config`, `world_id`);") + db.query("ALTER TABLE `server_config` ADD FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) ON DELETE CASCADE;") + + db.query("ALTER TABLE `players_online` ADD `world_id` int(3) UNSIGNED NOT NULL DEFAULT 1;") + db.query("ALTER TABLE `players_online` ADD FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) ON DELETE CASCADE;") + + db.query("ALTER TABLE `players` ADD `world_id` int(3) UNSIGNED NOT NULL DEFAULT 1;") + db.query("ALTER TABLE `players` ADD FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) ON DELETE CASCADE;") + + db.query("ALTER TABLE `guilds` ADD `world_id` int(3) UNSIGNED NOT NULL DEFAULT 1;") + db.query("ALTER TABLE `guilds` ADD FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) ON DELETE CASCADE;") + + db.query("ALTER TABLE `houses` ADD `world_id` int(3) UNSIGNED NOT NULL DEFAULT 1;") + db.query("ALTER TABLE `houses` ADD FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) ON DELETE CASCADE;") + + db.query("ALTER TABLE `house_lists` ADD `world_id` int(3) UNSIGNED NOT NULL DEFAULT 1;") + db.query("ALTER TABLE `house_lists` ADD FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) ON DELETE CASCADE;") + + db.query("ALTER TABLE `account_viplist` ADD `world_id` int(3) UNSIGNED NOT NULL DEFAULT 1;") + db.query("ALTER TABLE `account_viplist` ADD FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) ON DELETE CASCADE;") + + db.query("ALTER TABLE `tile_store` ADD `world_id` int(3) UNSIGNED NOT NULL DEFAULT 1;") + db.query("ALTER TABLE `tile_store` ADD FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) ON DELETE CASCADE;") + + db.query("ALTER TABLE `market_offers` ADD `world_id` int(3) UNSIGNED NOT NULL DEFAULT 1;") + db.query("ALTER TABLE `market_offers` ADD FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) ON DELETE CASCADE;") + + db.query("ALTER TABLE `market_history` ADD `world_id` int(3) UNSIGNED NOT NULL DEFAULT 1;") + db.query("ALTER TABLE `market_history` ADD FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) ON DELETE CASCADE;") + + db.query("ALTER TABLE `server_config` DROP PRIMARY KEY") + db.query("ALTER TABLE `server_config` ADD PRIMARY KEY (`world_id`, `config`);") + + db.query("ALTER TABLE `houses` CHANGE `id` `id` INT(11) NOT NULL;") + db.query("ALTER TABLE `houses` DROP PRIMARY KEY;") + db.query("ALTER TABLE `houses` ADD PRIMARY KEY (`id`, `world_id`);") + db.query("ALTER TABLE `houses` CHANGE `id` `id` INT(11) NOT NULL AUTO_INCREMENT;") + db.query("ALTER TABLE `houses` ADD FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) ON DELETE CASCADE;") + + db.query("DROP TRIGGER `ondelete_players`;") + db.query([[ + CREATE TRIGGER `ondelete_players` BEFORE DELETE ON `players` FOR EACH ROW BEGIN + UPDATE `houses` SET `owner` = 0 WHERE `owner` = OLD.`id` AND `world_id` = OLD.`world_id`; + END; + ]]) + + db.query("SET FOREIGN_KEY_CHECKS=1;") + + return true end diff --git a/data-otservbr-global/migrations/47.lua b/data-otservbr-global/migrations/47.lua new file mode 100644 index 00000000000..86a6d8ffec1 --- /dev/null +++ b/data-otservbr-global/migrations/47.lua @@ -0,0 +1,3 @@ +function onUpdateDatabase() + return false -- true = There are others migrations file | false = this is the last migration file +end diff --git a/data/XML/events.xml b/data/XML/events.xml index c4f483b21ef..0910cf6993f 100644 --- a/data/XML/events.xml +++ b/data/XML/events.xml @@ -1,15 +1,17 @@ - - - - -
+ + + + +
- - - - -
+ + + + +
diff --git a/data/events/scripts/creature.lua b/data/events/scripts/creature.lua index e7a74a33191..5292cf074d8 100644 --- a/data/events/scripts/creature.lua +++ b/data/events/scripts/creature.lua @@ -59,7 +59,7 @@ function Creature:onTargetCombat(target) return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE end - if not IsRetroPVP() or PARTY_PROTECTION ~= 0 then + if not IsRetroPVP() then if self:isPlayer() and target:isPlayer() then local party = self:getParty() if party then @@ -71,7 +71,7 @@ function Creature:onTargetCombat(target) end end - if not IsRetroPVP() or ADVANCED_SECURE_MODE ~= 0 then + if not IsRetroPVP() then if self:isPlayer() and target:isPlayer() then if self:hasSecureMode() then return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER diff --git a/data/global.lua b/data/global.lua index 9ea4fd128a9..118f25e49a0 100644 --- a/data/global.lua +++ b/data/global.lua @@ -15,18 +15,31 @@ function IsRunningGlobalDatapack() end end +function getWorldTypeName() + local worldType = Game.getWorldType() + if worldType == WORLD_TYPE_PVP then + return "Open PvP" + elseif worldType == WORLD_TYPE_NO_PVP then + return "Optional PvP" + elseif worldType == WORLD_TYPE_PVP_ENFORCED then + return "Hardcore PvP" + elseif worldType == WORLD_TYPE_RETRO_PVP then + return "Retro Open PvP" + elseif worldType == WORLD_TYPE_RETRO_PVP_ENFORCED then + return "Retro Hardcore PvP" + else + return "Unknown" + end +end + function IsRetroPVP() - return configManager.getBoolean(configKeys.TOGGLE_SERVER_IS_RETRO) + return table.contains({ WORLD_TYPE_RETRO_PVP, WORLD_TYPE_RETRO_PVP_ENFORCED }, Game.getWorldType()) end function IsTravelFree() return configManager.getBoolean(configKeys.TOGGLE_TRAVELS_FREE) end --- NOTE: 0 is disabled. -PARTY_PROTECTION = (IsRetroPVP() and 0) or 1 -ADVANCED_SECURE_MODE = (IsRetroPVP() and 0) or 1 - NORTH = DIRECTION_NORTH EAST = DIRECTION_EAST SOUTH = DIRECTION_SOUTH diff --git a/data/libs/compat/compat.lua b/data/libs/compat/compat.lua index 061d1dbe47e..87471fc5d2c 100644 --- a/data/libs/compat/compat.lua +++ b/data/libs/compat/compat.lua @@ -1461,8 +1461,6 @@ function setGlobalStorageValue(key, value) return true end -getWorldType = Game.getWorldType - numberToVariant = Variant stringToVariant = Variant positionToVariant = Variant diff --git a/data/libs/systems/hireling.lua b/data/libs/systems/hireling.lua index 75005a1adfc..0bdb6112c41 100644 --- a/data/libs/systems/hireling.lua +++ b/data/libs/systems/hireling.lua @@ -452,7 +452,8 @@ function GetHirelingOutfitNameById(id) end function HirelingsInit() - local rows = db.storeQuery("SELECT * FROM `player_hirelings`") + local query = string.format("SELECT `ph`.* FROM `player_hirelings` AS `ph` INNER JOIN `players` as `p` ON `p`.`id` = `ph`.`player_id` WHERE `p`.`world_id` = %d", configManager.getNumber(configKeys.WORLD_ID)) + local rows = db.storeQuery(query) if rows then local player_id, hireling repeat diff --git a/data/modules/scripts/blessings/blessings.lua b/data/modules/scripts/blessings/blessings.lua index e061501a330..fa405bdac37 100644 --- a/data/modules/scripts/blessings/blessings.lua +++ b/data/modules/scripts/blessings/blessings.lua @@ -21,7 +21,7 @@ Blessings.Credits = { Blessings.Config = { AdventurerBlessingLevel = configManager.getNumber(configKeys.ADVENTURERSBLESSING_LEVEL), -- Free full bless until level - HasToF = not configManager.getBoolean(configKeys.TOGGLE_SERVER_IS_RETRO), -- Enables/disables twist of fate + HasToF = not IsRetroPVP(), -- Enables/disables twist of fate InquisitonBlessPriceMultiplier = 1.1, -- Bless price multiplied by henricus SkulledDeathLoseStoreItem = configManager.getBoolean(configKeys.SKULLED_DEATH_LOSE_STORE_ITEM), -- Destroy all items on store when dying with red/blackskull InventoryGlowOnFiveBless = configManager.getBoolean(configKeys.INVENTORY_GLOW), -- Glow in yellow inventory items when the player has 5 or more bless, diff --git a/data/scripts/runes/magic_wall.lua b/data/scripts/runes/magic_wall.lua index b411fddd3a1..9a5bcd21821 100644 --- a/data/scripts/runes/magic_wall.lua +++ b/data/scripts/runes/magic_wall.lua @@ -3,12 +3,7 @@ function onCreateMagicWall(creature, position) if tile and tile:getTopCreature() and not tile:getTopCreature():isPlayer() then return false end - local magicWall - if Game.getWorldType() == WORLD_TYPE_NO_PVP then - magicWall = ITEM_MAGICWALL_SAFE - else - magicWall = ITEM_MAGICWALL - end + local magicWall = Game.getWorldType() == WORLD_TYPE_NO_PVP and ITEM_MAGICWALL_SAFE or ITEM_MAGICWALL local item = Game.createItem(magicWall, 1, position) item:setDuration(16, 24) end diff --git a/data/scripts/runes/wild_growth.lua b/data/scripts/runes/wild_growth.lua index 08feb66aa0a..23200940e75 100644 --- a/data/scripts/runes/wild_growth.lua +++ b/data/scripts/runes/wild_growth.lua @@ -3,12 +3,7 @@ function onCreateWildGrowth(creature, position) if tile and tile:getTopCreature() and not tile:getTopCreature():isPlayer() then return false end - local wildGrowth - if Game.getWorldType() == WORLD_TYPE_NO_PVP then - wildGrowth = ITEM_WILDGROWTH_SAFE - else - wildGrowth = ITEM_WILDGROWTH - end + local wildGrowth = Game.getWorldType() == WORLD_TYPE_NO_PVP and ITEM_WILDGROWTH_SAFE or ITEM_WILDGROWTH local item = Game.createItem(wildGrowth, 1, position) item:setDuration(30, 60) end diff --git a/data/scripts/talkactions/player/server_info.lua b/data/scripts/talkactions/player/server_info.lua index 136a271f7e4..420905d834c 100644 --- a/data/scripts/talkactions/player/server_info.lua +++ b/data/scripts/talkactions/player/server_info.lua @@ -64,7 +64,7 @@ function serverInfo.onSay(player, words, param) .. "\nProtection level: " .. configManager.getNumber(configKeys.PROTECTION_LEVEL) .. "\nWorldType: " - .. configManager.getString(configKeys.WORLD_TYPE) + .. getWorldTypeName() .. "\nKills/day to red skull: " .. configManager.getNumber(configKeys.DAY_KILLS_TO_RED) .. "\nKills/week to red skull: " diff --git a/schema.sql b/schema.sql index af245067057..16a59c86d50 100644 --- a/schema.sql +++ b/schema.sql @@ -1,13 +1,35 @@ -- Canary - Database (Schema) +-- Table structure `worlds` +CREATE TABLE IF NOT EXISTS `worlds` ( + `id` int(3) UNSIGNED NOT NULL AUTO_INCREMENT, + `name` varchar(80) NOT NULL, + `type` enum('no-pvp','pvp','retro-pvp','pvp-enforced','retro-pvp-enforced') NOT NULL, + `motd` varchar(255) NOT NULL DEFAULT '', + `location` enum('Europe','North America','South America','Oceania') NOT NULL, + `ip` varchar(15) NOT NULL, + `port` int(5) UNSIGNED NOT NULL, + `port_status` int(6) UNSIGNED NOT NULL, + `creation` int(11) NOT NULL DEFAULT 0, + CONSTRAINT `worlds_pk` PRIMARY KEY (`id`), + CONSTRAINT `worlds_unique` UNIQUE (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO `worlds` (`name`, `type`, `motd`, `location`, `ip`, `port`, `port_status`, `creation`) +VALUES ('OTServBR-Global', 'pvp', 'Welcome to the OTServBR-Global!', 'South America', '127.0.0.1', 7172, 97172, UNIX_TIMESTAMP()); + -- Table structure `server_config` CREATE TABLE IF NOT EXISTS `server_config` ( + `world_id` int(3) UNSIGNED NOT NULL, `config` varchar(50) NOT NULL, `value` varchar(256) NOT NULL DEFAULT '', - CONSTRAINT `server_config_pk` PRIMARY KEY (`config`) + CONSTRAINT `server_config_pk` PRIMARY KEY (`world_id`, `config`), + CONSTRAINT `server_config_worlds_fk` + FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) + ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '46'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0'); +INSERT INTO `server_config` (`config`, `value`, `world_id`) VALUES ('db_version', '47', 1), ('motd_hash', '', 1), ('motd_num', '0', 1), ('players_record', '0', 1); -- Table structure `accounts` CREATE TABLE IF NOT EXISTS `accounts` ( @@ -148,13 +170,17 @@ CREATE TABLE IF NOT EXISTS `players` ( `forge_dust_level` bigint(21) NOT NULL DEFAULT '100', `randomize_mount` tinyint(1) NOT NULL DEFAULT '0', `boss_points` int NOT NULL DEFAULT '0', + `world_id` int(3) UNSIGNED NOT NULL, INDEX `account_id` (`account_id`), INDEX `vocation` (`vocation`), CONSTRAINT `players_pk` PRIMARY KEY (`id`), CONSTRAINT `players_unique` UNIQUE (`name`), CONSTRAINT `players_account_fk` - FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) - ON DELETE CASCADE + FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) + ON DELETE CASCADE, + CONSTRAINT `players_worlds_fk` + FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) + ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- Table structure `account_bans` @@ -204,6 +230,7 @@ CREATE TABLE IF NOT EXISTS `account_viplist` ( `description` varchar(128) NOT NULL DEFAULT '', `icon` tinyint(2) UNSIGNED NOT NULL DEFAULT '0', `notify` tinyint(1) NOT NULL DEFAULT '0', + `world_id` int(3) UNSIGNED NOT NULL, INDEX `account_id` (`account_id`), INDEX `player_id` (`player_id`), CONSTRAINT `account_viplist_unique` UNIQUE (`account_id`, `player_id`), @@ -211,8 +238,11 @@ CREATE TABLE IF NOT EXISTS `account_viplist` ( FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE, CONSTRAINT `account_viplist_player_fk` - FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) - ON DELETE CASCADE + FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) + ON DELETE CASCADE, + CONSTRAINT `account_viplist_worlds_fk` + FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) + ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- Table structure `account_vipgroup` @@ -336,11 +366,15 @@ CREATE TABLE IF NOT EXISTS `guilds` ( `residence` int(11) NOT NULL DEFAULT '0', `balance` bigint(20) UNSIGNED NOT NULL DEFAULT '0', `points` int(11) NOT NULL DEFAULT '0', + `world_id` int(3) UNSIGNED NOT NULL, CONSTRAINT `guilds_pk` PRIMARY KEY (`id`), CONSTRAINT `guilds_name_unique` UNIQUE (`name`), CONSTRAINT `guilds_owner_unique` UNIQUE (`ownerid`), CONSTRAINT `guilds_ownerid_fk` FOREIGN KEY (`ownerid`) REFERENCES `players` (`id`) + ON DELETE CASCADE, + CONSTRAINT `guilds_worlds_fk` + FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -458,9 +492,13 @@ CREATE TABLE IF NOT EXISTS `houses` ( `size` int(11) NOT NULL DEFAULT '0', `guildid` int(11), `beds` int(11) NOT NULL DEFAULT '0', + `world_id` int(3) UNSIGNED NOT NULL, INDEX `owner` (`owner`), INDEX `town_id` (`town_id`), - CONSTRAINT `houses_pk` PRIMARY KEY (`id`) + CONSTRAINT `houses_pk` PRIMARY KEY (`id`, `world_id`), + CONSTRAINT `houses_worlds_fk` + FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) + ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- @@ -468,21 +506,27 @@ CREATE TABLE IF NOT EXISTS `houses` ( -- DELIMITER // CREATE TRIGGER `ondelete_players` BEFORE DELETE ON `players` FOR EACH ROW BEGIN - UPDATE `houses` SET `owner` = 0 WHERE `owner` = OLD.`id`; + UPDATE `houses` SET `owner` = 0 WHERE `owner` = OLD.`id` AND `world_id` = OLD.`world_id`; END // DELIMITER ; -- Table structure `house_lists` CREATE TABLE IF NOT EXISTS `house_lists` ( - `house_id` int NOT NULL, - `listid` int NOT NULL, - `version` bigint NOT NULL DEFAULT '0', - `list` text NOT NULL, - PRIMARY KEY (`house_id`, `listid`), - KEY `house_id_index` (`house_id`), - KEY `version` (`version`), - CONSTRAINT `houses_list_house_fk` FOREIGN KEY (`house_id`) REFERENCES `houses` (`id`) ON DELETE CASCADE + `house_id` int NOT NULL, + `listid` int NOT NULL, + `version` bigint NOT NULL DEFAULT '0', + `list` text NOT NULL, + `world_id` int(3) UNSIGNED NOT NULL, + PRIMARY KEY (`house_id`, `listid`), + KEY `house_id_index` (`house_id`), + KEY `version` (`version`), + CONSTRAINT `houses_list_house_fk` + FOREIGN KEY (`house_id`) REFERENCES `houses` (`id`) + ON DELETE CASCADE, + CONSTRAINT `house_lists_worlds_fk` + FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) + ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; -- Table structure `ip_bans` @@ -512,10 +556,14 @@ CREATE TABLE IF NOT EXISTS `market_history` ( `inserted` bigint(20) UNSIGNED NOT NULL, `state` tinyint(1) UNSIGNED NOT NULL, `tier` tinyint UNSIGNED NOT NULL DEFAULT '0', + `world_id` int(3) UNSIGNED NOT NULL, INDEX `player_id` (`player_id`,`sale`), CONSTRAINT `market_history_pk` PRIMARY KEY (`id`), CONSTRAINT `market_history_players_fk` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) + ON DELETE CASCADE, + CONSTRAINT `market_history_worlds_fk` + FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -530,19 +578,27 @@ CREATE TABLE IF NOT EXISTS `market_offers` ( `anonymous` tinyint(1) NOT NULL DEFAULT '0', `price` bigint(20) UNSIGNED NOT NULL DEFAULT '0', `tier` tinyint UNSIGNED NOT NULL DEFAULT '0', + `world_id` int(3) UNSIGNED NOT NULL, INDEX `sale` (`sale`,`itemtype`), INDEX `created` (`created`), INDEX `player_id` (`player_id`), CONSTRAINT `market_offers_pk` PRIMARY KEY (`id`), CONSTRAINT `market_offers_players_fk` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) + ON DELETE CASCADE, + CONSTRAINT `market_offers_worlds_fk` + FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- Table structure `players_online` CREATE TABLE IF NOT EXISTS `players_online` ( `player_id` int(11) NOT NULL, - CONSTRAINT `players_online_pk` PRIMARY KEY (`player_id`) + `world_id` int(3) UNSIGNED NOT NULL, + CONSTRAINT `players_online_pk` PRIMARY KEY (`player_id`), + CONSTRAINT `players_online_worlds_fk` + FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) + ON DELETE CASCADE ) ENGINE=MEMORY DEFAULT CHARSET=utf8; -- Table structure `player_charm` @@ -801,9 +857,13 @@ CREATE TABLE IF NOT EXISTS `store_history` ( CREATE TABLE IF NOT EXISTS `tile_store` ( `house_id` int(11) NOT NULL, `data` longblob NOT NULL, + `world_id` int(3) UNSIGNED NOT NULL, INDEX `house_id` (`house_id`), CONSTRAINT `tile_store_account_fk` FOREIGN KEY (`house_id`) REFERENCES `houses` (`id`) + ON DELETE CASCADE, + CONSTRAINT `tile_store_worlds_fk` + FOREIGN KEY (`world_id`) REFERENCES `worlds` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -843,10 +903,10 @@ INSERT INTO `accounts` -- Create player on GOD account -- Create sample characters INSERT INTO `players` -(`id`, `name`, `group_id`, `account_id`, `level`, `vocation`, `health`, `healthmax`, `experience`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `maglevel`, `mana`, `manamax`, `manaspent`, `town_id`, `conditions`, `cap`, `sex`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`) VALUES -(1, 'Rook Sample', 1, 1, 2, 0, 155, 155, 100, 113, 115, 95, 39, 129, 2, 60, 60, 5936, 1, '', 410, 1, 12, 155, 12, 155, 12, 155, 12, 93), -(2, 'Sorcerer Sample', 1, 1, 8, 1, 185, 185, 4200, 113, 115, 95, 39, 129, 0, 90, 90, 0, 8, '', 470, 1, 10, 0, 10, 0, 10, 0, 10, 0), -(3, 'Druid Sample', 1, 1, 8, 2, 185, 185, 4200, 113, 115, 95, 39, 129, 0, 90, 90, 0, 8, '', 470, 1, 10, 0, 10, 0, 10, 0, 10, 0), -(4, 'Paladin Sample', 1, 1, 8, 3, 185, 185, 4200, 113, 115, 95, 39, 129, 0, 90, 90, 0, 8, '', 470, 1, 10, 0, 10, 0, 10, 0, 10, 0), -(5, 'Knight Sample', 1, 1, 8, 4, 185, 185, 4200, 113, 115, 95, 39, 129, 0, 90, 90, 0, 8, '', 470, 1, 10, 0, 10, 0, 10, 0, 10, 0), -(6, 'GOD', 6, 1, 2, 0, 155, 155, 100, 113, 115, 95, 39, 75, 0, 60, 60, 0, 8, '', 410, 1, 10, 0, 10, 0, 10, 0, 10, 0); +(`id`, `name`, `group_id`, `account_id`, `level`, `vocation`, `health`, `healthmax`, `experience`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `maglevel`, `mana`, `manamax`, `manaspent`, `town_id`, `conditions`, `cap`, `sex`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `world_id`) VALUES +(1, 'Rook Sample', 1, 1, 2, 0, 155, 155, 100, 113, 115, 95, 39, 129, 2, 60, 60, 5936, 1, '', 410, 1, 12, 155, 12, 155, 12, 155, 12, 93, 1), +(2, 'Sorcerer Sample', 1, 1, 8, 1, 185, 185, 4200, 113, 115, 95, 39, 129, 0, 90, 90, 0, 8, '', 470, 1, 10, 0, 10, 0, 10, 0, 10, 0, 1), +(3, 'Druid Sample', 1, 1, 8, 2, 185, 185, 4200, 113, 115, 95, 39, 129, 0, 90, 90, 0, 8, '', 470, 1, 10, 0, 10, 0, 10, 0, 10, 0, 1), +(4, 'Paladin Sample', 1, 1, 8, 3, 185, 185, 4200, 113, 115, 95, 39, 129, 0, 90, 90, 0, 8, '', 470, 1, 10, 0, 10, 0, 10, 0, 10, 0, 1), +(5, 'Knight Sample', 1, 1, 8, 4, 185, 185, 4200, 113, 115, 95, 39, 129, 0, 90, 90, 0, 8, '', 470, 1, 10, 0, 10, 0, 10, 0, 10, 0, 1), +(6, 'GOD', 6, 1, 2, 0, 155, 155, 100, 113, 115, 95, 39, 75, 0, 60, 60, 0, 8, '', 410, 1, 10, 0, 10, 0, 10, 0, 10, 0, 1); diff --git a/src/account/account.cpp b/src/account/account.cpp index 2e5f58dd864..af35ce27892 100644 --- a/src/account/account.cpp +++ b/src/account/account.cpp @@ -249,7 +249,7 @@ void Account::updatePremiumTime() { } } -std::tuple, uint8_t> +std::tuple, uint8_t> Account::getAccountPlayers() const { auto valueToReturn = enumToValue(m_accLoaded ? AccountErrors_t::Ok : AccountErrors_t::NotInitialized); return { m_account.players, valueToReturn }; diff --git a/src/account/account.hpp b/src/account/account.hpp index d968ba8ddfb..6c8155c90a5 100644 --- a/src/account/account.hpp +++ b/src/account/account.hpp @@ -110,7 +110,7 @@ class Account { void updatePremiumTime(); - std::tuple, uint8_t> getAccountPlayers() const; + std::tuple, uint8_t> getAccountPlayers() const; // Old protocol compat void setProtocolCompat(bool toggle); diff --git a/src/account/account_info.hpp b/src/account/account_info.hpp index 698c3b96c1c..986618101c3 100644 --- a/src/account/account_info.hpp +++ b/src/account/account_info.hpp @@ -14,12 +14,21 @@ #include #endif +struct Character { + Character() = default; + Character(uint64_t deletion, uint16_t worldId) : + deletion(deletion), worldId(worldId) { } + + uint64_t deletion = 0; + uint16_t worldId = 0; +}; + struct AccountInfo { uint32_t id = 0; uint32_t premiumRemainingDays = 0; time_t premiumLastDay = 0; uint8_t accountType = 0; - phmap::flat_hash_map players; + phmap::flat_hash_map players; bool oldProtocol = false; time_t sessionExpires = 0; uint32_t premiumDaysPurchased = 0; diff --git a/src/account/account_repository_db.cpp b/src/account/account_repository_db.cpp index 81da30c08c5..49ec0f04a1d 100644 --- a/src/account/account_repository_db.cpp +++ b/src/account/account_repository_db.cpp @@ -18,6 +18,7 @@ #include "enums/account_type.hpp" #include "enums/account_coins.hpp" #include "account/account_info.hpp" +#include "game/game.hpp" AccountRepositoryDB::AccountRepositoryDB() : coinTypeToColumn({ { enumToValue(CoinType::Normal), "coins" }, { enumToValue(CoinType::Tournament), "tournament_coins" }, { enumToValue(CoinType::Transferable), "coins_transferable" } }) { } @@ -160,7 +161,7 @@ bool AccountRepositoryDB::registerCoinsTransaction( bool AccountRepositoryDB::loadAccountPlayers(AccountInfo &acc) { auto result = g_database().storeQuery( - fmt::format("SELECT `name`, `deletion` FROM `players` WHERE `account_id` = {} ORDER BY `name` ASC", acc.id) + fmt::format("SELECT `name`, `deletion`, `world_id` FROM `players` WHERE `account_id` = {} AND `world_id` = {} ORDER BY `name` ASC", acc.id, g_game().worlds()->getCurrentWorld()->id) ); if (!result) { @@ -169,11 +170,14 @@ bool AccountRepositoryDB::loadAccountPlayers(AccountInfo &acc) { } do { - if (result->getNumber("deletion") != 0) { + const auto deletion = result->getNumber("deletion"); + + if (deletion != 0) { continue; } - acc.players.try_emplace({ result->getString("name"), result->getNumber("deletion") }); + Character character = { deletion, result->getNumber("world_id") }; + acc.players.try_emplace(result->getString("name"), character); } while (result->next()); return true; diff --git a/src/canary_server.cpp b/src/canary_server.cpp index f71fc71ba9a..d3cc89fe247 100644 --- a/src/canary_server.cpp +++ b/src/canary_server.cpp @@ -75,8 +75,9 @@ int CanaryServer::run() { #endif rsa.start(); initializeDatabase(); + g_game().worlds()->load(); + loadThisWorld(); loadModules(); - setWorldType(); loadMaps(); logger.info("Initializing gamestate..."); @@ -134,7 +135,8 @@ int CanaryServer::run() { return EXIT_FAILURE; } - logger.info("{} {}", g_configManager().getString(SERVER_NAME, __FUNCTION__), "server online!"); + const auto curWorld = g_game().worlds()->getCurrentWorld(); + logger.info("World [{} - {} - {}] on port [{}] is online!", curWorld->id, curWorld->name, g_game().getWorldTypeNames().at(curWorld->type), curWorld->port); serviceManager.run(); @@ -142,24 +144,34 @@ int CanaryServer::run() { return EXIT_SUCCESS; } -void CanaryServer::setWorldType() { - const std::string worldType = asLowerCaseString(g_configManager().getString(WORLD_TYPE, __FUNCTION__)); - if (worldType == "pvp") { - g_game().setWorldType(WORLD_TYPE_PVP); - } else if (worldType == "no-pvp") { - g_game().setWorldType(WORLD_TYPE_NO_PVP); - } else if (worldType == "pvp-enforced") { - g_game().setWorldType(WORLD_TYPE_PVP_ENFORCED); - } else { +void CanaryServer::loadThisWorld() { + auto worldId = g_configManager().getNumber(WORLD_ID, __FUNCTION__); + auto world = g_game().worlds()->getWorldConfigsById(worldId); + if (!world) { + throw FailedToInitializeCanary(fmt::format("Unknown world with ID {}", worldId)); + } + + if (world->type == WORLD_TYPE_NONE) { + throw FailedToInitializeCanary( + fmt::format("Unknown world type: {}, valid world types are: no-pvp, pvp, retro-pvp, pvp-enforced and retro-pvp-enforced", world->type) + ); + } + + const auto location = Worlds::getWorldLocationByKey(world->location_str); + if (location == LOCATION_NONE) { throw FailedToInitializeCanary( - fmt::format( - "Unknown world type: {}, valid world types are: pvp, no-pvp and pvp-enforced", - g_configManager().getString(WORLD_TYPE, __FUNCTION__) - ) + fmt::format("Unknown world location: {}, valid world locations are: Europe, North America, South America and Oceania", world->location_str) ); } - logger.debug("World type set as {}", asUpperCaseString(worldType)); + if (g_configManager().getBoolean(TOGGLE_SERVER_IS_RETRO, __FUNCTION__)) { + g_logger().warn("[{}] - Config deprecated, you need to update your world type to 'retro-pvp' or 'retro-pvp-enforced'", __FUNCTION__); + world->type = world->type == WORLD_TYPE_PVP ? WORLD_TYPE_RETRO_PVP : WORLD_TYPE_RETRO_PVP_ENFORCED; + } + + g_game().worlds()->setCurrentWorld(world); + + logger.debug("World ID: {}, Name: {}, Type: {}, Location: {}, Port {}", world->id, world->name, g_game().getWorldTypeNames().at(world->type), world->location_str, world->port); } void CanaryServer::loadMaps() const { diff --git a/src/canary_server.hpp b/src/canary_server.hpp index 57c931cd133..f64c91b114e 100644 --- a/src/canary_server.hpp +++ b/src/canary_server.hpp @@ -62,8 +62,8 @@ class CanaryServer { void loadConfigLua(); void initializeDatabase(); + void loadThisWorld(); void loadModules(); - void setWorldType(); void loadMaps() const; void setupHousesRent(); void modulesLoadHelper(bool loaded, std::string moduleName); diff --git a/src/config/config_enums.hpp b/src/config/config_enums.hpp index 1dfa71ce7dd..03bcf315b48 100644 --- a/src/config/config_enums.hpp +++ b/src/config/config_enums.hpp @@ -121,7 +121,6 @@ enum ConfigKey_t : uint16_t { INVENTORY_GLOW, IP, KICK_AFTER_MINUTES, - LOCATION, LOGIN_PORT, LOGLEVEL, LOOTPOUCH_MAXLIMIT, @@ -323,6 +322,8 @@ enum ConfigKey_t : uint16_t { WHEEL_ATELIER_ROTATE_REGULAR_COST, WHEEL_POINTS_PER_LEVEL, WHITE_SKULL_TIME, + WORLD_ID, + WORLD_LOCATION, WORLD_TYPE, XP_DISPLAY_MODE }; diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index 9ce861665de..815f2aa0e60 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -65,7 +65,8 @@ bool ConfigManager::load() { loadIntConfig(L, PREMIUM_DEPOT_LIMIT, "premiumDepotLimit", 8000); loadIntConfig(L, SQL_PORT, "mysqlPort", 3306); loadIntConfig(L, STASH_ITEMS, "stashItemCount", 5000); - loadIntConfig(L, STATUS_PORT, "statusProtocolPort", 7171); + loadIntConfig(L, STATUS_PORT, "statusProtocolPort", 97172); + loadIntConfig(L, WORLD_ID, "worldId", 1); loadStringConfig(L, AUTH_TYPE, "authType", "password"); loadStringConfig(L, HOUSE_RENT_PERIOD, "houseRentPeriod", "never"); @@ -352,7 +353,6 @@ bool ConfigManager::load() { loadStringConfig(L, FORGE_FIENDISH_INTERVAL_TIME, "forgeFiendishIntervalTime", "1"); loadStringConfig(L, FORGE_FIENDISH_INTERVAL_TYPE, "forgeFiendishIntervalType", "hour"); loadStringConfig(L, GLOBAL_SERVER_SAVE_TIME, "globalServerSaveTime", "06:00"); - loadStringConfig(L, LOCATION, "location", ""); loadStringConfig(L, M_CONST, "memoryConst", "1<<16"); loadStringConfig(L, METRICS_PROMETHEUS_ADDRESS, "metricsPrometheusAddress", "localhost:9464"); loadStringConfig(L, OWNER_EMAIL, "ownerEmail", ""); @@ -363,6 +363,7 @@ bool ConfigManager::load() { loadStringConfig(L, STORE_IMAGES_URL, "coinImagesURL", ""); loadStringConfig(L, TIBIADROME_CONCOCTION_TICK_TYPE, "tibiadromeConcoctionTickType", "online"); loadStringConfig(L, URL, "url", ""); + loadStringConfig(L, WORLD_LOCATION, "worldLocation", "South America"); loadStringConfig(L, WORLD_TYPE, "worldType", "pvp"); loaded = true; diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index d65d10e4e6f..41c6f4c62fa 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -400,7 +400,7 @@ ReturnValue Combat::canDoCombat(std::shared_ptr attacker, std::shared_ return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE; } - if (g_game().getWorldType() == WORLD_TYPE_NO_PVP) { + if (g_game().worlds()->getCurrentWorld()->type == WORLD_TYPE_NO_PVP) { if (attacker->getPlayer() || (attackerMaster && attackerMaster->getPlayer())) { if (targetPlayer) { if (!isInPvpZone(attacker, target)) { @@ -849,7 +849,7 @@ void Combat::combatTileEffects(const CreatureVector &spectators, std::shared_ptr } if (casterPlayer) { - if (g_game().getWorldType() == WORLD_TYPE_NO_PVP || tile->hasFlag(TILESTATE_NOPVPZONE)) { + if (g_game().worlds()->getCurrentWorld()->type == WORLD_TYPE_NO_PVP || tile->hasFlag(TILESTATE_NOPVPZONE)) { if (itemId == ITEM_FIREFIELD_PVP_FULL) { itemId = ITEM_FIREFIELD_NOPVP; } else if (itemId == ITEM_POISONFIELD_PVP) { @@ -2103,7 +2103,7 @@ void AreaCombat::setupExtArea(const std::list &list, uint32_t rows) { void MagicField::onStepInField(const std::shared_ptr &creature) { // remove magic walls/wild growth - if ((!isBlocking() && g_game().getWorldType() == WORLD_TYPE_NO_PVP && id == ITEM_MAGICWALL_SAFE) || id == ITEM_WILDGROWTH_SAFE) { + if ((!isBlocking() && g_game().worlds()->getCurrentWorld()->type == WORLD_TYPE_NO_PVP && id == ITEM_MAGICWALL_SAFE) || id == ITEM_WILDGROWTH_SAFE) { if (!creature->isInGhostMode()) { g_game().internalRemoveItem(static_self_cast(), 1); } @@ -2118,7 +2118,7 @@ void MagicField::onStepInField(const std::shared_ptr &creature) { if (ownerId) { bool harmfulField = true; auto itemTile = getTile(); - if (g_game().getWorldType() == WORLD_TYPE_NO_PVP || (itemTile && itemTile->hasFlag(TILESTATE_NOPVPZONE))) { + if (g_game().worlds()->getCurrentWorld()->type == WORLD_TYPE_NO_PVP || (itemTile && itemTile->hasFlag(TILESTATE_NOPVPZONE))) { auto ownerPlayer = g_game().getPlayerByGUID(ownerId); if (ownerPlayer) { harmfulField = false; diff --git a/src/creatures/combat/spells.cpp b/src/creatures/combat/spells.cpp index 105bf734c23..0f3c98aabf9 100644 --- a/src/creatures/combat/spells.cpp +++ b/src/creatures/combat/spells.cpp @@ -883,7 +883,7 @@ bool InstantSpell::playerCastInstant(std::shared_ptr player, std::string return false; } - auto worldType = g_game().getWorldType(); + auto worldType = g_game().worlds()->getCurrentWorld()->type; if (pzLocked && (worldType == WORLD_TYPE_PVP || worldType == WORLD_TYPE_PVP_ENFORCED)) { player->addInFightTicks(true); player->updateLastAggressiveAction(); @@ -1056,7 +1056,7 @@ bool RuneSpell::executeUse(std::shared_ptr player, std::shared_ptr player->updateSupplyTracker(item); } - auto worldType = g_game().getWorldType(); + auto worldType = g_game().worlds()->getCurrentWorld()->type; if (pzLocked && (worldType == WORLD_TYPE_PVP || worldType == WORLD_TYPE_PVP_ENFORCED)) { player->addInFightTicks(true); player->updateLastAggressiveAction(); diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 8c49226b621..5504c0464d4 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -980,7 +980,7 @@ bool Player::canWalkthrough(std::shared_ptr creature) { if (player) { std::shared_ptr playerTile = player->getTile(); - if (!playerTile || (!playerTile->hasFlag(TILESTATE_NOPVPZONE) && !playerTile->hasFlag(TILESTATE_PROTECTIONZONE) && player->getLevel() > static_cast(g_configManager().getNumber(PROTECTION_LEVEL, __FUNCTION__)) && g_game().getWorldType() != WORLD_TYPE_NO_PVP)) { + if (!playerTile || (!playerTile->hasFlag(TILESTATE_NOPVPZONE) && !playerTile->hasFlag(TILESTATE_PROTECTIONZONE) && player->getLevel() > static_cast(g_configManager().getNumber(PROTECTION_LEVEL, __FUNCTION__)) && g_game().worlds()->getCurrentWorld()->type != WORLD_TYPE_NO_PVP)) { return false; } @@ -1028,7 +1028,7 @@ bool Player::canWalkthroughEx(std::shared_ptr creature) { std::shared_ptr npc = creature->getNpc(); if (player) { std::shared_ptr playerTile = player->getTile(); - return playerTile && (playerTile->hasFlag(TILESTATE_NOPVPZONE) || playerTile->hasFlag(TILESTATE_PROTECTIONZONE) || player->getLevel() <= static_cast(g_configManager().getNumber(PROTECTION_LEVEL, __FUNCTION__)) || g_game().getWorldType() == WORLD_TYPE_NO_PVP); + return playerTile && (playerTile->hasFlag(TILESTATE_NOPVPZONE) || playerTile->hasFlag(TILESTATE_PROTECTIONZONE) || player->getLevel() <= static_cast(g_configManager().getNumber(PROTECTION_LEVEL, __FUNCTION__)) || g_game().worlds()->getCurrentWorld()->type == WORLD_TYPE_NO_PVP); } else if (npc) { std::shared_ptr tile = npc->getTile(); std::shared_ptr houseTile = std::dynamic_pointer_cast(tile); @@ -1832,7 +1832,7 @@ void Player::onAttackedCreatureChangeZone(ZoneType_t zone) { onAttackedCreatureDisappear(false); } } - } else if (zone == ZONE_NORMAL && g_game().getWorldType() == WORLD_TYPE_NO_PVP) { + } else if (zone == ZONE_NORMAL && g_game().worlds()->getCurrentWorld()->type == WORLD_TYPE_NO_PVP) { // attackedCreature can leave a pvp zone if not pzlocked if (attackedCreature->getPlayer()) { setAttackedCreature(nullptr); @@ -2225,7 +2225,7 @@ void Player::onThink(uint32_t interval) { } } - if (g_game().getWorldType() != WORLD_TYPE_PVP_ENFORCED) { + if (g_game().worlds()->getCurrentWorld()->type != WORLD_TYPE_PVP_ENFORCED) { checkSkullTicks(interval / 1000); } @@ -4619,7 +4619,7 @@ void Player::onCombatRemoveCondition(std::shared_ptr condition) { // Creature::onCombatRemoveCondition(condition); if (condition->getId() > 0) { // Means the condition is from an item, id == slot - if (g_game().getWorldType() == WORLD_TYPE_PVP_ENFORCED) { + if (g_game().worlds()->getCurrentWorld()->type == WORLD_TYPE_PVP_ENFORCED) { std::shared_ptr item = getInventoryItem(static_cast(condition->getId())); if (item) { // 25% chance to destroy the item @@ -4661,7 +4661,7 @@ void Player::onAttackedCreature(std::shared_ptr target) { auto targetPlayer = target->getPlayer(); if (targetPlayer && !isPartner(targetPlayer) && !isGuildMate(targetPlayer)) { - if (!pzLocked && g_game().getWorldType() == WORLD_TYPE_PVP_ENFORCED) { + if (!pzLocked && g_game().worlds()->getCurrentWorld()->type == WORLD_TYPE_PVP_ENFORCED) { pzLocked = true; sendIcons(); } @@ -5124,7 +5124,7 @@ Skulls_t Player::getSkull() const { } Skulls_t Player::getSkullClient(std::shared_ptr creature) { - if (!creature || g_game().getWorldType() != WORLD_TYPE_PVP) { + if (!creature || g_game().worlds()->getCurrentWorld()->type != WORLD_TYPE_PVP) { return SKULL_NONE; } @@ -5192,7 +5192,7 @@ void Player::clearAttacked() { } void Player::addUnjustifiedDead(std::shared_ptr attacked) { - if (hasFlag(PlayerFlags_t::NotGainInFight) || attacked == getPlayer() || g_game().getWorldType() == WORLD_TYPE_PVP_ENFORCED) { + if (hasFlag(PlayerFlags_t::NotGainInFight) || attacked == getPlayer() || g_game().worlds()->getCurrentWorld()->type == WORLD_TYPE_PVP_ENFORCED) { return; } diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 124ad59ed6b..9397ba28925 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -148,6 +148,10 @@ class Player final : public Creature, public Cylinder, public Bankable { return online; } + uint8_t getWorldId() { + return worldId; + } + static uint32_t getFirstID(); static uint32_t getLastID(); @@ -1690,9 +1694,9 @@ class Player final : public Creature, public Cylinder, public Bankable { client->sendHighscoresNoData(); } } - void sendHighscores(const std::vector &characters, uint8_t categoryId, uint32_t vocationId, uint16_t page, uint16_t pages, uint32_t updateTimer) { + void sendHighscores(const std::string &selectedWorld, const std::vector &characters, uint8_t categoryId, uint32_t vocationId, uint16_t page, uint16_t pages, uint32_t updateTimer) { if (client) { - client->sendHighscores(characters, categoryId, vocationId, page, pages, updateTimer); + client->sendHighscores(selectedWorld, characters, categoryId, vocationId, page, pages, updateTimer); } } void addAsyncOngoingTask(uint64_t flags) { @@ -2867,6 +2871,7 @@ class Player final : public Creature, public Cylinder, public Bankable { uint32_t coinTransferableBalance = 0; uint16_t xpBoostTime = 0; uint8_t randomMount = 0; + uint8_t worldId = 1; uint16_t lastStatsTrainingTime = 0; uint16_t staminaMinutes = 2520; diff --git a/src/database/databasemanager.cpp b/src/database/databasemanager.cpp index dfbb7d9a64a..c1e3b8f85f2 100644 --- a/src/database/databasemanager.cpp +++ b/src/database/databasemanager.cpp @@ -13,6 +13,7 @@ #include "database/databasemanager.hpp" #include "lua/functions/core/libs/core_libs_functions.hpp" #include "lua/scripts/luascript.hpp" +#include "game/game.hpp" bool DatabaseManager::optimizeTables() { Database &db = Database::getInstance(); @@ -61,8 +62,8 @@ bool DatabaseManager::isDatabaseSetup() { int32_t DatabaseManager::getDatabaseVersion() { if (!tableExists("server_config")) { Database &db = Database::getInstance(); - db.executeQuery("CREATE TABLE `server_config` (`config` VARCHAR(50) NOT NULL, `value` VARCHAR(256) NOT NULL DEFAULT '', UNIQUE(`config`)) ENGINE = InnoDB"); - db.executeQuery("INSERT INTO `server_config` VALUES ('db_version', 0)"); + db.executeQuery("CREATE TABLE `server_config` (`config` VARCHAR(50) NOT NULL, `value` VARCHAR(256) NOT NULL DEFAULT '', `world_id` INT(11) NOT NULL DEFAULT '1', UNIQUE(`config`, `world_id`)) ENGINE = InnoDB"); + db.executeQuery(fmt::format("INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', 0)")); return 0; } @@ -85,9 +86,8 @@ void DatabaseManager::updateDatabase() { int32_t version = getDatabaseVersion(); do { - std::ostringstream ss; - ss << g_configManager().getString(DATA_DIRECTORY, __FUNCTION__) + "/migrations/" << version << ".lua"; - if (luaL_dofile(L, ss.str().c_str()) != 0) { + std::string file = fmt::format("{}/migrations/{}.lua", g_configManager().getString(DATA_DIRECTORY, __FUNCTION__), version); + if (luaL_dofile(L, file.c_str()) != 0) { g_logger().error("DatabaseManager::updateDatabase - Version: {}" "] {}", version, lua_tostring(L, -1)); @@ -121,10 +121,12 @@ void DatabaseManager::updateDatabase() { bool DatabaseManager::getDatabaseConfig(const std::string &config, int32_t &value) { Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `value` FROM `server_config` WHERE `config` = " << db.escapeString(config); + std::string query = fmt::format("SELECT `value` FROM `server_config` WHERE `world_id` = {} AND `config` = {}", g_game().worlds()->getCurrentWorld()->id, db.escapeString(config)); + if (config == "db_version") { + query = fmt::format("SELECT `value` FROM `server_config` WHERE `config` = {}", db.escapeString(config)); + } - DBResult_ptr result = db.storeQuery(query.str()); + const auto result = db.storeQuery(query); if (!result) { return false; } @@ -135,15 +137,18 @@ bool DatabaseManager::getDatabaseConfig(const std::string &config, int32_t &valu void DatabaseManager::registerDatabaseConfig(const std::string &config, int32_t value) { Database &db = Database::getInstance(); - std::ostringstream query; + std::string query; int32_t tmp; if (!getDatabaseConfig(config, tmp)) { - query << "INSERT INTO `server_config` VALUES (" << db.escapeString(config) << ", '" << value << "')"; + query = fmt::format("INSERT INTO `server_config` (`config`, `value`, `world_id`) VALUES ({}, {}, {})", db.escapeString(config), value, g_game().worlds()->getCurrentWorld()->id); } else { - query << "UPDATE `server_config` SET `value` = '" << value << "' WHERE `config` = " << db.escapeString(config); + query = fmt::format("UPDATE `server_config` SET `value` = {} WHERE `world_id` = {} AND `config` = {}", value, g_game().worlds()->getCurrentWorld()->id, db.escapeString(config)); + if (strcasecmp(config.c_str(), "db_version")) { + query = fmt::format("UPDATE `server_config` SET `value` = {} WHERE `config` = {}", value, db.escapeString(config)); + } } - db.executeQuery(query.str()); + db.executeQuery(query); } diff --git a/src/game/CMakeLists.txt b/src/game/CMakeLists.txt index dc7758f996d..c1b9bff0a15 100644 --- a/src/game/CMakeLists.txt +++ b/src/game/CMakeLists.txt @@ -8,5 +8,6 @@ target_sources(${PROJECT_NAME}_lib PRIVATE scheduling/dispatcher.cpp scheduling/task.cpp scheduling/save_manager.cpp + worlds/gameworlds.cpp zones/zone.cpp ) diff --git a/src/game/game.cpp b/src/game/game.cpp index 3a579ec2fdf..6937c2ce759 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -19,6 +19,7 @@ #include "lua/callbacks/event_callback.hpp" #include "lua/callbacks/events_callbacks.hpp" #include "creatures/players/highscore_category.hpp" +#include "game/worlds/gameworlds.hpp" #include "game/zones/zone.hpp" #include "lua/global/globalevent.hpp" #include "io/iologindata.hpp" @@ -195,6 +196,16 @@ Game::Game() { offlineTrainingWindow.defaultEnterButton = 0; offlineTrainingWindow.priority = true; + // Create instance of worlds + m_worlds = std::make_unique(); + m_worldTypesNames = { + { static_cast(WorldType_t::WORLD_TYPE_PVP), "Open PvP" }, + { static_cast(WorldType_t::WORLD_TYPE_NO_PVP), "Optional PvP" }, + { static_cast(WorldType_t::WORLD_TYPE_PVP_ENFORCED), "Hardcore PvP" }, + { static_cast(WorldType_t::WORLD_TYPE_RETRO_PVP), "Retro Open PvP" }, + { static_cast(WorldType_t::WORLD_TYPE_RETRO_PVP_ENFORCED), "Retro Hardcore PvP" }, + }; + // Create instance of IOWheel to Game class m_IOWheel = std::make_unique(); @@ -396,6 +407,19 @@ Game::Game() { Game::~Game() = default; +// Worlds interface +std::unique_ptr &Game::worlds() { + return m_worlds; +} + +const std::unique_ptr &Game::worlds() const { + return m_worlds; +} + +const std::unordered_map &Game::getWorldTypeNames() const { + return m_worldTypesNames; +} + void Game::resetMonsters() const { for (const auto &[monsterId, monster] : getMonsters()) { monster->clearTargetList(); @@ -413,11 +437,18 @@ void Game::resetNpcs() const { void Game::loadBoostedCreature() { auto &db = Database::getInstance(); - const auto result = db.storeQuery("SELECT * FROM `boosted_creature`"); + + const std::string selectQuery = "SELECT * FROM `boosted_creature`"; + + auto result = db.storeQuery(selectQuery); if (!result) { - g_logger().warn("[Game::loadBoostedCreature] - " - "Failed to detect boosted creature database. (CODE 01)"); - return; + db.storeQuery("INSERT INTO `boosted_creature` (`boostname`, `date`, `raceid`) VALUES ('default', 0, 0)"); + + result = db.storeQuery(selectQuery); + if (!result) { + g_logger().warn("{} - Failed to detect boosted creature database. (CODE 01)", __FUNCTION__); + return; + } } const uint16_t date = result->getNumber("date"); @@ -452,45 +483,38 @@ void Game::loadBoostedCreature() { } if (selectedMonster.raceId == 0) { - g_logger().warn("[Game::loadBoostedCreature] - " - "It was not possible to generate a new boosted creature->"); + g_logger().warn("{} - It was not possible to generate a new boosted creature->", __FUNCTION__); return; } const auto monsterType = g_monsters().getMonsterType(selectedMonster.name); if (!monsterType) { - g_logger().warn("[Game::loadBoostedCreature] - " - "It was not possible to generate a new boosted creature-> Monster '{}' not found.", - selectedMonster.name); + g_logger().warn("{} - It was not possible to generate a new boosted creature-> Monster '{}' not found.", __FUNCTION__, selectedMonster.name); return; } setBoostedName(selectedMonster.name); - auto query = std::string("UPDATE `boosted_creature` SET ") - + "`date` = '" + std::to_string(ltm->tm_mday) + "'," - + "`boostname` = " + db.escapeString(selectedMonster.name) + "," - + "`looktype` = " + std::to_string(monsterType->info.outfit.lookType) + "," - + "`lookfeet` = " + std::to_string(monsterType->info.outfit.lookFeet) + "," - + "`looklegs` = " + std::to_string(monsterType->info.outfit.lookLegs) + "," - + "`lookhead` = " + std::to_string(monsterType->info.outfit.lookHead) + "," - + "`lookbody` = " + std::to_string(monsterType->info.outfit.lookBody) + "," - + "`lookaddons` = " + std::to_string(monsterType->info.outfit.lookAddons) + "," - + "`lookmount` = " + std::to_string(monsterType->info.outfit.lookMount) + "," - + "`raceid` = '" + std::to_string(selectedMonster.raceId) + "'"; + auto query = fmt::format( + "UPDATE `boosted_creature` SET `date` = '{}', `boostname` = {}, `looktype` = {}, `lookfeet` = {}, `looklegs` = {}, `lookhead` = {}, `lookbody` = {}, `lookaddons` = {}, `lookmount` = {}, `raceid` = {}", + std::to_string(ltm->tm_mday), db.escapeString(selectedMonster.name), std::to_string(monsterType->info.outfit.lookType), std::to_string(monsterType->info.outfit.lookFeet), + std::to_string(monsterType->info.outfit.lookLegs), std::to_string(monsterType->info.outfit.lookHead), std::to_string(monsterType->info.outfit.lookBody), + std::to_string(monsterType->info.outfit.lookAddons), std::to_string(monsterType->info.outfit.lookMount), std::to_string(selectedMonster.raceId) + ); if (!db.executeQuery(query)) { - g_logger().warn("[Game::loadBoostedCreature] - " - "Failed to detect boosted creature database. (CODE 02)"); + g_logger().warn("{} - Failed to detect boosted creature database. (CODE 02)", __FUNCTION__); } } void Game::start(ServiceManager* manager) { + const std::shared_ptr ¤tWorld = worlds()->getCurrentWorld(); + // Game client protocols - manager->add(static_cast(g_configManager().getNumber(GAME_PORT, __FUNCTION__))); + manager->add(currentWorld->port); manager->add(static_cast(g_configManager().getNumber(LOGIN_PORT, __FUNCTION__))); // OT protocols - manager->add(static_cast(g_configManager().getNumber(STATUS_PORT, __FUNCTION__))); + manager->add(currentWorld->port_status); serviceManager = manager; @@ -536,10 +560,6 @@ GameState_t Game::getGameState() const { return gameState; } -void Game::setWorldType(WorldType_t type) { - worldType = type; -} - void Game::setGameState(GameState_t newState) { if (gameState == GAME_STATE_SHUTDOWN) { return; // this cannot be stopped @@ -8085,7 +8105,7 @@ void Game::updateCreatureWalkthrough(std::shared_ptr creature) { } void Game::updateCreatureSkull(std::shared_ptr creature) { - if (getWorldType() != WORLD_TYPE_PVP) { + if (worlds()->getCurrentWorld()->type != WORLD_TYPE_PVP) { return; } @@ -8135,35 +8155,35 @@ void Game::updateCreatureType(std::shared_ptr creature) { void Game::loadMotdNum() { Database &db = Database::getInstance(); + const auto worldId = worlds()->getCurrentWorld()->id; - DBResult_ptr result = db.storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_num'"); + auto result = db.storeQuery(fmt::format("SELECT `value` FROM `server_config` WHERE `config` = 'motd_num' AND `world_id` = {}", worldId)); if (result) { motdNum = result->getNumber("value"); } else { - db.executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_num', '0')"); + db.executeQuery(fmt::format("INSERT INTO `server_config` (`world_id`, `config`, `value`) VALUES ({}, 'motd_num', '0')", worldId)); } - result = db.storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_hash'"); + result = db.storeQuery(fmt::format("SELECT `value` FROM `server_config` WHERE `config` = 'motd_hash' AND `world_id` = {}", worldId)); if (result) { motdHash = result->getString("value"); if (motdHash != transformToSHA1(g_configManager().getString(SERVER_MOTD, __FUNCTION__))) { ++motdNum; } } else { - db.executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_hash', '')"); + db.executeQuery(fmt::format("INSERT INTO `server_config` (`world_id`, `config`, `value`) VALUES ({}, 'motd_hash', '')", worldId)); } } void Game::saveMotdNum() const { Database &db = Database::getInstance(); + const auto worldId = worlds()->getCurrentWorld()->id; - std::ostringstream query; - query << "UPDATE `server_config` SET `value` = '" << motdNum << "' WHERE `config` = 'motd_num'"; - db.executeQuery(query.str()); + std::string query = fmt::format("UPDATE `server_config` SET `value` = {} WHERE `config` = 'motd_num' AND `world_id` = {}", motdNum, worldId); + db.executeQuery(query); - query.str(std::string()); - query << "UPDATE `server_config` SET `value` = '" << transformToSHA1(g_configManager().getString(SERVER_MOTD, __FUNCTION__)) << "' WHERE `config` = 'motd_hash'"; - db.executeQuery(query.str()); + query = fmt::format("UPDATE `server_config` SET `value` = '{}' WHERE `config` = 'motd_hash' AND `world_id` = {}", transformToSHA1(g_configManager().getString(SERVER_MOTD, __FUNCTION__)), worldId); + db.executeQuery(query); } void Game::checkPlayersRecord() { @@ -8182,19 +8202,19 @@ void Game::checkPlayersRecord() { void Game::updatePlayersRecord() const { Database &db = Database::getInstance(); - std::ostringstream query; - query << "UPDATE `server_config` SET `value` = '" << playersRecord << "' WHERE `config` = 'players_record'"; - db.executeQuery(query.str()); + std::string query = fmt::format("UPDATE `server_config` SET `value` = {} WHERE `config` = 'players_record' AND `world_id` = {}", playersRecord, worlds()->getCurrentWorld()->id); + db.executeQuery(query); } void Game::loadPlayersRecord() { Database &db = Database::getInstance(); + const auto worldId = worlds()->getCurrentWorld()->id; - DBResult_ptr result = db.storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'players_record'"); + const auto result = db.storeQuery(fmt::format("SELECT `value` FROM `server_config` WHERE `config` = 'players_record' AND `world_id` = {}", worldId)); if (result) { playersRecord = result->getNumber("value"); } else { - db.executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('players_record', '0')"); + db.executeQuery(fmt::format("INSERT INTO `server_config` (`world_id`, `config`, `value`) VALUES ({}, 'players_record', '0')", worldId)); } } @@ -8422,39 +8442,30 @@ void Game::playerCyclopediaCharacterInfo(std::shared_ptr player, uint32_ } } -std::string Game::generateHighscoreQueryForEntries(const std::string &categoryName, uint32_t page, uint8_t entriesPerPage, uint32_t vocation) { - std::ostringstream query; +std::string Game::generateHighscoreQueryForEntries(const std::string &categoryName, const std::string &worldName, uint32_t page, uint8_t entriesPerPage, uint32_t vocation) { + Database &db = Database::getInstance(); uint32_t startPage = (static_cast(page - 1) * static_cast(entriesPerPage)); uint32_t endPage = startPage + static_cast(entriesPerPage); - query << "SELECT *, @row AS `entries`, " << page << " AS `page` FROM (SELECT *, (@row := @row + 1) AS `rn` FROM (SELECT `id`, `name`, `level`, `vocation`, `" - << categoryName << "` AS `points`, @curRank := IF(@prevRank = `" << categoryName << "`, @curRank, IF(@prevRank := `" << categoryName - << "`, @curRank + 1, @curRank + 1)) AS `rank` FROM `players` `p`, (SELECT @curRank := 0, @prevRank := NULL, @row := 0) `r` WHERE `group_id` < " - << static_cast(GROUP_TYPE_GAMEMASTER) << " ORDER BY `" << categoryName << "` DESC) `t`"; + const auto world = !worldName.empty() ? fmt::format("AND `w`.`name` = {}", db.escapeString(worldName)) : ""; - if (vocation != 0xFFFFFFFF) { - query << generateVocationConditionHighscore(vocation); - } - query << ") `T` WHERE `rn` > " << startPage << " AND `rn` <= " << endPage; + std::string query = fmt::format( + "SELECT *, @row AS `entries`, {0} AS `page` FROM (SELECT *, (@row := @row + 1) AS `rn` FROM (SELECT `p`.`id`, `p`.`name`, `p`.`level`, `p`.`vocation`, `w`.`name` AS `worldName`, `p`.`{1}` AS `points`, @curRank := IF(@prevRank = `{1}`, @curRank, IF(@prevRank := `{1}`, @curRank + 1, @curRank + 1)) AS `rank` FROM `players` `p` INNER JOIN `worlds` `w` ON `p`.`world_id` = `w`.`id`, (SELECT @curRank := 0, @prevRank := NULL, @row := 0) `r` WHERE `group_id` < {2} {6} ORDER BY `{1}` DESC) `t`{3}) `T` WHERE `rn` > {4} AND `rn` <= {5}", + page, categoryName, static_cast(GROUP_TYPE_GAMEMASTER), vocation != 0xFFFFFFFF ? generateVocationConditionHighscore(vocation) : "", startPage, endPage, world + ); - return query.str(); + return query; } std::string Game::generateHighscoreQueryForOurRank(const std::string &categoryName, uint8_t entriesPerPage, uint32_t playerGUID, uint32_t vocation) { - std::ostringstream query; std::string entriesStr = std::to_string(entriesPerPage); - query << "SELECT *, @row AS `entries`, (@ourRow DIV " << entriesStr << ") + 1 AS `page` FROM (SELECT *, (@row := @row + 1) AS `rn`, @ourRow := IF(`id` = " - << playerGUID << ", @row - 1, @ourRow) AS `rw` FROM (SELECT `id`, `name`, `level`, `vocation`, `" << categoryName << "` AS `points`, @curRank := IF(@prevRank = `" - << categoryName << "`, @curRank, IF(@prevRank := `" << categoryName << "`, @curRank + 1, @curRank + 1)) AS `rank` FROM `players` `p`, (SELECT @curRank := 0, @prevRank := NULL, @row := 0, @ourRow := 0) `r` WHERE `group_id` < " - << static_cast(GROUP_TYPE_GAMEMASTER) << " ORDER BY `" << categoryName << "` DESC) `t`"; - - if (vocation != 0xFFFFFFFF) { - query << generateVocationConditionHighscore(vocation); - } - query << ") `T` WHERE `rn` > ((@ourRow DIV " << entriesStr << ") * " << entriesStr << ") AND `rn` <= (((@ourRow DIV " << entriesStr << ") * " << entriesStr << ") + " << entriesStr << ")"; + std::string query = fmt::format( + "SELECT *, @row AS `entries`, (@ourRow DIV {0}) + 1 AS `page` FROM (SELECT *, (@row := @row + 1) AS `rn`, @ourRow := IF(`id` = {1}, @row - 1, @ourRow) AS `rw` FROM (SELECT `p`.`id`, `p`.`name`, `p`.`level`, `p`.`vocation`, `w`.`name` AS `worldName`, `{2}` AS `points`, @curRank := IF(@prevRank = `{2}`, @curRank, IF(@prevRank := `{2}`, @curRank + 1, @curRank + 1)) AS `rank` FROM `players` `p` INNER JOIN `worlds` `w` ON `p`.`world_id` = `w`.`id`, (SELECT @curRank := 0, @prevRank := NULL, @row := 0, @ourRow := 0) `r` WHERE `group_id` < {3} ORDER BY `{2}` DESC) `t`{4}) `T` WHERE `rn` > ((@ourRow DIV {0}) * {0}) AND `rn` <= (((@ourRow DIV {0}) * {0}) + {0})", + entriesStr, playerGUID, categoryName, static_cast(GROUP_TYPE_GAMEMASTER), vocation != 0xFFFFFFFF ? generateVocationConditionHighscore(vocation) : "" + ); - return query.str(); + return query; } std::string Game::generateVocationConditionHighscore(uint32_t vocation) { @@ -8466,10 +8477,10 @@ std::string Game::generateVocationConditionHighscore(uint32_t vocation) { const auto &voc = it.second; if (voc->getFromVocation() == vocation) { if (firstVocation) { - queryPart << " WHERE `vocation` = " << voc->getId(); + queryPart << " WHERE `p`.`vocation` = " << voc->getId(); firstVocation = false; } else { - queryPart << " OR `vocation` = " << voc->getId(); + queryPart << " OR `p`.`vocation` = " << voc->getId(); } } } @@ -8477,7 +8488,7 @@ std::string Game::generateVocationConditionHighscore(uint32_t vocation) { return queryPart.str(); } -void Game::processHighscoreResults(DBResult_ptr result, uint32_t playerID, uint8_t category, uint32_t vocation, uint8_t entriesPerPage) { +void Game::processHighscoreResults(DBResult_ptr result, const std::string &worldName, uint32_t playerID, uint8_t category, uint32_t vocation, uint8_t entriesPerPage) { std::shared_ptr player = g_game().getPlayerByID(playerID); if (!player) { return; @@ -8495,9 +8506,7 @@ void Game::processHighscoreResults(DBResult_ptr result, uint32_t playerID, uint8 pages += entriesPerPage - 1; pages /= entriesPerPage; - std::ostringstream cacheKeyStream; - cacheKeyStream << "Highscore_" << static_cast(category) << "_" << static_cast(vocation) << "_" << static_cast(entriesPerPage) << "_" << page; - std::string cacheKey = cacheKeyStream.str(); + std::string cacheKey = fmt::format("Highscore_{}_{}_{}_{}_{}", worldName.empty() ? "All" : worldName, static_cast(category), static_cast(vocation), static_cast(entriesPerPage), page); auto it = highscoreCache.find(cacheKey); auto now = std::chrono::system_clock::now(); @@ -8507,7 +8516,7 @@ void Game::processHighscoreResults(DBResult_ptr result, uint32_t playerID, uint8 auto durationSinceEpoch = cachedTime.time_since_epoch(); auto secondsSinceEpoch = std::chrono::duration_cast(durationSinceEpoch).count(); auto updateTimer = static_cast(secondsSinceEpoch); - player->sendHighscores(cacheEntry.characters, category, vocation, cacheEntry.page, static_cast(cacheEntry.entriesPerPage), updateTimer); + player->sendHighscores(worldName, cacheEntry.characters, category, vocation, cacheEntry.page, static_cast(cacheEntry.entriesPerPage), updateTimer); } else { std::vector characters; characters.reserve(result->countResults()); @@ -8516,11 +8525,11 @@ void Game::processHighscoreResults(DBResult_ptr result, uint32_t playerID, uint8 const auto &voc = g_vocations().getVocation(result->getNumber("vocation")); uint8_t characterVocation = voc ? voc->getClientId() : 0; std::string loyaltyTitle = ""; // todo get loyalty title from player - characters.emplace_back(std::move(result->getString("name")), result->getNumber("points"), result->getNumber("id"), result->getNumber("rank"), result->getNumber("level"), characterVocation, loyaltyTitle); + characters.emplace_back(std::move(result->getString("name")), result->getNumber("points"), result->getNumber("id"), result->getNumber("rank"), result->getNumber("level"), characterVocation, std::move(result->getString("worldName")), loyaltyTitle); } while (result->next()); } - player->sendHighscores(characters, category, vocation, page, static_cast(pages), getTimeNow()); + player->sendHighscores(worldName, characters, category, vocation, page, static_cast(pages), getTimeNow()); highscoreCache[cacheKey] = { characters, page, pages, now }; } } @@ -8530,10 +8539,8 @@ void Game::cacheQueryHighscore(const std::string &key, const std::string &query, queryCache[key] = queryEntry; } -std::string Game::generateHighscoreOrGetCachedQueryForEntries(const std::string &categoryName, uint32_t page, uint8_t entriesPerPage, uint32_t vocation) { - std::ostringstream cacheKeyStream; - cacheKeyStream << "Entries_" << categoryName << "_" << page << "_" << static_cast(entriesPerPage) << "_" << vocation; - std::string cacheKey = cacheKeyStream.str(); +std::string Game::generateHighscoreOrGetCachedQueryForEntries(const std::string &categoryName, const std::string &worldName, uint32_t page, uint8_t entriesPerPage, uint32_t vocation) { + std::string cacheKey = fmt::format("Entries_{}_{}_{}_{}_{}", worldName.empty() ? "All" : worldName, categoryName, page, static_cast(entriesPerPage), vocation); if (queryCache.find(cacheKey) != queryCache.end()) { const QueryHighscoreCacheEntry &cachedEntry = queryCache[cacheKey]; @@ -8542,7 +8549,7 @@ std::string Game::generateHighscoreOrGetCachedQueryForEntries(const std::string } } - std::string newQuery = generateHighscoreQueryForEntries(categoryName, page, entriesPerPage, vocation); + std::string newQuery = generateHighscoreQueryForEntries(categoryName, worldName, page, entriesPerPage, vocation); cacheQueryHighscore(cacheKey, newQuery, page, entriesPerPage); return newQuery; @@ -8566,7 +8573,7 @@ std::string Game::generateHighscoreOrGetCachedQueryForOurRank(const std::string return newQuery; } -void Game::playerHighscores(std::shared_ptr player, HighscoreType_t type, uint8_t category, uint32_t vocation, const std::string &, uint16_t page, uint8_t entriesPerPage) { +void Game::playerHighscores(std::shared_ptr player, HighscoreType_t type, uint8_t category, uint32_t vocation, const std::string &worldName, uint16_t page, uint8_t entriesPerPage) { if (player->hasAsyncOngoingTask(PlayerAsyncTask_Highscore)) { return; } @@ -8575,14 +8582,14 @@ void Game::playerHighscores(std::shared_ptr player, HighscoreType_t type std::string query; if (type == HIGHSCORE_GETENTRIES) { - query = generateHighscoreOrGetCachedQueryForEntries(categoryName, page, entriesPerPage, vocation); + query = generateHighscoreOrGetCachedQueryForEntries(categoryName, worldName, page, entriesPerPage, vocation); } else if (type == HIGHSCORE_OURRANK) { query = generateHighscoreOrGetCachedQueryForOurRank(categoryName, entriesPerPage, player->getGUID(), vocation); } uint32_t playerID = player->getID(); - std::function callback = [this, playerID, category, vocation, entriesPerPage](DBResult_ptr result, bool) { - processHighscoreResults(std::move(result), playerID, category, vocation, entriesPerPage); + std::function callback = [this, worldName, playerID, category, vocation, entriesPerPage](DBResult_ptr result, bool) { + processHighscoreResults(std::move(result), worldName, playerID, category, vocation, entriesPerPage); }; g_databaseTasks().store(query, callback); diff --git a/src/game/game.hpp b/src/game/game.hpp index e04dbb26cff..002c54b6561 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -25,6 +25,7 @@ #include "items/items_classification.hpp" #include "modal_window/modal_window.hpp" #include "enums/object_category.hpp" +#include "game/worlds/gameworlds.hpp" // Forward declaration for protobuf class namespace Canary { @@ -91,6 +92,11 @@ class Game { return inject(); } + // Game worlds interface + std::unique_ptr &worlds(); + [[nodiscard]] const std::unique_ptr &worlds() const; + [[nodiscard]] const std::unordered_map &getWorldTypeNames() const; + void resetMonsters() const; void resetNpcs() const; @@ -120,11 +126,6 @@ class Game { height = map.height; } - void setWorldType(WorldType_t type); - WorldType_t getWorldType() const { - return worldType; - } - const std::map> &getTeamFinderList() const { return teamFinderMap; } @@ -739,6 +740,8 @@ class Game { const std::unordered_map &getHirelingOutfits(); private: + std::unordered_map m_worldTypesNames; + std::map m_achievements; std::map m_achievementsNameToId; @@ -898,7 +901,6 @@ class Game { bool browseField = false; GameState_t gameState = GAME_STATE_NORMAL; - WorldType_t worldType = WORLD_TYPE_PVP; LightState_t lightState = LIGHT_STATE_DAY; LightState_t currentLightState = lightState; @@ -963,14 +965,15 @@ class Game { // Variable members (m_) std::unique_ptr m_IOWheel; + std::unique_ptr m_worlds; void cacheQueryHighscore(const std::string &key, const std::string &query, uint32_t page, uint8_t entriesPerPage); - void processHighscoreResults(DBResult_ptr result, uint32_t playerID, uint8_t category, uint32_t vocation, uint8_t entriesPerPage); + void processHighscoreResults(DBResult_ptr result, const std::string &worldName, uint32_t playerID, uint8_t category, uint32_t vocation, uint8_t entriesPerPage); std::string generateVocationConditionHighscore(uint32_t vocation); - std::string generateHighscoreQueryForEntries(const std::string &categoryName, uint32_t page, uint8_t entriesPerPage, uint32_t vocation); + std::string generateHighscoreQueryForEntries(const std::string &categoryName, const std::string &worldName, uint32_t page, uint8_t entriesPerPage, uint32_t vocation); std::string generateHighscoreQueryForOurRank(const std::string &categoryName, uint8_t entriesPerPage, uint32_t playerGUID, uint32_t vocation); - std::string generateHighscoreOrGetCachedQueryForEntries(const std::string &categoryName, uint32_t page, uint8_t entriesPerPage, uint32_t vocation); + std::string generateHighscoreOrGetCachedQueryForEntries(const std::string &categoryName, const std::string &worldName, uint32_t page, uint8_t entriesPerPage, uint32_t vocation); std::string generateHighscoreOrGetCachedQueryForOurRank(const std::string &categoryName, uint8_t entriesPerPage, uint32_t playerGUID, uint32_t vocation); }; diff --git a/src/game/game_definitions.hpp b/src/game/game_definitions.hpp index a6ce6e7eaa8..27b790c5dce 100644 --- a/src/game/game_definitions.hpp +++ b/src/game/game_definitions.hpp @@ -19,10 +19,21 @@ enum StackPosType_t { STACKPOS_FIND_THING, }; -enum WorldType_t { +enum WorldType_t : int8_t { + WORLD_TYPE_NONE = -1, + WORLD_TYPE_PVP = 0, WORLD_TYPE_NO_PVP = 1, - WORLD_TYPE_PVP = 2, - WORLD_TYPE_PVP_ENFORCED = 3, + WORLD_TYPE_PVP_ENFORCED = 2, + WORLD_TYPE_RETRO_PVP = 3, + WORLD_TYPE_RETRO_PVP_ENFORCED = 4, +}; + +enum Location_t { + LOCATION_NONE = 0, + LOCATION_EUROPE = 1, + LOCATION_NORTH_AMERICA = 2, + LOCATION_SOUTH_AMERICA = 3, + LOCATION_OCEANIA = 4, }; enum GameState_t { diff --git a/src/game/worlds/gameworlds.cpp b/src/game/worlds/gameworlds.cpp new file mode 100644 index 00000000000..d3b21e121ae --- /dev/null +++ b/src/game/worlds/gameworlds.cpp @@ -0,0 +1,83 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2024 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.com/ + */ + +#include "pch.hpp" + +#include "game/worlds/gameworlds.hpp" +#include "io/iologindata.hpp" +#include "utils/tools.hpp" + +void Worlds::load() { + IOLoginData::createFirstWorld(); + worlds = IOLoginData::loadWorlds(); +} + +void Worlds::reload() { + worlds.clear(); + load(); +} + +[[nodiscard]] std::shared_ptr Worlds::getWorldConfigsById(uint8_t id) const { + auto it = std::find_if(worlds.begin(), worlds.end(), [id](const std::shared_ptr &world) { + return world->id == id; + }); + + if (it != worlds.end()) { + return *it; + } + + g_logger().error(fmt::format("World with the specified ID: {} not found", id)); + return nullptr; +} + +void Worlds::setCurrentWorld(const std::shared_ptr &world) { + m_currentWorld = world; +} + +const std::shared_ptr &Worlds::getCurrentWorld() const { + return m_currentWorld; +} + +[[nodiscard]] WorldType_t Worlds::getWorldTypeIdByKey(const std::string &key) { + const std::string worldType = asLowerCaseString(key); + + if (worldType == "pvp") { + return WORLD_TYPE_PVP; + } else if (worldType == "no-pvp") { + return WORLD_TYPE_NO_PVP; + } else if (worldType == "pvp-enforced") { + return WORLD_TYPE_PVP_ENFORCED; + } else if (worldType == "retro-pvp") { + return WORLD_TYPE_RETRO_PVP; + } else if (worldType == "retro-pvp-enforced") { + return WORLD_TYPE_RETRO_PVP_ENFORCED; + } + + g_logger().error("[{}] - Unable to get world type from string '{}'", __FUNCTION__, worldType); + + return WORLD_TYPE_NONE; +} + +[[nodiscard]] Location_t Worlds::getWorldLocationByKey(const std::string &key) { + const std::string location = asLowerCaseString(key); + + if (location == "europe") { + return LOCATION_EUROPE; + } else if (location == "north america") { + return LOCATION_NORTH_AMERICA; + } else if (location == "south america") { + return LOCATION_SOUTH_AMERICA; + } else if (location == "oceania") { + return LOCATION_OCEANIA; + } + + g_logger().error("[{}] - Unable to get world location from string '{}'", __FUNCTION__, location); + + return LOCATION_NONE; +} diff --git a/src/game/worlds/gameworlds.hpp b/src/game/worlds/gameworlds.hpp new file mode 100644 index 00000000000..c679bfffd40 --- /dev/null +++ b/src/game/worlds/gameworlds.hpp @@ -0,0 +1,62 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2024 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.com/ + */ + +#include "game/game_definitions.hpp" + +#pragma once + +struct World { + uint8_t id = 0; + std::string name; + WorldType_t type = WORLD_TYPE_PVP; + std::string motd; + std::string location_str; + Location_t location = LOCATION_SOUTH_AMERICA; + std::string ip; + uint16_t port = 7172; + uint32_t port_status = 97172; + uint16_t creation = 0; + + World() = default; + + World(uint8_t id, std::string name, WorldType_t type, std::string motd, std::string location_str, Location_t location, std::string ip, uint16_t port, uint32_t port_status, uint16_t creation) : + id(id), name(std::move(name)), type(type), motd(std::move(motd)), location_str(std::move(location_str)), location(location), ip(std::move(ip)), port(port), port_status(port_status), creation(creation) { } + + bool operator==(const World &other) const { + return id == other.id; + } +}; + +namespace std { + template <> + struct hash { + std::size_t operator()(const World &w) const { + return hash()(w.id); + } + }; +} + +class Worlds { +public: + void load(); + void reload(); + + [[nodiscard]] std::shared_ptr getWorldConfigsById(uint8_t id) const; + void setCurrentWorld(const std::shared_ptr &world); + [[nodiscard]] const std::shared_ptr &getCurrentWorld() const; + [[nodiscard]] static WorldType_t getWorldTypeIdByKey(const std::string &key); + [[nodiscard]] static Location_t getWorldLocationByKey(const std::string &key); + [[nodiscard]] const std::vector> &getWorlds() const noexcept { + return worlds; + } + +private: + std::vector> worlds; + std::shared_ptr m_currentWorld = std::make_shared(); +}; diff --git a/src/io/functions/iologindata_load_player.cpp b/src/io/functions/iologindata_load_player.cpp index 63ca7939c46..65e2ccf106a 100644 --- a/src/io/functions/iologindata_load_player.cpp +++ b/src/io/functions/iologindata_load_player.cpp @@ -54,9 +54,8 @@ void IOLoginDataLoad::loadItems(ItemsMap &itemsMap, DBResult_ptr result, const s bool IOLoginDataLoad::preLoadPlayer(std::shared_ptr player, const std::string &name) { Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `id`, `account_id`, `group_id`, `deletion` FROM `players` WHERE `name` = " << db.escapeString(name); - DBResult_ptr result = db.storeQuery(query.str()); + std::string query = fmt::format("SELECT `id`, `account_id`, `group_id`, `deletion`, `world_id` FROM `players` WHERE `name` = {}", db.escapeString(name)); + DBResult_ptr result = db.storeQuery(query); if (!result) { return false; } @@ -65,6 +64,8 @@ bool IOLoginDataLoad::preLoadPlayer(std::shared_ptr player, const std::s return false; } + player->worldId = result->getNumber("world_id"); + player->setGUID(result->getNumber("id")); const auto &group = g_game().groups.getGroup(result->getNumber("group_id")); if (!group) { @@ -278,7 +279,7 @@ void IOLoginDataLoad::loadPlayerSkullSystem(std::shared_ptr player, DBRe return; } - if (g_game().getWorldType() != WORLD_TYPE_PVP_ENFORCED) { + if (g_game().worlds()->getCurrentWorld()->type != WORLD_TYPE_PVP_ENFORCED) { const time_t skullSeconds = result->getNumber("skulltime") - time(nullptr); if (skullSeconds > 0) { // ensure that we round up the number of ticks diff --git a/src/io/functions/iologindata_save_player.cpp b/src/io/functions/iologindata_save_player.cpp index eccb88c8229..c3e477b5e12 100644 --- a/src/io/functions/iologindata_save_player.cpp +++ b/src/io/functions/iologindata_save_player.cpp @@ -240,7 +240,7 @@ bool IOLoginDataSave::savePlayerFirst(std::shared_ptr player) { query << "`conditions` = " << db.escapeBlob(attributes, static_cast(attributesSize)) << ","; - if (g_game().getWorldType() != WORLD_TYPE_PVP_ENFORCED) { + if (g_game().worlds()->getCurrentWorld()->type != WORLD_TYPE_PVP_ENFORCED) { int64_t skullTime = 0; if (player->skullTicks > 0) { diff --git a/src/io/io_bosstiary.cpp b/src/io/io_bosstiary.cpp index 7c7c8108ce7..ecb4757650b 100644 --- a/src/io/io_bosstiary.cpp +++ b/src/io/io_bosstiary.cpp @@ -19,9 +19,10 @@ void IOBosstiary::loadBoostedBoss() { Database &database = Database::getInstance(); - std::ostringstream query; - query << "SELECT * FROM `boosted_boss`"; - DBResult_ptr result = database.storeQuery(query.str()); + + std::string query = "SELECT * FROM `boosted_boss`"; + auto result = database.storeQuery(query); + if (!result) { g_logger().error("[{}] Failed to detect boosted boss database. (CODE 01)", __FUNCTION__); return; @@ -84,36 +85,34 @@ void IOBosstiary::loadBoostedBoss() { break; } - query.str(std::string()); - query << "UPDATE `boosted_boss` SET "; - query << "`date` = '" << today << "',"; - query << "`boostname` = " << database.escapeString(bossName) << ","; - if (const auto bossType = getMonsterTypeByBossRaceId(bossId); - bossType) { - query << "`looktypeEx` = " << static_cast(bossType->info.outfit.lookTypeEx) << ","; - query << "`looktype` = " << static_cast(bossType->info.outfit.lookType) << ","; - query << "`lookfeet` = " << static_cast(bossType->info.outfit.lookFeet) << ","; - query << "`looklegs` = " << static_cast(bossType->info.outfit.lookLegs) << ","; - query << "`lookhead` = " << static_cast(bossType->info.outfit.lookHead) << ","; - query << "`lookbody` = " << static_cast(bossType->info.outfit.lookBody) << ","; - query << "`lookaddons` = " << static_cast(bossType->info.outfit.lookAddons) << ","; - query << "`lookmount` = " << static_cast(bossType->info.outfit.lookMount) << ","; - } - query << "`raceid` = '" << bossId << "'"; - if (!database.executeQuery(query.str())) { + const auto bossType = getMonsterTypeByBossRaceId(bossId); + + const auto lookTypeEx = bossType ? bossType->info.outfit.lookTypeEx : 0; + const auto lookType = bossType ? bossType->info.outfit.lookType : 136; + const auto lookFeet = bossType ? bossType->info.outfit.lookFeet : 0; + const auto lookLegs = bossType ? bossType->info.outfit.lookLegs : 0; + const auto lookHead = bossType ? bossType->info.outfit.lookHead : 0; + const auto lookBody = bossType ? bossType->info.outfit.lookBody : 0; + const auto lookAddons = bossType ? bossType->info.outfit.lookAddons : 0; + const auto lookMount = bossType ? bossType->info.outfit.lookMount : 0; + + query = fmt::format( + "UPDATE `boosted_boss` SET `date` = {}, `boostname` = {}, `looktypeEx` = {}, `looktype` = {}, `lookfeet` = {}, `looklegs` = {}, `lookhead` = {}, `lookbody` = {}, `lookaddons` = {}, `lookmount` = {}, `raceid` = {}", + today, database.escapeString(bossName), lookTypeEx, lookType, lookFeet, lookLegs, lookHead, lookBody, lookAddons, lookMount, bossId + ); + + if (!database.executeQuery(query)) { g_logger().error("[{}] Failed to detect boosted boss database. (CODE 03)", __FUNCTION__); return; } - query.str(std::string()); - query << "UPDATE `player_bosstiary` SET `bossIdSlotOne` = 0 WHERE `bossIdSlotOne` = " << bossId; - if (!database.executeQuery(query.str())) { + query = fmt::format("UPDATE `player_bosstiary` SET `bossIdSlotOne` = 0 WHERE `bossIdSlotOne` = {}", bossId); + if (!database.executeQuery(query)) { g_logger().error("[{}] Failed to reset players selected boss slot 1. (CODE 03)", __FUNCTION__); } - query.str(std::string()); - query << "UPDATE `player_bosstiary` SET `bossIdSlotTwo` = 0 WHERE `bossIdSlotTwo` = " << bossId; - if (!database.executeQuery(query.str())) { + query = fmt::format("UPDATE `player_bosstiary` SET `bossIdSlotTwo` = 0 WHERE `bossIdSlotTwo` = {}", bossId); + if (!database.executeQuery(query)) { g_logger().error("[{}] Failed to reset players selected boss slot 1. (CODE 03)", __FUNCTION__); } diff --git a/src/io/iologindata.cpp b/src/io/iologindata.cpp index 41f8cea8985..76e38015258 100644 --- a/src/io/iologindata.cpp +++ b/src/io/iologindata.cpp @@ -12,7 +12,6 @@ #include "io/iologindata.hpp" #include "io/functions/iologindata_load_player.hpp" #include "io/functions/iologindata_save_player.hpp" -#include "game/game.hpp" #include "creatures/monsters/monster.hpp" #include "creatures/players/wheel/player_wheel.hpp" #include "lib/metrics/metrics.hpp" @@ -54,7 +53,7 @@ bool IOLoginData::gameWorldAuthentication(const std::string &accountDescriptor, return false; } - if (players[characterName] != 0) { + if (players[characterName].deletion != 0) { g_logger().error("Account [{}] player [{}] not found or deleted.", accountDescriptor, characterName); return false; } @@ -81,32 +80,32 @@ void IOLoginData::updateOnlineStatus(uint32_t guid, bool login) { return; } - std::ostringstream query; + const auto worldId = g_game().worlds()->getCurrentWorld()->id; + + std::string query; if (login) { g_metrics().addUpDownCounter("players_online", 1); - query << "INSERT INTO `players_online` VALUES (" << guid << ')'; + query = fmt::format("INSERT INTO `players_online` VALUES ({}, {})", guid, worldId); updateOnline[guid] = true; } else { g_metrics().addUpDownCounter("players_online", -1); - query << "DELETE FROM `players_online` WHERE `player_id` = " << guid; + query = fmt::format("DELETE FROM `players_online` WHERE `player_id` = {} AND `world_id` = {}", guid, worldId); updateOnline.erase(guid); } - Database::getInstance().executeQuery(query.str()); + Database::getInstance().executeQuery(query); } // The boolean "disableIrrelevantInfo" will deactivate the loading of information that is not relevant to the preload, for example, forge, bosstiary, etc. None of this we need to access if the player is offline bool IOLoginData::loadPlayerById(std::shared_ptr player, uint32_t id, bool disableIrrelevantInfo /* = true*/) { Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT * FROM `players` WHERE `id` = " << id; - return loadPlayer(player, db.storeQuery(query.str()), disableIrrelevantInfo); + std::string query = fmt::format("SELECT * FROM `players` WHERE `id` = {} AND `world_id` = {}", id, g_game().worlds()->getCurrentWorld()->id); + return loadPlayer(player, db.storeQuery(query), disableIrrelevantInfo); } bool IOLoginData::loadPlayerByName(std::shared_ptr player, const std::string &name, bool disableIrrelevantInfo /* = true*/) { Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT * FROM `players` WHERE `name` = " << db.escapeString(name); - return loadPlayer(player, db.storeQuery(query.str()), disableIrrelevantInfo); + std::string query = fmt::format("SELECT * FROM `players` WHERE `name` = {} AND `world_id` = {}", db.escapeString(name), g_game().worlds()->getCurrentWorld()->id); + return loadPlayer(player, db.storeQuery(query), disableIrrelevantInfo); } bool IOLoginData::loadPlayer(std::shared_ptr player, DBResult_ptr result, bool disableIrrelevantInfo /* = false*/) { @@ -284,9 +283,8 @@ bool IOLoginData::savePlayerGuard(std::shared_ptr player) { } std::string IOLoginData::getNameByGuid(uint32_t guid) { - std::ostringstream query; - query << "SELECT `name` FROM `players` WHERE `id` = " << guid; - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + std::string query = fmt::format("SELECT `name` FROM `players` WHERE `id` = {} AND `world_id` = {}", guid, g_game().worlds()->getCurrentWorld()->id); + DBResult_ptr result = Database::getInstance().storeQuery(query); if (!result) { return std::string(); } @@ -296,9 +294,8 @@ std::string IOLoginData::getNameByGuid(uint32_t guid) { uint32_t IOLoginData::getGuidByName(const std::string &name) { Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `id` FROM `players` WHERE `name` = " << db.escapeString(name); - DBResult_ptr result = db.storeQuery(query.str()); + std::string query = fmt::format("SELECT `id` FROM `players` WHERE `name` = {} AND `world_id` = {}", db.escapeString(name), g_game().worlds()->getCurrentWorld()->id); + DBResult_ptr result = db.storeQuery(query); if (!result) { return 0; } @@ -308,9 +305,8 @@ uint32_t IOLoginData::getGuidByName(const std::string &name) { bool IOLoginData::getGuidByNameEx(uint32_t &guid, bool &specialVip, std::string &name) { Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `name`, `id`, `group_id`, `account_id` FROM `players` WHERE `name` = " << db.escapeString(name); - DBResult_ptr result = db.storeQuery(query.str()); + std::string query = fmt::format("SELECT `name`, `id`, `group_id`, `account_id` FROM `players` WHERE `name` = {} AND `world_id` = {}", db.escapeString(name), g_game().worlds()->getCurrentWorld()->id); + DBResult_ptr result = db.storeQuery(query); if (!result) { return false; } @@ -328,10 +324,9 @@ bool IOLoginData::getGuidByNameEx(uint32_t &guid, bool &specialVip, std::string bool IOLoginData::formatPlayerName(std::string &name) { Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `name` FROM `players` WHERE `name` = " << db.escapeString(name); + std::string query = fmt::format("SELECT `name` FROM `players` WHERE `name` = {} AND `world_id` = {}", db.escapeString(name), g_game().worlds()->getCurrentWorld()->id); - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = db.storeQuery(query); if (!result) { return false; } @@ -341,21 +336,22 @@ bool IOLoginData::formatPlayerName(std::string &name) { } void IOLoginData::increaseBankBalance(uint32_t guid, uint64_t bankBalance) { - std::ostringstream query; - query << "UPDATE `players` SET `balance` = `balance` + " << bankBalance << " WHERE `id` = " << guid; - Database::getInstance().executeQuery(query.str()); + std::string query = fmt::format("UPDATE `players` SET `balance` = `balance` + {} WHERE `id` = {} AND `world_id` = {}", bankBalance, guid, g_game().worlds()->getCurrentWorld()->id); + Database::getInstance().executeQuery(query); } bool IOLoginData::hasBiddedOnHouse(uint32_t guid) { Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `id` FROM `houses` WHERE `highest_bidder` = " << guid << " LIMIT 1"; - return db.storeQuery(query.str()).get() != nullptr; + std::string query = fmt::format("SELECT `id` FROM `houses` WHERE `highest_bidder` = {} AND `world_id` = {} LIMIT 1", guid, g_game().worlds()->getCurrentWorld()->id); + return db.storeQuery(query).get() != nullptr; } std::vector IOLoginData::getVIPEntries(uint32_t accountId) { - std::string query = fmt::format("SELECT `player_id`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `name`, `description`, `icon`, `notify` FROM `account_viplist` WHERE `account_id` = {}", accountId); + std::string query = fmt::format( + "SELECT `player_id`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `name`, `description`, `icon`, `notify` FROM `account_viplist` WHERE `account_id` = {} AND `world_id` = {}", + accountId, g_game().worlds()->getCurrentWorld()->id + ); std::vector entries; if (const auto &result = Database::getInstance().storeQuery(query)) { @@ -375,21 +371,27 @@ std::vector IOLoginData::getVIPEntries(uint32_t accountId) { } void IOLoginData::addVIPEntry(uint32_t accountId, uint32_t guid, const std::string &description, uint32_t icon, bool notify) { - std::string query = fmt::format("INSERT INTO `account_viplist` (`account_id`, `player_id`, `description`, `icon`, `notify`) VALUES ({}, {}, {}, {}, {})", accountId, guid, g_database().escapeString(description), icon, notify); + std::string query = fmt::format( + "INSERT INTO `account_viplist` (`account_id`, `player_id`, `world_id`, `description`, `icon`, `notify`) VALUES ({}, {}, {}, {}, {}, {})", + accountId, guid, g_game().worlds()->getCurrentWorld()->id, g_database().escapeString(description), icon, notify + ); if (!g_database().executeQuery(query)) { g_logger().error("Failed to add VIP entry for account {}. QUERY: {}", accountId, query.c_str()); } } void IOLoginData::editVIPEntry(uint32_t accountId, uint32_t guid, const std::string &description, uint32_t icon, bool notify) { - std::string query = fmt::format("UPDATE `account_viplist` SET `description` = {}, `icon` = {}, `notify` = {} WHERE `account_id` = {} AND `player_id` = {}", g_database().escapeString(description), icon, notify, accountId, guid); + std::string query = fmt::format( + "UPDATE `account_viplist` SET `description` = {}, `icon` = {}, `notify` = {} WHERE `account_id` = {} AND `player_id` = {} AND `world_id` = {}", + g_database().escapeString(description), icon, notify, accountId, guid, g_game().worlds()->getCurrentWorld()->id + ); if (!g_database().executeQuery(query)) { g_logger().error("Failed to edit VIP entry for account {}. QUERY: {}", accountId, query.c_str()); } } void IOLoginData::removeVIPEntry(uint32_t accountId, uint32_t guid) { - std::string query = fmt::format("DELETE FROM `account_viplist` WHERE `account_id` = {} AND `player_id` = {}", accountId, guid); + std::string query = fmt::format("DELETE FROM `account_viplist` WHERE `account_id` = {} AND `player_id` = {} AND `world_id` = {}", accountId, guid, g_game().worlds()->getCurrentWorld()->id); g_database().executeQuery(query); } @@ -442,3 +444,54 @@ void IOLoginData::removeGuidVIPGroupEntry(uint32_t accountId, uint32_t guid) { std::string query = fmt::format("DELETE FROM `account_vipgrouplist` WHERE `account_id` = {} AND `player_id` = {}", accountId, guid); g_database().executeQuery(query); } + +void IOLoginData::createFirstWorld() { + const auto &result = g_database().storeQuery("SELECT * FROM `worlds`"); + + if (result.get() == nullptr || result->countResults() < 1) { + const auto &retro = g_configManager().getBoolean(TOGGLE_SERVER_IS_RETRO, __FUNCTION__) ? "retro-" : ""; + const auto &serverName = g_configManager().getString(SERVER_NAME, __FUNCTION__); + const auto &worldType = fmt::format("{}{}", retro, g_configManager().getString(WORLD_TYPE, __FUNCTION__)); + const auto &worldMotd = g_configManager().getString(SERVER_MOTD, __FUNCTION__); + const auto &location = g_configManager().getString(WORLD_LOCATION, __FUNCTION__); + const auto &ip = g_configManager().getString(IP, __FUNCTION__); + const auto &port = g_configManager().getNumber(GAME_PORT, __FUNCTION__); + const auto &portStatus = fmt::format("9{}", port); + + std::string query = fmt::format( + "INSERT INTO `worlds` (`name`, `type`, `motd`, `location`, `ip`, `port`, `port_status`, `creation`) VALUES ({}, {}, {}, {}, {}, {}, {}, {})", + g_database().escapeString(serverName), g_database().escapeString(worldType), g_database().escapeString(worldMotd), g_database().escapeString(location), g_database().escapeString(ip), port, portStatus, getTimeNow() + ); + const auto &insertResult = g_database().executeQuery(query); + + if (insertResult) { + g_logger().info("Added initial world id 1 - {} to database", serverName); + } else { + g_logger().error("Failed to add initial world id 1 - {} to database", serverName); + } + } +} + +std::vector> IOLoginData::loadWorlds() { + std::vector> entries; + + if (const auto &result = Database::getInstance().storeQuery("SELECT * FROM `worlds`")) { + entries.reserve(result->countResults()); + do { + entries.emplace_back(std::make_shared( + result->getNumber("id"), + result->getString("name"), + g_game().worlds()->getWorldTypeIdByKey(result->getString("type")), + result->getString("motd"), + result->getString("location"), + g_game().worlds()->getWorldLocationByKey(result->getString("location")), + result->getString("ip"), + result->getNumber("port"), + result->getNumber("port_status"), + result->getNumber("creation") + )); + } while (result->next()); + } + + return entries; +} diff --git a/src/io/iologindata.hpp b/src/io/iologindata.hpp index 79fa3b59ad7..69aff870351 100644 --- a/src/io/iologindata.hpp +++ b/src/io/iologindata.hpp @@ -11,6 +11,7 @@ #include "account/account.hpp" #include "creatures/players/player.hpp" +#include "game/game.hpp" #include "database/database.hpp" using ItemBlockList = std::list>>; @@ -43,6 +44,9 @@ class IOLoginData { static void addGuidVIPGroupEntry(uint8_t groupId, uint32_t accountId, uint32_t guid); static void removeGuidVIPGroupEntry(uint32_t accountId, uint32_t guid); + static void createFirstWorld(); + static std::vector> loadWorlds(); + private: static bool savePlayerGuard(std::shared_ptr player); }; diff --git a/src/io/iomapserialize.cpp b/src/io/iomapserialize.cpp index e2a367c4ec4..c1e3cba1764 100644 --- a/src/io/iomapserialize.cpp +++ b/src/io/iomapserialize.cpp @@ -17,7 +17,9 @@ void IOMapSerialize::loadHouseItems(Map* map) { Benchmark bm_context; - DBResult_ptr result = Database::getInstance().storeQuery("SELECT `data` FROM `tile_store`"); + DBResult_ptr result = Database::getInstance().storeQuery( + fmt::format("SELECT `data` FROM `tile_store` WHERE `world_id` = {}", g_game().worlds()->getCurrentWorld()->id) + ); if (!result) { return; } @@ -79,11 +81,11 @@ bool IOMapSerialize::SaveHouseItemsGuard() { std::ostringstream query; // clear old tile data - if (!db.executeQuery("DELETE FROM `tile_store`")) { + if (!db.executeQuery(fmt::format("DELETE FROM `tile_store` WHERE `world_id` = {}", g_game().worlds()->getCurrentWorld()->id))) { return false; } - DBInsert stmt("INSERT INTO `tile_store` (`house_id`, `data`) VALUES "); + DBInsert stmt("INSERT INTO `tile_store` (`house_id`, `data`, `world_id`) VALUES "); PropWriteStream stream; for (const auto &[key, house] : g_game().map.houses.getHouses()) { @@ -94,7 +96,7 @@ bool IOMapSerialize::SaveHouseItemsGuard() { size_t attributesSize; const char* attributes = stream.getStream(attributesSize); if (attributesSize > 0) { - query << house->getId() << ',' << db.escapeBlob(attributes, attributesSize); + query << house->getId() << ',' << db.escapeBlob(attributes, attributesSize) << ',' << static_cast(g_game().worlds()->getCurrentWorld()->id); if (!stmt.addRow(query)) { return false; } @@ -275,7 +277,7 @@ void IOMapSerialize::saveTile(PropWriteStream &stream, std::shared_ptr til bool IOMapSerialize::loadHouseInfo() { Database &db = Database::getInstance(); - DBResult_ptr result = db.storeQuery("SELECT `id`, `owner`, `new_owner`, `paid`, `warnings` FROM `houses`"); + DBResult_ptr result = db.storeQuery(fmt::format("SELECT `id`, `owner`, `new_owner`, `paid`, `warnings` FROM `houses` WHERE `world_id` = {}", g_game().worlds()->getCurrentWorld()->id)); if (!result) { return false; } @@ -305,7 +307,7 @@ bool IOMapSerialize::loadHouseInfo() { } } while (result->next()); - result = db.storeQuery("SELECT `house_id`, `listid`, `list` FROM `house_lists`"); + result = db.storeQuery(fmt::format("SELECT `house_id`, `listid`, `list` FROM `house_lists` WHERE `world_id` = {}", g_game().worlds()->getCurrentWorld()->id)); if (result) { do { const auto &house = g_game().map.houses.getHouse(result->getNumber("house_id")); @@ -331,13 +333,18 @@ bool IOMapSerialize::saveHouseInfo() { bool IOMapSerialize::SaveHouseInfoGuard() { Database &db = Database::getInstance(); + const auto worldId = g_game().worlds()->getCurrentWorld()->id; std::ostringstream query; - DBInsert houseUpdate("INSERT INTO `houses` (`id`, `owner`, `paid`, `warnings`, `name`, `town_id`, `rent`, `size`, `beds`) VALUES "); + DBInsert houseUpdate("INSERT INTO `houses` (`id`, `owner`, `paid`, `warnings`, `name`, `town_id`, `rent`, `size`, `beds`, `world_id`) VALUES "); houseUpdate.upsert({ "owner", "paid", "warnings", "name", "town_id", "rent", "size", "beds" }); for (const auto &[key, house] : g_game().map.houses.getHouses()) { - std::string values = fmt::format("{},{},{},{},{},{},{},{},{}", house->getId(), house->getOwner(), house->getPaidUntil(), house->getPayRentWarnings(), db.escapeString(house->getName()), house->getTownId(), house->getRent(), house->getSize(), house->getBedCount()); + std::string values = fmt::format( + "{},{},{},{},{},{},{},{},{},{}", + house->getId(), house->getOwner(), house->getPaidUntil(), house->getPayRentWarnings(), db.escapeString(house->getName()), + house->getTownId(), house->getRent(), house->getSize(), house->getBedCount(), worldId + ); if (!houseUpdate.addRow(values)) { return false; @@ -348,14 +355,14 @@ bool IOMapSerialize::SaveHouseInfoGuard() { return false; } - DBInsert listUpdate("INSERT INTO `house_lists` (`house_id` , `listid` , `list`, `version`) VALUES "); + DBInsert listUpdate("INSERT INTO `house_lists` (`house_id` , `listid` , `list`, `version`, `world_id`) VALUES "); listUpdate.upsert({ "list", "version" }); auto version = getTimeUsNow(); for (const auto &[key, house] : g_game().map.houses.getHouses()) { std::string listText; if (house->getAccessList(GUEST_LIST, listText) && !listText.empty()) { - query << house->getId() << ',' << GUEST_LIST << ',' << db.escapeString(listText) << ',' << version; + query << house->getId() << ',' << GUEST_LIST << ',' << db.escapeString(listText) << ',' << version << ',' << worldId; if (!listUpdate.addRow(query)) { return false; } @@ -364,7 +371,7 @@ bool IOMapSerialize::SaveHouseInfoGuard() { } if (house->getAccessList(SUBOWNER_LIST, listText) && !listText.empty()) { - query << house->getId() << ',' << SUBOWNER_LIST << ',' << db.escapeString(listText) << ',' << version; + query << house->getId() << ',' << SUBOWNER_LIST << ',' << db.escapeString(listText) << ',' << version << ',' << worldId; if (!listUpdate.addRow(query)) { return false; } @@ -374,7 +381,7 @@ bool IOMapSerialize::SaveHouseInfoGuard() { for (std::shared_ptr door : house->getDoors()) { if (door->getAccessList(listText) && !listText.empty()) { - query << house->getId() << ',' << door->getDoorId() << ',' << db.escapeString(listText) << ',' << version; + query << house->getId() << ',' << door->getDoorId() << ',' << db.escapeString(listText) << ',' << version << ',' << worldId; if (!listUpdate.addRow(query)) { return false; } @@ -388,7 +395,7 @@ bool IOMapSerialize::SaveHouseInfoGuard() { return false; } - if (!db.executeQuery(fmt::format("DELETE FROM `house_lists` WHERE `version` < {}", version))) { + if (!db.executeQuery(fmt::format("DELETE FROM `house_lists` WHERE `version` < {} AND `world_id` = {}", version, worldId))) { return false; } diff --git a/src/io/iomarket.cpp b/src/io/iomarket.cpp index a0253e2ea01..455da591197 100644 --- a/src/io/iomarket.cpp +++ b/src/io/iomarket.cpp @@ -32,8 +32,8 @@ MarketOfferList IOMarket::getActiveOffers(MarketAction_t action) { std::string query = fmt::format( "SELECT `id`, `itemtype`, `amount`, `price`, `tier`, `created`, `anonymous`, " "(SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `player_name` " - "FROM `market_offers` WHERE `sale` = {}", - action + "FROM `market_offers` WHERE `sale` = {} AND `world_id` = {}", + action, g_game().worlds()->getCurrentWorld()->id ); DBResult_ptr result = g_database().storeQuery(query); @@ -64,10 +64,12 @@ MarketOfferList IOMarket::getActiveOffers(MarketAction_t action) { MarketOfferList IOMarket::getActiveOffers(MarketAction_t action, uint16_t itemId, uint8_t tier) { MarketOfferList offerList; - std::ostringstream query; - query << "SELECT `id`, `amount`, `price`, `tier`, `created`, `anonymous`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `player_name` FROM `market_offers` WHERE `sale` = " << action << " AND `itemtype` = " << itemId << " AND `tier` = " << std::to_string(tier); + std::string query = fmt::format( + "SELECT `id`, `amount`, `price`, `tier`, `created`, `anonymous`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `player_name` FROM `market_offers` WHERE `sale` = {} AND `itemtype` = {} AND `tier` = {} AND `world_id` = {}", + action, itemId, std::to_string(tier), g_game().worlds()->getCurrentWorld()->id + ); - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + DBResult_ptr result = Database::getInstance().storeQuery(query); if (!result) { return offerList; } @@ -97,10 +99,12 @@ MarketOfferList IOMarket::getOwnOffers(MarketAction_t action, uint32_t playerId) const int32_t marketOfferDuration = g_configManager().getNumber(MARKET_OFFER_DURATION, __FUNCTION__); - std::ostringstream query; - query << "SELECT `id`, `amount`, `price`, `created`, `itemtype`, `tier` FROM `market_offers` WHERE `player_id` = " << playerId << " AND `sale` = " << action; + std::string query = fmt::format( + "SELECT `id`, `amount`, `price`, `created`, `itemtype`, `tier` FROM `market_offers` WHERE `player_id` = {} AND `sale` = {} AND `world_id` = {}", + playerId, action, g_game().worlds()->getCurrentWorld()->id + ); - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + DBResult_ptr result = Database::getInstance().storeQuery(query); if (!result) { return offerList; } @@ -121,10 +125,12 @@ MarketOfferList IOMarket::getOwnOffers(MarketAction_t action, uint32_t playerId) HistoryMarketOfferList IOMarket::getOwnHistory(MarketAction_t action, uint32_t playerId) { HistoryMarketOfferList offerList; - std::ostringstream query; - query << "SELECT `itemtype`, `amount`, `price`, `expires_at`, `state`, `tier` FROM `market_history` WHERE `player_id` = " << playerId << " AND `sale` = " << action; + std::string query = fmt::format( + "SELECT `itemtype`, `amount`, `price`, `expires_at`, `state`, `tier` FROM `market_history` WHERE `player_id` = {} AND `sale` = {} AND `world_id` = {}", + playerId, action, g_game().worlds()->getCurrentWorld()->id + ); - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + DBResult_ptr result = Database::getInstance().storeQuery(query); if (!result) { return offerList; } @@ -229,9 +235,11 @@ void IOMarket::processExpiredOffers(DBResult_ptr result, bool) { void IOMarket::checkExpiredOffers() { const time_t lastExpireDate = getTimeNow() - g_configManager().getNumber(MARKET_OFFER_DURATION, __FUNCTION__); - std::ostringstream query; - query << "SELECT `id`, `amount`, `price`, `itemtype`, `player_id`, `sale`, `tier` FROM `market_offers` WHERE `created` <= " << lastExpireDate; - g_databaseTasks().store(query.str(), IOMarket::processExpiredOffers); + std::string query = fmt::format( + "SELECT `id`, `amount`, `price`, `itemtype`, `player_id`, `sale`, `tier` FROM `market_offers` WHERE `created` <= {} AND `world_id` = {}", + lastExpireDate, g_game().worlds()->getCurrentWorld()->id + ); + g_databaseTasks().store(query, IOMarket::processExpiredOffers); int32_t checkExpiredMarketOffersEachMinutes = g_configManager().getNumber(CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES, __FUNCTION__); if (checkExpiredMarketOffersEachMinutes <= 0) { @@ -242,10 +250,12 @@ void IOMarket::checkExpiredOffers() { } uint32_t IOMarket::getPlayerOfferCount(uint32_t playerId) { - std::ostringstream query; - query << "SELECT COUNT(*) AS `count` FROM `market_offers` WHERE `player_id` = " << playerId; + std::string query = fmt::format( + "SELECT COUNT(*) AS `count` FROM `market_offers` WHERE `player_id` = {} AND `world_id` = {}", + playerId, g_game().worlds()->getCurrentWorld()->id + ); - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + DBResult_ptr result = Database::getInstance().storeQuery(query); if (!result) { return 0; } @@ -257,10 +267,12 @@ MarketOfferEx IOMarket::getOfferByCounter(uint32_t timestamp, uint16_t counter) const int32_t created = timestamp - g_configManager().getNumber(MARKET_OFFER_DURATION, __FUNCTION__); - std::ostringstream query; - query << "SELECT `id`, `sale`, `itemtype`, `amount`, `created`, `price`, `player_id`, `anonymous`, `tier`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `player_name` FROM `market_offers` WHERE `created` = " << created << " AND (`id` & 65535) = " << counter << " LIMIT 1"; + std::string query = fmt::format( + "SELECT `id`, `sale`, `itemtype`, `amount`, `created`, `price`, `player_id`, `anonymous`, `tier`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `player_name` FROM `market_offers` WHERE `created` = {} AND (`id` & 65535) = {} AND `world_id` = {} LIMIT 1", + created, counter, g_game().worlds()->getCurrentWorld()->id + ); - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + DBResult_ptr result = Database::getInstance().storeQuery(query); if (!result) { offer.id = 0; return offer; @@ -284,45 +296,47 @@ MarketOfferEx IOMarket::getOfferByCounter(uint32_t timestamp, uint16_t counter) } void IOMarket::createOffer(uint32_t playerId, MarketAction_t action, uint32_t itemId, uint16_t amount, uint64_t price, uint8_t tier, bool anonymous) { - std::ostringstream query; - query << "INSERT INTO `market_offers` (`player_id`, `sale`, `itemtype`, `amount`, `created`, `anonymous`, `price`, `tier`) VALUES (" << playerId << ',' << action << ',' << itemId << ',' << amount << ',' << getTimeNow() << ',' << anonymous << ',' << price << ',' << std::to_string(tier) << ')'; - Database::getInstance().executeQuery(query.str()); + std::string query = fmt::format( + "INSERT INTO `market_offers` (`player_id`, `sale`, `itemtype`, `amount`, `created`, `anonymous`, `price`, `tier`, `world_id`) VALUES ({}, {}, {}, {}, {}, {}, {}, {}, {})", + playerId, action, itemId, amount, getTimeNow(), anonymous, price, std::to_string(tier), g_game().worlds()->getCurrentWorld()->id + ); + Database::getInstance().executeQuery(query); } void IOMarket::acceptOffer(uint32_t offerId, uint16_t amount) { - std::ostringstream query; - query << "UPDATE `market_offers` SET `amount` = `amount` - " << amount << " WHERE `id` = " << offerId; - Database::getInstance().executeQuery(query.str()); + std::string query = fmt::format("UPDATE `market_offers` SET `amount` = `amount` - {} WHERE `id` = {} AND `world_id` = {}", amount, offerId, g_game().worlds()->getCurrentWorld()->id); + Database::getInstance().executeQuery(query); } void IOMarket::deleteOffer(uint32_t offerId) { - std::ostringstream query; - query << "DELETE FROM `market_offers` WHERE `id` = " << offerId; - Database::getInstance().executeQuery(query.str()); + std::string query = fmt::format("DELETE FROM `market_offers` WHERE `id` = {} AND `world_id` = {}", offerId, g_game().worlds()->getCurrentWorld()->id); + Database::getInstance().executeQuery(query); } void IOMarket::appendHistory(uint32_t playerId, MarketAction_t type, uint16_t itemId, uint16_t amount, uint64_t price, time_t timestamp, uint8_t tier, MarketOfferState_t state) { - std::ostringstream query; - query << "INSERT INTO `market_history` (`player_id`, `sale`, `itemtype`, `amount`, `price`, `expires_at`, `inserted`, `state`, `tier`) VALUES (" - << playerId << ',' << type << ',' << itemId << ',' << amount << ',' << price << ',' - << timestamp << ',' << getTimeNow() << ',' << state << ',' << std::to_string(tier) << ')'; - g_databaseTasks().execute(query.str()); + std::string query = fmt::format( + "INSERT INTO `market_history` (`player_id`, `sale`, `itemtype`, `amount`, `price`, `expires_at`, `inserted`, `state`, `tier`, `world_id`) VALUES ({}, {}, {}, {}, {}, {}, {}, {}, {}, {})", + playerId, type, itemId, amount, price, timestamp, getTimeNow(), state, std::to_string(tier), g_game().worlds()->getCurrentWorld()->id + ); + g_databaseTasks().execute(query); } bool IOMarket::moveOfferToHistory(uint32_t offerId, MarketOfferState_t state) { Database &db = Database::getInstance(); + const auto worldId = g_game().worlds()->getCurrentWorld()->id; - std::ostringstream query; - query << "SELECT `player_id`, `sale`, `itemtype`, `amount`, `price`, `created`, `tier` FROM `market_offers` WHERE `id` = " << offerId; + std::string query = fmt::format( + "SELECT `player_id`, `sale`, `itemtype`, `amount`, `price`, `created`, `tier` FROM `market_offers` WHERE `id` = {} AND `world_id` = {}", + offerId, worldId + ); - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = db.storeQuery(query); if (!result) { return false; } - query.str(std::string()); - query << "DELETE FROM `market_offers` WHERE `id` = " << offerId; - if (!db.executeQuery(query.str())) { + query = fmt::format("DELETE FROM `market_offers` WHERE `id` = {} AND `world_id` = {}", offerId, worldId); + if (!db.executeQuery(query)) { return false; } @@ -342,9 +356,9 @@ void IOMarket::updateStatistics() { auto query = fmt::format( "SELECT sale, itemtype, COUNT(price) AS num, MIN(price) AS min, MAX(price) AS max, SUM(price) AS sum, tier " "FROM market_history " - "WHERE state = '{}' " + "WHERE state = '{}' AND `world_id` = {} " "GROUP BY itemtype, sale, tier", - OFFERSTATE_ACCEPTED + OFFERSTATE_ACCEPTED, g_game().worlds()->getCurrentWorld()->id ); DBResult_ptr result = g_database().storeQuery(query); diff --git a/src/items/containers/mailbox/mailbox.cpp b/src/items/containers/mailbox/mailbox.cpp index c2c9b2f61e1..2f122578025 100644 --- a/src/items/containers/mailbox/mailbox.cpp +++ b/src/items/containers/mailbox/mailbox.cpp @@ -82,13 +82,14 @@ bool Mailbox::sendItem(std::shared_ptr item) const { return false; } + std::shared_ptr player = g_game().getPlayerByName(receiver, true); + if (item && item->getContainer() && item->getTile()) { for (const auto &spectator : Spectators().find(item->getTile()->getPosition())) { spectator->getPlayer()->autoCloseContainers(item->getContainer()); } } - std::shared_ptr player = g_game().getPlayerByName(receiver, true); std::string writer; time_t date = time(0); std::string text; diff --git a/src/items/tile.cpp b/src/items/tile.cpp index fc16dd5799c..658bdbb4a0f 100644 --- a/src/items/tile.cpp +++ b/src/items/tile.cpp @@ -711,7 +711,7 @@ ReturnValue Tile::queryAdd(int32_t, const std::shared_ptr &thing, uint32_ } if ((!playerTile->hasFlag(TILESTATE_NOPVPZONE) && hasFlag(TILESTATE_NOPVPZONE)) || (!playerTile->hasFlag(TILESTATE_PROTECTIONZONE) && hasFlag(TILESTATE_PROTECTIONZONE))) { - // player is trying to enter a non-pvp/protection zone while being pz-locked + // player is trying to enter a no-pvp/protection zone while being pz-locked return RETURNVALUE_PLAYERISPZLOCKED; } } diff --git a/src/lua/functions/core/game/game_functions.cpp b/src/lua/functions/core/game/game_functions.cpp index 83544ded535..52098fe4a0a 100644 --- a/src/lua/functions/core/game/game_functions.cpp +++ b/src/lua/functions/core/game/game_functions.cpp @@ -278,15 +278,7 @@ int GameFunctions::luaGameSetGameState(lua_State* L) { int GameFunctions::luaGameGetWorldType(lua_State* L) { // Game.getWorldType() - lua_pushnumber(L, g_game().getWorldType()); - return 1; -} - -int GameFunctions::luaGameSetWorldType(lua_State* L) { - // Game.setWorldType(type) - WorldType_t type = getNumber(L, 1); - g_game().setWorldType(type); - pushBoolean(L, true); + lua_pushnumber(L, g_game().worlds()->getCurrentWorld()->type); return 1; } diff --git a/src/lua/functions/core/game/game_functions.hpp b/src/lua/functions/core/game/game_functions.hpp index 70e81061c9c..2cf706dbc9c 100644 --- a/src/lua/functions/core/game/game_functions.hpp +++ b/src/lua/functions/core/game/game_functions.hpp @@ -41,7 +41,6 @@ class GameFunctions final : LuaScriptInterface { registerMethod(L, "Game", "setGameState", GameFunctions::luaGameSetGameState); registerMethod(L, "Game", "getWorldType", GameFunctions::luaGameGetWorldType); - registerMethod(L, "Game", "setWorldType", GameFunctions::luaGameSetWorldType); registerMethod(L, "Game", "getReturnMessage", GameFunctions::luaGameGetReturnMessage); @@ -117,7 +116,6 @@ class GameFunctions final : LuaScriptInterface { static int luaGameSetGameState(lua_State* L); static int luaGameGetWorldType(lua_State* L); - static int luaGameSetWorldType(lua_State* L); static int luaGameGetReturnMessage(lua_State* L); diff --git a/src/lua/functions/core/game/lua_enums.cpp b/src/lua/functions/core/game/lua_enums.cpp index e4dc252be0d..27c00dee013 100644 --- a/src/lua/functions/core/game/lua_enums.cpp +++ b/src/lua/functions/core/game/lua_enums.cpp @@ -63,6 +63,7 @@ is "SILENCE", the registered full name will be "SOUND_EFFECT_TYPE_SILENCE". void(0) void LuaEnums::init(lua_State* L) { + initWorldTypes(L); initOthersEnums(L); initAccountEnums(L); initDailyRewardEnums(L); @@ -114,10 +115,16 @@ void LuaEnums::init(lua_State* L) { initConcoctionsEnum(L); } -void LuaEnums::initOthersEnums(lua_State* L) { - registerEnum(L, WORLD_TYPE_NO_PVP); +void LuaEnums::initWorldTypes(lua_State* L) { + registerEnum(L, WORLD_TYPE_NONE); registerEnum(L, WORLD_TYPE_PVP); + registerEnum(L, WORLD_TYPE_NO_PVP); registerEnum(L, WORLD_TYPE_PVP_ENFORCED); + registerEnum(L, WORLD_TYPE_RETRO_PVP); + registerEnum(L, WORLD_TYPE_RETRO_PVP_ENFORCED); +} + +void LuaEnums::initOthersEnums(lua_State* L) { registerEnum(L, AMMO_NONE); registerEnum(L, AMMO_BOLT); registerEnum(L, AMMO_ARROW); diff --git a/src/lua/functions/core/game/lua_enums.hpp b/src/lua/functions/core/game/lua_enums.hpp index 8beabbbacee..b1fb56a1eb0 100644 --- a/src/lua/functions/core/game/lua_enums.hpp +++ b/src/lua/functions/core/game/lua_enums.hpp @@ -18,6 +18,7 @@ class LuaEnums final : LuaScriptInterface { static void init(lua_State* L); private: + static void initWorldTypes(lua_State* L); static void initOthersEnums(lua_State* L); static void initAccountEnums(lua_State* L); static void initDailyRewardEnums(lua_State* L); diff --git a/src/map/house/house.cpp b/src/map/house/house.cpp index f2cb26114bf..48870974d4e 100644 --- a/src/map/house/house.cpp +++ b/src/map/house/house.cpp @@ -33,11 +33,10 @@ void House::setNewOwnerGuid(int32_t newOwnerGuid, bool serverStartup) { return; } - std::ostringstream query; - query << "UPDATE `houses` SET `new_owner` = " << newOwnerGuid << " WHERE `id` = " << id; + std::string query = fmt::format("UPDATE `houses` SET `new_owner` = {} WHERE `id` = {} AND `world_id` = {}", newOwnerGuid, id, g_game().worlds()->getCurrentWorld()->id); Database &db = Database::getInstance(); - db.executeQuery(query.str()); + db.executeQuery(query); if (!serverStartup) { setNewOwnership(); } @@ -88,12 +87,17 @@ bool House::tryTransferOwnership(std::shared_ptr player, bool serverStar } void House::setOwner(uint32_t guid, bool updateDatabase /* = true*/, std::shared_ptr player /* = nullptr*/) { + const auto worldId = g_game().worlds()->getCurrentWorld()->id; + if (updateDatabase && owner != guid) { Database &db = Database::getInstance(); - std::ostringstream query; - query << "UPDATE `houses` SET `owner` = " << guid << ", `new_owner` = -1, `bid` = 0, `bid_end` = 0, `last_bid` = 0, `highest_bidder` = 0 WHERE `id` = " << id; - db.executeQuery(query.str()); + std::string query = fmt::format( + "UPDATE `houses` SET `owner` = {}, `new_owner` = -1, `bid` = 0, `bid_end` = 0, `last_bid` = 0, `highest_bidder` = 0 WHERE `id` = {} AND `world_id` = {}", + guid, id, worldId + ); + + db.executeQuery(query); } if (isLoaded && owner == guid) { @@ -126,9 +130,8 @@ void House::setOwner(uint32_t guid, bool updateDatabase /* = true*/, std::shared if (guid != 0) { Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `name`, `account_id` FROM `players` WHERE `id` = " << guid; - DBResult_ptr result = db.storeQuery(query.str()); + std::string query = fmt::format("SELECT `name`, `account_id` FROM `players` WHERE `id` = {} AND `world_id` = {}", guid, worldId); + DBResult_ptr result = db.storeQuery(query); if (!result) { return; } diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 7ae02e31b10..eaf50f6c2a5 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -2158,14 +2158,15 @@ void ProtocolGame::parseHighscores(NetworkMessage &msg) { uint8_t category = msg.getByte(); uint32_t vocation = msg.get(); uint16_t page = 1; - const std::string worldName = msg.getString(); + auto worldName = msg.getString(); msg.getByte(); // Game World Category msg.getByte(); // BattlEye World Type if (type == HIGHSCORE_GETENTRIES) { page = std::max(1, msg.get()); } uint8_t entriesPerPage = std::min(30, std::max(5, msg.getByte())); - g_game().playerHighscores(player, type, category, vocation, worldName, page, entriesPerPage); + + g_game().playerHighscores(player, type, category, vocation, worldName == "OWN" || type == HIGHSCORE_OURRANK ? g_game().worlds()->getCurrentWorld()->name : worldName, page, entriesPerPage); } void ProtocolGame::parseTaskHuntingAction(NetworkMessage &msg) { @@ -2196,7 +2197,7 @@ void ProtocolGame::sendHighscoresNoData() { writeToOutputBuffer(msg); } -void ProtocolGame::sendHighscores(const std::vector &characters, uint8_t categoryId, uint32_t vocationId, uint16_t page, uint16_t pages, uint32_t updateTimer) { +void ProtocolGame::sendHighscores(const std::string &selectedWorld, const std::vector &characters, uint8_t categoryId, uint32_t vocationId, uint16_t page, uint16_t pages, uint32_t updateTimer) { if (oldProtocol) { return; } @@ -2205,10 +2206,13 @@ void ProtocolGame::sendHighscores(const std::vector &charact msg.addByte(0xB1); msg.addByte(0x00); // All data available - msg.addByte(1); // Worlds - auto serverName = g_configManager().getString(SERVER_NAME, __FUNCTION__); - msg.addString(serverName, "ProtocolGame::sendHighscores - g_configManager().getString(SERVER_NAME)"); // First World - msg.addString(serverName, "ProtocolGame::sendHighscores - g_configManager().getString(SERVER_NAME)"); // Selected World + const auto worlds = g_game().worlds()->getWorlds(); + msg.addByte(worlds.size()); // Worlds + for (const auto &world : worlds) { + msg.addString(world->name, "ProtocolGame::sendHighscores - world->name"); // Worlds + } + + msg.addString(selectedWorld, "ProtocolGame::sendHighscores - g_configManager().getString(SERVER_NAME)"); // Selected World msg.addByte(0); // Game World Category: 0xFF(-1) - Selected World msg.addByte(0); // BattlEye World Type @@ -2253,12 +2257,12 @@ void ProtocolGame::sendHighscores(const std::vector &charact msg.add(pages); // Pages msg.addByte(characters.size()); // Character Count - for (const HighscoreCharacter &character : characters) { + for (const auto &character : characters) { msg.add(character.rank); // Rank msg.addString(character.name, "ProtocolGame::sendHighscores - character.name"); // Character Name msg.addString(character.loyaltyTitle, "ProtocolGame::sendHighscores - character.loyaltyTitle"); // Character Loyalty Title msg.addByte(character.vocation); // Vocation Id - msg.addString(serverName, "ProtocolGame::sendHighscores - g_configManager().getString(SERVER_NAME)"); // World + msg.addString(character.worldName, "ProtocolGame::sendHighscores - character.worldName"); // World msg.add(character.level); // Level msg.addByte((player->getGUID() == character.id)); // Player Indicator Boolean msg.add(character.points); // Points @@ -3370,7 +3374,7 @@ void ProtocolGame::sendCreatureEmblem(std::shared_ptr creature) { } void ProtocolGame::sendCreatureSkull(std::shared_ptr creature) { - if (g_game().getWorldType() != WORLD_TYPE_PVP) { + if (g_game().worlds()->getCurrentWorld()->type != WORLD_TYPE_PVP) { return; } @@ -6462,7 +6466,7 @@ void ProtocolGame::sendPartyCreatureShield(std::shared_ptr target) { } void ProtocolGame::sendPartyCreatureSkull(std::shared_ptr target) { - if (g_game().getWorldType() != WORLD_TYPE_PVP) { + if (g_game().worlds()->getCurrentWorld()->type != WORLD_TYPE_PVP) { return; } diff --git a/src/server/network/protocol/protocolgame.hpp b/src/server/network/protocol/protocolgame.hpp index 2a5fde6c6c4..c8ad016280c 100644 --- a/src/server/network/protocol/protocolgame.hpp +++ b/src/server/network/protocol/protocolgame.hpp @@ -139,7 +139,7 @@ class ProtocolGame final : public Protocol { void parseHighscores(NetworkMessage &msg); void parseTaskHuntingAction(NetworkMessage &msg); void sendHighscoresNoData(); - void sendHighscores(const std::vector &characters, uint8_t categoryId, uint32_t vocationId, uint16_t page, uint16_t pages, uint32_t updateTimer); + void sendHighscores(const std::string &selectedWorld, const std::vector &characters, uint8_t categoryId, uint32_t vocationId, uint16_t page, uint16_t pages, uint32_t updateTimer); void parseGreet(NetworkMessage &msg); void parseBugReport(NetworkMessage &msg); diff --git a/src/server/network/protocol/protocollogin.cpp b/src/server/network/protocol/protocollogin.cpp index f8c68634f64..f7f038315d8 100644 --- a/src/server/network/protocol/protocollogin.cpp +++ b/src/server/network/protocol/protocollogin.cpp @@ -72,20 +72,22 @@ void ProtocolLogin::getCharacterList(const std::string &accountDescriptor, const output->addByte(0x64); - output->addByte(1); // number of worlds + std::vector> worlds = g_game().worlds()->getWorlds(); - output->addByte(0); // world id - output->addString(g_configManager().getString(SERVER_NAME, __FUNCTION__), "ProtocolLogin::getCharacterList - _configManager().getString(SERVER_NAME)"); - output->addString(g_configManager().getString(IP, __FUNCTION__), "ProtocolLogin::getCharacterList - g_configManager().getString(IP)"); + output->addByte(worlds.size()); // number of worlds - output->add(g_configManager().getNumber(GAME_PORT, __FUNCTION__)); - - output->addByte(0); + for (const auto &world : worlds) { + output->addByte(world->id); // world id + output->addString(world->name, "ProtocolLogin::getCharacterList - world.name"); + output->addString(world->ip, "ProtocolLogin::getCharacterList - world.ip"); + output->add(world->port); + output->addByte(0); // preview state + } uint8_t size = std::min(std::numeric_limits::max(), players.size()); output->addByte(size); - for (const auto &[name, deletion] : players) { - output->addByte(0); + for (const auto &[name, characterInfo] : players) { + output->addByte(characterInfo.worldId); output->addString(name, "ProtocolLogin::getCharacterList - name"); } diff --git a/src/server/network/protocol/protocolstatus.cpp b/src/server/network/protocol/protocolstatus.cpp index f9ec878eda9..d1eb7d60b3b 100644 --- a/src/server/network/protocol/protocolstatus.cpp +++ b/src/server/network/protocol/protocolstatus.cpp @@ -91,13 +91,14 @@ void ProtocolStatus::sendStatusString() { pugi::xml_node tsqp = doc.append_child("tsqp"); tsqp.append_attribute("version") = "1.0"; + const auto currentWorld = g_game().worlds()->getCurrentWorld(); pugi::xml_node serverinfo = tsqp.append_child("serverinfo"); uint64_t uptime = (OTSYS_TIME() - ProtocolStatus::start) / 1000; serverinfo.append_attribute("uptime") = std::to_string(uptime).c_str(); - serverinfo.append_attribute("ip") = g_configManager().getString(IP, __FUNCTION__).c_str(); - serverinfo.append_attribute("servername") = g_configManager().getString(ConfigKey_t::SERVER_NAME, __FUNCTION__).c_str(); + serverinfo.append_attribute("ip") = currentWorld->ip.c_str(); + serverinfo.append_attribute("servername") = currentWorld->name.c_str(); serverinfo.append_attribute("port") = std::to_string(g_configManager().getNumber(LOGIN_PORT, __FUNCTION__)).c_str(); - serverinfo.append_attribute("location") = g_configManager().getString(LOCATION, __FUNCTION__).c_str(); + serverinfo.append_attribute("location") = currentWorld->location_str.c_str(); serverinfo.append_attribute("url") = g_configManager().getString(URL, __FUNCTION__).c_str(); serverinfo.append_attribute("server") = ProtocolStatus::SERVER_NAME.c_str(); serverinfo.append_attribute("version") = ProtocolStatus::SERVER_VERSION.c_str(); @@ -165,10 +166,11 @@ void ProtocolStatus::sendStatusString() { void ProtocolStatus::sendInfo(uint16_t requestedInfo, const std::string &characterName) { auto output = OutputMessagePool::getOutputMessage(); + const auto currentWorld = g_game().worlds()->getCurrentWorld(); if (requestedInfo & REQUEST_BASIC_SERVER_INFO) { output->addByte(0x10); - output->addString(g_configManager().getString(ConfigKey_t::SERVER_NAME, __FUNCTION__), "ProtocolStatus::sendInfo - g_configManager().getString(stringConfig_t::SERVER_NAME)"); - output->addString(g_configManager().getString(IP, __FUNCTION__), "ProtocolStatus::sendInfo - g_configManager().getString(IP)"); + output->addString(currentWorld->name, "ProtocolStatus::sendInfo - currentWorld->name"); + output->addString(currentWorld->ip, "ProtocolStatus::sendInfo - currentWorld->ip"); output->addString(std::to_string(g_configManager().getNumber(LOGIN_PORT, __FUNCTION__)), "ProtocolStatus::sendInfo - std::to_string(g_configManager().getNumber(LOGIN_PORT))"); } @@ -180,8 +182,8 @@ void ProtocolStatus::sendInfo(uint16_t requestedInfo, const std::string &charact if (requestedInfo & REQUEST_MISC_SERVER_INFO) { output->addByte(0x12); - output->addString(g_configManager().getString(SERVER_MOTD, __FUNCTION__), "ProtocolStatus::sendInfo - g_configManager().getString(SERVER_MOTD)"); - output->addString(g_configManager().getString(LOCATION, __FUNCTION__), "ProtocolStatus::sendInfo - g_configManager().getString(LOCATION)"); + output->addString(currentWorld->motd, "ProtocolStatus::sendInfo - currentWorld->motd"); + output->addString(currentWorld->location_str, "ProtocolStatus::sendInfo - currentWorld->location_str"); output->addString(g_configManager().getString(URL, __FUNCTION__), "ProtocolStatus::sendInfo - g_configManager().getString(URL)"); output->add((OTSYS_TIME() - ProtocolStatus::start) / 1000); } diff --git a/src/server/server_definitions.hpp b/src/server/server_definitions.hpp index b957292b579..1e3e576310c 100644 --- a/src/server/server_definitions.hpp +++ b/src/server/server_definitions.hpp @@ -114,13 +114,14 @@ enum Supply_Stash_Actions_t : uint8_t { }; struct HighscoreCharacter { - HighscoreCharacter(std::string name, uint64_t points, uint32_t id, uint32_t rank, uint16_t level, uint8_t vocation, std::string loyaltyTitle) : + HighscoreCharacter(std::string name, uint64_t points, uint32_t id, uint32_t rank, uint16_t level, uint8_t vocation, std::string worldName, std::string loyaltyTitle) : name(std::move(name)), points(points), id(id), rank(rank), level(level), vocation(vocation), + worldName(std::move(worldName)), loyaltyTitle(std::move(loyaltyTitle)) { } std::string name; @@ -129,5 +130,6 @@ struct HighscoreCharacter { uint32_t rank; uint16_t level; uint8_t vocation; + std::string worldName; std::string loyaltyTitle; }; diff --git a/tests/unit/account/account_test.cpp b/tests/unit/account/account_test.cpp index c0c3ebbb832..59e37cb1a93 100644 --- a/tests/unit/account/account_test.cpp +++ b/tests/unit/account/account_test.cpp @@ -552,7 +552,7 @@ suite<"account"> accountTest = [] { Account acc { 1 }; accountRepository.addAccount( "canary@test.com", - AccountInfo { 1, 1, 1, AccountType::ACCOUNT_TYPE_GOD, {{ "Canary", 1 }, { "Canary2", 2 }} } + AccountInfo { 1, 1, 1, AccountType::ACCOUNT_TYPE_GOD, {{ "Canary", Character { 1, 1 } }, { "Canary2", Character { 2, 1 } }} } ); expect(acc.load() == enumToValue(AccountErrors_t::Ok)); @@ -561,8 +561,8 @@ suite<"account"> accountTest = [] { expect( eq(error, enumToValue(AccountErrors_t::Ok)) and eq(players.size(), 2) and - eq(players["Canary"], 1) and - eq(players["Canary2"], 2) + eq(players["Canary"].deletion, 1) and + eq(players["Canary2"].deletion, 2) ); }; @@ -572,7 +572,7 @@ suite<"account"> accountTest = [] { Account acc { 1 }; accountRepository.addAccount( "canary@test.com", - AccountInfo { 1, 1, 1, AccountType::ACCOUNT_TYPE_GOD, { { "Canary", 1 }, { "Canary2", 2 } } } + AccountInfo { 1, 1, 1, AccountType::ACCOUNT_TYPE_GOD, { { "Canary", Character { 1, 1 } }, { "Canary2", Character { 2, 1 } } } } ); expect(acc.load() == enumToValue(AccountErrors_t::Ok)); @@ -586,7 +586,7 @@ suite<"account"> accountTest = [] { Account acc { 1 }; accountRepository.addAccount( "session-key", - AccountInfo { 1, 1, 1, AccountType::ACCOUNT_TYPE_GOD, { { "Canary", 1 }, { "Canary2", 2 } }, false, getTimeNow() + 24 * 60 * 60 * 1000 } + AccountInfo { 1, 1, 1, AccountType::ACCOUNT_TYPE_GOD, { { "Canary", Character { 1, 1 } }, { "Canary2", Character { 2, 1 } } }, false, getTimeNow() + 24 * 60 * 60 * 1000 } ); expect(acc.load() == enumToValue(AccountErrors_t::Ok)); diff --git a/vcproj/canary.vcxproj b/vcproj/canary.vcxproj index 77ece957935..9b8eb953c26 100644 --- a/vcproj/canary.vcxproj +++ b/vcproj/canary.vcxproj @@ -68,6 +68,7 @@ + @@ -279,6 +280,7 @@ +