diff --git a/config.lua.dist b/config.lua.dist index 3bb7a1b3b7..bf56a2aec1 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -89,7 +89,6 @@ mysqlSock = "" -- intervals regardless of other actions such as item (potion) use. This setting -- may cause high CPU usage with many players and potentially affect performance! -- checkDuplicateStorageKeys checks the values stored in the variables for duplicates. -allowChangeOutfit = true freePremium = false maxMessageBuffer = 4 emoteSpells = false diff --git a/data/XML/mounts.xml b/data/XML/mounts.xml deleted file mode 100644 index b50f8e885d..0000000000 --- a/data/XML/mounts.xml +++ /dev/null @@ -1,212 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/XML/outfits.xml b/data/XML/outfits.xml deleted file mode 100644 index 0902c44aa3..0000000000 --- a/data/XML/outfits.xml +++ /dev/null @@ -1,226 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/cpplinter.lua b/data/cpplinter.lua index 6d63776399..56fadcafad 100644 --- a/data/cpplinter.lua +++ b/data/cpplinter.lua @@ -63,12 +63,13 @@ configManager = {} ---@field getBestiary fun(): table ---@field getCurrencyItems fun(): table ---@field getItemTypeByClientId fun(clientId: number): ItemType ----@field getMountIdByLookType fun(lookType: number): number ---@field getParties fun(): table ---@field getTowns fun(): table ---@field getHouses fun(): table ---@field getOutfits fun(sex: number): table +---@field getOutfitByLookType fun(lookType: number): Outfit_t ---@field getMounts fun(): table +---@field getMountByLookType fun(lookType: number): table ---@field getVocations fun(): table ---@field getGameState fun(): string ---@field setGameState fun(state: string): boolean @@ -422,12 +423,23 @@ Creature = {} ---@field removeOutfitAddon fun(self: Player, outfitId: number, addonId: number) ---@field hasOutfit fun(self: Player, outfitId: number, addon?: number): boolean ---@field canWearOutfit fun(self: Player, outfitId: number, addonId?: number): boolean +---@field getCurrentOutfit fun(self: Player): Outfit +---@field setCurrentOutfit fun(self: Player, outfit: Outfit) +---@field getDefaultOutfit fun(self: Player): Outfit +---@field setDefaultOutfit fun(self: Player, outfit: Outfit) ---@field sendOutfitWindow fun(self: Player) ----@field sendEditPodium fun(self: Player, item: Item) +---@field getRandomizeMount fun(self: Player): boolean +---@field setRandomizeMount fun(self: Player, randomize: boolean) +---@field getLastMountToggle fun(self: Player): number +---@field setLastMountToggle fun(self: Player, timestamp: number) +---@field getCurrentMount fun(self: Player): number +---@field mount fun(self: Player, mountId: number) +---@field dismount fun(self: Player) +---@field sendPodiumWindow fun(self: Player, item: Item) ---@field addMount fun(self: Player, mountId: number) ---@field removeMount fun(self: Player, mountId: number) ---@field hasMount fun(self: Player, mountId: number): boolean ----@field toggleMount fun(self: Player, active: boolean) +---@field toggleMount fun(self: Player, active: boolean): boolean ---@field getPremiumEndsAt fun(self: Player): number ---@field setPremiumEndsAt fun(self: Player, timestamp: number) ---@field hasBlessing fun(self: Player, blessingId: number): boolean @@ -1855,14 +1867,13 @@ RELOAD_TYPE_GLOBAL = 5 RELOAD_TYPE_GLOBALEVENTS = 6 RELOAD_TYPE_ITEMS = 7 RELOAD_TYPE_MONSTERS = 8 -RELOAD_TYPE_MOUNTS = 9 -RELOAD_TYPE_MOVEMENTS = 10 -RELOAD_TYPE_NPCS = 11 -RELOAD_TYPE_QUESTS = 12 -RELOAD_TYPE_SCRIPTS = 13 -RELOAD_TYPE_SPELLS = 14 -RELOAD_TYPE_TALKACTIONS = 15 -RELOAD_TYPE_WEAPONS = 16 +RELOAD_TYPE_MOVEMENTS = 9 +RELOAD_TYPE_NPCS = 10 +RELOAD_TYPE_QUESTS = 11 +RELOAD_TYPE_SCRIPTS = 12 +RELOAD_TYPE_SPELLS = 13 +RELOAD_TYPE_TALKACTIONS = 14 +RELOAD_TYPE_WEAPONS = 15 PlayerFlag_CannotUseCombat = 1 * 2 ^ 0 PlayerFlag_CannotAttackPlayer = 1 * 2 ^ 1 @@ -2039,44 +2050,43 @@ SKILL_LAST = SKILL_FISHING ---@type table configKeys = { -- ConfigKeysBoolean - ALLOW_CHANGEOUTFIT = 0, - ONE_PLAYER_ON_ACCOUNT = 1, - AIMBOT_HOTKEY_ENABLED = 2, - REMOVE_RUNE_CHARGES = 3, - REMOVE_WEAPON_AMMO = 4, - REMOVE_WEAPON_CHARGES = 5, - REMOVE_POTION_CHARGES = 6, - EXPERIENCE_FROM_PLAYERS = 7, - FREE_PREMIUM = 8, - REPLACE_KICK_ON_LOGIN = 9, - ALLOW_CLONES = 10, - ALLOW_WALKTHROUGH = 11, - BIND_ONLY_GLOBAL_ADDRESS = 12, - OPTIMIZE_DATABASE = 13, - MARKET_PREMIUM = 14, - EMOTE_SPELLS = 15, - STAMINA_SYSTEM = 16, - WARN_UNSAFE_SCRIPTS = 17, - CONVERT_UNSAFE_SCRIPTS = 18, - CLASSIC_EQUIPMENT_SLOTS = 19, - CLASSIC_ATTACK_SPEED = 20, - SCRIPTS_CONSOLE_LOGS = 21, - SERVER_SAVE_NOTIFY_MESSAGE = 22, - SERVER_SAVE_CLEAN_MAP = 23, - SERVER_SAVE_CLOSE = 24, - SERVER_SAVE_SHUTDOWN = 25, - ONLINE_OFFLINE_CHARLIST = 26, - YELL_ALLOW_PREMIUM = 27, - PREMIUM_TO_SEND_PRIVATE = 28, - HOUSE_OWNED_BY_ACCOUNT = 29, - CLEAN_PROTECTION_ZONES = 30, - HOUSE_DOOR_SHOW_PRICE = 31, - ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS = 32, - REMOVE_ON_DESPAWN = 33, - TWO_FACTOR_AUTH = 34, - MANASHIELD_BREAKABLE = 35, - CHECK_DUPLICATE_STORAGE_KEYS = 36, - MONSTER_OVERSPAWN = 37, + ONE_PLAYER_ON_ACCOUNT = 0, + AIMBOT_HOTKEY_ENABLED = 1, + REMOVE_RUNE_CHARGES = 2, + REMOVE_WEAPON_AMMO = 3, + REMOVE_WEAPON_CHARGES = 4, + REMOVE_POTION_CHARGES = 5, + EXPERIENCE_FROM_PLAYERS = 6, + FREE_PREMIUM = 7, + REPLACE_KICK_ON_LOGIN = 8, + ALLOW_CLONES = 9, + ALLOW_WALKTHROUGH = 10, + BIND_ONLY_GLOBAL_ADDRESS = 11, + OPTIMIZE_DATABASE = 12, + MARKET_PREMIUM = 13, + EMOTE_SPELLS = 14, + STAMINA_SYSTEM = 15, + WARN_UNSAFE_SCRIPTS = 16, + CONVERT_UNSAFE_SCRIPTS = 17, + CLASSIC_EQUIPMENT_SLOTS = 18, + CLASSIC_ATTACK_SPEED = 19, + SCRIPTS_CONSOLE_LOGS = 20, + SERVER_SAVE_NOTIFY_MESSAGE = 21, + SERVER_SAVE_CLEAN_MAP = 22, + SERVER_SAVE_CLOSE = 23, + SERVER_SAVE_SHUTDOWN = 24, + ONLINE_OFFLINE_CHARLIST = 25, + YELL_ALLOW_PREMIUM = 26, + PREMIUM_TO_SEND_PRIVATE = 27, + HOUSE_OWNED_BY_ACCOUNT = 28, + CLEAN_PROTECTION_ZONES = 29, + HOUSE_DOOR_SHOW_PRICE = 30, + ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS = 31, + REMOVE_ON_DESPAWN = 32, + TWO_FACTOR_AUTH = 33, + MANASHIELD_BREAKABLE = 34, + CHECK_DUPLICATE_STORAGE_KEYS = 35, + MONSTER_OVERSPAWN = 36, -- ConfigKeysString MAP_NAME = 0, diff --git a/data/lib/compat/compat.lua b/data/lib/compat/compat.lua index 05a97d4e6f..3fac9bdc93 100644 --- a/data/lib/compat/compat.lua +++ b/data/lib/compat/compat.lua @@ -1518,17 +1518,6 @@ do end end -do - local mounts = {} - for _, mountData in pairs(Game.getMounts()) do - mounts[mountData.clientId] = mountData.name - end - - function getMountNameByLookType(lookType) - return mounts[lookType] - end -end - function indexToCombatType(idx) return 1 << idx end diff --git a/data/lib/core/network_message.lua b/data/lib/core/network_message.lua index 7cb867c44b..5f5d074425 100644 --- a/data/lib/core/network_message.lua +++ b/data/lib/core/network_message.lua @@ -9,3 +9,21 @@ end function NetworkMessage:addBool(value) self:addByte(value and 1 or 0) end + +function NetworkMessage:addItemId(itemId) + local it = ItemType(itemId) + self:addU16(it:getClientId()) +end + +function NetworkMessage:addOutfit(outfit) + self:addU16(outfit.lookType) + if outfit.lookType ~= 0 then + self:addByte(outfit.lookHead) + self:addByte(outfit.lookBody) + self:addByte(outfit.lookLegs) + self:addByte(outfit.lookFeet) + self:addByte(outfit.lookAddons) + else + self:addItemId(outfit.lookTypeEx) + end +end diff --git a/data/lib/core/player.lua b/data/lib/core/player.lua index 86c1468b8f..ccc8339ee9 100644 --- a/data/lib/core/player.lua +++ b/data/lib/core/player.lua @@ -337,22 +337,6 @@ function Player.getTotalMoney(self) return self:getMoney() + self:getBankBalance() end -function Player.addAddonToAllOutfits(self, addon) - for sex = 0, 1 do - local outfits = Game.getOutfits(sex) - for outfit = 1, #outfits do - self:addOutfitAddon(outfits[outfit].lookType, addon) - end - end -end - -function Player.addAllMounts(self) - local mounts = Game.getMounts() - for mount = 1, #mounts do - self:addMount(mounts[mount].id) - end -end - function Player.setSpecialContainersAvailable(self, available) local msg = NetworkMessage() msg:addByte(0x2A) diff --git a/data/lib/core/storages.lua b/data/lib/core/storages.lua index 0f260029d5..31efe320cb 100644 --- a/data/lib/core/storages.lua +++ b/data/lib/core/storages.lua @@ -2,7 +2,6 @@ Reserved player storage ranges: - 300000 to 301000+ reserved for achievements - 20000 to 21000+ reserved for achievement progress -- 10000000 to 20000000 reserved for outfits and mounts on source ]]-- AccountStorageKeys = { @@ -49,4 +48,10 @@ PlayerStorageKeys = { -- Bestiary: bestiaryKillsBase = 400000, bestiaryTrackerBase = 500000, + + -- Outfits and mounts: + currentMount = 60000, + randomizeMount = 60001, + outfitsBase = 600000, + mountsBase = 610000, } diff --git a/data/migrations/37.lua b/data/migrations/37.lua index d0ffd9c0cb..eea226154e 100644 --- a/data/migrations/37.lua +++ b/data/migrations/37.lua @@ -1,3 +1,89 @@ +-- must match PlayerStorageKeys in storages.lua, change accordingly if modified +local CURRENT_MOUNT = 60000 +local RANDOMIZE_MOUNT = 60001 +local OUTFITS_BASE = 600000 +local MOUNTS_BASE = 610000 + function onUpdateDatabase() - return false + print("> Updating database to version 38 (revert outfits/mounts to storages)") + + local tx = DBTransaction() + if not tx.begin() then + return false + end + + local query = DBInsert("INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES ") + + do + local resultId = db.storeQuery("SELECT `player_id`, `outfit_id`, `addons` FROM `player_outfits`") + if resultId then + repeat + local playerId = result.getNumber(resultId, "player_id") + local outfitId = result.getNumber(resultId, "outfit_id") + local addons = result.getNumber(resultId, "addons") + + local storageKey = OUTFITS_BASE + outfitId + query:addRow(string.format("%d, %d, %d", playerId, storageKey, addons)) + until not result.next(resultId) + result.free(resultId) + end + end + + do + local resultId = db.storeQuery("SELECT `player_id`, `mount_id` FROM `player_mounts`") + if resultId then + repeat + local playerId = result.getNumber(resultId, "player_id") + local mountId = result.getNumber(resultId, "mount_id") + + local storageKey = MOUNTS_BASE + mountId + query:addRow(string.format("%d, %d, %d", playerId, storageKey, 1)) + until not result.next(resultId) + result.free(resultId) + end + end + + -- Migrate currentmount and randomizemount from players table + do + local resultId = db.storeQuery( + "SELECT `id`, `currentmount`, `randomizemount` FROM `players` WHERE `currentmount` > 0 OR `randomizemount` > 0") + if resultId then + repeat + local playerId = result.getNumber(resultId, "id") + local currentMount = result.getNumber(resultId, "currentmount") + local randomizeMount = result.getNumber(resultId, "randomizemount") + + if currentMount > 0 then + query:addRow(string.format("%d, %d, %d", playerId, CURRENT_MOUNT, currentMount)) + end + + if randomizeMount > 0 then + query:addRow(string.format("%d, %d, %d", playerId, RANDOMIZE_MOUNT, randomizeMount)) + end + until not result.next(resultId) + result.free(resultId) + end + end + + if not query:execute() then + tx.rollback() + return false + end + + if not db.query("DROP TABLE IF EXISTS `player_outfits`") then + tx.rollback() + return false + end + + if not db.query("DROP TABLE IF EXISTS `player_mounts`") then + tx.rollback() + return false + end + + if not db.query("ALTER TABLE `players` DROP COLUMN `currentmount`, DROP COLUMN `randomizemount`") then + tx.rollback() + return false + end + + return tx.commit() end diff --git a/data/migrations/38.lua b/data/migrations/38.lua new file mode 100644 index 0000000000..d0ffd9c0cb --- /dev/null +++ b/data/migrations/38.lua @@ -0,0 +1,3 @@ +function onUpdateDatabase() + return false +end diff --git a/data/scripts/events/player.lua b/data/scripts/events/player.lua index 42ea256ef3..b9d91d401d 100644 --- a/data/scripts/events/player.lua +++ b/data/scripts/events/player.lua @@ -100,7 +100,7 @@ function Player:onPodiumRequest(item) return end - self:sendEditPodium(item) + self:sendPodiumWindow(item) end function Player:onPodiumEdit(item, outfit, direction, isVisible) @@ -122,8 +122,8 @@ function Player:onPodiumEdit(item, outfit, direction, isVisible) end -- reset mount if unable to ride - local mount = Game.getMountIdByLookType(outfit.lookMount) - if not (mount and self:hasMount(mount)) then + local mount = Game.getMountByLookType(outfit.lookMount) + if not mount or not self:hasMount(mount.lookType) then outfit.lookMount = 0 end end diff --git a/data/scripts/network/outfit.lua b/data/scripts/network/outfit.lua deleted file mode 100644 index 37571e511c..0000000000 --- a/data/scripts/network/outfit.lua +++ /dev/null @@ -1,11 +0,0 @@ -local handler = PacketHandler(0xD2) - -function handler.onReceive(player, msg) - if not configManager.getBoolean(configKeys.ALLOW_CHANGEOUTFIT) then - return - end - - player:sendOutfitWindow() -end - -handler:register() diff --git a/data/scripts/network/toggle_mount.lua b/data/scripts/network/toggle_mount.lua deleted file mode 100644 index 36da2c6045..0000000000 --- a/data/scripts/network/toggle_mount.lua +++ /dev/null @@ -1,8 +0,0 @@ -local handler = PacketHandler(0xD4) - -function handler.onReceive(player, msg) - local mount = msg:getByte() ~= 0 - player:toggleMount(mount) -end - -handler:register() diff --git a/data/scripts/systems/outfits/commands/add_addon.lua b/data/scripts/systems/outfits/commands/add_addon.lua new file mode 100644 index 0000000000..4e22bcab46 --- /dev/null +++ b/data/scripts/systems/outfits/commands/add_addon.lua @@ -0,0 +1,58 @@ +-- /addaddon , , +-- Adds addon 1 or 2 to an owned outfit for the target player. +local talkaction = TalkAction("/addaddon") + +function talkaction.onSay(player, words, param) + local split = param:splitTrimmed(",") + if #split < 3 then + player:sendCancelMessage("Insufficient parameters.") + return false + end + + local target = Player(split[1]) + if not target then + player:sendCancelMessage("A player with that name is not online.") + return false + end + + local outfit = Game.getOutfit(split[2], target:getSex()) + if not outfit then + player:sendCancelMessage("Outfit " .. split[2] .. " does not exist.") + return false + end + + if not target:hasOutfit(outfit.lookType) then + player:sendCancelMessage("Target does not have this outfit.") + return false + end + + local addon = tonumber(split[3]) + if addon ~= 1 and addon ~= 2 then + player:sendCancelMessage("Invalid addon value.") + return false + end + + if target:hasOutfitAddon(outfit.lookType, addon) then + player:sendCancelMessage("Target already has this outfit with this addon.") + return false + end + + if not target:addOutfitAddon(outfit.lookType, addon) then + player:sendCancelMessage("Failed to add addon to the outfit.") + return false + end + + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have added addon " .. addon .. " for outfit " .. outfit.name .. + " to " .. target:getName() .. ".") + + if Outfits.PrintCommandsToConsole then + print(player:getName() .. " has granted addon " .. addon .. " for outfit " .. outfit.name .. " to " .. + target:getName() .. ".") + end + + return true +end + +talkaction:separator(" ") +talkaction:accountType(ACCOUNT_TYPE_GAMEMASTER) +talkaction:register() diff --git a/data/scripts/systems/outfits/commands/add_mount.lua b/data/scripts/systems/outfits/commands/add_mount.lua new file mode 100644 index 0000000000..0f8f0ee645 --- /dev/null +++ b/data/scripts/systems/outfits/commands/add_mount.lua @@ -0,0 +1,46 @@ +-- /addmount , +-- Grants the mount to the target player if not already owned. +local talkaction = TalkAction("/addmount") + +function talkaction.onSay(player, words, param) + local split = param:splitTrimmed(",") + if #split < 2 then + player:sendCancelMessage("Insufficient parameters.") + return false + end + + local target = Player(split[1]) + if not target then + player:sendCancelMessage("A player with that name is not online.") + return false + end + + local mount = Game.getMount(split[2]) + if not mount then + player:sendCancelMessage("Mount " .. split[2] .. " does not exist.") + return false + end + + if target:hasMount(mount.lookType) then + player:sendCancelMessage("Target already has this mount.") + return false + end + + if not target:addMount(mount.lookType) then + player:sendCancelMessage("Failed to add the mount.") + return false + end + + player:sendTextMessage(MESSAGE_INFO_DESCR, + "You have granted mount " .. mount.name .. " to " .. target:getName() .. ".") + + if Outfits.PrintCommandsToConsole then + print(player:getName() .. " has granted mount " .. mount.name .. " to " .. target:getName() .. ".") + end + + return true +end + +talkaction:separator(" ") +talkaction:accountType(ACCOUNT_TYPE_GAMEMASTER) +talkaction:register() diff --git a/data/scripts/systems/outfits/commands/add_outfit.lua b/data/scripts/systems/outfits/commands/add_outfit.lua new file mode 100644 index 0000000000..46f4c765fc --- /dev/null +++ b/data/scripts/systems/outfits/commands/add_outfit.lua @@ -0,0 +1,46 @@ +-- /addoutfit , +-- Grants the outfit to the target player if not already owned. +local talkaction = TalkAction("/addoutfit") + +function talkaction.onSay(player, words, param) + local split = param:splitTrimmed(",") + if #split < 2 then + player:sendCancelMessage("Insufficient parameters.") + return false + end + + local target = Player(split[1]) + if not target then + player:sendCancelMessage("A player with that name is not online.") + return false + end + + local outfit = Game.getOutfit(split[2], target:getSex()) + if not outfit then + player:sendCancelMessage("Outfit " .. split[2] .. " does not exist.") + return false + end + + if target:hasOutfit(outfit.lookType) then + player:sendCancelMessage("Target already has this outfit.") + return false + end + + if not target:addOutfit(outfit.lookType) then + player:sendCancelMessage("Failed to add the outfit.") + return false + end + + player:sendTextMessage(MESSAGE_INFO_DESCR, + "You have granted outfit " .. outfit.name .. " to " .. target:getName() .. ".") + + if Outfits.PrintCommandsToConsole then + print(player:getName() .. " has granted outfit " .. outfit.name .. " to " .. target:getName() .. ".") + end + + return true +end + +talkaction:separator(" ") +talkaction:accountType(ACCOUNT_TYPE_GAMEMASTER) +talkaction:register() diff --git a/data/scripts/systems/outfits/commands/remove_addon.lua b/data/scripts/systems/outfits/commands/remove_addon.lua new file mode 100644 index 0000000000..84354c6a51 --- /dev/null +++ b/data/scripts/systems/outfits/commands/remove_addon.lua @@ -0,0 +1,58 @@ +-- /removeaddon , , +-- Removes addon 1 or 2 from an owned outfit for the target player. +local talkaction = TalkAction("/removeaddon") + +function talkaction.onSay(player, words, param) + local split = param:splitTrimmed(",") + if #split < 3 then + player:sendCancelMessage("Insufficient parameters.") + return false + end + + local target = Player(split[1]) + if not target then + player:sendCancelMessage("A player with that name is not online.") + return false + end + + local outfit = Game.getOutfit(split[2], target:getSex()) + if not outfit then + player:sendCancelMessage("Outfit " .. split[2] .. " does not exist.") + return false + end + + if not target:hasOutfit(outfit.lookType) then + player:sendCancelMessage("Target does not have this outfit.") + return false + end + + local addon = tonumber(split[3]) + if addon ~= 1 and addon ~= 2 then + player:sendCancelMessage("Invalid addon value.") + return false + end + + if not target:hasOutfitAddon(outfit.lookType, addon) then + player:sendCancelMessage("Target does not have this outfit with this addon.") + return false + end + + if not target:removeOutfitAddon(outfit.lookType, addon) then + player:sendCancelMessage("Failed to remove the addon from the outfit.") + return false + end + + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have removed addon " .. addon .. " for outfit " .. outfit.name .. + " from " .. target:getName() .. ".") + + if Outfits.PrintCommandsToConsole then + print(player:getName() .. " has removed addon " .. addon .. " for outfit " .. outfit.name .. " from " .. + target:getName() .. ".") + end + + return true +end + +talkaction:separator(" ") +talkaction:accountType(ACCOUNT_TYPE_GAMEMASTER) +talkaction:register() diff --git a/data/scripts/systems/outfits/commands/remove_mount.lua b/data/scripts/systems/outfits/commands/remove_mount.lua new file mode 100644 index 0000000000..bb58fc1a2c --- /dev/null +++ b/data/scripts/systems/outfits/commands/remove_mount.lua @@ -0,0 +1,46 @@ +-- /removemount , +-- Removes the mount from the target player; dismounts if currently used. +local talkaction = TalkAction("/removemount") + +function talkaction.onSay(player, words, param) + local split = param:splitTrimmed(",") + if #split < 2 then + player:sendCancelMessage("Insufficient parameters.") + return false + end + + local target = Player(split[1]) + if not target then + player:sendCancelMessage("A player with that name is not online.") + return false + end + + local mount = Game.getMount(split[2]) + if not mount then + player:sendCancelMessage("Mount " .. split[2] .. " does not exist.") + return false + end + + if not target:hasMount(mount.lookType) then + player:sendCancelMessage("Target does not have this mount.") + return false + end + + if not target:removeMount(mount.lookType) then + player:sendCancelMessage("Failed to remove the mount.") + return false + end + + player:sendTextMessage(MESSAGE_INFO_DESCR, + "You have removed mount " .. mount.name .. " from " .. target:getName() .. ".") + + if Outfits.PrintCommandsToConsole then + print(player:getName() .. " has removed mount " .. mount.name .. " from " .. target:getName() .. ".") + end + + return true +end + +talkaction:separator(" ") +talkaction:accountType(ACCOUNT_TYPE_GAMEMASTER) +talkaction:register() diff --git a/data/scripts/systems/outfits/commands/remove_outfit.lua b/data/scripts/systems/outfits/commands/remove_outfit.lua new file mode 100644 index 0000000000..dce2f8c77f --- /dev/null +++ b/data/scripts/systems/outfits/commands/remove_outfit.lua @@ -0,0 +1,46 @@ +-- /removeoutfit , +-- Removes the owned outfit from the target player. +local talkaction = TalkAction("/removeoutfit") + +function talkaction.onSay(player, words, param) + local split = param:splitTrimmed(",") + if #split < 2 then + player:sendCancelMessage("Insufficient parameters.") + return false + end + + local target = Player(split[1]) + if not target then + player:sendCancelMessage("A player with that name is not online.") + return false + end + + local outfit = Game.getOutfit(split[2], target:getSex()) + if not outfit then + player:sendCancelMessage("Outfit " .. split[2] .. " does not exist.") + return false + end + + if not target:hasOutfit(outfit.lookType) then + player:sendCancelMessage("Target does not have this outfit.") + return false + end + + if not target:removeOutfit(outfit.lookType) then + player:sendCancelMessage("Failed to remove the outfit.") + return false + end + + player:sendTextMessage(MESSAGE_INFO_DESCR, + "You have removed outfit " .. outfit.name .. " from " .. target:getName() .. ".") + + if Outfits.PrintCommandsToConsole then + print(player:getName() .. " has removed outfit " .. outfit.name .. " from " .. target:getName() .. ".") + end + + return true +end + +talkaction:separator(" ") +talkaction:accountType(ACCOUNT_TYPE_GAMEMASTER) +talkaction:register() diff --git a/data/scripts/systems/outfits/config.lua b/data/scripts/systems/outfits/config.lua new file mode 100644 index 0000000000..e8ae3abacc --- /dev/null +++ b/data/scripts/systems/outfits/config.lua @@ -0,0 +1,24 @@ +-- Outfits & Mounts module configuration. +-- +-- Networking entry points: +-- - 0xD2: systems/outfits/network/request_outfit_window.lua +-- - 0xD3: systems/outfits/network/set_outfit.lua +-- - 0xD4: systems/outfits/network/toggle_mount.lua +-- +-- Core behavior: +-- - Mount selection/toggling: systems/outfits/core/mounts.lua +-- - Outfit/podium windows: systems/outfits/core/windows.lua +-- - Outfit ownership/addons: systems/outfits/core/outfits.lua +Outfits = { + -- Set false to block outfit changes via incoming packets (0xD2/0xD3). + AllowChangeOutfit = true, + + -- Set false to block mount toggles via incoming packets (0xD4). + AllowToggleMount = true, + + -- Cooldown (ms) enforced for manual mount toggles (Ctrl+R). Forced zone toggles may bypass this. + ToggleMountCooldown = 3000, + + -- Print outfit/mount command usage to console. + PrintCommandsToConsole = true +} diff --git a/data/scripts/systems/outfits/core/mounts.lua b/data/scripts/systems/outfits/core/mounts.lua new file mode 100644 index 0000000000..8b37e0fe55 --- /dev/null +++ b/data/scripts/systems/outfits/core/mounts.lua @@ -0,0 +1,239 @@ +-- Mount ownership, selection, and toggling. +-- +-- Persistent state: +-- - PlayerStorageKeys.mountsBase + lookType: mount ownership +-- - PlayerStorageKeys.currentMount: selected mount lookType (used when mounting) +-- - PlayerStorageKeys.randomizeMount: whether to select a random owned mount when mounting +-- +-- Session-only state: +-- - lastMountToggle[playerId]: used for Outfits.ToggleMountCooldown +-- - wasMounted[playerId]: remembers intent while forcibly dismounted in protection zones +do + local lastMountToggle = {} + function Player.getLastMountToggle(self) + return lastMountToggle[self:getId()] or 0 + end + + function Player.setLastMountToggle(self, time) + lastMountToggle[self:getId()] = time + end +end + +do + local wasMounted = {} + function Player.getWasMounted(self) + return wasMounted[self:getId()] or false + end + + function Player.setWasMounted(self, mounted) + wasMounted[self:getId()] = mounted or nil + end +end + +function Player.addMount(self, mountId) + return self:setStorageValue(PlayerStorageKeys.mountsBase + mountId, 1) +end + +function Player.addAllMounts(self) + local mounts = Game.getMounts() + for _, mount in ipairs(mounts) do + self:addMount(mount.lookType) + end +end + +function Player.hasMount(self, mountId) + local value = self:getStorageValue(PlayerStorageKeys.mountsBase + mountId) + return value ~= nil and value ~= -1 +end + +function Player.removeMount(self, mountId) + local value = self:removeStorageValue(PlayerStorageKeys.mountsBase + mountId) + if self:getCurrentMount() == mountId and self:isMounted() then + self:dismount() + end + return value +end + +function Player.removeAllMounts(self) + local mounts = Game.getMounts() + for _, mount in ipairs(mounts) do + self:removeMount(mount.lookType) + end + + if self:isMounted() then + self:dismount() + end +end + +-- Returns the selected mount lookType stored in PlayerStorageKeys.currentMount, or nil if unset. +function Player.getCurrentMount(self) + local value = self:getStorageValue(PlayerStorageKeys.currentMount) + if value == nil or value == -1 then + return nil + end + return value +end + +-- Sets the selected mount lookType (nil clears). For non-staff players, the mount must be owned. +function Player.setCurrentMount(self, mountId) + if mountId == nil then + return self:removeStorageValue(PlayerStorageKeys.currentMount) + end + + if not self:getGroup():getAccess() and not self:hasMount(mountId) then + return false + end + + return self:setStorageValue(PlayerStorageKeys.currentMount, mountId) +end + +function Player.getRandomizeMount(self) + local randomizeMount = self:getStorageValue(PlayerStorageKeys.randomizeMount) + return randomizeMount ~= nil and randomizeMount ~= -1 +end + +function Player.setRandomizeMount(self, randomize) + if randomize then + return self:setStorageValue(PlayerStorageKeys.randomizeMount, 1) + end + return self:removeStorageValue(PlayerStorageKeys.randomizeMount) +end + +-- Returns whether the player can ride the given mount lookType. +function Player.canRideMount(self, mountId) + if self:getGroup():getAccess() then + return true + end + + local mount = Game.getMountByLookType(mountId) + if not mount then + return false + end + + if mount.premium and not self:isPremium() then + return false + end + + return self:hasMount(mount.lookType) +end + +function Player.isMounted(self) + return self:getOutfit().lookMount ~= 0 +end + +-- Mounts the given mount object (from Game.getMountByLookType): updates outfit + speed. +function Player.mount(self, mount) + if mount == nil or mount.lookType == nil or mount.speed == nil then + return false + end + + local outfit = self:getDefaultOutfit() + outfit.lookMount = mount.lookType + self:setOutfit(outfit) + self:changeSpeed(mount.speed) + return true +end + +-- Dismounts the current mount: clears outfit mount + removes speed bonus. +function Player.dismount(self) + local outfit = self:getDefaultOutfit() + local lookMount = outfit.lookMount + outfit.lookMount = 0 + self:setOutfit(outfit) + + local mount = Game.getMountByLookType(lookMount) + if mount ~= nil then + self:changeSpeed(-mount.speed) + end +end + +local function getRandomMount(player) + local mounts = Game.getMounts() + + local availableMounts = {} + for _, mount in ipairs(mounts) do + if player:hasMount(mount.lookType) then + table.insert(availableMounts, mount.lookType) + end + end + + if #availableMounts == 0 then + return nil + end + + local idx = math.random(1, #availableMounts) + return availableMounts[idx] +end + +-- player:toggleMount(mounted) +-- Behavior: +-- - When mounted is true: mounts using the selected mount (or a random owned mount if randomize is enabled). +-- - When mounted is false: dismounts. +-- Enforces cooldown when mounting, protection-zone restriction, premium/ownership rules, and CONDITION_OUTFIT. +function Player.toggleMount(self, mounted) + if mounted then + if not self:getGroup():getAccess() and self:getWasMounted() then + local lastMountToggle = self:getLastMountToggle() + if os.mtime() - lastMountToggle < Outfits.ToggleMountCooldown then + return false + end + end + + if self:isMounted() then + return false + end + + local tile = self:getTile() + if not self:getGroup():getAccess() and tile:hasFlag(TILESTATE_PROTECTIONZONE) then + self:sendCancelMessage(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE) + return false + end + + local lookMount = self:getCurrentMount() + if not lookMount then + self:sendOutfitWindow() + return false + end + + if self:getRandomizeMount() then + lookMount = getRandomMount(self) + if not lookMount then + self:sendOutfitWindow() + return false + end + end + + local currentMount = Game.getMountByLookType(lookMount) + if not currentMount then + return false + end + + if not self:getGroup():getAccess() and currentMount.premium and not self:isPremium() then + self:sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT) + return false + end + + if self:hasCondition(CONDITION_OUTFIT) then + self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return false + end + + self:mount(currentMount) + else + if not self:isMounted() then + return false + end + + self:dismount() + end + + self:setOutfit(self:getDefaultOutfit()) + self:setLastMountToggle(os.mtime()) + return true +end + +-- Deprecated helper; mount lookType is already the identifier. +function Game.getMountIdByLookType(lookType) + print("Warning: Game.getMountIdByLookType is deprecated. Mounts are now identified by client ID.") + return lookType +end diff --git a/data/scripts/systems/outfits/core/outfits.lua b/data/scripts/systems/outfits/core/outfits.lua new file mode 100644 index 0000000000..e1a9472a18 --- /dev/null +++ b/data/scripts/systems/outfits/core/outfits.lua @@ -0,0 +1,145 @@ +-- Outfit ownership and addon helpers. +-- +-- This file manages outfit storage state using PlayerStorageKeys.outfitsBase. +-- Wearability checks (premium/unlocked/addons) are implemented in Player.canWearOutfit. +-- +-- Addon representation: +-- - Storage value is a bitmask of unlocked addons for a given lookType. +-- - bit 0 => addon 1 +-- - bit 1 => addon 2 +-- - Functions that take `addon` expect an addon index (1 or 2), not a bitmask. +-- - Functions that take `addons` expect a bitmask (e.g. 1, 2, or 3). +-- player:addOutfit(lookType) +-- Grants outfit ownership for the given lookType. +function Player.addOutfit(self, lookType) + local addons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) + if addons ~= nil and addons ~= -1 then + return true + end + return self:setStorageValue(PlayerStorageKeys.outfitsBase + lookType, 0) +end + +-- player:addAllOutfits() +-- Grants all outfits available for the player's sex. +-- This grants ownership only (addon mask = 0); it does not grant addons. +function Player.addAllOutfits(self) + local outfits = Game.getOutfits(self:getSex()) + for _, outfit in ipairs(outfits) do + self:addOutfit(outfit.lookType) + end +end + +-- player:addOutfitAddon(lookType, addon) +-- Adds addon 1 or 2 to an already-owned outfit. +-- +-- Note: `addon` is an index (1 or 2). Internally it sets the corresponding bit in storage. +function Player.addOutfitAddon(self, lookType, addon) + local addons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) + if addons == nil or addons == -1 then + return false + end + + addons = addons | (1 << (addon - 1)) + return self:setStorageValue(PlayerStorageKeys.outfitsBase + lookType, addons) +end + +-- player:addAddonToAllOutfits(addon) +-- Adds addon 1 or 2 to all outfits for both sexes. +-- This function also grants missing outfits before setting the addon bit. +function Player.addAddonToAllOutfits(self, addon) + for sex = 0, 1 do + local outfits = Game.getOutfits(sex) + for _, outfit in ipairs(outfits) do + self:addOutfit(outfit.lookType) + self:addOutfitAddon(outfit.lookType, addon) + end + end +end + +-- player:getOutfitAddons(lookType) -> number +-- Returns the stored addon bitmask for an owned outfit. +-- Returns 0 when the player does not own the outfit or has no addons. +function Player.getOutfitAddons(self, lookType) + local outfitAddons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) + if outfitAddons == nil or outfitAddons == -1 then + return 0 + end + return outfitAddons +end + +-- player:hasOutfit(lookType) -> boolean +-- Returns true if the player owns the outfit (regardless of addons). +function Player.hasOutfit(self, lookType) + local addons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) + return addons ~= nil and addons ~= -1 +end + +-- player:hasOutfitAddons(lookType, addons) -> boolean +-- Returns true if the player owns the outfit and has all addons in the given bitmask. +-- Example: addons == 3 means addon 1 and addon 2. +function Player.hasOutfitAddons(self, lookType, addons) + local currentAddons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) + if currentAddons == nil or currentAddons == -1 then + return false + end + return (currentAddons & addons) == addons +end + +-- player:hasOutfitAddon(lookType, addon) -> boolean +-- Returns true if the player has a specific addon (1 or 2) for the outfit. +function Player.hasOutfitAddon(self, lookType, addon) + local addons = self:getOutfitAddons(lookType) + return addons & (1 << (addon - 1)) ~= 0 +end + +-- player:removeOutfit(lookType) +-- Removes outfit ownership (and all addon bits) for the given lookType. +function Player.removeOutfit(self, lookType) + return self:removeStorageValue(PlayerStorageKeys.outfitsBase + lookType) +end + +-- player:removeOutfitAddon(lookType, addon) +-- Removes addon 1 or 2 from an owned outfit by clearing the corresponding bit. +function Player.removeOutfitAddon(self, lookType, addon) + local addons = self:getOutfitAddons(lookType) + addons = addons & ~(1 << (addon - 1)) + return self:setStorageValue(PlayerStorageKeys.outfitsBase + lookType, addons) +end + +-- player:canWearOutfit(lookType[, addons]) -> boolean +-- Returns whether the player is allowed to wear an outfit with the requested addons. +-- +-- Parameters: +-- - lookType: outfit lookType (client id) +-- - addons: addon bitmask (0, 1, 2, or 3). This is not an addon index. +-- +-- Checks: +-- - staff bypass +-- - valid outfit +-- - premium requirement +-- - ownership requirement for locked outfits +-- - addon bitmask subset requirement +function Player.canWearOutfit(self, lookType, addons) + if self:getGroup():getAccess() then + return true + end + + local outfit = Game.getOutfitByLookType(lookType) + if not outfit then + return false + end + + if outfit.premium and not self:isPremium() then + return false + end + + if not outfit.unlocked and not self:hasOutfit(lookType) then + return false + end + + if addons == nil or addons == 0 then + return true + end + + return self:getOutfitAddons(lookType) & addons == addons +end diff --git a/data/scripts/systems/outfits/core/windows.lua b/data/scripts/systems/outfits/core/windows.lua new file mode 100644 index 0000000000..da37cccd80 --- /dev/null +++ b/data/scripts/systems/outfits/core/windows.lua @@ -0,0 +1,260 @@ +-- Outfit and podium window builders. +-- +-- This file builds outgoing packets: +-- - 0xC8: Outfit Window (Player.sendOutfitWindow) +-- - 0xD8: Podium Window (Player.sendPodiumWindow) +local function canWearOutfit(player, outfit) + if not outfit.unlocked then + return player:hasOutfit(outfit.lookType) + end + + return not outfit.premium or player:isPremium() +end + +local function getAvailableOutfits(player) + local availableOutfits = {} + + local isAccessPlayer = player:getGroup():getAccess() + if isAccessPlayer then + -- Add GM outfit for staff members. + local gamemaster = { + name = "Gamemaster", + lookType = 75, + addons = 0 + } + table.insert(availableOutfits, gamemaster) + end + + local outfits = Game.getOutfits(player:getSex()) + for _, outfit in ipairs(outfits) do + if isAccessPlayer then + table.insert(availableOutfits, { + name = outfit.name, + lookType = outfit.lookType, + addons = 3 + }) + elseif canWearOutfit(player, outfit) then + local addons = player:getOutfitAddons(outfit.lookType) + table.insert(availableOutfits, { + name = outfit.name, + lookType = outfit.lookType, + addons = addons + }) + end + end + + return availableOutfits +end + +local function getAvailableMounts(player) + local mounts = Game.getMounts() + + local isAccessPlayer = player:getGroup():getAccess() + + local availableMounts = {} + for _, mount in ipairs(mounts) do + if isAccessPlayer or player:hasMount(mount.lookType) then + table.insert(availableMounts, { + lookType = mount.lookType, + name = mount.name + }) + end + end + return availableMounts +end + +local function getAvailableFamiliars(player) + return {} +end + +-- player:sendOutfitWindow() +-- Builds and sends packet 0xC8 (Outfit Window). +-- Structure: +-- - 0xC8 +-- - currentOutfit via msg:addOutfit +-- - if lookMount == 0: four mount color bytes (head/body/legs/feet) +-- - currentFamiliarLookType:u16 (sent as 0) +-- - outfits: count:u16 then {lookType:u16, name:string, addons:byte, mode:byte} +-- - mounts: count:u16 then {lookType:u16, name:string, mode:byte} +-- - familiars: count:u16 (currently 0) +-- - tryOutfitMode:byte (0) +-- - mounted:bool (uses getWasMounted to preserve intent while forcibly dismounted in PZ) +-- - randomizeMount:bool +function Player.sendOutfitWindow(self) + local availableOutfits = getAvailableOutfits(self) + if #availableOutfits == 0 then + self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return + end + + local currentOutfit = self:getDefaultOutfit() + if currentOutfit.lookType == 0 then + currentOutfit.lookType = availableOutfits[1].lookType + end + + -- If the player was forcibly dismounted (e.g. PZ), keep the checkbox state coherent. + local mounted = self:isMounted() or self:getWasMounted() + + local availableMounts = getAvailableMounts(self) + local availableFamiliars = getAvailableFamiliars(self) + + local msg = NetworkMessage() + msg:addByte(0xC8) + + msg:addOutfit(currentOutfit) + + msg:addU16(currentOutfit.lookMount) + msg:addByte(currentOutfit.lookMountHead) + msg:addByte(currentOutfit.lookMountBody) + msg:addByte(currentOutfit.lookMountLegs) + msg:addByte(currentOutfit.lookMountFeet) + + msg:addU16(0) -- current familiar looktype + + msg:addU16(#availableOutfits) + for _, outfit in ipairs(availableOutfits) do + msg:addU16(outfit.lookType) + msg:addString(outfit.name) + msg:addByte(outfit.addons) + msg:addByte(0) -- mode: 0x00 - available, 0x01 store (requires U32 store offerId), 0x02 golden outfit tooltip (hardcoded) + end + + msg:addU16(#availableMounts) + for _, mount in ipairs(availableMounts) do + msg:addU16(mount.lookType) + msg:addString(mount.name) + msg:addByte(0) -- mode: 0x00 - available, 0x01 store (requires U32 store offerlookType) + end + + msg:addU16(#availableFamiliars) + for _, familiar in ipairs(availableFamiliars) do + msg:addU16(familiar.lookType) + msg:addString(familiar.name) + msg:addByte(0) -- mode: 0x00 - available, 0x01 store (requires U32 store offerId) + end + + msg:addByte(0x00) -- try outfit mode (?) + msg:addBool(mounted) + msg:addBool(self:getRandomizeMount()) + msg:sendToPlayer(self) + msg:delete() +end + +-- player:sendPodiumWindow(item) +-- Builds and sends packet 0xD8 (Podium Window). +-- Structure: +-- - 0xD8 +-- - currentPodiumOutfit via msg:addOutfit +-- - currentMount block: lookMount:u16 + four mount color bytes +-- - currentFamiliarLookType:u16 (0) +-- - outfits list (same layout as 0xC8) +-- - mounts list (same layout as 0xC8) +-- - familiar count:u16 (0) +-- - windowMode:byte (5) +-- - showMountCheckbox:bool +-- - unknown:u16 (0) +-- - position:Position + itemClientId:u16 + stackpos:byte +-- - showPlatform:bool + outfitCheckbox:bool (ignored by client) + direction:byte +function Player.sendPodiumWindow(self, item) + local podium = item:getPodium() + if not podium then + return + end + + local tile = item:getTile() + if not tile then + return + end + + local it = ItemType(item:getId()) + if not it then + return + end + + local availableOutfits = getAvailableOutfits(self) + if #availableOutfits == 0 then + self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return + end + + local stackpos = tile:getThingIndex(item) + + local podiumOutfit = podium:getOutfit() + local playerOutfit = self:getDefaultOutfit() + local isEmpty = podiumOutfit.lookType == 0 and podiumOutfit.lookMount == 0 + + if podiumOutfit.lookType == 0 then + -- Copy player outfit. + podiumOutfit.lookType = playerOutfit.lookType + podiumOutfit.lookHead = playerOutfit.lookHead + podiumOutfit.lookBody = playerOutfit.lookBody + podiumOutfit.lookLegs = playerOutfit.lookLegs + podiumOutfit.lookFeet = playerOutfit.lookFeet + podiumOutfit.lookAddons = playerOutfit.lookAddons + end + + if podiumOutfit.lookMount == 0 then + -- Copy player mount. + podiumOutfit.lookMount = playerOutfit.lookMount + podiumOutfit.lookMountHead = playerOutfit.lookMountHead + podiumOutfit.lookMountBody = playerOutfit.lookMountBody + podiumOutfit.lookMountLegs = playerOutfit.lookMountLegs + podiumOutfit.lookMountFeet = playerOutfit.lookMountFeet + end + + if not self:canWearOutfit(podiumOutfit.lookType) then + -- select first outfit available when the one from podium is not unlocked + podiumOutfit.lookType = availableOutfits[1].lookType + end + + local availableMounts = getAvailableMounts(self) + + local msg = NetworkMessage() + msg:addByte(0xD8) + + -- current outfit + msg:addOutfit(podiumOutfit) + + -- current mount + msg:addU16(podiumOutfit.lookMount) + msg:addByte(podiumOutfit.lookMountHead) + msg:addByte(podiumOutfit.lookMountBody) + msg:addByte(podiumOutfit.lookMountLegs) + msg:addByte(podiumOutfit.lookMountFeet) + + -- current familiar (not used in podium) + msg:addU16(0) + + -- available outfits + msg:addU16(#availableOutfits) + for _, outfit in ipairs(availableOutfits) do + msg:addU16(outfit.lookType) + msg:addString(outfit.name) + msg:addByte(outfit.addons) + msg:addByte(0) -- mode: 0x00 - available, 0x01 store (requires U32 store offerId), 0x02 golden outfit tooltip (hardcoded) + end + + -- available mounts + msg:addU16(#availableMounts) + for _, mount in ipairs(availableMounts) do + msg:addU16(mount.lookType) + msg:addString(mount.name) + msg:addByte(0) -- mode: 0x00 - available, 0x01 store (requires U32 store offerId) + end + + -- available familiars (not used in podium) + msg:addU16(0) + + msg:addByte(0x05) -- "set outfit" window mode (5 = podium) + msg:addBool((isEmpty and playerOutfit.lookMount ~= 0) or podium:hasFlag(PODIUM_SHOW_MOUNT)) -- "mount" checkbox + msg:addU16(0) -- unknown + msg:addPosition(item:getPosition()) + msg:addU16(it:getClientId()) + msg:addByte(stackpos) + + msg:addBool(podium:hasFlag(PODIUM_SHOW_PLATFORM)) -- is platform visible + msg:addBool(true) -- "outfit" checkbox, ignored by the client + msg:addByte(podium:getDirection()) + msg:sendToPlayer(self) + msg:delete() +end diff --git a/data/scripts/systems/outfits/data/mounts.lua b/data/scripts/systems/outfits/data/mounts.lua new file mode 100644 index 0000000000..9c8c83154a --- /dev/null +++ b/data/scripts/systems/outfits/data/mounts.lua @@ -0,0 +1,1112 @@ +-- Mount data source for the Outfits & Mounts module. +-- +-- This file overrides the Game mount lookup helpers used throughout the module: +-- - Game.getMounts() +-- - Game.getMountByLookType(lookType) +-- - Game.getMountByName(name) +-- - Game.getMount(param) +-- +-- Each entry is keyed by lookType and includes: +-- - name (string) +-- - speed (number) +-- - premium (bool) +local mounts = { + [368] = { + name = "Widow Queen", + speed = 20, + premium = true + }, + [369] = { + name = "Racing Bird", + speed = 20, + premium = true + }, + [370] = { + name = "War Bear", + speed = 20, + premium = true + }, + [371] = { + name = "Black Sheep", + speed = 20, + premium = true + }, + [372] = { + name = "Midnight Panther", + speed = 20, + premium = true + }, + [373] = { + name = "Draptor", + speed = 20, + premium = true + }, + [374] = { + name = "Titanica", + speed = 20, + premium = true + }, + [375] = { + name = "Tin Lizzard", + speed = 20, + premium = true + }, + [376] = { + name = "Blazebringer", + speed = 20, + premium = true + }, + [377] = { + name = "Rapid Boar", + speed = 20, + premium = true + }, + [378] = { + name = "Stampor", + speed = 20, + premium = true + }, + [379] = { + name = "Undead Cavebear", + speed = 20, + premium = true + }, + [387] = { + name = "Donkey", + speed = 20, + premium = true + }, + [388] = { + name = "Tiger Slug", + speed = 20, + premium = true + }, + [389] = { + name = "Uniwheel", + speed = 20, + premium = true + }, + [390] = { + name = "Crystal Wolf", + speed = 20, + premium = true + }, + [392] = { + name = "War Horse", + speed = 20, + premium = true + }, + [401] = { + name = "Kingly Deer", + speed = 20, + premium = true + }, + [402] = { + name = "Tamed Panda", + speed = 20, + premium = true + }, + [405] = { + name = "Dromedary", + speed = 20, + premium = true + }, + [406] = { + name = "Scorpion King", + speed = 20, + premium = true + }, + [421] = { + name = "Rented Horse", + speed = 20, + premium = false + }, + [426] = { + name = "Armoured War Horse", + speed = 20, + premium = false + }, + [427] = { + name = "Shadow Draptor", + speed = 20, + premium = false + }, + [437] = { + name = "Rented Horse", + speed = 20, + premium = false + }, + [438] = { + name = "Rented Horse", + speed = 20, + premium = false + }, + [447] = { + name = "Lady Bug", + speed = 20, + premium = true + }, + [450] = { + name = "Manta Ray", + speed = 20, + premium = true + }, + [502] = { + name = "Ironblight", + speed = 20, + premium = true + }, + [503] = { + name = "Magma Crawler", + speed = 20, + premium = true + }, + [506] = { + name = "Dragonling", + speed = 20, + premium = true + }, + [515] = { + name = "Gnarlhound", + speed = 20, + premium = true + }, + [521] = { + name = "Crimson Ray", + speed = 20, + premium = false + }, + [522] = { + name = "Steelbeak", + speed = 20, + premium = false + }, + [526] = { + name = "Water Buffalo", + speed = 20, + premium = true + }, + [546] = { + name = "Tombstinger", + speed = 20, + premium = false + }, + [547] = { + name = "Platesaurian", + speed = 20, + premium = false + }, + [548] = { + name = "Ursagrodon", + speed = 20, + premium = true + }, + [559] = { + name = "The Hellgrip", + speed = 20, + premium = true + }, + [571] = { + name = "Noble Lion", + speed = 20, + premium = true + }, + [572] = { + name = "Desert King", + speed = 20, + premium = false + }, + [580] = { + name = "Shock Head", + speed = 20, + premium = true + }, + [606] = { + name = "Walker", + speed = 20, + premium = true + }, + [621] = { + name = "Azudocus", + speed = 20, + premium = false + }, + [622] = { + name = "Carpacosaurus", + speed = 20, + premium = false + }, + [624] = { + name = "Death Crawler", + speed = 20, + premium = false + }, + [626] = { + name = "Flamesteed", + speed = 20, + premium = false + }, + [627] = { + name = "Jade Lion", + speed = 20, + premium = false + }, + [628] = { + name = "Jade Pincer", + speed = 20, + premium = false + }, + [629] = { + name = "Nethersteed", + speed = 20, + premium = false + }, + [630] = { + name = "Tempest", + speed = 20, + premium = false + }, + [631] = { + name = "Winter King", + speed = 20, + premium = false + }, + [644] = { + name = "Doombringer", + speed = 20, + premium = false + }, + [647] = { + name = "Woodland Prince", + speed = 20, + premium = false + }, + [648] = { + name = "Hailstorm Fury", + speed = 20, + premium = false + }, + [649] = { + name = "Siegebreaker", + speed = 20, + premium = false + }, + [650] = { + name = "Poisonbane", + speed = 20, + premium = false + }, + [651] = { + name = "Blackpelt", + speed = 20, + premium = false + }, + [669] = { + name = "Golden Dragonfly", + speed = 20, + premium = false + }, + [670] = { + name = "Steel Bee", + speed = 20, + premium = false + }, + [671] = { + name = "Copper Fly", + speed = 20, + premium = false + }, + [672] = { + name = "Tundra Rambler", + speed = 20, + premium = false + }, + [673] = { + name = "Highland Yak", + speed = 20, + premium = false + }, + [674] = { + name = "Glacier Vagabond", + speed = 20, + premium = false + }, + [688] = { + name = "Flying Divan", + speed = 20, + premium = false + }, + [689] = { + name = "Magic Carpet", + speed = 20, + premium = false + }, + [690] = { + name = "Floating Kashmir", + speed = 20, + premium = false + }, + [691] = { + name = "Ringtail Waccoon", + speed = 20, + premium = false + }, + [692] = { + name = "Night Waccoon", + speed = 20, + premium = false + }, + [693] = { + name = "Emerald Waccoon", + speed = 20, + premium = false + }, + [682] = { + name = "Glooth Glider", + speed = 20, + premium = true + }, + [685] = { + name = "Shadow Hart", + speed = 20, + premium = false + }, + [686] = { + name = "Black Stag", + speed = 20, + premium = false + }, + [687] = { + name = "Emperor Deer", + speed = 20, + premium = false + }, + [726] = { + name = "Flitterkatzen", + speed = 20, + premium = false + }, + [727] = { + name = "Venompaw", + speed = 20, + premium = false + }, + [728] = { + name = "Batcat", + speed = 20, + premium = false + }, + [734] = { + name = "Sea Devil", + speed = 20, + premium = false + }, + [735] = { + name = "Coralripper", + speed = 20, + premium = false + }, + [736] = { + name = "Plumfish", + speed = 20, + premium = false + }, + [738] = { + name = "Gorongra", + speed = 20, + premium = false + }, + [739] = { + name = "Noctungra", + speed = 20, + premium = false + }, + [740] = { + name = "Silverneck", + speed = 20, + premium = false + }, + [761] = { + name = "Slagsnare", + speed = 20, + premium = false + }, + [762] = { + name = "Nightstinger", + speed = 20, + premium = false + }, + [763] = { + name = "Razorcreep", + speed = 20, + premium = false + }, + [848] = { + name = "Rift Runner", + speed = 20, + premium = true + }, + [849] = { + name = "Nightdweller", + speed = 20, + premium = false + }, + [850] = { + name = "Frostflare", + speed = 20, + premium = false + }, + [851] = { + name = "Cinderhoof", + speed = 20, + premium = false + }, + [868] = { + name = "Mouldpincer", + speed = 20, + premium = false + }, + [869] = { + name = "Bloodcurl", + speed = 20, + premium = false + }, + [870] = { + name = "Leafscuttler", + speed = 20, + premium = false + }, + [883] = { + name = "Sparkion", + speed = 20, + premium = true + }, + [886] = { + name = "Swamp Snapper", + speed = 20, + premium = false + }, + [887] = { + name = "Mould Shell", + speed = 20, + premium = false + }, + [888] = { + name = "Reed Lurker", + speed = 20, + premium = false + }, + [889] = { + name = "Neon Sparkid", + speed = 20, + premium = true + }, + [890] = { + name = "Vortexion", + speed = 20, + premium = true + }, + [901] = { + name = "Ivory Fang", + speed = 20, + premium = false + }, + [902] = { + name = "Shadow Claw", + speed = 20, + premium = false + }, + [903] = { + name = "Snow Pelt", + speed = 20, + premium = false + }, + [905] = { + name = "Jackalope", + speed = 20, + premium = false + }, + [906] = { + name = "Dreadhare", + speed = 20, + premium = false + }, + [907] = { + name = "Wolpertinger", + speed = 20, + premium = false + }, + [937] = { + name = "Stone Rhino", + speed = 20, + premium = true + }, + [950] = { + name = "Gold Sphinx", + speed = 20, + premium = false + }, + [951] = { + name = "Emerald Sphinx", + speed = 20, + premium = false + }, + [952] = { + name = "Shadow Sphinx", + speed = 20, + premium = false + }, + [959] = { + name = "Jungle Saurian", + speed = 20, + premium = false + }, + [960] = { + name = "Ember Saurian", + speed = 20, + premium = false + }, + [961] = { + name = "Lagoon Saurian", + speed = 20, + premium = false + }, + [1017] = { + name = "Blazing Unicorn", + speed = 20, + premium = false + }, + [1018] = { + name = "Arctic Unicorn", + speed = 20, + premium = false + }, + [1019] = { + name = "Prismatic unicorn", + speed = 20, + premium = false + }, + [1025] = { + name = "Cranium Spider", + speed = 20, + premium = false + }, + [1026] = { + name = "Cave Tarantula", + speed = 20, + premium = false + }, + [1027] = { + name = "Gloom Widow", + speed = 20, + premium = false + }, + [1049] = { + name = "Mole", + speed = 20, + premium = true + }, + [1052] = { + name = "Marsh Toad", + speed = 20, + premium = false + }, + [1053] = { + name = "Sanguine Frog", + speed = 20, + premium = false + }, + [1054] = { + name = "Toxic Toad", + speed = 20, + premium = false + }, + [1091] = { + name = "Ebony Tiger", + speed = 20, + premium = false + }, + [1092] = { + name = "Feral Tiger", + speed = 20, + premium = false + }, + [1093] = { + name = "Jungle Tiger", + speed = 20, + premium = false + }, + [1101] = { + name = "Fleeting Knowledge", + speed = 20, + premium = true + }, + [1104] = { + name = "Tawny Owl", + speed = 20, + premium = false + }, + [1105] = { + name = "Snowy Owl", + speed = 20, + premium = false + }, + [1106] = { + name = "Boreal Owl", + speed = 20, + premium = false + }, + [1150] = { + name = "Lacewing Moth", + speed = 20, + premium = true + }, + [1151] = { + name = "Hibernal Moth", + speed = 20, + premium = true + }, + [1163] = { + name = "Cold Percht Sleigh", + speed = 20, + premium = true + }, + [1164] = { + name = "Bright Percht Sleigh", + speed = 20, + premium = true + }, + [1165] = { + name = "Dark Percht Sleigh", + speed = 20, + premium = true + }, + [1167] = { + name = "Festive Snowman", + speed = 20, + premium = false + }, + [1168] = { + name = "Muffled Snowman", + speed = 20, + premium = false + }, + [1169] = { + name = "Caped Snowman", + speed = 20, + premium = false + }, + [1179] = { + name = "Rabbit Rickshaw", + speed = 20, + premium = false + }, + [1180] = { + name = "Bunny Dray", + speed = 20, + premium = false + }, + [1181] = { + name = "Cony Cart", + speed = 20, + premium = false + }, + [1183] = { + name = "River Crocovile", + speed = 20, + premium = false + }, + [1184] = { + name = "Swamp Crocovile", + speed = 20, + premium = false + }, + [1185] = { + name = "Nightmarish Crocovile", + speed = 20, + premium = false + }, + [1191] = { + name = "Gryphon", + speed = 20, + premium = true + }, + [1208] = { + name = "Jousting Eagle", + speed = 20, + premium = false + }, + [1209] = { + name = "Cerberus Champion", + speed = 20, + premium = false + }, + [1229] = { + name = "Cold Percht Sleigh Variant", + speed = 20, + premium = true + }, + [1230] = { + name = "Bright Percht Sleigh Variant", + speed = 20, + premium = true + }, + [1231] = { + name = "Dark Percht Sleigh Variant", + speed = 20, + premium = true + }, + [1232] = { + name = "Cold Percht Sleigh Final", + speed = 20, + premium = true + }, + [1233] = { + name = "Bright Percht Sleigh Final", + speed = 20, + premium = true + }, + [1234] = { + name = "Dark Percht Sleigh Final", + speed = 20, + premium = true + }, + [1247] = { + name = "Battle Badger", + speed = 20, + premium = false + }, + [1248] = { + name = "Ether Badger", + speed = 20, + premium = false + }, + [1249] = { + name = "Zaoan Badger", + speed = 20, + premium = false + }, + [1257] = { + name = "Blue Rolling Barrel", + speed = 20, + premium = true + }, + [1258] = { + name = "Red Rolling Barrel", + speed = 20, + premium = true + }, + [1259] = { + name = "Green Rolling Barrel", + speed = 20, + premium = true + }, + [1264] = { + name = "Floating Sage", + speed = 20, + premium = false + }, + [1265] = { + name = "Floating Scholar", + speed = 20, + premium = false + }, + [1266] = { + name = "Floating Augur", + speed = 20, + premium = false + }, + [1269] = { + name = "Haze", + speed = 20, + premium = true + }, + [1281] = { + name = "Antelope", + speed = 20, + premium = true + }, + [1284] = { + name = "Snow Strider", + speed = 20, + premium = false + }, + [1285] = { + name = "Dusk Pryer", + speed = 20, + premium = false + }, + [1286] = { + name = "Dawn Strayer", + speed = 20, + premium = false + }, + [1321] = { + name = "Phantasmal Jade", + speed = 20, + premium = true + }, + [1324] = { + name = "Savanna Ostrich", + speed = 20, + premium = true + }, + [1325] = { + name = "Coral Rhea", + speed = 20, + premium = true + }, + [1326] = { + name = "Eventide Nandu", + speed = 20, + premium = true + }, + [1333] = { + name = "Voracious Hyaena", + speed = 20, + premium = false + }, + [1334] = { + name = "Cunning Hyaena", + speed = 20, + premium = false + }, + [1335] = { + name = "Scruffy Hyaena", + speed = 20, + premium = false + }, + [1336] = { + name = "White Lion", + speed = 20, + premium = true + }, + [1363] = { + name = "Krakoloss", + speed = 20, + premium = true + }, + [1379] = { + name = "Merry Mammoth", + speed = 20, + premium = false + }, + [1380] = { + name = "Holiday Mammoth", + speed = 20, + premium = false + }, + [1381] = { + name = "Festive Mammoth", + speed = 20, + premium = false + }, + [1389] = { + name = "Void Watcher", + speed = 20, + premium = false + }, + [1390] = { + name = "Rune Watcher", + speed = 20, + premium = false + }, + [1391] = { + name = "Rift Watcher", + speed = 20, + premium = false + }, + [1417] = { + name = "Phant", + speed = 20, + premium = true + }, + [1430] = { + name = "Shellodon", + speed = 20, + premium = true + }, + [1431] = { + name = "Singeing Steed", + speed = 20, + premium = true + }, + [1439] = { + name = "Hyacinth", + speed = 20, + premium = false + }, + [1440] = { + name = "Peony", + speed = 20, + premium = false + }, + [1441] = { + name = "Dandelion", + speed = 20, + premium = false + }, + [1446] = { + name = "Rustwurm", + speed = 20, + premium = false + }, + [1447] = { + name = "Bogwurm", + speed = 20, + premium = false + }, + [1448] = { + name = "Gloomwurm", + speed = 20, + premium = false + }, + [1453] = { + name = "Emerald Raven", + speed = 20, + premium = false + }, + [1454] = { + name = "Mystic Raven", + speed = 20, + premium = false + }, + [1455] = { + name = "Radiant Raven", + speed = 20, + premium = false + }, + [1459] = { + name = "Gloothomotive", + speed = 20, + premium = true + }, + [1491] = { + name = "Topaz Shrine", + speed = 20, + premium = false + }, + [1492] = { + name = "Jade Shrine", + speed = 20, + premium = false + }, + [1493] = { + name = "Obsidian Shrine", + speed = 20, + premium = false + }, + [1526] = { + name = "Poppy Ibex", + speed = 20, + premium = false + }, + [1527] = { + name = "Mint Ibex", + speed = 20, + premium = false + }, + [1528] = { + name = "Cinnamon Ibex", + speed = 20, + premium = false + }, + [1536] = { + name = "Giant Beaver", + speed = 20, + premium = true + }, + [1577] = { + name = "Ripptor", + speed = 20, + premium = true + }, + [1578] = { + name = "Parade Horse", + speed = 20, + premium = false + }, + [1579] = { + name = "Jousting Horse", + speed = 20, + premium = false + }, + [1580] = { + name = "Tourney Horse", + speed = 20, + premium = false + }, + [1599] = { + name = "Mutated Abomination", + speed = 20, + premium = true + }, + [1608] = { + name = "Tangerine Flecked Koi", + speed = 20, + premium = false + }, + [1609] = { + name = "Brass Speckled Koi", + speed = 20, + premium = false + }, + [1610] = { + name = "Ink Spotted Koi", + speed = 20, + premium = false + } +} + +function Game.getMountByLookType(lookType) + local mount = mounts[lookType] + if not mount then + return nil + end + + return { + lookType = lookType, + name = mount.name, + speed = mount.speed, + premium = mount.premium + } +end + +function Game.getMountByName(name) + for lookType, mount in pairs(mounts) do + if mount.name:lower() == name:lower() then + return { + lookType = lookType, + name = mount.name, + speed = mount.speed, + premium = mount.premium + } + end + end + return nil +end + +function Game.getMount(param) + local lookType = tonumber(param) + if lookType then + return Game.getMountByLookType(lookType) + end + return Game.getMountByName(param) +end + +do + local cachedMounts = {} + + for lookType, mount in pairs(mounts) do + table.insert(cachedMounts, { + lookType = lookType, + name = mount.name, + speed = mount.speed, + premium = mount.premium + }) + end + + function Game.getMounts() + return cachedMounts + end +end diff --git a/data/scripts/systems/outfits/data/outfits.lua b/data/scripts/systems/outfits/data/outfits.lua new file mode 100644 index 0000000000..a358a558ee --- /dev/null +++ b/data/scripts/systems/outfits/data/outfits.lua @@ -0,0 +1,1624 @@ +-- Outfit data source for the Outfits & Mounts module. +-- +-- This file overrides the Game outfit lookup helpers used throughout the module: +-- - Game.getOutfits(sex) +-- - Game.getOutfitByLookType(lookType) +-- - Game.getOutfitByName(name, sex) +-- - Game.getOutfit(param, sex) +-- +-- Each entry is keyed by lookType and includes: +-- - name (string) +-- - sex (PLAYERSEX_FEMALE / PLAYERSEX_MALE) +-- - premium (bool) +-- - unlocked (bool): if false, player must own it to wear +-- - enabled (bool): if false, excluded from lists +local outfits = { + -- Female outfits + [136] = { + name = "Citizen", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = true, + enabled = true + }, + [137] = { + name = "Hunter", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = true, + enabled = true + }, + [138] = { + name = "Mage", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = true, + enabled = true + }, + [139] = { + name = "Knight", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = true, + enabled = true + }, + [140] = { + name = "Noblewoman", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = true, + enabled = true + }, + [141] = { + name = "Summoner", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = true, + enabled = true + }, + [142] = { + name = "Warrior", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = true, + enabled = true + }, + [147] = { + name = "Barbarian", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = true, + enabled = true + }, + [148] = { + name = "Druid", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = true, + enabled = true + }, + [149] = { + name = "Wizard", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = true, + enabled = true + }, + [150] = { + name = "Oriental", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = true, + enabled = true + }, + [155] = { + name = "Pirate", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [156] = { + name = "Assassin", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [157] = { + name = "Beggar", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [158] = { + name = "Shaman", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [252] = { + name = "Norsewoman", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [269] = { + name = "Nightmare", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [270] = { + name = "Jester", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [279] = { + name = "Brotherhood", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [288] = { + name = "Demon Hunter", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [324] = { + name = "Yalaharian", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [329] = { + name = "Newly Wed", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [336] = { + name = "Warmaster", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [366] = { + name = "Wayfarer", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [431] = { + name = "Afflicted", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [433] = { + name = "Elementalist", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [464] = { + name = "Deepling", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [466] = { + name = "Insectoid", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [471] = { + name = "Entrepreneur", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [513] = { + name = "Crystal Warlord", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [514] = { + name = "Soil Guardian", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [542] = { + name = "Demon Outfit", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [575] = { + name = "Cave Explorer", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [578] = { + name = "Dream Warden", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [618] = { + name = "Glooth Engineer", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [620] = { + name = "Jersey", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [632] = { + name = "Champion", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [635] = { + name = "Conjurer", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [636] = { + name = "Beastmaster", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [664] = { + name = "Chaos Acolyte", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [666] = { + name = "Death Herald", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [683] = { + name = "Ranger", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [694] = { + name = "Ceremonial Garb", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [696] = { + name = "Puppeteer", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [698] = { + name = "Spirit Caller", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [724] = { + name = "Evoker", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [732] = { + name = "Seaweaver", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [745] = { + name = "Recruiter", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [749] = { + name = "Sea Dog", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [759] = { + name = "Royal Pumpkin", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [845] = { + name = "Rift Warrior", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [852] = { + name = "Winter Warden", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [874] = { + name = "Philosopher", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [885] = { + name = "Arena Champion", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [900] = { + name = "Lupine Warden", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [909] = { + name = "Grove Keeper", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [929] = { + name = "Festive Outfit", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [956] = { + name = "Pharaoh", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [958] = { + name = "Trophy Hunter", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [963] = { + name = "Retro Warrior", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [965] = { + name = "Retro Summoner", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [967] = { + name = "Retro Noblewoman", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [969] = { + name = "Retro Mage", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [971] = { + name = "Retro Knight", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [973] = { + name = "Retro Hunter", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [975] = { + name = "Retro Citizen", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1020] = { + name = "Herbalist", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1024] = { + name = "Sun Priest", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1043] = { + name = "Makeshift Warrior", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [1050] = { + name = "Siege Master", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1057] = { + name = "Mercenary", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1070] = { + name = "Battle Mage", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [1095] = { + name = "Discoverer", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [1103] = { + name = "Sinister Archer", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1128] = { + name = "Pumpkin Mummy", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1147] = { + name = "Dream Warrior", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [1162] = { + name = "Percht Raider", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [1174] = { + name = "Owl Keeper", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1187] = { + name = "Guidon Bearer", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1203] = { + name = "Void Master", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1205] = { + name = "Veteran Paladin", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1207] = { + name = "Lion of War", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1211] = { + name = "Golden Outfit", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [1244] = { + name = "Hand of the Inquisition", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [1246] = { + name = "Breezy Garb", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1252] = { + name = "Orcsoberfest Garb", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [1271] = { + name = "Poltergeist", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [1280] = { + name = "Herder", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1283] = { + name = "Falconer", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [1289] = { + name = "Dragon Slayer", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1293] = { + name = "Trailblazer", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1323] = { + name = "Revenant", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [1332] = { + name = "Jouster", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1339] = { + name = "Moth Cape", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1372] = { + name = "Rascoohan", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [1383] = { + name = "Merry Garb", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1385] = { + name = "Rune Master", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1387] = { + name = "Citizen of Issavi", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [1416] = { + name = "Forest Warden", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1437] = { + name = "Royal Bounacean Advisor", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [1445] = { + name = "Dragon Knight", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1450] = { + name = "Arbalester", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1456] = { + name = "Royal Costume", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1461] = { + name = "Formal Dress", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [1490] = { + name = "Ghost Blade", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1501] = { + name = "Nordic Chieftain", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1569] = { + name = "Fire-Fighter", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + [1576] = { + name = "Fencer", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1582] = { + name = "Shadowlotus Disciple", + sex = PLAYERSEX_FEMALE, + premium = false, + unlocked = false, + enabled = true + }, + [1598] = { + name = "Ancient Aucar", + sex = PLAYERSEX_FEMALE, + premium = true, + unlocked = false, + enabled = true + }, + + -- Male outfits + [128] = { + name = "Citizen", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = true, + enabled = true + }, + [129] = { + name = "Hunter", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = true, + enabled = true + }, + [130] = { + name = "Mage", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = true, + enabled = true + }, + [131] = { + name = "Knight", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = true, + enabled = true + }, + [132] = { + name = "Nobleman", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = true, + enabled = true + }, + [133] = { + name = "Summoner", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = true, + enabled = true + }, + [134] = { + name = "Warrior", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = true, + enabled = true + }, + [143] = { + name = "Barbarian", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = true, + enabled = true + }, + [144] = { + name = "Druid", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = true, + enabled = true + }, + [145] = { + name = "Wizard", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = true, + enabled = true + }, + [146] = { + name = "Oriental", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = true, + enabled = true + }, + [151] = { + name = "Pirate", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [152] = { + name = "Assassin", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [153] = { + name = "Beggar", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [154] = { + name = "Shaman", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [251] = { + name = "Norseman", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [268] = { + name = "Nightmare", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [273] = { + name = "Jester", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [278] = { + name = "Brotherhood", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [289] = { + name = "Demon Hunter", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [325] = { + name = "Yalaharian", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [328] = { + name = "Newly Wed", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [335] = { + name = "Warmaster", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [367] = { + name = "Wayfarer", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [430] = { + name = "Afflicted", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [432] = { + name = "Elementalist", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [463] = { + name = "Deepling", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [465] = { + name = "Insectoid", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [472] = { + name = "Entrepreneur", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [512] = { + name = "Crystal Warlord", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [516] = { + name = "Soil Guardian", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [541] = { + name = "Demon Outfit", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [574] = { + name = "Cave Explorer", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [577] = { + name = "Dream Warden", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [610] = { + name = "Glooth Engineer", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [619] = { + name = "Jersey", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [633] = { + name = "Champion", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [634] = { + name = "Conjurer", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [637] = { + name = "Beastmaster", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [665] = { + name = "Chaos Acolyte", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [667] = { + name = "Death Herald", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [684] = { + name = "Ranger", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [695] = { + name = "Ceremonial Garb", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [697] = { + name = "Puppeteer", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [699] = { + name = "Spirit Caller", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [725] = { + name = "Evoker", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [733] = { + name = "Seaweaver", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [746] = { + name = "Recruiter", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [750] = { + name = "Sea Dog", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [760] = { + name = "Royal Pumpkin", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [846] = { + name = "Rift Warrior", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [853] = { + name = "Winter Warden", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [873] = { + name = "Philosopher", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [884] = { + name = "Arena Champion", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [899] = { + name = "Lupine Warden", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [908] = { + name = "Grove Keeper", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [931] = { + name = "Festive Outfit", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [955] = { + name = "Pharaoh", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [957] = { + name = "Trophy Hunter", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [962] = { + name = "Retro Warrior", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [964] = { + name = "Retro Summoner", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [966] = { + name = "Retro Nobleman", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [968] = { + name = "Retro Mage", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [970] = { + name = "Retro Knight", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [972] = { + name = "Retro Hunter", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [974] = { + name = "Retro Citizen", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1021] = { + name = "Herbalist", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1023] = { + name = "Sun Priest", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1042] = { + name = "Makeshift Warrior", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [1051] = { + name = "Siege Master", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1056] = { + name = "Mercenary", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1069] = { + name = "Battle Mage", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [1094] = { + name = "Discoverer", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [1102] = { + name = "Sinister Archer", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1127] = { + name = "Pumpkin Mummy", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1146] = { + name = "Dream Warrior", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [1161] = { + name = "Percht Raider", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [1173] = { + name = "Owl Keeper", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1186] = { + name = "Guidon Bearer", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1202] = { + name = "Void Master", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1204] = { + name = "Veteran Paladin", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1206] = { + name = "Lion of War", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1210] = { + name = "Golden Outfit", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [1243] = { + name = "Hand of the Inquisition", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [1245] = { + name = "Breezy Garb", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1251] = { + name = "Orcsoberfest Garb", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [1270] = { + name = "Poltergeist", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [1279] = { + name = "Herder", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1282] = { + name = "Falconer", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [1288] = { + name = "Dragon Slayer", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1292] = { + name = "Trailblazer", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1322] = { + name = "Revenant", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [1331] = { + name = "Jouster", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1338] = { + name = "Moth Cape", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1371] = { + name = "Rascoohan", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [1382] = { + name = "Merry Garb", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1384] = { + name = "Rune Master", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1386] = { + name = "Citizen of Issavi", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [1415] = { + name = "Forest Warden", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1436] = { + name = "Royal Bounacean Advisor", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [1444] = { + name = "Dragon Knight", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1449] = { + name = "Arbalester", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1457] = { + name = "Royal Costume", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1460] = { + name = "Formal Dress", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [1489] = { + name = "Ghost Blade", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1500] = { + name = "Nordic Chieftain", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1568] = { + name = "Fire-Fighter", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + }, + [1575] = { + name = "Fencer", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1581] = { + name = "Shadowlotus Disciple", + sex = PLAYERSEX_MALE, + premium = false, + unlocked = false, + enabled = true + }, + [1597] = { + name = "Ancient Aucar", + sex = PLAYERSEX_MALE, + premium = true, + unlocked = false, + enabled = true + } +} + +function Game.getOutfitByLookType(lookType) + local outfit = outfits[lookType] + if not outfit then + return nil + end + + return { + lookType = lookType, + name = outfit.name, + sex = outfit.sex, + premium = outfit.premium, + unlocked = outfit.unlocked + } +end + +function Game.getOutfitByName(name, sex) + if type(name) ~= "string" then + return nil + end + + for lookType, outfit in pairs(outfits) do + if outfit.name:lower() == name:lower() and outfit.sex == sex then + return { + lookType = lookType, + name = outfit.name, + sex = outfit.sex, + premium = outfit.premium, + unlocked = outfit.unlocked + } + end + end + return nil +end + +function Game.getOutfit(param, sex) + local lookType = tonumber(param) + if lookType then + return Game.getOutfitByLookType(lookType) + end + return Game.getOutfitByName(param, sex) +end + +do + local cachedOutfits = { + [PLAYERSEX_FEMALE] = {}, + [PLAYERSEX_MALE] = {} + } + + for lookType, outfit in pairs(outfits) do + if outfit.enabled then + table.insert(cachedOutfits[outfit.sex], { + lookType = lookType, + name = outfit.name, + sex = outfit.sex, + premium = outfit.premium, + unlocked = outfit.unlocked + }) + end + end + + function Game.getOutfits(sex) + return cachedOutfits[sex] + end +end diff --git a/data/scripts/systems/outfits/events/change_zone.lua b/data/scripts/systems/outfits/events/change_zone.lua new file mode 100644 index 0000000000..39161f3af8 --- /dev/null +++ b/data/scripts/systems/outfits/events/change_zone.lua @@ -0,0 +1,23 @@ +-- Handles auto dismount/remount when crossing protection zones. +-- +-- The flag wasMounted is used to remember the player's intent to stay mounted while +-- forcibly dismounted in protection zones, and to bypass the normal toggle cooldown +-- for these forced transitions. +local event = Event() + +function event.onCreatureChangeZone(self, fromZone, toZone) + if not self:isPlayer() then + return + end + + if toZone == ZONE_PROTECTION and not self:getGroup():getAccess() and self:isMounted() then + self:setWasMounted(true) + self:toggleMount(false) + elseif fromZone == ZONE_PROTECTION and self:getWasMounted() then + if self:toggleMount(true) then + self:setWasMounted(false) + end + end +end + +event:register() diff --git a/data/scripts/systems/outfits/events/cleanup.lua b/data/scripts/systems/outfits/events/cleanup.lua new file mode 100644 index 0000000000..10e0d9a29c --- /dev/null +++ b/data/scripts/systems/outfits/events/cleanup.lua @@ -0,0 +1,12 @@ +-- Clears per-session mount bookkeeping on logout. +-- +-- These values are not persisted and should not be carried between sessions. +local event = Event() + +function event.onPlayerLogout(self) + self:setLastMountToggle(nil) + self:setWasMounted(nil) + return true +end + +event:register() diff --git a/data/scripts/systems/outfits/network/request_outfit_window.lua b/data/scripts/systems/outfits/network/request_outfit_window.lua new file mode 100644 index 0000000000..8f3b9f700c --- /dev/null +++ b/data/scripts/systems/outfits/network/request_outfit_window.lua @@ -0,0 +1,14 @@ +local handler = PacketHandler(0xD2) + +-- 0xD2: Request Outfit Window (client -> server) +-- Payload: (empty) +-- Response: 0xC8 (Outfit Window) via Player.sendOutfitWindow in systems/outfits/core/windows.lua +function handler.onReceive(player, msg) + if not Outfits.AllowChangeOutfit then + return + end + + player:sendOutfitWindow() +end + +handler:register() diff --git a/data/scripts/systems/outfits/network/set_outfit.lua b/data/scripts/systems/outfits/network/set_outfit.lua new file mode 100644 index 0000000000..8ed7ca02ff --- /dev/null +++ b/data/scripts/systems/outfits/network/set_outfit.lua @@ -0,0 +1,132 @@ +local handler = PacketHandler(0xD3) + +-- 0xD3: Set Outfit (client -> server) +-- Payload: +-- - outfitType:byte +-- - outfit (always): +-- - lookType:u16 +-- - lookHead:byte, lookBody:byte, lookLegs:byte, lookFeet:byte +-- - lookAddons:byte +-- - branches: +-- - outfitType == 0 (Customize/Set outfit): +-- - lookMount:u16 +-- - lookMountHead:byte, lookMountBody:byte, lookMountLegs:byte, lookMountFeet:byte +-- - familiarLookType:u16 (ignored) +-- - randomizeMount:bool +-- - outfitType == 1 (Store try-on): +-- - clears lookMount and reads 4 mount color bytes (client preview) +-- - outfitType == 2 (Podium edit): +-- - position:Position +-- - clientId:u16 +-- - stackpos:byte +-- - lookMount:u16 + 4 mount color bytes +-- - direction:byte +-- - isVisible:bool +function handler.onReceive(player, msg) + if not Outfits.AllowChangeOutfit then + return + end + + local outfitType = msg:getByte() + + local outfit = player:getCurrentOutfit() + outfit.lookType = msg:getU16() + outfit.lookHead = msg:getByte() + outfit.lookBody = msg:getByte() + outfit.lookLegs = msg:getByte() + outfit.lookFeet = msg:getByte() + outfit.lookAddons = msg:getByte() + + if outfitType == 0 then -- set outfit from customize character window + local lookMount = msg:getU16() + local lookMountHead = msg:getByte() + local lookMountBody = msg:getByte() + local lookMountLegs = msg:getByte() + local lookMountFeet = msg:getByte() + + outfit.lookMount = lookMount + if lookMount ~= 0 then -- only update colors if a mount with colors is selected + outfit.lookMountHead = lookMountHead + outfit.lookMountBody = lookMountBody + outfit.lookMountLegs = lookMountLegs + outfit.lookMountFeet = lookMountFeet + end + + msg:getU16() -- familiar looktype + local randomizeMount = msg:getBool() + + if not player:canWearOutfit(outfit.lookType, outfit.lookAddons) then + return + end + + if outfit.lookMount ~= 0 then + if not player:canRideMount(outfit.lookMount) then + player:setCurrentMount(nil) + return + end + + player:setCurrentMount(outfit.lookMount) + else + player:setCurrentMount(nil) + end + + player:setOutfit(outfit) + player:setRandomizeMount(randomizeMount) + elseif outfitType == 1 then -- try outfit from store window + outfit.lookMount = 0 + outfit.lookMountHead = msg:getByte() + outfit.lookMountBody = msg:getByte() + outfit.lookMountLegs = msg:getByte() + outfit.lookMountFeet = msg:getByte() + + -- TODO: Implement store outfit preview window + elseif outfitType == 2 then -- set podium outfit + local position = msg:getPosition() + local clientId = msg:getU16() + local stackpos = msg:getByte() + + outfit.lookMount = msg:getU16() + outfit.lookMountHead = msg:getByte() + outfit.lookMountBody = msg:getByte() + outfit.lookMountLegs = msg:getByte() + outfit.lookMountFeet = msg:getByte() + + local direction = msg:getByte() + local isVisible = msg:getBool() + + if not player:canWearOutfit(outfit.lookType, outfit.lookAddons) then + return + end + + if outfit.lookMount ~= 0 and not player:canRideMount(outfit.lookMount) then + return + end + + local tile = Tile(position) + if not tile then + return + end + + local item = tile:getTopDownItem() + if not item then + return + end + + -- Verify item is at expected stack position + local thingAtPos = tile:getThing(stackpos) + if not thingAtPos or thingAtPos ~= item then + return + end + + local it = Game.getItemTypeByClientId(clientId) + if not it or it:getClientId() ~= clientId or it:getId() ~= item:getId() then + return + end + + player:onPodiumEdit(item, outfit, direction, isVisible) + else + print("Warning: Unknown outfitType received in set outfit message: " .. outfitType) + end +end + +handler:register() diff --git a/data/scripts/systems/outfits/network/toggle_mount.lua b/data/scripts/systems/outfits/network/toggle_mount.lua new file mode 100644 index 0000000000..393da585a3 --- /dev/null +++ b/data/scripts/systems/outfits/network/toggle_mount.lua @@ -0,0 +1,17 @@ +local handler = PacketHandler(0xD4) + +-- 0xD4: Toggle Mount (client -> server) +-- Payload: mounted:bool (true = mount, false = dismount) +-- Notes: +-- - Cooldown is enforced by Player.toggleMount (systems/outfits/core/mounts.lua). +-- - Mounting from a protection zone is rejected by Player.toggleMount. +function handler.onReceive(player, msg) + if not Outfits.AllowToggleMount then + return + end + + local mounted = msg:getBool() + player:toggleMount(mounted) +end + +handler:register() diff --git a/data/scripts/talkactions/commands/reload.lua b/data/scripts/talkactions/commands/reload.lua index 81519d5e4e..8b5922334c 100644 --- a/data/scripts/talkactions/commands/reload.lua +++ b/data/scripts/talkactions/commands/reload.lua @@ -25,9 +25,6 @@ local reloadTypes = { ["monster"] = RELOAD_TYPE_MONSTERS, ["monsters"] = RELOAD_TYPE_MONSTERS, - ["mount"] = RELOAD_TYPE_MOUNTS, - ["mounts"] = RELOAD_TYPE_MOUNTS, - ["move"] = RELOAD_TYPE_MOVEMENTS, ["movement"] = RELOAD_TYPE_MOVEMENTS, ["movements"] = RELOAD_TYPE_MOVEMENTS, diff --git a/schema.sql b/schema.sql index b41feca8a6..79fe5ef56a 100644 --- a/schema.sql +++ b/schema.sql @@ -32,8 +32,6 @@ CREATE TABLE IF NOT EXISTS `players` ( `lookmountbody` int NOT NULL DEFAULT '0', `lookmountlegs` int NOT NULL DEFAULT '0', `lookmountfeet` int NOT NULL DEFAULT '0', - `currentmount` smallint unsigned NOT NULL DEFAULT '0', - `randomizemount` tinyint NOT NULL DEFAULT '0', `direction` tinyint unsigned NOT NULL DEFAULT '2', `maglevel` int NOT NULL DEFAULT '0', `mana` int NOT NULL DEFAULT '0', @@ -342,21 +340,6 @@ CREATE TABLE IF NOT EXISTS `player_storage` ( FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; -CREATE TABLE IF NOT EXISTS `player_outfits` ( - `player_id` int NOT NULL DEFAULT '0', - `outfit_id` smallint unsigned NOT NULL DEFAULT '0', - `addons` tinyint unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`player_id`,`outfit_id`), - FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; - -CREATE TABLE IF NOT EXISTS `player_mounts` ( - `player_id` int NOT NULL DEFAULT '0', - `mount_id` smallint unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`player_id`,`mount_id`), - FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; - CREATE TABLE IF NOT EXISTS `server_config` ( `config` varchar(50) NOT NULL, `value` varchar(256) NOT NULL DEFAULT '', @@ -391,7 +374,7 @@ CREATE TABLE IF NOT EXISTS `towns` ( UNIQUE KEY `name` (`name`) ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; -INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '37'), ('players_record', '0'); +INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '38'), ('players_record', '0'); DROP TRIGGER IF EXISTS `ondelete_players`; DROP TRIGGER IF EXISTS `oncreate_guilds`; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3d3bdecd4d..0bec4b64a5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,11 +35,9 @@ set(tfs_SRC ${CMAKE_CURRENT_LIST_DIR}/matrixarea.cpp ${CMAKE_CURRENT_LIST_DIR}/monster.cpp ${CMAKE_CURRENT_LIST_DIR}/monsters.cpp - ${CMAKE_CURRENT_LIST_DIR}/mounts.cpp ${CMAKE_CURRENT_LIST_DIR}/movement.cpp ${CMAKE_CURRENT_LIST_DIR}/networkmessage.cpp ${CMAKE_CURRENT_LIST_DIR}/npc.cpp - ${CMAKE_CURRENT_LIST_DIR}/outfit.cpp ${CMAKE_CURRENT_LIST_DIR}/outputmessage.cpp ${CMAKE_CURRENT_LIST_DIR}/party.cpp ${CMAKE_CURRENT_LIST_DIR}/player.cpp @@ -113,11 +111,9 @@ set(tfs_HDR ${CMAKE_CURRENT_LIST_DIR}/matrixarea.h ${CMAKE_CURRENT_LIST_DIR}/monster.h ${CMAKE_CURRENT_LIST_DIR}/monsters.h - ${CMAKE_CURRENT_LIST_DIR}/mounts.h ${CMAKE_CURRENT_LIST_DIR}/movement.h ${CMAKE_CURRENT_LIST_DIR}/networkmessage.h ${CMAKE_CURRENT_LIST_DIR}/npc.h - ${CMAKE_CURRENT_LIST_DIR}/outfit.h ${CMAKE_CURRENT_LIST_DIR}/outputmessage.h ${CMAKE_CURRENT_LIST_DIR}/party.h ${CMAKE_CURRENT_LIST_DIR}/player.h diff --git a/src/configmanager.cpp b/src/configmanager.cpp index 9e4fa245ac..de875cf126 100644 --- a/src/configmanager.cpp +++ b/src/configmanager.cpp @@ -203,7 +203,6 @@ bool ConfigManager::load() integer[MARKET_OFFER_DURATION] = getGlobalNumber(L, "marketOfferDuration", 30 * 24 * 60 * 60); } - boolean[ALLOW_CHANGEOUTFIT] = getGlobalBoolean(L, "allowChangeOutfit", true); boolean[ONE_PLAYER_ON_ACCOUNT] = getGlobalBoolean(L, "onePlayerOnlinePerAccount", true); boolean[AIMBOT_HOTKEY_ENABLED] = getGlobalBoolean(L, "hotkeyAimbotEnabled", true); boolean[REMOVE_RUNE_CHARGES] = getGlobalBoolean(L, "removeChargesFromRunes", true); diff --git a/src/configmanager.h b/src/configmanager.h index c476f24b3c..7c90047afa 100644 --- a/src/configmanager.h +++ b/src/configmanager.h @@ -8,7 +8,6 @@ namespace ConfigManager { enum boolean_config_t { - ALLOW_CHANGEOUTFIT, ONE_PLAYER_ON_ACCOUNT, AIMBOT_HOTKEY_ENABLED, REMOVE_RUNE_CHARGES, diff --git a/src/const.h b/src/const.h index a67e373569..e257401cfb 100644 --- a/src/const.h +++ b/src/const.h @@ -669,7 +669,6 @@ enum ReloadTypes_t : uint8_t RELOAD_TYPE_GLOBALEVENTS, RELOAD_TYPE_ITEMS, RELOAD_TYPE_MONSTERS, - RELOAD_TYPE_MOUNTS, RELOAD_TYPE_MOVEMENTS, RELOAD_TYPE_NPCS, RELOAD_TYPE_QUESTS, diff --git a/src/creature.h b/src/creature.h index 503880414f..f50355cebe 100644 --- a/src/creature.h +++ b/src/creature.h @@ -409,7 +409,6 @@ class Creature : public Thing Outfit_t currentOutfit; Outfit_t defaultOutfit; - uint16_t currentMount; LightInfo internalLight; diff --git a/src/game.cpp b/src/game.cpp index e3de8520dd..9ff9b68999 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -17,7 +17,6 @@ #include "iomarket.h" #include "items.h" #include "movement.h" -#include "outfit.h" #include "party.h" #include "podium.h" #include "scheduler.h" @@ -79,8 +78,6 @@ void Game::setGameState(GameState_t newState) map.spawns.startup(); - mounts.loadFromXml(); - tfs::events::game::onStartup(); break; } @@ -587,6 +584,7 @@ bool Game::removeCreature(const std::shared_ptr& creature, bool isLogo summon->setSkillLoss(false); removeCreature(summon); } + return true; } @@ -3351,82 +3349,6 @@ void Game::playerEditPodium(uint32_t playerId, Outfit_t outfit, const Position& tfs::events::player::onPodiumEdit(player, item, outfit, podiumVisible, direction); } -void Game::playerToggleMount(uint32_t playerId, bool mount) -{ - const auto& player = getPlayerByID(playerId); - if (!player) { - return; - } - - player->toggleMount(mount); -} - -void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit, bool randomizeMount /* = false*/) -{ - if (!getBoolean(ConfigManager::ALLOW_CHANGEOUTFIT)) { - return; - } - - const auto& player = getPlayerByID(playerId); - if (!player) { - return; - } - - player->setRandomizeMount(randomizeMount); - - const Outfit* playerOutfit = Outfits::getInstance().getOutfitByLookType(player->getSex(), outfit.lookType); - if (!playerOutfit) { - outfit.lookMount = 0; - } - - if (outfit.lookMount != 0) { - Mount* mount = mounts.getMountByClientID(outfit.lookMount); - if (!mount) { - return; - } - - if (!player->hasMount(mount)) { - return; - } - - int32_t speedChange = mount->speed; - if (player->isMounted()) { - Mount* prevMount = mounts.getMountByID(player->getCurrentMount()); - if (prevMount) { - speedChange -= prevMount->speed; - } - } - - changeSpeed(player, speedChange); - player->setCurrentMount(mount->id); - } else { - if (player->isMounted()) { - player->dismount(); - } - - player->setWasMounted(false); - } - - if (player->canWear(outfit.lookType, outfit.lookAddons)) { - player->defaultOutfit = outfit; - - if (player->hasCondition(CONDITION_OUTFIT)) { - return; - } - - if (player->getRandomizeMount() && player->hasMounts()) { - const Mount* mount = mounts.getMountByID(player->getRandomMount()); - outfit.lookMount = mount->clientId; - } - - internalCreatureChangeOutfit(player, outfit); - } - - if (player->isMounted()) { - player->onChangeZone(player->getZone()); - } -} - void Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, const std::string& receiver, const std::string& text) { @@ -5498,8 +5420,6 @@ bool Game::reload(ReloadTypes_t reloadType) return Item::items.reload(); case RELOAD_TYPE_MONSTERS: return g_monsters.reload(); - case RELOAD_TYPE_MOUNTS: - return mounts.reload(); case RELOAD_TYPE_MOVEMENTS: return g_moveEvents->reload(); case RELOAD_TYPE_NPCS: { @@ -5539,7 +5459,6 @@ bool Game::reload(ReloadTypes_t reloadType) /* Npcs::reload(); Item::items.reload(); - mounts.reload(); ConfigManager::reload(); tfs::events::load(); g_chat.load(); @@ -5565,7 +5484,6 @@ bool Game::reload(ReloadTypes_t reloadType) Item::items.reload(); g_weapons->clear(true); g_weapons->loadDefaults(); - mounts.reload(); g_globalEvents->reload(); tfs::events::reload(); g_chat.load(); diff --git a/src/game.h b/src/game.h index f196a7ba6d..0680de545e 100644 --- a/src/game.h +++ b/src/game.h @@ -7,7 +7,6 @@ #include "groups.h" #include "map.h" #include "monster.h" -#include "mounts.h" #include "npc.h" #include "player.h" #include "wildcardtree.h" @@ -375,14 +374,12 @@ class Game const uint16_t spriteId, bool podiumVisible, Direction direction); void playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, const std::string& receiver, const std::string& text); - void playerChangeOutfit(uint32_t playerId, Outfit_t outfit, bool randomizeMount = false); void playerInviteToParty(uint32_t playerId, uint32_t invitedId); void playerJoinParty(uint32_t playerId, uint32_t leaderId); void playerRevokePartyInvitation(uint32_t playerId, uint32_t invitedId); void playerPassPartyLeadership(uint32_t playerId, uint32_t newLeaderId); void playerLeaveParty(uint32_t playerId); void playerEnableSharedPartyExperience(uint32_t playerId, bool sharedExpActive); - void playerToggleMount(uint32_t playerId, bool mount); void playerLeaveMarket(uint32_t playerId); void playerBrowseMarket(uint32_t playerId, uint16_t spriteId); void playerBrowseMarketOwnOffers(uint32_t playerId); @@ -470,7 +467,6 @@ class Game Groups groups; Map map; - Mounts mounts; std::vector> toDecayItems; diff --git a/src/iologindata.cpp b/src/iologindata.cpp index f834747f9f..d76fc22632 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -95,7 +95,7 @@ bool IOLoginData::loadPlayerById(const std::shared_ptr& player, uint32_t return loadPlayer( player, db.storeQuery(std::format( - "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `lookmount`, `lookmounthead`, `lookmountbody`, `lookmountlegs`, `lookmountfeet`, `currentmount`, `randomizemount`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `id` = {:d}", + "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `lookmount`, `lookmounthead`, `lookmountbody`, `lookmountlegs`, `lookmountfeet`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `id` = {:d}", id))); } @@ -105,7 +105,7 @@ bool IOLoginData::loadPlayerByName(const std::shared_ptr& player, const return loadPlayer( player, db.storeQuery(std::format( - "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `lookmount`, `lookmounthead`, `lookmountbody`, `lookmountlegs`, `lookmountfeet`, `currentmount`, `randomizemount`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `name` = {:s}", + "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `lookmount`, `lookmounthead`, `lookmountbody`, `lookmountlegs`, `lookmountfeet`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `name` = {:s}", db.escapeString(name)))); } @@ -235,9 +235,7 @@ bool IOLoginData::loadPlayer(const std::shared_ptr& player, std::shared_ player->defaultOutfit.lookMountLegs = result->getNumber("lookmountlegs"); player->defaultOutfit.lookMountFeet = result->getNumber("lookmountfeet"); player->currentOutfit = player->defaultOutfit; - player->currentMount = result->getNumber("currentmount"); player->setDirection(static_cast(result->getNumber("direction"))); - player->randomizeMount = result->getNumber("randomizemount") != 0; if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { const time_t skullSeconds = result->getNumber("skulltime") - time(nullptr); @@ -477,22 +475,6 @@ bool IOLoginData::loadPlayer(const std::shared_ptr& player, std::shared_ } while (vipRes->next()); } - // load outfits & addons - if (const auto& outfitsRes = db.storeQuery(std::format( - "SELECT `outfit_id`, `addons` FROM `player_outfits` WHERE `player_id` = {:d}", player->getGUID()))) { - do { - player->addOutfit(outfitsRes->getNumber("outfit_id"), outfitsRes->getNumber("addons")); - } while (outfitsRes->next()); - } - - // load mounts - if (const auto& mountsRes = db.storeQuery( - std::format("SELECT `mount_id` FROM `player_mounts` WHERE `player_id` = {:d}", player->getGUID()))) { - do { - player->tameMount(mountsRes->getNumber("mount_id")); - } while (mountsRes->next()); - } - player->updateBaseSpeed(); player->updateInventoryWeight(); player->updateItemsLight(true); @@ -629,8 +611,6 @@ bool IOLoginData::savePlayer(const std::shared_ptr& player) query << "`lookmountbody` = " << static_cast(player->defaultOutfit.lookMountBody) << ','; query << "`lookmountlegs` = " << static_cast(player->defaultOutfit.lookMountLegs) << ','; query << "`lookmountfeet` = " << static_cast(player->defaultOutfit.lookMountFeet) << ','; - query << "`currentmount` = " << static_cast(player->currentMount) << ','; - query << "`randomizemount` = " << player->randomizeMount << ","; query << "`maglevel` = " << player->magLevel << ','; query << "`mana` = " << player->mana << ','; query << "`manamax` = " << player->manaMax << ','; @@ -815,40 +795,6 @@ bool IOLoginData::savePlayer(const std::shared_ptr& player) return false; } - // save outfits & addons - if (!db.executeQuery(std::format("DELETE FROM `player_outfits` WHERE `player_id` = {:d}", player->getGUID()))) { - return false; - } - - DBInsert outfitQuery("INSERT INTO `player_outfits` (`player_id`, `outfit_id`, `addons`) VALUES "); - - for (auto&& [outfitId, addons] : player->outfits | std::views::as_const) { - if (!outfitQuery.addRow(std::format("{:d}, {:d}, {:d}", player->getGUID(), outfitId, addons))) { - return false; - } - } - - if (!outfitQuery.execute()) { - return false; - } - - // save mounts - if (!db.executeQuery(std::format("DELETE FROM `player_mounts` WHERE `player_id` = {:d}", player->getGUID()))) { - return false; - } - - DBInsert mountQuery("INSERT INTO `player_mounts` (`player_id`, `mount_id`) VALUES "); - - for (const auto& it : player->mounts) { - if (!mountQuery.addRow(std::format("{:d}, {:d}", player->getGUID(), it))) { - return false; - } - } - - if (!mountQuery.execute()) { - return false; - } - // End the transaction return transaction.commit(); } diff --git a/src/lua/CMakeLists.txt b/src/lua/CMakeLists.txt index a24b3cfb86..d3a9d5c67a 100644 --- a/src/lua/CMakeLists.txt +++ b/src/lua/CMakeLists.txt @@ -29,7 +29,6 @@ set(luamodules_SRC ${CMAKE_CURRENT_LIST_DIR}/modules/moveevent.cpp ${CMAKE_CURRENT_LIST_DIR}/modules/networkmessage.cpp ${CMAKE_CURRENT_LIST_DIR}/modules/npc.cpp - ${CMAKE_CURRENT_LIST_DIR}/modules/outfit.cpp ${CMAKE_CURRENT_LIST_DIR}/modules/party.cpp ${CMAKE_CURRENT_LIST_DIR}/modules/player.cpp ${CMAKE_CURRENT_LIST_DIR}/modules/podium.cpp diff --git a/src/lua/api.cpp b/src/lua/api.cpp index 82a70160bf..27f4e2294f 100644 --- a/src/lua/api.cpp +++ b/src/lua/api.cpp @@ -5,7 +5,6 @@ #include "../game.h" #include "../monster.h" #include "../monsters.h" -#include "../mounts.h" #include "../npc.h" #include "../podium.h" #include "../spells.h" @@ -182,19 +181,6 @@ Outfit_t getOutfit(lua_State* L, int32_t arg) return outfit; } -Outfit getOutfitClass(lua_State* L, int32_t arg) -{ - Outfit outfit{ - .name = getFieldString(L, arg, "name"), - .lookType = getField(L, arg, "lookType"), - .premium = getField(L, arg, "premium") == 1, - .unlocked = getField(L, arg, "unlocked") == 1, - }; - - lua_pop(L, 4); - return outfit; -} - LuaVariant getVariant(lua_State* L, int32_t arg) { LuaVariant var; @@ -345,16 +331,6 @@ void pushOutfit(lua_State* L, const Outfit_t& outfit) setField(L, "lookMountFeet", outfit.lookMountFeet); } -void pushOutfit(lua_State* L, const Outfit* outfit) -{ - lua_createtable(L, 0, 4); - setField(L, "lookType", outfit->lookType); - setField(L, "name", outfit->name); - setField(L, "premium", outfit->premium); - setField(L, "unlocked", outfit->unlocked); - setMetatable(L, -1, "Outfit"); -} - void pushLoot(lua_State* L, const std::vector& lootList) { lua_createtable(L, lootList.size(), 0); diff --git a/src/lua/api.h b/src/lua/api.h index 5045d09114..e7c89041a1 100644 --- a/src/lua/api.h +++ b/src/lua/api.h @@ -1,6 +1,6 @@ #pragma once -#include "../outfit.h" +#include "../enums.h" #include "error.h" #include "variant.h" @@ -15,7 +15,6 @@ class Thing; class ItemType; struct LootBlock; -struct Mount; struct Town; namespace tfs::lua { @@ -149,7 +148,6 @@ Position getPosition(lua_State* L, int32_t arg); Position getPosition(lua_State* L, int32_t arg, int32_t& stackpos); Outfit_t getOutfit(lua_State* L, int32_t arg); -Outfit getOutfitClass(lua_State* L, int32_t arg); LuaVariant getVariant(lua_State* L, int32_t arg); @@ -160,7 +158,6 @@ std::shared_ptr getPlayer(lua_State* L, int32_t arg); // High-level push helpers (C++ -> Lua) void pushPosition(lua_State* L, const Position& position, int32_t stackpos = 0); void pushOutfit(lua_State* L, const Outfit_t& outfit); -void pushOutfit(lua_State* L, const Outfit* outfit); void pushVariant(lua_State* L, const LuaVariant& var); void pushThing(lua_State* L, const std::shared_ptr& thing); diff --git a/src/lua/modules.cpp b/src/lua/modules.cpp index ee3fcd7a1b..2cb99ee5a0 100644 --- a/src/lua/modules.cpp +++ b/src/lua/modules.cpp @@ -40,7 +40,6 @@ void importModules(LuaScriptInterface& lsi) registerMonsters(lsi); registerMoveEvent(lsi); registerNetworkMessage(lsi); - registerOutfit(lsi); registerParty(lsi); registerPosition(lsi); registerSpell(lsi); diff --git a/src/lua/modules/configmanager.cpp b/src/lua/modules/configmanager.cpp index 96105332af..9b7518dd23 100644 --- a/src/lua/modules/configmanager.cpp +++ b/src/lua/modules/configmanager.cpp @@ -48,7 +48,6 @@ void tfs::lua::registerConfigManager(LuaScriptInterface& lsi) lsi.registerTable("configKeys"); - registerEnumIn(lsi, "configKeys", ConfigManager::ALLOW_CHANGEOUTFIT); registerEnumIn(lsi, "configKeys", ConfigManager::ONE_PLAYER_ON_ACCOUNT); registerEnumIn(lsi, "configKeys", ConfigManager::AIMBOT_HOTKEY_ENABLED); registerEnumIn(lsi, "configKeys", ConfigManager::REMOVE_RUNE_CHARGES); diff --git a/src/lua/modules/game.cpp b/src/lua/modules/game.cpp index 8d520f5a16..b2ee213d56 100644 --- a/src/lua/modules/game.cpp +++ b/src/lua/modules/game.cpp @@ -222,22 +222,6 @@ int luaGameGetItemTypeByClientId(lua_State* L) return 1; } -int luaGameGetMountIdByLookType(lua_State* L) -{ - // Game.getMountIdByLookType(lookType) - Mount* mount = nullptr; - if (tfs::lua::isNumber(L, 1)) { - mount = g_game.mounts.getMountByClientID(tfs::lua::getNumber(L, 1)); - } - - if (mount) { - tfs::lua::pushNumber(L, mount->id); - } else { - lua_pushnil(L); - } - return 1; -} - int luaGameGetParties(lua_State* L) { // Game.getParties() @@ -282,54 +266,6 @@ int luaGameGetHouses(lua_State* L) return 1; } -int luaGameGetOutfits(lua_State* L) -{ - // Game.getOutfits(playerSex) - if (!tfs::lua::isNumber(L, 1)) { - lua_pushnil(L); - return 1; - } - - PlayerSex_t playerSex = tfs::lua::getNumber(L, 1); - if (playerSex > PLAYERSEX_LAST) { - lua_pushnil(L); - return 1; - } - - const auto& outfits = Outfits::getInstance().getOutfits(playerSex); - lua_createtable(L, outfits.size(), 0); - - int index = 0; - for (const auto& outfit : outfits) { - tfs::lua::pushOutfit(L, &outfit); - lua_rawseti(L, -2, ++index); - } - - return 1; -} - -int luaGameGetMounts(lua_State* L) -{ - // Game.getMounts() - const auto& mounts = g_game.mounts.getMounts(); - lua_createtable(L, mounts.size(), 0); - - int index = 0; - for (const auto& mount : mounts) { - lua_createtable(L, 0, 5); - - tfs::lua::setField(L, "name", mount.name); - tfs::lua::setField(L, "speed", mount.speed); - tfs::lua::setField(L, "clientId", mount.clientId); - tfs::lua::setField(L, "id", mount.id); - tfs::lua::setField(L, "premium", mount.premium); - - lua_rawseti(L, -2, ++index); - } - - return 1; -} - int luaGameGetVocations(lua_State* L) { // Game.getVocations() @@ -715,13 +651,10 @@ void tfs::lua::registerGame(LuaScriptInterface& lsi) lsi.registerMethod("Game", "getBestiary", luaGameGetBestiary); lsi.registerMethod("Game", "getCurrencyItems", luaGameGetCurrencyItems); lsi.registerMethod("Game", "getItemTypeByClientId", luaGameGetItemTypeByClientId); - lsi.registerMethod("Game", "getMountIdByLookType", luaGameGetMountIdByLookType); lsi.registerMethod("Game", "getParties", luaGameGetParties); lsi.registerMethod("Game", "getTowns", luaGameGetTowns); lsi.registerMethod("Game", "getHouses", luaGameGetHouses); - lsi.registerMethod("Game", "getOutfits", luaGameGetOutfits); - lsi.registerMethod("Game", "getMounts", luaGameGetMounts); lsi.registerMethod("Game", "getVocations", luaGameGetVocations); lsi.registerMethod("Game", "getRuneSpells", luaGameGetRuneSpells); lsi.registerMethod("Game", "getInstantSpells", luaGameGetInstantSpells); diff --git a/src/lua/modules/globals.cpp b/src/lua/modules/globals.cpp index ed9e5531ea..2766b141f6 100644 --- a/src/lua/modules/globals.cpp +++ b/src/lua/modules/globals.cpp @@ -319,7 +319,6 @@ void tfs::lua::registerGlobals(LuaScriptInterface& lsi) registerEnum(lsi, RELOAD_TYPE_GLOBALEVENTS); registerEnum(lsi, RELOAD_TYPE_ITEMS); registerEnum(lsi, RELOAD_TYPE_MONSTERS); - registerEnum(lsi, RELOAD_TYPE_MOUNTS); registerEnum(lsi, RELOAD_TYPE_MOVEMENTS); registerEnum(lsi, RELOAD_TYPE_NPCS); registerEnum(lsi, RELOAD_TYPE_QUESTS); diff --git a/src/lua/modules/outfit.cpp b/src/lua/modules/outfit.cpp deleted file mode 100644 index 5fc8e7ce10..0000000000 --- a/src/lua/modules/outfit.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "../../otpch.h" - -#include "../../outfit.h" - -#include "../api.h" -#include "../register.h" -#include "../script.h" - -namespace { - -int luaOutfitCreate(lua_State* L) -{ - // Outfit(looktype) - const Outfit* outfit = Outfits::getInstance().getOutfitByLookType(tfs::lua::getNumber(L, 2)); - if (outfit) { - tfs::lua::pushOutfit(L, outfit); - } else { - lua_pushnil(L); - } - return 1; -} - -int luaOutfitCompare(lua_State* L) -{ - // outfit == outfitEx - Outfit outfitEx = tfs::lua::getOutfitClass(L, 2); - Outfit outfit = tfs::lua::getOutfitClass(L, 1); - tfs::lua::pushBoolean(L, outfit == outfitEx); - return 1; -} - -} // namespace - -void tfs::lua::registerOutfit(LuaScriptInterface& lsi) -{ - lsi.registerClass("Outfit", "", luaOutfitCreate); - lsi.registerMetaMethod("Outfit", "__eq", luaOutfitCompare); -} diff --git a/src/lua/modules/player.cpp b/src/lua/modules/player.cpp index 8f37463b9f..df69587b8d 100644 --- a/src/lua/modules/player.cpp +++ b/src/lua/modules/player.cpp @@ -1537,192 +1537,53 @@ int luaPlayerGetParty(lua_State* L) return 1; } -int luaPlayerAddOutfit(lua_State* L) +int luaPlayerGetCurrentOutfit(lua_State* L) { - // player:addOutfit(lookType) + // player:getCurrentOutfit() if (const auto& player = tfs::lua::getSharedPtr(L, 1)) { - player->addOutfit(tfs::lua::getNumber(L, 2), 0); - tfs::lua::pushBoolean(L, true); + tfs::lua::pushOutfit(L, player->getCurrentOutfit()); } else { lua_pushnil(L); } return 1; } -int luaPlayerAddOutfitAddon(lua_State* L) +int luaPlayerSetCurrentOutfit(lua_State* L) { - // player:addOutfitAddon(lookType, addon) - if (const auto& player = tfs::lua::getSharedPtr(L, 1)) { - uint16_t lookType = tfs::lua::getNumber(L, 2); - uint8_t addon = tfs::lua::getNumber(L, 3); - player->addOutfit(lookType, addon); - tfs::lua::pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int luaPlayerRemoveOutfit(lua_State* L) -{ - // player:removeOutfit(lookType) - if (const auto& player = tfs::lua::getSharedPtr(L, 1)) { - uint16_t lookType = tfs::lua::getNumber(L, 2); - tfs::lua::pushBoolean(L, player->removeOutfit(lookType)); - } else { - lua_pushnil(L); - } - return 1; -} - -int luaPlayerRemoveOutfitAddon(lua_State* L) -{ - // player:removeOutfitAddon(lookType, addon) - if (const auto& player = tfs::lua::getSharedPtr(L, 1)) { - uint16_t lookType = tfs::lua::getNumber(L, 2); - uint8_t addon = tfs::lua::getNumber(L, 3); - tfs::lua::pushBoolean(L, player->removeOutfitAddon(lookType, addon)); - } else { - lua_pushnil(L); - } - return 1; -} - -int luaPlayerHasOutfit(lua_State* L) -{ - // player:hasOutfit(lookType[, addon = 0]) - if (const auto& player = tfs::lua::getSharedPtr(L, 1)) { - uint16_t lookType = tfs::lua::getNumber(L, 2); - uint8_t addon = tfs::lua::getNumber(L, 3, 0); - tfs::lua::pushBoolean(L, player->hasOutfit(lookType, addon)); - } else { - lua_pushnil(L); - } - return 1; -} - -int luaPlayerCanWearOutfit(lua_State* L) -{ - // player:canWearOutfit(lookType[, addon = 0]) - if (const auto& player = tfs::lua::getSharedPtr(L, 1)) { - uint16_t lookType = tfs::lua::getNumber(L, 2); - uint8_t addon = tfs::lua::getNumber(L, 3, 0); - tfs::lua::pushBoolean(L, player->canWear(lookType, addon)); - } else { - lua_pushnil(L); - } - return 1; -} - -int luaPlayerSendOutfitWindow(lua_State* L) -{ - // player:sendOutfitWindow() - if (const auto& player = tfs::lua::getSharedPtr(L, 1)) { - player->sendOutfitWindow(); - tfs::lua::pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int luaPlayerSendEditPodium(lua_State* L) -{ - // player:sendEditPodium(item) - auto player = tfs::lua::getSharedPtr(L, 1); - const auto& item = tfs::lua::getSharedPtr(L, 2); - if (player && item) { - player->sendPodiumWindow(item); - tfs::lua::pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int luaPlayerAddMount(lua_State* L) -{ - // player:addMount(mountId or mountName) - const auto& player = tfs::lua::getSharedPtr(L, 1); - if (!player) { - lua_pushnil(L); - return 1; - } - - uint16_t mountId; - if (tfs::lua::isNumber(L, 2)) { - mountId = tfs::lua::getNumber(L, 2); - } else { - Mount* mount = g_game.mounts.getMountByName(tfs::lua::getString(L, 2)); - if (!mount) { - lua_pushnil(L); - return 1; - } - mountId = mount->id; - } - tfs::lua::pushBoolean(L, player->tameMount(mountId)); - return 1; -} - -int luaPlayerRemoveMount(lua_State* L) -{ - // player:removeMount(mountId or mountName) + // player:setCurrentOutfit(outfit) const auto& player = tfs::lua::getSharedPtr(L, 1); if (!player) { lua_pushnil(L); return 1; } - uint16_t mountId; - if (tfs::lua::isNumber(L, 2)) { - mountId = tfs::lua::getNumber(L, 2); - } else { - Mount* mount = g_game.mounts.getMountByName(tfs::lua::getString(L, 2)); - if (!mount) { - lua_pushnil(L); - return 1; - } - mountId = mount->id; - } - tfs::lua::pushBoolean(L, player->untameMount(mountId)); + player->setCurrentOutfit(tfs::lua::getOutfit(L, 2)); + tfs::lua::pushBoolean(L, true); return 1; } -int luaPlayerHasMount(lua_State* L) +int luaPlayerGetDefaultOutfit(lua_State* L) { - // player:hasMount(mountId or mountName) - const auto& player = tfs::lua::getSharedPtr(L, 1); - if (!player) { - lua_pushnil(L); - return 1; - } - - Mount* mount = nullptr; - if (tfs::lua::isNumber(L, 2)) { - mount = g_game.mounts.getMountByID(tfs::lua::getNumber(L, 2)); - } else { - mount = g_game.mounts.getMountByName(tfs::lua::getString(L, 2)); - } - - if (mount) { - tfs::lua::pushBoolean(L, player->hasMount(mount)); + // player:getDefaultOutfit() + if (const auto& player = tfs::lua::getSharedPtr(L, 1)) { + tfs::lua::pushOutfit(L, player->getDefaultOutfit()); } else { lua_pushnil(L); } return 1; } -int luaPlayerToggleMount(lua_State* L) +int luaPlayerSetDefaultOutfit(lua_State* L) { - // player:toggleMount(mount) + // player:setDefaultOutfit(outfit) const auto& player = tfs::lua::getSharedPtr(L, 1); if (!player) { lua_pushnil(L); return 1; } - bool mount = tfs::lua::getBoolean(L, 2); - tfs::lua::pushBoolean(L, player->toggleMount(mount)); + player->setDefaultOutfit(tfs::lua::getOutfit(L, 2)); + tfs::lua::pushBoolean(L, true); return 1; } @@ -2599,20 +2460,10 @@ void tfs::lua::registerPlayer(LuaScriptInterface& lsi) lsi.registerMethod("Player", "getParty", luaPlayerGetParty); - lsi.registerMethod("Player", "addOutfit", luaPlayerAddOutfit); - lsi.registerMethod("Player", "addOutfitAddon", luaPlayerAddOutfitAddon); - lsi.registerMethod("Player", "removeOutfit", luaPlayerRemoveOutfit); - lsi.registerMethod("Player", "removeOutfitAddon", luaPlayerRemoveOutfitAddon); - lsi.registerMethod("Player", "hasOutfit", luaPlayerHasOutfit); - lsi.registerMethod("Player", "canWearOutfit", luaPlayerCanWearOutfit); - lsi.registerMethod("Player", "sendOutfitWindow", luaPlayerSendOutfitWindow); - - lsi.registerMethod("Player", "sendEditPodium", luaPlayerSendEditPodium); - - lsi.registerMethod("Player", "addMount", luaPlayerAddMount); - lsi.registerMethod("Player", "removeMount", luaPlayerRemoveMount); - lsi.registerMethod("Player", "hasMount", luaPlayerHasMount); - lsi.registerMethod("Player", "toggleMount", luaPlayerToggleMount); + lsi.registerMethod("Player", "getCurrentOutfit", luaPlayerGetCurrentOutfit); + lsi.registerMethod("Player", "setCurrentOutfit", luaPlayerSetCurrentOutfit); + lsi.registerMethod("Player", "getDefaultOutfit", luaPlayerGetDefaultOutfit); + lsi.registerMethod("Player", "setDefaultOutfit", luaPlayerSetDefaultOutfit); lsi.registerMethod("Player", "getPremiumEndsAt", luaPlayerGetPremiumEndsAt); lsi.registerMethod("Player", "setPremiumEndsAt", luaPlayerSetPremiumEndsAt); diff --git a/src/lua/register.h b/src/lua/register.h index f0220d750e..456fa07a2b 100644 --- a/src/lua/register.h +++ b/src/lua/register.h @@ -25,7 +25,6 @@ void registerMonsters(LuaScriptInterface& i); void registerMoveEvent(LuaScriptInterface& i); void registerNetworkMessage(LuaScriptInterface& i); void registerNpc(LuaScriptInterface& i); -void registerOutfit(LuaScriptInterface& i); void registerParty(LuaScriptInterface& i); void registerPlayer(LuaScriptInterface& i); void registerPodium(LuaScriptInterface& i); diff --git a/src/lua/script.h b/src/lua/script.h index 426a254d99..ad98488db0 100644 --- a/src/lua/script.h +++ b/src/lua/script.h @@ -14,7 +14,6 @@ class LuaVariant; class Npc; class Player; class Thing; -struct Outfit; using Combat_ptr = std::shared_ptr; diff --git a/src/mounts.cpp b/src/mounts.cpp deleted file mode 100644 index d4daee95de..0000000000 --- a/src/mounts.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2023 The Forgotten Server Authors. All rights reserved. -// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. - -#include "otpch.h" - -#include "mounts.h" - -#include "pugicast.h" -#include "tools.h" - -bool Mounts::reload() -{ - mounts.clear(); - return loadFromXml(); -} - -bool Mounts::loadFromXml() -{ - pugi::xml_document doc; - pugi::xml_parse_result result = doc.load_file("data/XML/mounts.xml"); - if (!result) { - printXMLError("Error - Mounts::loadFromXml", "data/XML/mounts.xml", result); - return false; - } - - for (auto mountNode : doc.child("mounts").children()) { - uint32_t nodeId = pugi::cast(mountNode.attribute("id").value()); - if (nodeId == 0 || nodeId > std::numeric_limits::max()) { - std::cout << "[Notice - Mounts::loadFromXml] Mount id \"" << nodeId << "\" is not within 1 and 65535 range" - << std::endl; - continue; - } - - if (getMountByID(nodeId)) { - std::cout << "[Notice - Mounts::loadFromXml] Duplicate mount with id: " << nodeId << std::endl; - continue; - } - - mounts.emplace_back( - static_cast(nodeId), pugi::cast(mountNode.attribute("clientid").value()), - mountNode.attribute("name").as_string(), pugi::cast(mountNode.attribute("speed").value()), - mountNode.attribute("premium").as_bool()); - } - mounts.shrink_to_fit(); - return true; -} - -Mount* Mounts::getMountByID(uint16_t id) -{ - auto it = std::find_if(mounts.begin(), mounts.end(), [id](const Mount& mount) { return mount.id == id; }); - - return it != mounts.end() ? &*it : nullptr; -} - -Mount* Mounts::getMountByName(const std::string& name) -{ - for (auto& it : mounts) { - if (boost::iequals(name, it.name)) { - return ⁢ - } - } - - return nullptr; -} - -Mount* Mounts::getMountByClientID(uint16_t clientId) -{ - auto it = std::find_if(mounts.begin(), mounts.end(), - [clientId](const Mount& mount) { return mount.clientId == clientId; }); - - return it != mounts.end() ? &*it : nullptr; -} diff --git a/src/mounts.h b/src/mounts.h deleted file mode 100644 index f89f9a673a..0000000000 --- a/src/mounts.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2023 The Forgotten Server Authors. All rights reserved. -// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. - -#ifndef FS_MOUNTS_H -#define FS_MOUNTS_H - -struct Mount -{ - Mount(uint16_t id, uint16_t clientId, std::string name, int32_t speed, bool premium) : - name(std::move(name)), speed(speed), clientId(clientId), id(id), premium(premium) - {} - - std::string name; - int32_t speed; - uint16_t clientId; - uint16_t id; - bool premium; -}; - -class Mounts -{ -public: - bool reload(); - bool loadFromXml(); - Mount* getMountByID(uint16_t id); - Mount* getMountByName(const std::string& name); - Mount* getMountByClientID(uint16_t clientId); - - const std::vector& getMounts() const { return mounts; } - -private: - std::vector mounts; -}; - -#endif // FS_MOUNTS_H diff --git a/src/otserv.cpp b/src/otserv.cpp index 60a90d4fe1..6075ea2ab4 100644 --- a/src/otserv.cpp +++ b/src/otserv.cpp @@ -10,7 +10,6 @@ #include "http/http.h" #include "iomarket.h" #include "monsters.h" -#include "outfit.h" #include "protocolstatus.h" #include "rsa.h" #include "scheduler.h" @@ -212,12 +211,6 @@ void mainLoader(ServiceManager* services) return; } - std::cout << ">> Loading outfits" << std::endl; - if (!Outfits::getInstance().loadFromXml()) { - startupErrorMessage("Unable to load outfits!"); - return; - } - std::cout << ">> Checking world type... " << std::flush; std::string worldType = boost::algorithm::to_lower_copy(getString(ConfigManager::WORLD_TYPE)); if (worldType == "pvp") { diff --git a/src/outfit.cpp b/src/outfit.cpp deleted file mode 100644 index 84978d4373..0000000000 --- a/src/outfit.cpp +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2023 The Forgotten Server Authors. All rights reserved. -// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. - -#include "otpch.h" - -#include "outfit.h" - -#include "pugicast.h" -#include "tools.h" - -bool Outfits::loadFromXml() -{ - pugi::xml_document doc; - pugi::xml_parse_result result = doc.load_file("data/XML/outfits.xml"); - if (!result) { - printXMLError("Error - Outfits::loadFromXml", "data/XML/outfits.xml", result); - return false; - } - - for (auto outfitNode : doc.child("outfits").children()) { - pugi::xml_attribute attr; - if ((attr = outfitNode.attribute("enabled")) && !attr.as_bool()) { - continue; - } - - if (!(attr = outfitNode.attribute("type"))) { - std::cout << "[Warning - Outfits::loadFromXml] Missing outfit type." << std::endl; - continue; - } - - uint16_t type = pugi::cast(attr.value()); - if (type > PLAYERSEX_LAST) { - std::cout << "[Warning - Outfits::loadFromXml] Invalid outfit type " << type << "." << std::endl; - continue; - } - - pugi::xml_attribute lookTypeAttribute = outfitNode.attribute("looktype"); - if (!lookTypeAttribute) { - std::cout << "[Warning - Outfits::loadFromXml] Missing looktype on outfit." << std::endl; - continue; - } - - outfits[type].push_back({.name = outfitNode.attribute("name").as_string(), - .lookType = pugi::cast(lookTypeAttribute.value()), - .premium = outfitNode.attribute("premium").as_bool(), - .unlocked = outfitNode.attribute("unlocked").as_bool(true)}); - } - return true; -} - -const Outfit* Outfits::getOutfitByLookType(PlayerSex_t sex, uint16_t lookType) const -{ - for (const Outfit& outfit : outfits[sex]) { - if (outfit.lookType == lookType) { - return &outfit; - } - } - return nullptr; -} - -const Outfit* Outfits::getOutfitByLookType(uint16_t lookType) const -{ - for (uint8_t sex = PLAYERSEX_FEMALE; sex <= PLAYERSEX_LAST; sex++) { - for (const Outfit& outfit : outfits[sex]) { - if (outfit.lookType == lookType) { - return &outfit; - } - } - } - return nullptr; -} diff --git a/src/outfit.h b/src/outfit.h deleted file mode 100644 index 337ded5d6a..0000000000 --- a/src/outfit.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2023 The Forgotten Server Authors. All rights reserved. -// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. - -#ifndef FS_OUTFIT_H -#define FS_OUTFIT_H - -#include "enums.h" - -struct Outfit -{ - std::string name; - uint16_t lookType; - bool premium; - bool unlocked; -}; - -inline bool operator==(const Outfit& lhs, const Outfit& rhs) -{ - return lhs.name == rhs.name && lhs.lookType == rhs.lookType && lhs.premium == rhs.premium && - lhs.unlocked == rhs.unlocked; -} - -struct ProtocolOutfit -{ - ProtocolOutfit(std::string_view name, uint16_t lookType, uint8_t addons) : - name{name}, lookType{lookType}, addons{addons} - {} - - std::string name; - uint16_t lookType; - uint8_t addons; -}; - -class Outfits -{ -public: - static Outfits& getInstance() - { - static Outfits instance; - return instance; - } - - bool loadFromXml(); - - const Outfit* getOutfitByLookType(PlayerSex_t sex, uint16_t lookType) const; - const Outfit* getOutfitByLookType(uint16_t lookType) const; - const std::vector& getOutfits(PlayerSex_t sex) const { return outfits[sex]; } - -private: - std::vector outfits[PLAYERSEX_LAST + 1]; -}; - -#endif // FS_OUTFIT_H diff --git a/src/player.cpp b/src/player.cpp index b242a4ccf6..d746d77c18 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -14,7 +14,6 @@ #include "house.h" #include "iologindata.h" #include "movement.h" -#include "outfit.h" #include "party.h" #include "scheduler.h" #include "tools.h" @@ -1029,18 +1028,6 @@ void Player::onCreatureAppear(const std::shared_ptr& creature, bool is onChangeZone(getZone()); - uint16_t currentMountId = currentOutfit.lookMount; - if (currentMountId != 0) { - if (Mount* currentMount = g_game.mounts.getMountByClientID(currentMountId)) { - if (hasMount(currentMount)) { - g_game.changeSpeed(asPlayer(), currentMount->speed); - } else { - defaultOutfit.lookMount = 0; - g_game.internalCreatureChangeOutfit(asPlayer(), defaultOutfit); - } - } - } - IOLoginData::updateOnlineStatus(guid, true); if (const auto& guild = getGuild()) { @@ -1120,17 +1107,6 @@ void Player::onChangeZone(ZoneType_t zone) removeAttackedCreature(); onAttackedCreatureDisappear(false); } - - if (!group->access && isMounted()) { - dismount(); - g_game.internalCreatureChangeOutfit(asPlayer(), defaultOutfit); - wasMounted_ = true; - } - } else { - if (wasMounted_) { - toggleMount(true); - wasMounted_ = false; - } } g_game.updateCreatureWalkthrough(asPlayer()); @@ -3410,10 +3386,6 @@ void Player::onAddCondition(ConditionType_t type) { Creature::onAddCondition(type); - if (type == CONDITION_OUTFIT && isMounted()) { - dismount(); - } - sendIcons(); } @@ -3757,119 +3729,6 @@ void Player::changeSoul(int32_t soulChange) sendStats(); } -bool Player::canWear(uint32_t lookType, uint8_t addons) const -{ - if (group->access) { - return true; - } - - const Outfit* outfit = Outfits::getInstance().getOutfitByLookType(sex, lookType); - if (!outfit) { - return false; - } - - if (outfit->premium && !isPremium()) { - return false; - } - - if (outfit->unlocked && addons == 0) { - return true; - } - - for (const auto& [outfitType, addon] : outfits) { - if (outfitType == lookType) { - if (addon == addons || addon == 3 || addons == 0) { - return true; - } - return false; // have lookType on list and addons don't match - } - } - return false; -} - -bool Player::hasOutfit(uint32_t lookType, uint8_t addons) -{ - const Outfit* outfit = Outfits::getInstance().getOutfitByLookType(sex, lookType); - if (!outfit) { - return false; - } - - if (outfit->unlocked && addons == 0) { - return true; - } - - for (const auto& [outfitType, addon] : outfits) { - if (outfitType == lookType) { - if (addon == addons || addon == 3 || addons == 0) { - return true; - } - return false; // have lookType on list and addons don't match - } - } - return false; -} - -void Player::addOutfit(uint16_t lookType, uint8_t addons) -{ - for (auto& [outfit, addon] : outfits) { - if (outfit == lookType) { - addon |= addons; - return; - } - } - outfits.insert(std::pair(lookType, addons)); -} - -bool Player::removeOutfit(uint16_t lookType) -{ - for (const auto& [outfit, addon] : outfits) { - if (outfit == lookType) { - outfits.erase(outfit); - return true; - } - } - return false; -} - -bool Player::removeOutfitAddon(uint16_t lookType, uint8_t addons) -{ - for (auto& [outfit, addon] : outfits) { - if (outfit == lookType) { - addon &= ~addons; - return true; - } - } - return false; -} - -bool Player::getOutfitAddons(const Outfit& outfit, uint8_t& addons) const -{ - if (group->access) { - addons = 3; - return true; - } - - if (outfit.premium && !isPremium()) { - return false; - } - - for (const auto& [lookType, addon] : outfits) { - if (lookType != outfit.lookType) { - continue; - } - - addons = addon; - return true; - } - - if (!outfit.unlocked) { - return false; - } - - addons = 0; - return true; -} - void Player::setSex(PlayerSex_t newSex) { sex = newSex; } Skulls_t Player::getSkull() const @@ -4216,158 +4075,6 @@ GuildEmblems_t Player::getGuildEmblem(const std::shared_ptr& playe return GUILDEMBLEM_NEUTRAL; } -uint16_t Player::getRandomMount() const -{ - std::vector mountsId; - for (const Mount& mount : g_game.mounts.getMounts()) { - if (hasMount(&mount)) { - mountsId.push_back(mount.id); - } - } - - return mountsId[uniform_random(0, mountsId.size() - 1)]; -} - -uint16_t Player::getCurrentMount() const { return currentMount; } - -void Player::setCurrentMount(uint16_t mountId) { currentMount = mountId; } - -bool Player::toggleMount(bool mount) -{ - if ((OTSYS_TIME() - lastToggleMount) < 3000 && !wasMounted_) { - sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); - return false; - } - - if (mount) { - if (isMounted()) { - return false; - } - - if (const auto& tile = getTile(); !group->access && tile->hasFlag(TILESTATE_PROTECTIONZONE)) { - sendCancelMessage(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE); - return false; - } - - const Outfit* playerOutfit = Outfits::getInstance().getOutfitByLookType(getSex(), defaultOutfit.lookType); - if (!playerOutfit) { - return false; - } - - uint16_t currentMountId = getCurrentMount(); - if (currentMountId == 0) { - sendOutfitWindow(); - return false; - } - - if (randomizeMount) { - currentMountId = getRandomMount(); - } - - Mount* currentMount = g_game.mounts.getMountByID(currentMountId); - if (!currentMount) { - return false; - } - - if (!hasMount(currentMount)) { - setCurrentMount(0); - sendOutfitWindow(); - return false; - } - - if (currentMount->premium && !isPremium()) { - sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT); - return false; - } - - if (hasCondition(CONDITION_OUTFIT)) { - sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - return false; - } - - defaultOutfit.lookMount = currentMount->clientId; - - if (currentMount->speed != 0) { - g_game.changeSpeed(asPlayer(), currentMount->speed); - } - } else { - if (!isMounted()) { - return false; - } - - dismount(); - } - - g_game.internalCreatureChangeOutfit(asPlayer(), defaultOutfit); - lastToggleMount = OTSYS_TIME(); - return true; -} - -bool Player::tameMount(uint16_t mountId) -{ - Mount* mount = g_game.mounts.getMountByID(mountId); - if (!mount || hasMount(mount)) { - return false; - } - - mounts.insert(mountId); - return true; -} - -bool Player::untameMount(uint16_t mountId) -{ - Mount* mount = g_game.mounts.getMountByID(mountId); - if (!mount || !hasMount(mount)) { - return false; - } - - mounts.erase(mountId); - - if (getCurrentMount() == mountId) { - if (isMounted()) { - dismount(); - g_game.internalCreatureChangeOutfit(asPlayer(), defaultOutfit); - } - - setCurrentMount(0); - } - - return true; -} - -bool Player::hasMount(const Mount* mount) const -{ - if (isAccessPlayer()) { - return true; - } - - if (mount->premium && !isPremium()) { - return false; - } - - return mounts.find(mount->id) != mounts.end(); -} - -bool Player::hasMounts() const -{ - for (const Mount& mount : g_game.mounts.getMounts()) { - if (hasMount(&mount)) { - return true; - } - } - return false; -} - -void Player::dismount() -{ - Mount* mount = g_game.mounts.getMountByID(getCurrentMount()); - if (mount && mount->speed > 0) { - g_game.changeSpeed(asPlayer(), -mount->speed); - } - - defaultOutfit.lookMount = 0; -} - bool Player::addOfflineTrainingTries(skills_t skill, uint64_t tries) { if (tries == 0 || skill == SKILL_LEVEL) { diff --git a/src/player.h b/src/player.h index c14f9e7818..99becae986 100644 --- a/src/player.h +++ b/src/player.h @@ -18,7 +18,6 @@ #include "vocation.h" class House; -struct Mount; class NetworkMessage; class Npc; class Party; @@ -122,23 +121,6 @@ class Player final : public Creature CreatureType_t getType() const override { return CREATURETYPE_PLAYER; } - uint16_t getRandomMount() const; - uint16_t getCurrentMount() const; - void setCurrentMount(uint16_t mountId); - bool isMounted() const { return defaultOutfit.lookMount != 0; } - bool toggleMount(bool mount); - bool tameMount(uint16_t mountId); - bool untameMount(uint16_t mountId); - bool hasMount(const Mount* mount) const; - bool hasMounts() const; - void dismount(); - - bool wasMounted() const { return wasMounted_; } - void setWasMounted(bool wasMounted) { wasMounted_ = wasMounted; } - - bool getRandomizeMount() const { return randomizeMount; } - void setRandomizeMount(bool mode) { randomizeMount = mode; } - void sendFYIBox(const std::string& message) { if (client) { @@ -566,12 +548,6 @@ class Player final : public Creature client->sendCreatureSkull(creature); } } - bool canWear(uint32_t lookType, uint8_t addons) const; - bool hasOutfit(uint32_t lookType, uint8_t addons); - void addOutfit(uint16_t lookType, uint8_t addons); - bool removeOutfit(uint16_t lookType); - bool removeOutfitAddon(uint16_t lookType, uint8_t addons); - bool getOutfitAddons(const Outfit& outfit, uint8_t& addons) const; size_t getMaxVIPEntries() const; size_t getMaxDepotItems() const; @@ -1093,18 +1069,6 @@ class Player final : public Creature client->sendOpenPrivateChannel(receiver); } } - void sendOutfitWindow() - { - if (client) { - client->sendOutfitWindow(); - } - } - void sendPodiumWindow(const std::shared_ptr& item) - { - if (client) { - client->sendPodiumWindow(item); - } - } void sendCloseContainer(uint8_t cid) { if (client) { @@ -1259,8 +1223,6 @@ class Player final : public Creature std::map openContainers; std::map> depotChests; - std::map outfits; - std::unordered_set mounts; GuildWarVector guildWarVector; std::list shopItemList; @@ -1366,12 +1328,10 @@ class Player final : public Creature bool chaseMode = false; bool secureMode = false; bool inMarket = false; - bool wasMounted_ = false; bool ghostMode = false; bool pzLocked = false; bool addAttackSkillPoint = false; bool inventoryAbilities[CONST_SLOT_LAST + 1] = {}; - bool randomizeMount = false; void updateItemsLight(bool internal = false); int32_t getStepSpeed() const override diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp index 34c2a4adee..77614624f4 100644 --- a/src/protocolgame.cpp +++ b/src/protocolgame.cpp @@ -13,7 +13,6 @@ #include "game.h" #include "iologindata.h" #include "iomarket.h" -#include "outfit.h" #include "outputmessage.h" #include "player.h" #include "podium.h" @@ -732,9 +731,8 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) parseSeekInContainer(msg); break; // case 0xCD: break; // request inspect window - case 0xD3: - parseSetOutfit(msg); - break; + // case 0xD2: break; // request outfit window + // case 0xD3: break; // set outfit // case 0xD5: break; // apply imbuement // case 0xD6: break; // clear imbuement // case 0xD7: break; // close imbuing window @@ -1061,72 +1059,6 @@ void ProtocolGame::parseAutoWalk(NetworkMessage& msg) [playerID = player->getID(), path = std::move(path)]() { g_game.playerAutoWalk(playerID, path); }); } -void ProtocolGame::parseSetOutfit(NetworkMessage& msg) -{ - uint8_t outfitType = msg.getByte(); - - Outfit_t newOutfit; - newOutfit.lookType = msg.get(); - newOutfit.lookHead = msg.getByte(); - newOutfit.lookBody = msg.getByte(); - newOutfit.lookLegs = msg.getByte(); - newOutfit.lookFeet = msg.getByte(); - newOutfit.lookAddons = msg.getByte(); - - // Set outfit window - if (outfitType == 0) { - newOutfit.lookMount = msg.get(); - if (newOutfit.lookMount != 0) { - newOutfit.lookMountHead = msg.getByte(); - newOutfit.lookMountBody = msg.getByte(); - newOutfit.lookMountLegs = msg.getByte(); - newOutfit.lookMountFeet = msg.getByte(); - } else { - msg.skipBytes(4); - - // prevent mount color settings from resetting - const Outfit_t& currentOutfit = player->getCurrentOutfit(); - newOutfit.lookMountHead = currentOutfit.lookMountHead; - newOutfit.lookMountBody = currentOutfit.lookMountBody; - newOutfit.lookMountLegs = currentOutfit.lookMountLegs; - newOutfit.lookMountFeet = currentOutfit.lookMountFeet; - } - - msg.get(); // familiar looktype - bool randomizeMount = msg.getByte() == 0x01; - g_dispatcher.addTask( - [=, playerID = player->getID()]() { g_game.playerChangeOutfit(playerID, newOutfit, randomizeMount); }); - - // Store "try outfit" window - } else if (outfitType == 1) { - newOutfit.lookMount = 0; - // mount colors or store offerId (needs testing) - newOutfit.lookMountHead = msg.getByte(); - newOutfit.lookMountBody = msg.getByte(); - newOutfit.lookMountLegs = msg.getByte(); - newOutfit.lookMountFeet = msg.getByte(); - // player->? (open store?) - - // Podium interaction - } else if (outfitType == 2) { - Position pos = msg.getPosition(); - uint16_t spriteId = msg.get(); - uint8_t stackpos = msg.getByte(); - newOutfit.lookMount = msg.get(); - newOutfit.lookMountHead = msg.getByte(); - newOutfit.lookMountBody = msg.getByte(); - newOutfit.lookMountLegs = msg.getByte(); - newOutfit.lookMountFeet = msg.getByte(); - Direction direction = static_cast(msg.getByte()); - bool podiumVisible = msg.getByte() == 1; - - // apply to podium - g_dispatcher.addTask(DISPATCHER_TASK_EXPIRATION, [=, playerID = player->getID()]() { - g_game.playerEditPodium(playerID, newOutfit, pos, stackpos, spriteId, podiumVisible, direction); - }); - } -} - void ProtocolGame::parseEditPodiumRequest(NetworkMessage& msg) { Position pos = msg.getPosition(); @@ -2999,233 +2931,6 @@ void ProtocolGame::sendCombatAnalyzer(CombatType_t type, int32_t amount, DamageA writeToOutputBuffer(msg); } -void ProtocolGame::sendOutfitWindow() -{ - const auto& outfits = Outfits::getInstance().getOutfits(player->getSex()); - if (outfits.size() == 0) { - return; - } - - NetworkMessage msg; - msg.addByte(0xC8); - - Outfit_t currentOutfit = player->getDefaultOutfit(); - - if (currentOutfit.lookType == 0) { - Outfit_t newOutfit; - newOutfit.lookType = outfits.front().lookType; - currentOutfit = newOutfit; - } - - Mount* currentMount = g_game.mounts.getMountByID(player->getCurrentMount()); - if (currentMount) { - currentOutfit.lookMount = currentMount->clientId; - } - - bool mounted; - if (player->wasMounted()) { - mounted = currentOutfit.lookMount != 0; - } else { - mounted = player->isMounted(); - } - - AddOutfit(msg, currentOutfit); - - // mount color bytes are required here regardless of having one - if (currentOutfit.lookMount == 0) { - msg.addByte(currentOutfit.lookMountHead); - msg.addByte(currentOutfit.lookMountBody); - msg.addByte(currentOutfit.lookMountLegs); - msg.addByte(currentOutfit.lookMountFeet); - } - - msg.add(0); // current familiar looktype - - std::vector protocolOutfits; - if (player->isAccessPlayer()) { - protocolOutfits.emplace_back("Gamemaster", 75, 0); - } - - for (const Outfit& outfit : outfits) { - uint8_t addons; - if (!player->getOutfitAddons(outfit, addons)) { - continue; - } - - protocolOutfits.emplace_back(outfit.name, outfit.lookType, addons); - } - - msg.add(protocolOutfits.size()); - for (const ProtocolOutfit& outfit : protocolOutfits) { - msg.add(outfit.lookType); - msg.addString(outfit.name); - msg.addByte(outfit.addons); - msg.addByte(0x00); // mode: 0x00 - available, 0x01 store (requires U32 store offerId), 0x02 golden outfit - // tooltip (hardcoded) - } - - std::vector mounts; - for (const Mount& mount : g_game.mounts.getMounts()) { - if (player->hasMount(&mount)) { - mounts.push_back(&mount); - } - } - - msg.add(mounts.size()); - for (const Mount* mount : mounts) { - msg.add(mount->clientId); - msg.addString(mount->name); - msg.addByte(0x00); // mode: 0x00 - available, 0x01 store (requires U32 store offerId) - } - - msg.add(0x00); // familiars.size() - // size > 0 - // U16 looktype - // String name - // 0x00 // mode: 0x00 - available, 0x01 store (requires U32 store offerId) - - msg.addByte(0x00); // Try outfit mode (?) - msg.addByte(mounted ? 0x01 : 0x00); - msg.addByte(player->getRandomizeMount() ? 0x01 : 0x00); - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendPodiumWindow(const std::shared_ptr& item) -{ - if (!item) { - return; - } - - const auto& podium = item->asPodium(); - if (!podium) { - return; - } - - const auto& tile = item->getTile(); - if (!tile) { - return; - } - - int32_t stackpos = tile->getThingIndex(item); - - // read podium outfit - Outfit_t podiumOutfit = podium->getOutfit(); - Outfit_t playerOutfit = player->getDefaultOutfit(); - bool isEmpty = podiumOutfit.lookType == 0 && podiumOutfit.lookMount == 0; - - if (podiumOutfit.lookType == 0) { - // copy player outfit - podiumOutfit.lookType = playerOutfit.lookType; - podiumOutfit.lookHead = playerOutfit.lookHead; - podiumOutfit.lookBody = playerOutfit.lookBody; - podiumOutfit.lookLegs = playerOutfit.lookLegs; - podiumOutfit.lookFeet = playerOutfit.lookFeet; - podiumOutfit.lookAddons = playerOutfit.lookAddons; - } - - if (podiumOutfit.lookMount == 0) { - // copy player mount - podiumOutfit.lookMount = playerOutfit.lookMount; - podiumOutfit.lookMountHead = playerOutfit.lookMountHead; - podiumOutfit.lookMountBody = playerOutfit.lookMountBody; - podiumOutfit.lookMountLegs = playerOutfit.lookMountLegs; - podiumOutfit.lookMountFeet = playerOutfit.lookMountFeet; - } - - // fetch player outfits - const auto& outfits = Outfits::getInstance().getOutfits(player->getSex()); - if (outfits.size() == 0) { - player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - return; - } - - // add GM outfit for staff members - std::vector protocolOutfits; - if (player->isAccessPlayer()) { - protocolOutfits.emplace_back("Gamemaster", 75, 0); - } - - // fetch player addons info - for (const Outfit& outfit : outfits) { - uint8_t addons; - if (!player->getOutfitAddons(outfit, addons)) { - continue; - } - - protocolOutfits.emplace_back(outfit.name, outfit.lookType, addons); - } - - // select first outfit available when the one from podium is not unlocked - if (!player->canWear(podiumOutfit.lookType, 0)) { - podiumOutfit.lookType = outfits.front().lookType; - } - - // fetch player mounts - std::vector mounts; - for (const Mount& mount : g_game.mounts.getMounts()) { - if (player->hasMount(&mount)) { - mounts.push_back(&mount); - } - } - - // packet header - NetworkMessage msg; - msg.addByte(0xC8); - - // current outfit - msg.add(podiumOutfit.lookType); - msg.addByte(podiumOutfit.lookHead); - msg.addByte(podiumOutfit.lookBody); - msg.addByte(podiumOutfit.lookLegs); - msg.addByte(podiumOutfit.lookFeet); - msg.addByte(podiumOutfit.lookAddons); - - // current mount - msg.add(podiumOutfit.lookMount); - msg.addByte(podiumOutfit.lookMountHead); - msg.addByte(podiumOutfit.lookMountBody); - msg.addByte(podiumOutfit.lookMountLegs); - msg.addByte(podiumOutfit.lookMountFeet); - - // current familiar (not used in podium mode) - msg.add(0); - - // available outfits - msg.add(protocolOutfits.size()); - for (const ProtocolOutfit& outfit : protocolOutfits) { - msg.add(outfit.lookType); - msg.addString(outfit.name); - msg.addByte(outfit.addons); - msg.addByte(0x00); // mode: 0x00 - available, 0x01 store (requires U32 store offerId), 0x02 golden outfit - // tooltip (hardcoded) - } - - // available mounts - msg.add(mounts.size()); - for (const Mount* mount : mounts) { - msg.add(mount->clientId); - msg.addString(mount->name); - msg.addByte(0x00); // mode: 0x00 - available, 0x01 store (requires U32 store offerId) - } - - // available familiars (not used in podium mode) - msg.add(0); - - msg.addByte(0x05); // "set outfit" window mode (5 = podium) - msg.addByte((isEmpty && playerOutfit.lookMount != 0) || podium->hasFlag(PODIUM_SHOW_MOUNT) - ? 0x01 - : 0x00); // "mount" checkbox - msg.add(0); // unknown - msg.addPosition(item->getPosition()); - msg.add(item->getClientID()); - msg.addByte(stackpos); - - msg.addByte(podium->hasFlag(PODIUM_SHOW_PLATFORM) ? 0x01 : 0x00); // is platform visible - msg.addByte(0x01); // "outfit" checkbox, ignored by the client - msg.addByte(podium->getDirection()); // outfit direction - writeToOutputBuffer(msg); -} - void ProtocolGame::sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus) { NetworkMessage msg; diff --git a/src/protocolgame.h b/src/protocolgame.h index 3dec2e5df4..6b241b8e1b 100644 --- a/src/protocolgame.h +++ b/src/protocolgame.h @@ -88,7 +88,6 @@ class ProtocolGame final : public Protocol // Parse methods void parseAutoWalk(NetworkMessage& msg); - void parseSetOutfit(NetworkMessage& msg); void parseEditPodiumRequest(NetworkMessage& msg); void parseSay(NetworkMessage& msg); void parseLookAt(NetworkMessage& msg); @@ -212,9 +211,6 @@ class ProtocolGame final : public Protocol void sendHouseWindow(uint32_t windowTextId, const std::string& text); void sendCombatAnalyzer(CombatType_t type, int32_t amount, DamageAnalyzerImpactType impactType, const std::string& target); - void sendOutfitWindow(); - - void sendPodiumWindow(const std::shared_ptr& item); void sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus); void sendVIP(uint32_t guid, const std::string& name, const std::string& description, uint32_t icon, bool notify, diff --git a/src/script.cpp b/src/script.cpp index 36ec236d1c..ab93cefac3 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -35,7 +35,7 @@ bool Scripts::loadScripts(std::string_view folderName, bool isLib, bool reload) size_t found = it->path().filename().string().find(disable); if (found != std::string::npos) { if (getBoolean(ConfigManager::SCRIPTS_CONSOLE_LOGS)) { - std::cout << "> " << it->path().filename().string() << " [disabled]" << std::endl; + std::cout << "> " << it->path().lexically_relative(dir).string() << " [disabled]" << std::endl; } continue; } @@ -57,16 +57,16 @@ bool Scripts::loadScripts(std::string_view folderName, bool isLib, bool reload) } if (scriptInterface.loadFile(scriptFile) == -1) { - std::cout << "> " << it->filename().string() << " [error]" << std::endl; + std::cout << "> " << it->lexically_relative(dir).string() << " [error]" << std::endl; std::cout << "^ " << scriptInterface.getLastLuaError() << std::endl; continue; } if (getBoolean(ConfigManager::SCRIPTS_CONSOLE_LOGS)) { if (!reload) { - std::cout << "> " << it->filename().string() << " [loaded]" << std::endl; + std::cout << "> " << it->lexically_relative(dir).string() << " [loaded]" << std::endl; } else { - std::cout << "> " << it->filename().string() << " [reloaded]" << std::endl; + std::cout << "> " << it->lexically_relative(dir).string() << " [reloaded]" << std::endl; } } } diff --git a/src/signals.cpp b/src/signals.cpp index fb82fccbb0..4a0c334790 100644 --- a/src/signals.cpp +++ b/src/signals.cpp @@ -12,7 +12,6 @@ #include "game.h" #include "globalevent.h" #include "monsters.h" -#include "mounts.h" #include "movement.h" #include "scheduler.h" #include "spells.h" @@ -80,9 +79,6 @@ void sighupHandler() g_weapons->loadDefaults(); std::cout << "Reloaded weapons." << std::endl; - g_game.mounts.reload(); - std::cout << "Reloaded mounts." << std::endl; - g_globalEvents->reload(); std::cout << "Reloaded globalevents." << std::endl; diff --git a/vc18/atlas.vcxproj b/vc18/atlas.vcxproj index 6218c335f9..2af8c1a057 100644 --- a/vc18/atlas.vcxproj +++ b/vc18/atlas.vcxproj @@ -185,7 +185,6 @@ - @@ -206,7 +205,6 @@ - @@ -215,7 +213,6 @@ Create otpch.h - @@ -302,13 +299,11 @@ - - diff --git a/vc18/atlas.vcxproj.filters b/vc18/atlas.vcxproj.filters index c1238e4bac..fe96336bd9 100644 --- a/vc18/atlas.vcxproj.filters +++ b/vc18/atlas.vcxproj.filters @@ -197,9 +197,6 @@ Source Files - - Source Files - Source Files @@ -212,9 +209,6 @@ Source Files - - Source Files - Source Files @@ -302,9 +296,6 @@ Source Files\lua\modules - - Source Files\lua\modules - Source Files\lua\modules @@ -550,9 +541,6 @@ Header Files - - Header Files - Header Files @@ -568,9 +556,6 @@ Header Files - - Header Files - Header Files