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 @@
+