From 988b14054da51ce2b45dc8f0ca90824f0f2f8bd5 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 25 Dec 2025 02:30:44 -0300 Subject: [PATCH 01/55] refactor: migrate outfits and mounts to Lua - Deleted the Mounts class and related methods for managing mounts. - Removed outfit management from Player class, including outfit-related methods and attributes. - Eliminated outfit loading from XML and outfit-related network message handling. - Cleaned up includes and references to outfits and mounts across various files. --- .gitignore | 2 +- data/XML/mounts.xml | 212 ------------- data/XML/outfits.xml | 226 ------------- data/cpplinter.lua | 16 +- data/lib/compat/compat.lua | 11 - data/lib/core/creature.lua | 4 + data/lib/core/network_message.lua | 28 ++ data/lib/core/storages.lua | 5 + data/migrations/37.lua | 46 ++- data/migrations/38.lua | 3 + data/scripts/events/player.lua | 4 +- data/scripts/systems/outfits/config.lua | 4 + data/scripts/systems/outfits/core/mounts.lua | 157 +++++++++ data/scripts/systems/outfits/core/outfits.lua | 57 ++++ data/scripts/systems/outfits/core/windows.lua | 207 ++++++++++++ data/scripts/systems/outfits/data/mounts.lua | 238 ++++++++++++++ data/scripts/systems/outfits/data/outfits.lua | 256 +++++++++++++++ data/scripts/systems/outfits/events.lua | 20 ++ .../network/request_outfit_window.lua} | 2 +- .../systems/outfits/network/set_outfit.lua | 86 +++++ schema.sql | 18 +- src/CMakeLists.txt | 4 - src/creature.h | 1 - src/game.cpp | 83 ----- src/game.h | 4 - src/iologindata.cpp | 58 +--- src/lua/CMakeLists.txt | 1 - src/lua/api.cpp | 24 -- src/lua/api.h | 5 +- src/lua/modules.cpp | 1 - src/lua/modules/game.cpp | 67 ---- src/lua/modules/outfit.cpp | 38 --- src/lua/modules/player.cpp | 187 ++--------- src/lua/register.h | 1 - src/lua/script.h | 1 - src/mounts.cpp | 72 ----- src/mounts.h | 35 -- src/otserv.cpp | 7 - src/outfit.cpp | 71 ----- src/outfit.h | 53 ---- src/player.cpp | 293 ----------------- src/player.h | 40 --- src/protocolgame.cpp | 299 +----------------- src/protocolgame.h | 4 - src/script.cpp | 8 +- src/signals.cpp | 4 - 46 files changed, 1157 insertions(+), 1806 deletions(-) delete mode 100644 data/XML/mounts.xml delete mode 100644 data/XML/outfits.xml create mode 100644 data/migrations/38.lua create mode 100644 data/scripts/systems/outfits/config.lua create mode 100644 data/scripts/systems/outfits/core/mounts.lua create mode 100644 data/scripts/systems/outfits/core/outfits.lua create mode 100644 data/scripts/systems/outfits/core/windows.lua create mode 100644 data/scripts/systems/outfits/data/mounts.lua create mode 100644 data/scripts/systems/outfits/data/outfits.lua create mode 100644 data/scripts/systems/outfits/events.lua rename data/scripts/{network/outfit.lua => systems/outfits/network/request_outfit_window.lua} (67%) create mode 100644 data/scripts/systems/outfits/network/set_outfit.lua delete mode 100644 src/lua/modules/outfit.cpp delete mode 100644 src/mounts.cpp delete mode 100644 src/mounts.h delete mode 100644 src/outfit.cpp delete mode 100644 src/outfit.h diff --git a/.gitignore b/.gitignore index 793720d9d9..741786d1bc 100644 --- a/.gitignore +++ b/.gitignore @@ -184,4 +184,4 @@ pip-log.txt ############# ## TFS / OT ############# -config.lua +/config.lua 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 bac452b5b4..1ed602d428 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 @@ -421,8 +422,19 @@ 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 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/creature.lua b/data/lib/core/creature.lua index 943e027465..4488c5902e 100644 --- a/data/lib/core/creature.lua +++ b/data/lib/core/creature.lua @@ -200,6 +200,10 @@ function Creature.getKillers(self, onlyPlayers) return killers end +function Creature.hasStorageValue(self, key) + return self:getStorageValue(key) ~= nil +end + function Creature.removeStorageValue(self, key) return self:setStorageValue(key, nil) end diff --git a/data/lib/core/network_message.lua b/data/lib/core/network_message.lua index 7cb867c44b..8421e05189 100644 --- a/data/lib/core/network_message.lua +++ b/data/lib/core/network_message.lua @@ -9,3 +9,31 @@ end function NetworkMessage:addBool(value) self:addByte(value and 1 or 0) end + +function NetworkMessage:addItemId(itemId) + local it = ItemType(itemId) + msg:addU16(it:getClientId()) +end + +function NetworkMessage:addOutfit(outfit) + -- 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 + + -- mount + self:addU16(outfit.lookMount) + if outfit.lookMount ~= 0 then + self:addByte(outfit.lookMountHead) + self:addByte(outfit.lookMountBody) + self:addByte(outfit.lookMountLegs) + self:addByte(outfit.lookMountFeet) + end +end diff --git a/data/lib/core/storages.lua b/data/lib/core/storages.lua index cec8998cd2..d51687b5e9 100644 --- a/data/lib/core/storages.lua +++ b/data/lib/core/storages.lua @@ -48,4 +48,9 @@ PlayerStorageKeys = { -- Bestiary: bestiaryKillsBase = 400000, bestiaryTrackerBase = 500000, + + -- Outfits and mounts: + randomizeMount = 60000, + outfitsBase = 600000, + mountsBase = 610000, } diff --git a/data/migrations/37.lua b/data/migrations/37.lua index d0ffd9c0cb..a90d7c3ca1 100644 --- a/data/migrations/37.lua +++ b/data/migrations/37.lua @@ -1,3 +1,47 @@ function onUpdateDatabase() - return false + print("> Updating database to version 38 (revert outfits/mounts to storages)") + + local rows = {} + + 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 = PlayerStorageKeys.outfitsBase + outfitId + table.insert(rows, {playerId = playerId, key = storageKey, value = addons}) + until not result.next(resultId) + result.free(resultId) + end + + 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 = PlayerStorageKeys.mountsBase + mountId + table.insert(rows, {playerId = playerId, key = storageKey, value = 1}) + until not result.next(resultId) + result.free(resultId) + end + + if #rows > 0 then + local query = "INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES " + for i, row in ipairs(rows) do + query = query .. string.format("(%d, %d, %d)", row.playerId, row.key, row.value) + if i < #rows then + query = query .. "," + end + end + db.asyncQuery(query) + end + + db.asyncQuery("DROP TABLE IF EXISTS `player_outfits`") + db.asyncQuery("DROP TABLE IF EXISTS `player_mounts`") + db.asyncQuery("ALTER TABLE `players` DROP COLUMN `currentmount`, DROP COLUMN `randomizemount`") + + return true 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..60f3f93d77 100644 --- a/data/scripts/events/player.lua +++ b/data/scripts/events/player.lua @@ -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.id) then outfit.lookMount = 0 end end diff --git a/data/scripts/systems/outfits/config.lua b/data/scripts/systems/outfits/config.lua new file mode 100644 index 0000000000..b3110f74fa --- /dev/null +++ b/data/scripts/systems/outfits/config.lua @@ -0,0 +1,4 @@ +Outfits = { + AllowChangeOutfit = true, + ToggleMountCooldown = 3000, -- in milliseconds +} diff --git a/data/scripts/systems/outfits/core/mounts.lua b/data/scripts/systems/outfits/core/mounts.lua new file mode 100644 index 0000000000..fb18eab270 --- /dev/null +++ b/data/scripts/systems/outfits/core/mounts.lua @@ -0,0 +1,157 @@ +function Player.addMount(self, mountId) + return self:setStorageValue(PlayerStorageKeys.mountsBase + mountId, 1) +end + +function Player.hasMount(self, mountId) + return self:hasStorageValue(PlayerStorageKeys.mountsBase + mountId) +end + +function Player.removeMount(self, mountId) + local result = self:removeStorageValue(PlayerStorageKeys.mountsBase + mountId) + if self:getCurrentMount() == mountId and self:isMounted() then + self:dismount() + end + return result +end + +function Player.getCurrentMount(self) + local lookType = self:getOutfit().lookMount + if lookType == 0 then + return nil + end + return lookType +end + +function Player.getRandomizeMount(self) + return self:hasStorageValue(PlayerStorageKeys.randomizeMount) +end + +function Player.setRandomizeMount(self, randomize) + if not randomize then + self:removeStorageValue(PlayerStorageKeys.randomizeMount) + else + self:setStorageValue(PlayerStorageKeys.randomizeMount, 1) + end +end + +local lastMountToggle = {} +function Player.getLastMountToggle(self) + return lastMountToggle[self:getId()] or 0 +end +function Player.setLastMountToggle(self, time) + lastMountToggle[self:getId()] = time +end + +local wasMounted = {} +function Player.getWasMounted(self) + return wasMounted[self:getId()] or false +end +function Player.setWasMounted(self, wasMounted) + wasMounted[self:getId()] = wasMounted or nil +end + +function Player.isMounted(self) + return self:getOutfit().lookMount ~= 0 +end + +local function getRandomMount(player) + local mounts = Game.getMounts() + + local availableMounts = {} + for _, mount in ipairs(mounts) do + if player:hasMount(mount.id) then + table.insert(availableMounts, mount.id) + end + end + + if #availableMounts == 0 then + return nil + end + + local idx = math.random(1, #availableMounts) + return availableMounts[idx] +end + +function Player.mount(self, mount) + local outfit = self:getDefaultOutfit() + outfit.lookMount = mount.id + self:setOutfit(outfit) + + self:changeSpeed(mount.speed) +end + +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 + +function Player.toggleMount(self, mounted) + if os.mtime() - self:getLastMountToggle() < Outfits.ToggleMountCooldown or not self:getWasMounted() then + return false + end + + if mounted then + 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 lookMount == nil then + self:sendOutfitWindow() + return false + end + + if self:getRandomizeMount() then + lookMount = getRandomMount(self) + if lookMount == nil then + self:sendOutfitWindow() + return false + end + end + + local currentMount = Game.getMountByLookType(lookMount) + if currentMount == nil then + return false + end + + if 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 + +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..f324b1495c --- /dev/null +++ b/data/scripts/systems/outfits/core/outfits.lua @@ -0,0 +1,57 @@ +function Player.addOutfit(self, lookType, addons) + return self:setStorageValue(PlayerStorageKeys.outfitsBase + lookType, addons or 0) +end + +function Player.addOutfitAddon(self, lookType, addon) + local addons = self:getOutfitAddons(lookType) + addons = addons | (1 << (addon - 1)) + return self:setStorageValue(PlayerStorageKeys.outfitsBase + lookType, addons) +end + +function Player.getOutfitAddons(self, lookType) + return self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) or 0 +end + +function Player.hasOutfit(self, lookType, addons) + return self:hasStorageValue(PlayerStorageKeys.outfitsBase + lookType) +end + +function Player.hasOutfitAddon(self, lookType, addon) + local addons = self:getOutfitAddons(lookType) + return (addons & (1 << (addon - 1))) ~= 0 +end + +function Player.removeOutfit(self, lookType) + return self:removeStorageValue(PlayerStorageKeys.outfitsBase + lookType) +end + +function Player.removeOutfitAddon(self, lookType, addon) + local addons = self:getOutfitAddons(lookType) + addons = addons & ~(1 << (addon - 1)) + return self:setStorageValue(PlayerStorageKeys.outfitsBase + lookType, addons) +end + +function Player.canWearOutfit(self, lookType, addons) + if self:getGroup():getAccess() then + return true + end + + local outfit = Game.getOutfitByLookType(lookType) + if outfit == nil 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 and addons ~= 0 and not self:hasOutfitAddon(lookType, addons) then + return false + end + + return true +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..5cb5c42e9e --- /dev/null +++ b/data/scripts/systems/outfits/core/windows.lua @@ -0,0 +1,207 @@ +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 availableMounts = {} + for _, mount in ipairs(mounts) do + if player:hasMount(mount.id) then + table.insert(availableMounts, { id = mount.id, name = mount.name }) + end + end + return availableMounts +end + +local function getAvailableFamiliars(player) + return {} +end + +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 + + local mounted = self:isMounted() + if self:getWasMounted() then + mounted = currentOutfit.lookMount ~= 0 + end + + local availableMounts = getAvailableMounts(self) + local availableFamiliars = getAvailableFamiliars(self) + + local msg = NetworkMessage() + msg:addByte(0xC8) + + msg:addOutfit(currentOutfit) + if currentOutfit.lookMount == 0 then + msg:addByte(currentOutfit.lookMountHead) + msg:addByte(currentOutfit.lookMountBody) + msg:addByte(currentOutfit.lookMountLegs) + msg:addByte(currentOutfit.lookMountFeet) + end + + 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.id) + msg:addString(mount.name) + msg:addByte(0) -- mode: 0x00 - available, 0x01 store (requires U32 store offerId) + 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) +end + +function Player.sendPodiumWindow(self, item) + local podium = item:getPodium() + if podium == nil then + return + end + + local tile = item:getTile() + if tile == nil 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.id) + 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.lookType ~= 0) or podium:hasFlag(PODIUM_SHOW_MOUNT)) -- "mount" checkbox + msg:addU16(0) -- unknown + msg:addPosition(item:getPosition()) + msg:addU16(item: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) +end + +function Player.sendEditPodium(self, item) return self:sendPodiumWindow(item) 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..764dc3f664 --- /dev/null +++ b/data/scripts/systems/outfits/data/mounts.lua @@ -0,0 +1,238 @@ +local mounts = { + [368] = { legacyId = 1, name = "Widow Queen", speed = 20, premium = true }, + [369] = { legacyId = 2, name = "Racing Bird", speed = 20, premium = true }, + [370] = { legacyId = 3, name = "War Bear", speed = 20, premium = true }, + [371] = { legacyId = 4, name = "Black Sheep", speed = 20, premium = true }, + [372] = { legacyId = 5, name = "Midnight Panther", speed = 20, premium = true }, + [373] = { legacyId = 6, name = "Draptor", speed = 20, premium = true }, + [374] = { legacyId = 7, name = "Titanica", speed = 20, premium = true }, + [375] = { legacyId = 8, name = "Tin Lizzard", speed = 20, premium = true }, + [376] = { legacyId = 9, name = "Blazebringer", speed = 20, premium = true }, + [377] = { legacyId = 10, name = "Rapid Boar", speed = 20, premium = true }, + [378] = { legacyId = 11, name = "Stampor", speed = 20, premium = true }, + [379] = { legacyId = 12, name = "Undead Cavebear", speed = 20, premium = true }, + [387] = { legacyId = 13, name = "Donkey", speed = 20, premium = true }, + [388] = { legacyId = 14, name = "Tiger Slug", speed = 20, premium = true }, + [389] = { legacyId = 15, name = "Uniwheel", speed = 20, premium = true }, + [390] = { legacyId = 16, name = "Crystal Wolf", speed = 20, premium = true }, + [392] = { legacyId = 17, name = "War Horse", speed = 20, premium = true }, + [401] = { legacyId = 18, name = "Kingly Deer", speed = 20, premium = true }, + [402] = { legacyId = 19, name = "Tamed Panda", speed = 20, premium = true }, + [405] = { legacyId = 20, name = "Dromedary", speed = 20, premium = true }, + [406] = { legacyId = 21, name = "Scorpion King", speed = 20, premium = true }, + [421] = { legacyId = 22, name = "Rented Horse", speed = 20, premium = false }, + [426] = { legacyId = 23, name = "Armoured War Horse", speed = 20, premium = false }, + [427] = { legacyId = 24, name = "Shadow Draptor", speed = 20, premium = false }, + [437] = { legacyId = 25, name = "Rented Horse", speed = 20, premium = false }, + [438] = { legacyId = 26, name = "Rented Horse", speed = 20, premium = false }, + [447] = { legacyId = 27, name = "Lady Bug", speed = 20, premium = true }, + [450] = { legacyId = 28, name = "Manta Ray", speed = 20, premium = true }, + [502] = { legacyId = 29, name = "Ironblight", speed = 20, premium = true }, + [503] = { legacyId = 30, name = "Magma Crawler", speed = 20, premium = true }, + [506] = { legacyId = 31, name = "Dragonling", speed = 20, premium = true }, + [515] = { legacyId = 32, name = "Gnarlhound", speed = 20, premium = true }, + [521] = { legacyId = 33, name = "Crimson Ray", speed = 20, premium = false }, + [522] = { legacyId = 34, name = "Steelbeak", speed = 20, premium = false }, + [526] = { legacyId = 35, name = "Water Buffalo", speed = 20, premium = true }, + [546] = { legacyId = 36, name = "Tombstinger", speed = 20, premium = false }, + [547] = { legacyId = 37, name = "Platesaurian", speed = 20, premium = false }, + [548] = { legacyId = 38, name = "Ursagrodon", speed = 20, premium = true }, + [559] = { legacyId = 39, name = "The Hellgrip", speed = 20, premium = true }, + [571] = { legacyId = 40, name = "Noble Lion", speed = 20, premium = true }, + [572] = { legacyId = 41, name = "Desert King", speed = 20, premium = false }, + [580] = { legacyId = 42, name = "Shock Head", speed = 20, premium = true }, + [606] = { legacyId = 43, name = "Walker", speed = 20, premium = true }, + [621] = { legacyId = 44, name = "Azudocus", speed = 20, premium = false }, + [622] = { legacyId = 45, name = "Carpacosaurus", speed = 20, premium = false }, + [624] = { legacyId = 46, name = "Death Crawler", speed = 20, premium = false }, + [626] = { legacyId = 47, name = "Flamesteed", speed = 20, premium = false }, + [627] = { legacyId = 48, name = "Jade Lion", speed = 20, premium = false }, + [628] = { legacyId = 49, name = "Jade Pincer", speed = 20, premium = false }, + [629] = { legacyId = 50, name = "Nethersteed", speed = 20, premium = false }, + [630] = { legacyId = 51, name = "Tempest", speed = 20, premium = false }, + [631] = { legacyId = 52, name = "Winter King", speed = 20, premium = false }, + [644] = { legacyId = 53, name = "Doombringer", speed = 20, premium = false }, + [647] = { legacyId = 54, name = "Woodland Prince", speed = 20, premium = false }, + [648] = { legacyId = 55, name = "Hailstorm Fury", speed = 20, premium = false }, + [649] = { legacyId = 56, name = "Siegebreaker", speed = 20, premium = false }, + [650] = { legacyId = 57, name = "Poisonbane", speed = 20, premium = false }, + [651] = { legacyId = 58, name = "Blackpelt", speed = 20, premium = false }, + [669] = { legacyId = 59, name = "Golden Dragonfly", speed = 20, premium = false }, + [670] = { legacyId = 60, name = "Steel Bee", speed = 20, premium = false }, + [671] = { legacyId = 61, name = "Copper Fly", speed = 20, premium = false }, + [672] = { legacyId = 62, name = "Tundra Rambler", speed = 20, premium = false }, + [673] = { legacyId = 63, name = "Highland Yak", speed = 20, premium = false }, + [674] = { legacyId = 64, name = "Glacier Vagabond", speed = 20, premium = false }, + [688] = { legacyId = 65, name = "Flying Divan", speed = 20, premium = false }, + [689] = { legacyId = 66, name = "Magic Carpet", speed = 20, premium = false }, + [690] = { legacyId = 67, name = "Floating Kashmir", speed = 20, premium = false }, + [691] = { legacyId = 68, name = "Ringtail Waccoon", speed = 20, premium = false }, + [692] = { legacyId = 69, name = "Night Waccoon", speed = 20, premium = false }, + [693] = { legacyId = 70, name = "Emerald Waccoon", speed = 20, premium = false }, + [682] = { legacyId = 71, name = "Glooth Glider", speed = 20, premium = true }, + [685] = { legacyId = 72, name = "Shadow Hart", speed = 20, premium = false }, + [686] = { legacyId = 73, name = "Black Stag", speed = 20, premium = false }, + [687] = { legacyId = 74, name = "Emperor Deer", speed = 20, premium = false }, + [726] = { legacyId = 75, name = "Flitterkatzen", speed = 20, premium = false }, + [727] = { legacyId = 76, name = "Venompaw", speed = 20, premium = false }, + [728] = { legacyId = 77, name = "Batcat", speed = 20, premium = false }, + [734] = { legacyId = 78, name = "Sea Devil", speed = 20, premium = false }, + [735] = { legacyId = 79, name = "Coralripper", speed = 20, premium = false }, + [736] = { legacyId = 80, name = "Plumfish", speed = 20, premium = false }, + [738] = { legacyId = 81, name = "Gorongra", speed = 20, premium = false }, + [739] = { legacyId = 82, name = "Noctungra", speed = 20, premium = false }, + [740] = { legacyId = 83, name = "Silverneck", speed = 20, premium = false }, + [761] = { legacyId = 84, name = "Slagsnare", speed = 20, premium = false }, + [762] = { legacyId = 85, name = "Nightstinger", speed = 20, premium = false }, + [763] = { legacyId = 86, name = "Razorcreep", speed = 20, premium = false }, + [848] = { legacyId = 87, name = "Rift Runner", speed = 20, premium = true }, + [849] = { legacyId = 88, name = "Nightdweller", speed = 20, premium = false }, + [850] = { legacyId = 89, name = "Frostflare", speed = 20, premium = false }, + [851] = { legacyId = 90, name = "Cinderhoof", speed = 20, premium = false }, + [868] = { legacyId = 91, name = "Mouldpincer", speed = 20, premium = false }, + [869] = { legacyId = 92, name = "Bloodcurl", speed = 20, premium = false }, + [870] = { legacyId = 93, name = "Leafscuttler", speed = 20, premium = false }, + [883] = { legacyId = 94, name = "Sparkion", speed = 20, premium = true }, + [886] = { legacyId = 95, name = "Swamp Snapper", speed = 20, premium = false }, + [887] = { legacyId = 96, name = "Mould Shell", speed = 20, premium = false }, + [888] = { legacyId = 97, name = "Reed Lurker", speed = 20, premium = false }, + [889] = { legacyId = 98, name = "Neon Sparkid", speed = 20, premium = true }, + [890] = { legacyId = 99, name = "Vortexion", speed = 20, premium = true }, + [901] = { legacyId = 100, name = "Ivory Fang", speed = 20, premium = false }, + [902] = { legacyId = 101, name = "Shadow Claw", speed = 20, premium = false }, + [903] = { legacyId = 102, name = "Snow Pelt", speed = 20, premium = false }, + [905] = { legacyId = 103, name = "Jackalope", speed = 20, premium = false }, + [906] = { legacyId = 104, name = "Dreadhare", speed = 20, premium = false }, + [907] = { legacyId = 105, name = "Wolpertinger", speed = 20, premium = false }, + [937] = { legacyId = 106, name = "Stone Rhino", speed = 20, premium = true }, + [950] = { legacyId = 107, name = "Gold Sphinx", speed = 20, premium = false }, + [951] = { legacyId = 108, name = "Emerald Sphinx", speed = 20, premium = false }, + [952] = { legacyId = 109, name = "Shadow Sphinx", speed = 20, premium = false }, + [959] = { legacyId = 110, name = "Jungle Saurian", speed = 20, premium = false }, + [960] = { legacyId = 111, name = "Ember Saurian", speed = 20, premium = false }, + [961] = { legacyId = 112, name = "Lagoon Saurian", speed = 20, premium = false }, + [1017] = { legacyId = 113, name = "Blazing Unicorn", speed = 20, premium = false }, + [1018] = { legacyId = 114, name = "Arctic Unicorn", speed = 20, premium = false }, + [1019] = { legacyId = 115, name = "Prismatic unicorn", speed = 20, premium = false }, + [1025] = { legacyId = 116, name = "Cranium Spider", speed = 20, premium = false }, + [1026] = { legacyId = 117, name = "Cave Tarantula", speed = 20, premium = false }, + [1027] = { legacyId = 118, name = "Gloom Widow", speed = 20, premium = false }, + [1049] = { legacyId = 119, name = "Mole", speed = 20, premium = true }, + [1052] = { legacyId = 120, name = "Marsh Toad", speed = 20, premium = false }, + [1053] = { legacyId = 121, name = "Sanguine Frog", speed = 20, premium = false }, + [1054] = { legacyId = 122, name = "Toxic Toad", speed = 20, premium = false }, + [1091] = { legacyId = 123, name = "Ebony Tiger", speed = 20, premium = false }, + [1092] = { legacyId = 124, name = "Feral Tiger", speed = 20, premium = false }, + [1093] = { legacyId = 125, name = "Jungle Tiger", speed = 20, premium = false }, + [1101] = { legacyId = 126, name = "Fleeting Knowledge", speed = 20, premium = true }, + [1104] = { legacyId = 127, name = "Tawny Owl", speed = 20, premium = false }, + [1105] = { legacyId = 128, name = "Snowy Owl", speed = 20, premium = false }, + [1106] = { legacyId = 129, name = "Boreal Owl", speed = 20, premium = false }, + [1150] = { legacyId = 130, name = "Lacewing Moth", speed = 20, premium = true }, + [1151] = { legacyId = 131, name = "Hibernal Moth", speed = 20, premium = true }, + [1163] = { legacyId = 132, name = "Cold Percht Sleigh", speed = 20, premium = true }, + [1164] = { legacyId = 133, name = "Bright Percht Sleigh", speed = 20, premium = true }, + [1165] = { legacyId = 134, name = "Dark Percht Sleigh", speed = 20, premium = true }, + [1167] = { legacyId = 135, name = "Festive Snowman", speed = 20, premium = false }, + [1168] = { legacyId = 136, name = "Muffled Snowman", speed = 20, premium = false }, + [1169] = { legacyId = 137, name = "Caped Snowman", speed = 20, premium = false }, + [1179] = { legacyId = 138, name = "Rabbit Rickshaw", speed = 20, premium = false }, + [1180] = { legacyId = 139, name = "Bunny Dray", speed = 20, premium = false }, + [1181] = { legacyId = 140, name = "Cony Cart", speed = 20, premium = false }, + [1183] = { legacyId = 141, name = "River Crocovile", speed = 20, premium = false }, + [1184] = { legacyId = 142, name = "Swamp Crocovile", speed = 20, premium = false }, + [1185] = { legacyId = 143, name = "Nightmarish Crocovile", speed = 20, premium = false }, + [1191] = { legacyId = 144, name = "Gryphon", speed = 20, premium = true }, + [1208] = { legacyId = 145, name = "Jousting Eagle", speed = 20, premium = false }, + [1209] = { legacyId = 146, name = "Cerberus Champion", speed = 20, premium = false }, + [1229] = { legacyId = 147, name = "Cold Percht Sleigh Variant", speed = 20, premium = true }, + [1230] = { legacyId = 148, name = "Bright Percht Sleigh Variant", speed = 20, premium = true }, + [1231] = { legacyId = 149, name = "Dark Percht Sleigh Variant", speed = 20, premium = true }, + [1232] = { legacyId = 150, name = "Cold Percht Sleigh Final", speed = 20, premium = true }, + [1233] = { legacyId = 151, name = "Bright Percht Sleigh Final", speed = 20, premium = true }, + [1234] = { legacyId = 152, name = "Dark Percht Sleigh Final", speed = 20, premium = true }, + [1247] = { legacyId = 153, name = "Battle Badger", speed = 20, premium = false }, + [1248] = { legacyId = 154, name = "Ether Badger", speed = 20, premium = false }, + [1249] = { legacyId = 155, name = "Zaoan Badger", speed = 20, premium = false }, + [1257] = { legacyId = 156, name = "Blue Rolling Barrel", speed = 20, premium = true }, + [1258] = { legacyId = 157, name = "Red Rolling Barrel", speed = 20, premium = true }, + [1259] = { legacyId = 158, name = "Green Rolling Barrel", speed = 20, premium = true }, + [1264] = { legacyId = 159, name = "Floating Sage", speed = 20, premium = false }, + [1265] = { legacyId = 160, name = "Floating Scholar", speed = 20, premium = false }, + [1266] = { legacyId = 161, name = "Floating Augur", speed = 20, premium = false }, + [1269] = { legacyId = 162, name = "Haze", speed = 20, premium = true }, + [1281] = { legacyId = 163, name = "Antelope", speed = 20, premium = true }, + [1284] = { legacyId = 164, name = "Snow Strider", speed = 20, premium = false }, + [1285] = { legacyId = 165, name = "Dusk Pryer", speed = 20, premium = false }, + [1286] = { legacyId = 166, name = "Dawn Strayer", speed = 20, premium = false }, + [1321] = { legacyId = 167, name = "Phantasmal Jade", speed = 20, premium = true }, + [1324] = { legacyId = 168, name = "Savanna Ostrich", speed = 20, premium = true }, + [1325] = { legacyId = 169, name = "Coral Rhea", speed = 20, premium = true }, + [1326] = { legacyId = 170, name = "Eventide Nandu", speed = 20, premium = true }, + [1333] = { legacyId = 171, name = "Voracious Hyaena", speed = 20, premium = false }, + [1334] = { legacyId = 172, name = "Cunning Hyaena", speed = 20, premium = false }, + [1335] = { legacyId = 173, name = "Scruffy Hyaena", speed = 20, premium = false }, + [1336] = { legacyId = 174, name = "White Lion", speed = 20, premium = true }, + [1363] = { legacyId = 175, name = "Krakoloss", speed = 20, premium = true }, + [1379] = { legacyId = 176, name = "Merry Mammoth", speed = 20, premium = false }, + [1380] = { legacyId = 177, name = "Holiday Mammoth", speed = 20, premium = false }, + [1381] = { legacyId = 178, name = "Festive Mammoth", speed = 20, premium = false }, + [1389] = { legacyId = 179, name = "Void Watcher", speed = 20, premium = false }, + [1390] = { legacyId = 180, name = "Rune Watcher", speed = 20, premium = false }, + [1391] = { legacyId = 181, name = "Rift Watcher", speed = 20, premium = false }, + [1417] = { legacyId = 182, name = "Phant", speed = 20, premium = true }, + [1430] = { legacyId = 183, name = "Shellodon", speed = 20, premium = true }, + [1431] = { legacyId = 184, name = "Singeing Steed", speed = 20, premium = true }, + [1439] = { legacyId = 185, name = "Hyacinth", speed = 20, premium = false }, + [1440] = { legacyId = 186, name = "Peony", speed = 20, premium = false }, + [1441] = { legacyId = 187, name = "Dandelion", speed = 20, premium = false }, + [1446] = { legacyId = 188, name = "Rustwurm", speed = 20, premium = false }, + [1447] = { legacyId = 189, name = "Bogwurm", speed = 20, premium = false }, + [1448] = { legacyId = 190, name = "Gloomwurm", speed = 20, premium = false }, + [1453] = { legacyId = 191, name = "Emerald Raven", speed = 20, premium = false }, + [1454] = { legacyId = 192, name = "Mystic Raven", speed = 20, premium = false }, + [1455] = { legacyId = 193, name = "Radiant Raven", speed = 20, premium = false }, + [1459] = { legacyId = 194, name = "Gloothomotive", speed = 20, premium = true }, + [1491] = { legacyId = 195, name = "Topaz Shrine", speed = 20, premium = false }, + [1492] = { legacyId = 196, name = "Jade Shrine", speed = 20, premium = false }, + [1493] = { legacyId = 197, name = "Obsidian Shrine", speed = 20, premium = false }, + [1526] = { legacyId = 198, name = "Poppy Ibex", speed = 20, premium = false }, + [1527] = { legacyId = 199, name = "Mint Ibex", speed = 20, premium = false }, + [1528] = { legacyId = 200, name = "Cinnamon Ibex", speed = 20, premium = false }, + [1536] = { legacyId = 201, name = "Giant Beaver", speed = 20, premium = true }, + [1577] = { legacyId = 202, name = "Ripptor", speed = 20, premium = true }, + [1578] = { legacyId = 203, name = "Parade Horse", speed = 20, premium = false }, + [1579] = { legacyId = 204, name = "Jousting Horse", speed = 20, premium = false }, + [1580] = { legacyId = 205, name = "Tourney Horse", speed = 20, premium = false }, + [1599] = { legacyId = 206, name = "Mutated Abomination", speed = 20, premium = true }, + [1608] = { legacyId = 207, name = "Tangerine Flecked Koi", speed = 20, premium = false }, + [1609] = { legacyId = 208, name = "Brass Speckled Koi", speed = 20, premium = false }, + [1610] = { legacyId = 209, name = "Ink Spotted Koi", speed = 20, premium = false }, +} + +function Game.getMounts() + local result = {} + for mountId, mountData in pairs(mounts) do + table.insert(result, { + id = mountId, + name = mountData.name, + speed = mountData.speed, + premium = mountData.premium, + }) + end + return result +end + +function Game.getMountByLookType(id) + local mount = mounts[id] + if mount == nil then + return nil + end + + return { + id = id, + name = mount.name, + speed = mount.speed, + premium = mount.premium, + } +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..8abcaf996f --- /dev/null +++ b/data/scripts/systems/outfits/data/outfits.lua @@ -0,0 +1,256 @@ +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.getOutfits(sex) + local result = {} + for lookType, outfit in pairs(outfits) do + if outfit.sex == sex and outfit.enabled then + table.insert(result, { + lookType = lookType, + name = outfit.name, + sex = outfit.sex, + premium = outfit.premium, + unlocked = outfit.unlocked, + }) + end + end + return result +end + +function Game.getOutfitByLookType(lookType) + local outfit = outfits[lookType] + if outfit == nil then + return nil + end + + return { + lookType = lookType, + name = outfit.name, + sex = outfit.sex, + premium = outfit.premium, + unlocked = outfit.unlocked, + } +end diff --git a/data/scripts/systems/outfits/events.lua b/data/scripts/systems/outfits/events.lua new file mode 100644 index 0000000000..361b3b85e8 --- /dev/null +++ b/data/scripts/systems/outfits/events.lua @@ -0,0 +1,20 @@ +local event = Event() + +function event.onChangeZone(creature, fromZone, toZone) + if not creature:isPlayer() then + return + end + + local player = creature:getPlayer() + if toZone == ZONE_PROTECTION then + if not player:getGroup():getAccess() and player:isMounted() then + player:toggleMount(false) + player:setWasMounted(true) + end + elseif player:getWasMounted() then + player:toggleMount(true) + player:setWasMounted(false) + end +end + +event:register() diff --git a/data/scripts/network/outfit.lua b/data/scripts/systems/outfits/network/request_outfit_window.lua similarity index 67% rename from data/scripts/network/outfit.lua rename to data/scripts/systems/outfits/network/request_outfit_window.lua index 37571e511c..c46b62d967 100644 --- a/data/scripts/network/outfit.lua +++ b/data/scripts/systems/outfits/network/request_outfit_window.lua @@ -1,7 +1,7 @@ local handler = PacketHandler(0xD2) function handler.onReceive(player, msg) - if not configManager.getBoolean(configKeys.ALLOW_CHANGEOUTFIT) then + if not Outfits.AllowChangeOutfit then return end 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..28ed60170c --- /dev/null +++ b/data/scripts/systems/outfits/network/set_outfit.lua @@ -0,0 +1,86 @@ +local handler = PacketHandler(0xD3) + +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() + + if lookMount ~= 0 then + outfit.lookMount = lookMount + outfit.lookMountHead = lookMountHead + outfit.lookMountBody = lookMountBody + outfit.lookMountLegs = lookMountLegs + outfit.lookMountFeet = lookMountFeet + end + + msg:getU16() -- familiar looktype + local randomizeMount = msg:getBool() + + 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() + + -- open store? + 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() + + local tile = Tile(position) + if tile == nil then + return + end + + local item = tile:getTopDownItem() + if not item then + return + end + + local itemPosition = item:getPosition() + if itemPosition.stackpos ~= stackpos then + return + end + + local it = Game.getItemTypeByClientId(clientId) + if not it or it:getClientId() ~= clientId 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/schema.sql b/schema.sql index b41feca8a6..e69fbfc9a4 100644 --- a/schema.sql +++ b/schema.sql @@ -33,7 +33,6 @@ CREATE TABLE IF NOT EXISTS `players` ( `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 +341,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 +375,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 4c376085fe..ea8be1ccc9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -36,11 +36,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 @@ -115,11 +113,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/creature.h b/src/creature.h index 522fdf3920..75f3ef4210 100644 --- a/src/creature.h +++ b/src/creature.h @@ -410,7 +410,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 568dde4ec4..abacf67c05 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -18,7 +18,6 @@ #include "iomarket.h" #include "items.h" #include "movement.h" -#include "outfit.h" #include "party.h" #include "podium.h" #include "scheduler.h" @@ -94,8 +93,6 @@ void Game::setGameState(GameState_t newState) map.spawns.startup(); - mounts.loadFromXml(); - tfs::events::game::onStartup(); break; } @@ -3365,82 +3362,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) { @@ -5563,8 +5484,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: { @@ -5604,7 +5523,6 @@ bool Game::reload(ReloadTypes_t reloadType) /* Npcs::reload(); Item::items.reload(); - mounts.reload(); ConfigManager::reload(); tfs::events::load(); g_chat->load(); @@ -5630,7 +5548,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 d34b1fabe4..40ec734a12 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" @@ -377,14 +376,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); @@ -478,7 +475,6 @@ class Game Groups groups; Map map; - Mounts mounts; std::vector> toDecayItems; diff --git a/src/iologindata.cpp b/src/iologindata.cpp index 660074ed8a..c7a540281a 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`, `currentmount`, `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`, `currentmount`, `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 200a7cffa1..863194f977 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/game.cpp b/src/lua/modules/game.cpp index 8b9c4f8455..b1a61055d3 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() @@ -713,13 +649,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/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 93db6146da..d5386d5f2d 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 6f97307795..768493b6df 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -15,7 +15,6 @@ #include "house.h" #include "iologindata.h" #include "movement.h" -#include "outfit.h" #include "party.h" #include "scheduler.h" #include "tools.h" @@ -1028,18 +1027,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& bed = g_game.getBedBySleeper(guid)) { @@ -1123,17 +1110,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()); @@ -3413,10 +3389,6 @@ void Player::onAddCondition(ConditionType_t type) { Creature::onAddCondition(type); - if (type == CONDITION_OUTFIT && isMounted()) { - dismount(); - } - sendIcons(); } @@ -3760,119 +3732,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 @@ -4219,158 +4078,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 53a63cfe38..9104f4194f 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) { @@ -569,12 +551,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; @@ -1095,18 +1071,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) { @@ -1261,8 +1225,6 @@ class Player final : public Creature std::map openContainers; std::map> depotChests; - std::map outfits; - std::unordered_set mounts; GuildWarVector guildWarVector; std::list shopItemList; @@ -1369,12 +1331,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 a9ef1e67cd..6197ce3c02 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 @@ -1064,72 +1062,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(); @@ -3016,233 +2948,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->getPodium(); - 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 571b4fcef5..0a14127c3a 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); @@ -213,9 +212,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 ce34d26c2c..c89294948e 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -35,7 +35,7 @@ bool Scripts::loadScripts(std::string 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 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 748db07392..a53d2665b0 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; From 02ccd8fa08b8a4c890d9c6c8c77cca1161d19a1a Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 25 Dec 2025 02:34:11 -0300 Subject: [PATCH 02/55] chore(mounts): remove legacyId --- data/scripts/systems/outfits/data/mounts.lua | 418 +++++++++---------- 1 file changed, 209 insertions(+), 209 deletions(-) diff --git a/data/scripts/systems/outfits/data/mounts.lua b/data/scripts/systems/outfits/data/mounts.lua index 764dc3f664..3a5007eee2 100644 --- a/data/scripts/systems/outfits/data/mounts.lua +++ b/data/scripts/systems/outfits/data/mounts.lua @@ -1,213 +1,213 @@ local mounts = { - [368] = { legacyId = 1, name = "Widow Queen", speed = 20, premium = true }, - [369] = { legacyId = 2, name = "Racing Bird", speed = 20, premium = true }, - [370] = { legacyId = 3, name = "War Bear", speed = 20, premium = true }, - [371] = { legacyId = 4, name = "Black Sheep", speed = 20, premium = true }, - [372] = { legacyId = 5, name = "Midnight Panther", speed = 20, premium = true }, - [373] = { legacyId = 6, name = "Draptor", speed = 20, premium = true }, - [374] = { legacyId = 7, name = "Titanica", speed = 20, premium = true }, - [375] = { legacyId = 8, name = "Tin Lizzard", speed = 20, premium = true }, - [376] = { legacyId = 9, name = "Blazebringer", speed = 20, premium = true }, - [377] = { legacyId = 10, name = "Rapid Boar", speed = 20, premium = true }, - [378] = { legacyId = 11, name = "Stampor", speed = 20, premium = true }, - [379] = { legacyId = 12, name = "Undead Cavebear", speed = 20, premium = true }, - [387] = { legacyId = 13, name = "Donkey", speed = 20, premium = true }, - [388] = { legacyId = 14, name = "Tiger Slug", speed = 20, premium = true }, - [389] = { legacyId = 15, name = "Uniwheel", speed = 20, premium = true }, - [390] = { legacyId = 16, name = "Crystal Wolf", speed = 20, premium = true }, - [392] = { legacyId = 17, name = "War Horse", speed = 20, premium = true }, - [401] = { legacyId = 18, name = "Kingly Deer", speed = 20, premium = true }, - [402] = { legacyId = 19, name = "Tamed Panda", speed = 20, premium = true }, - [405] = { legacyId = 20, name = "Dromedary", speed = 20, premium = true }, - [406] = { legacyId = 21, name = "Scorpion King", speed = 20, premium = true }, - [421] = { legacyId = 22, name = "Rented Horse", speed = 20, premium = false }, - [426] = { legacyId = 23, name = "Armoured War Horse", speed = 20, premium = false }, - [427] = { legacyId = 24, name = "Shadow Draptor", speed = 20, premium = false }, - [437] = { legacyId = 25, name = "Rented Horse", speed = 20, premium = false }, - [438] = { legacyId = 26, name = "Rented Horse", speed = 20, premium = false }, - [447] = { legacyId = 27, name = "Lady Bug", speed = 20, premium = true }, - [450] = { legacyId = 28, name = "Manta Ray", speed = 20, premium = true }, - [502] = { legacyId = 29, name = "Ironblight", speed = 20, premium = true }, - [503] = { legacyId = 30, name = "Magma Crawler", speed = 20, premium = true }, - [506] = { legacyId = 31, name = "Dragonling", speed = 20, premium = true }, - [515] = { legacyId = 32, name = "Gnarlhound", speed = 20, premium = true }, - [521] = { legacyId = 33, name = "Crimson Ray", speed = 20, premium = false }, - [522] = { legacyId = 34, name = "Steelbeak", speed = 20, premium = false }, - [526] = { legacyId = 35, name = "Water Buffalo", speed = 20, premium = true }, - [546] = { legacyId = 36, name = "Tombstinger", speed = 20, premium = false }, - [547] = { legacyId = 37, name = "Platesaurian", speed = 20, premium = false }, - [548] = { legacyId = 38, name = "Ursagrodon", speed = 20, premium = true }, - [559] = { legacyId = 39, name = "The Hellgrip", speed = 20, premium = true }, - [571] = { legacyId = 40, name = "Noble Lion", speed = 20, premium = true }, - [572] = { legacyId = 41, name = "Desert King", speed = 20, premium = false }, - [580] = { legacyId = 42, name = "Shock Head", speed = 20, premium = true }, - [606] = { legacyId = 43, name = "Walker", speed = 20, premium = true }, - [621] = { legacyId = 44, name = "Azudocus", speed = 20, premium = false }, - [622] = { legacyId = 45, name = "Carpacosaurus", speed = 20, premium = false }, - [624] = { legacyId = 46, name = "Death Crawler", speed = 20, premium = false }, - [626] = { legacyId = 47, name = "Flamesteed", speed = 20, premium = false }, - [627] = { legacyId = 48, name = "Jade Lion", speed = 20, premium = false }, - [628] = { legacyId = 49, name = "Jade Pincer", speed = 20, premium = false }, - [629] = { legacyId = 50, name = "Nethersteed", speed = 20, premium = false }, - [630] = { legacyId = 51, name = "Tempest", speed = 20, premium = false }, - [631] = { legacyId = 52, name = "Winter King", speed = 20, premium = false }, - [644] = { legacyId = 53, name = "Doombringer", speed = 20, premium = false }, - [647] = { legacyId = 54, name = "Woodland Prince", speed = 20, premium = false }, - [648] = { legacyId = 55, name = "Hailstorm Fury", speed = 20, premium = false }, - [649] = { legacyId = 56, name = "Siegebreaker", speed = 20, premium = false }, - [650] = { legacyId = 57, name = "Poisonbane", speed = 20, premium = false }, - [651] = { legacyId = 58, name = "Blackpelt", speed = 20, premium = false }, - [669] = { legacyId = 59, name = "Golden Dragonfly", speed = 20, premium = false }, - [670] = { legacyId = 60, name = "Steel Bee", speed = 20, premium = false }, - [671] = { legacyId = 61, name = "Copper Fly", speed = 20, premium = false }, - [672] = { legacyId = 62, name = "Tundra Rambler", speed = 20, premium = false }, - [673] = { legacyId = 63, name = "Highland Yak", speed = 20, premium = false }, - [674] = { legacyId = 64, name = "Glacier Vagabond", speed = 20, premium = false }, - [688] = { legacyId = 65, name = "Flying Divan", speed = 20, premium = false }, - [689] = { legacyId = 66, name = "Magic Carpet", speed = 20, premium = false }, - [690] = { legacyId = 67, name = "Floating Kashmir", speed = 20, premium = false }, - [691] = { legacyId = 68, name = "Ringtail Waccoon", speed = 20, premium = false }, - [692] = { legacyId = 69, name = "Night Waccoon", speed = 20, premium = false }, - [693] = { legacyId = 70, name = "Emerald Waccoon", speed = 20, premium = false }, - [682] = { legacyId = 71, name = "Glooth Glider", speed = 20, premium = true }, - [685] = { legacyId = 72, name = "Shadow Hart", speed = 20, premium = false }, - [686] = { legacyId = 73, name = "Black Stag", speed = 20, premium = false }, - [687] = { legacyId = 74, name = "Emperor Deer", speed = 20, premium = false }, - [726] = { legacyId = 75, name = "Flitterkatzen", speed = 20, premium = false }, - [727] = { legacyId = 76, name = "Venompaw", speed = 20, premium = false }, - [728] = { legacyId = 77, name = "Batcat", speed = 20, premium = false }, - [734] = { legacyId = 78, name = "Sea Devil", speed = 20, premium = false }, - [735] = { legacyId = 79, name = "Coralripper", speed = 20, premium = false }, - [736] = { legacyId = 80, name = "Plumfish", speed = 20, premium = false }, - [738] = { legacyId = 81, name = "Gorongra", speed = 20, premium = false }, - [739] = { legacyId = 82, name = "Noctungra", speed = 20, premium = false }, - [740] = { legacyId = 83, name = "Silverneck", speed = 20, premium = false }, - [761] = { legacyId = 84, name = "Slagsnare", speed = 20, premium = false }, - [762] = { legacyId = 85, name = "Nightstinger", speed = 20, premium = false }, - [763] = { legacyId = 86, name = "Razorcreep", speed = 20, premium = false }, - [848] = { legacyId = 87, name = "Rift Runner", speed = 20, premium = true }, - [849] = { legacyId = 88, name = "Nightdweller", speed = 20, premium = false }, - [850] = { legacyId = 89, name = "Frostflare", speed = 20, premium = false }, - [851] = { legacyId = 90, name = "Cinderhoof", speed = 20, premium = false }, - [868] = { legacyId = 91, name = "Mouldpincer", speed = 20, premium = false }, - [869] = { legacyId = 92, name = "Bloodcurl", speed = 20, premium = false }, - [870] = { legacyId = 93, name = "Leafscuttler", speed = 20, premium = false }, - [883] = { legacyId = 94, name = "Sparkion", speed = 20, premium = true }, - [886] = { legacyId = 95, name = "Swamp Snapper", speed = 20, premium = false }, - [887] = { legacyId = 96, name = "Mould Shell", speed = 20, premium = false }, - [888] = { legacyId = 97, name = "Reed Lurker", speed = 20, premium = false }, - [889] = { legacyId = 98, name = "Neon Sparkid", speed = 20, premium = true }, - [890] = { legacyId = 99, name = "Vortexion", speed = 20, premium = true }, - [901] = { legacyId = 100, name = "Ivory Fang", speed = 20, premium = false }, - [902] = { legacyId = 101, name = "Shadow Claw", speed = 20, premium = false }, - [903] = { legacyId = 102, name = "Snow Pelt", speed = 20, premium = false }, - [905] = { legacyId = 103, name = "Jackalope", speed = 20, premium = false }, - [906] = { legacyId = 104, name = "Dreadhare", speed = 20, premium = false }, - [907] = { legacyId = 105, name = "Wolpertinger", speed = 20, premium = false }, - [937] = { legacyId = 106, name = "Stone Rhino", speed = 20, premium = true }, - [950] = { legacyId = 107, name = "Gold Sphinx", speed = 20, premium = false }, - [951] = { legacyId = 108, name = "Emerald Sphinx", speed = 20, premium = false }, - [952] = { legacyId = 109, name = "Shadow Sphinx", speed = 20, premium = false }, - [959] = { legacyId = 110, name = "Jungle Saurian", speed = 20, premium = false }, - [960] = { legacyId = 111, name = "Ember Saurian", speed = 20, premium = false }, - [961] = { legacyId = 112, name = "Lagoon Saurian", speed = 20, premium = false }, - [1017] = { legacyId = 113, name = "Blazing Unicorn", speed = 20, premium = false }, - [1018] = { legacyId = 114, name = "Arctic Unicorn", speed = 20, premium = false }, - [1019] = { legacyId = 115, name = "Prismatic unicorn", speed = 20, premium = false }, - [1025] = { legacyId = 116, name = "Cranium Spider", speed = 20, premium = false }, - [1026] = { legacyId = 117, name = "Cave Tarantula", speed = 20, premium = false }, - [1027] = { legacyId = 118, name = "Gloom Widow", speed = 20, premium = false }, - [1049] = { legacyId = 119, name = "Mole", speed = 20, premium = true }, - [1052] = { legacyId = 120, name = "Marsh Toad", speed = 20, premium = false }, - [1053] = { legacyId = 121, name = "Sanguine Frog", speed = 20, premium = false }, - [1054] = { legacyId = 122, name = "Toxic Toad", speed = 20, premium = false }, - [1091] = { legacyId = 123, name = "Ebony Tiger", speed = 20, premium = false }, - [1092] = { legacyId = 124, name = "Feral Tiger", speed = 20, premium = false }, - [1093] = { legacyId = 125, name = "Jungle Tiger", speed = 20, premium = false }, - [1101] = { legacyId = 126, name = "Fleeting Knowledge", speed = 20, premium = true }, - [1104] = { legacyId = 127, name = "Tawny Owl", speed = 20, premium = false }, - [1105] = { legacyId = 128, name = "Snowy Owl", speed = 20, premium = false }, - [1106] = { legacyId = 129, name = "Boreal Owl", speed = 20, premium = false }, - [1150] = { legacyId = 130, name = "Lacewing Moth", speed = 20, premium = true }, - [1151] = { legacyId = 131, name = "Hibernal Moth", speed = 20, premium = true }, - [1163] = { legacyId = 132, name = "Cold Percht Sleigh", speed = 20, premium = true }, - [1164] = { legacyId = 133, name = "Bright Percht Sleigh", speed = 20, premium = true }, - [1165] = { legacyId = 134, name = "Dark Percht Sleigh", speed = 20, premium = true }, - [1167] = { legacyId = 135, name = "Festive Snowman", speed = 20, premium = false }, - [1168] = { legacyId = 136, name = "Muffled Snowman", speed = 20, premium = false }, - [1169] = { legacyId = 137, name = "Caped Snowman", speed = 20, premium = false }, - [1179] = { legacyId = 138, name = "Rabbit Rickshaw", speed = 20, premium = false }, - [1180] = { legacyId = 139, name = "Bunny Dray", speed = 20, premium = false }, - [1181] = { legacyId = 140, name = "Cony Cart", speed = 20, premium = false }, - [1183] = { legacyId = 141, name = "River Crocovile", speed = 20, premium = false }, - [1184] = { legacyId = 142, name = "Swamp Crocovile", speed = 20, premium = false }, - [1185] = { legacyId = 143, name = "Nightmarish Crocovile", speed = 20, premium = false }, - [1191] = { legacyId = 144, name = "Gryphon", speed = 20, premium = true }, - [1208] = { legacyId = 145, name = "Jousting Eagle", speed = 20, premium = false }, - [1209] = { legacyId = 146, name = "Cerberus Champion", speed = 20, premium = false }, - [1229] = { legacyId = 147, name = "Cold Percht Sleigh Variant", speed = 20, premium = true }, - [1230] = { legacyId = 148, name = "Bright Percht Sleigh Variant", speed = 20, premium = true }, - [1231] = { legacyId = 149, name = "Dark Percht Sleigh Variant", speed = 20, premium = true }, - [1232] = { legacyId = 150, name = "Cold Percht Sleigh Final", speed = 20, premium = true }, - [1233] = { legacyId = 151, name = "Bright Percht Sleigh Final", speed = 20, premium = true }, - [1234] = { legacyId = 152, name = "Dark Percht Sleigh Final", speed = 20, premium = true }, - [1247] = { legacyId = 153, name = "Battle Badger", speed = 20, premium = false }, - [1248] = { legacyId = 154, name = "Ether Badger", speed = 20, premium = false }, - [1249] = { legacyId = 155, name = "Zaoan Badger", speed = 20, premium = false }, - [1257] = { legacyId = 156, name = "Blue Rolling Barrel", speed = 20, premium = true }, - [1258] = { legacyId = 157, name = "Red Rolling Barrel", speed = 20, premium = true }, - [1259] = { legacyId = 158, name = "Green Rolling Barrel", speed = 20, premium = true }, - [1264] = { legacyId = 159, name = "Floating Sage", speed = 20, premium = false }, - [1265] = { legacyId = 160, name = "Floating Scholar", speed = 20, premium = false }, - [1266] = { legacyId = 161, name = "Floating Augur", speed = 20, premium = false }, - [1269] = { legacyId = 162, name = "Haze", speed = 20, premium = true }, - [1281] = { legacyId = 163, name = "Antelope", speed = 20, premium = true }, - [1284] = { legacyId = 164, name = "Snow Strider", speed = 20, premium = false }, - [1285] = { legacyId = 165, name = "Dusk Pryer", speed = 20, premium = false }, - [1286] = { legacyId = 166, name = "Dawn Strayer", speed = 20, premium = false }, - [1321] = { legacyId = 167, name = "Phantasmal Jade", speed = 20, premium = true }, - [1324] = { legacyId = 168, name = "Savanna Ostrich", speed = 20, premium = true }, - [1325] = { legacyId = 169, name = "Coral Rhea", speed = 20, premium = true }, - [1326] = { legacyId = 170, name = "Eventide Nandu", speed = 20, premium = true }, - [1333] = { legacyId = 171, name = "Voracious Hyaena", speed = 20, premium = false }, - [1334] = { legacyId = 172, name = "Cunning Hyaena", speed = 20, premium = false }, - [1335] = { legacyId = 173, name = "Scruffy Hyaena", speed = 20, premium = false }, - [1336] = { legacyId = 174, name = "White Lion", speed = 20, premium = true }, - [1363] = { legacyId = 175, name = "Krakoloss", speed = 20, premium = true }, - [1379] = { legacyId = 176, name = "Merry Mammoth", speed = 20, premium = false }, - [1380] = { legacyId = 177, name = "Holiday Mammoth", speed = 20, premium = false }, - [1381] = { legacyId = 178, name = "Festive Mammoth", speed = 20, premium = false }, - [1389] = { legacyId = 179, name = "Void Watcher", speed = 20, premium = false }, - [1390] = { legacyId = 180, name = "Rune Watcher", speed = 20, premium = false }, - [1391] = { legacyId = 181, name = "Rift Watcher", speed = 20, premium = false }, - [1417] = { legacyId = 182, name = "Phant", speed = 20, premium = true }, - [1430] = { legacyId = 183, name = "Shellodon", speed = 20, premium = true }, - [1431] = { legacyId = 184, name = "Singeing Steed", speed = 20, premium = true }, - [1439] = { legacyId = 185, name = "Hyacinth", speed = 20, premium = false }, - [1440] = { legacyId = 186, name = "Peony", speed = 20, premium = false }, - [1441] = { legacyId = 187, name = "Dandelion", speed = 20, premium = false }, - [1446] = { legacyId = 188, name = "Rustwurm", speed = 20, premium = false }, - [1447] = { legacyId = 189, name = "Bogwurm", speed = 20, premium = false }, - [1448] = { legacyId = 190, name = "Gloomwurm", speed = 20, premium = false }, - [1453] = { legacyId = 191, name = "Emerald Raven", speed = 20, premium = false }, - [1454] = { legacyId = 192, name = "Mystic Raven", speed = 20, premium = false }, - [1455] = { legacyId = 193, name = "Radiant Raven", speed = 20, premium = false }, - [1459] = { legacyId = 194, name = "Gloothomotive", speed = 20, premium = true }, - [1491] = { legacyId = 195, name = "Topaz Shrine", speed = 20, premium = false }, - [1492] = { legacyId = 196, name = "Jade Shrine", speed = 20, premium = false }, - [1493] = { legacyId = 197, name = "Obsidian Shrine", speed = 20, premium = false }, - [1526] = { legacyId = 198, name = "Poppy Ibex", speed = 20, premium = false }, - [1527] = { legacyId = 199, name = "Mint Ibex", speed = 20, premium = false }, - [1528] = { legacyId = 200, name = "Cinnamon Ibex", speed = 20, premium = false }, - [1536] = { legacyId = 201, name = "Giant Beaver", speed = 20, premium = true }, - [1577] = { legacyId = 202, name = "Ripptor", speed = 20, premium = true }, - [1578] = { legacyId = 203, name = "Parade Horse", speed = 20, premium = false }, - [1579] = { legacyId = 204, name = "Jousting Horse", speed = 20, premium = false }, - [1580] = { legacyId = 205, name = "Tourney Horse", speed = 20, premium = false }, - [1599] = { legacyId = 206, name = "Mutated Abomination", speed = 20, premium = true }, - [1608] = { legacyId = 207, name = "Tangerine Flecked Koi", speed = 20, premium = false }, - [1609] = { legacyId = 208, name = "Brass Speckled Koi", speed = 20, premium = false }, - [1610] = { legacyId = 209, name = "Ink Spotted Koi", speed = 20, premium = false }, + [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.getMounts() From f9ceebb5e3bd06288df5912bb9ba329feb938b24 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 25 Dec 2025 02:36:54 -0300 Subject: [PATCH 03/55] refactor(podium): validate item type before processing podium window --- data/scripts/systems/outfits/core/windows.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/scripts/systems/outfits/core/windows.lua b/data/scripts/systems/outfits/core/windows.lua index 5cb5c42e9e..076de9a86f 100644 --- a/data/scripts/systems/outfits/core/windows.lua +++ b/data/scripts/systems/outfits/core/windows.lua @@ -117,6 +117,11 @@ function Player.sendPodiumWindow(self, item) 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) @@ -195,7 +200,7 @@ function Player.sendPodiumWindow(self, item) msg:addBool((isEmpty and playerOutfit.lookType ~= 0) or podium:hasFlag(PODIUM_SHOW_MOUNT)) -- "mount" checkbox msg:addU16(0) -- unknown msg:addPosition(item:getPosition()) - msg:addU16(item:getClientID()) + msg:addU16(it:getClientId()) msg:addByte(stackpos) msg:addBool(podium:hasFlag(PODIUM_SHOW_PLATFORM)) -- is platform visible From f99fb33d8127710c5497146a165ca29848b66239 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 25 Dec 2025 02:38:14 -0300 Subject: [PATCH 04/55] refactor(players): remove unused currentmount column from players table --- schema.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/schema.sql b/schema.sql index e69fbfc9a4..79fe5ef56a 100644 --- a/schema.sql +++ b/schema.sql @@ -32,7 +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', `direction` tinyint unsigned NOT NULL DEFAULT '2', `maglevel` int NOT NULL DEFAULT '0', `mana` int NOT NULL DEFAULT '0', From 065de9ffb75bd1ca3b76c5d44cb9a84d886fa883 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 25 Dec 2025 02:40:17 -0300 Subject: [PATCH 05/55] refactor(network_message): fix incorrect reference to 'msg' in addItemId function --- data/lib/core/network_message.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/lib/core/network_message.lua b/data/lib/core/network_message.lua index 8421e05189..d5a7b100de 100644 --- a/data/lib/core/network_message.lua +++ b/data/lib/core/network_message.lua @@ -12,7 +12,7 @@ end function NetworkMessage:addItemId(itemId) local it = ItemType(itemId) - msg:addU16(it:getClientId()) + self:addU16(it:getClientId()) end function NetworkMessage:addOutfit(outfit) From 61742287a262bfad17d6093f503ce6f70d6820d2 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 25 Dec 2025 02:40:23 -0300 Subject: [PATCH 06/55] refactor(database): switch to synchronous queries for outfit and mount migration --- data/migrations/37.lua | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/data/migrations/37.lua b/data/migrations/37.lua index a90d7c3ca1..07fa1007d4 100644 --- a/data/migrations/37.lua +++ b/data/migrations/37.lua @@ -36,12 +36,20 @@ function onUpdateDatabase() query = query .. "," end end - db.asyncQuery(query) + if not db.query(query) then + return false + end end - db.asyncQuery("DROP TABLE IF EXISTS `player_outfits`") - db.asyncQuery("DROP TABLE IF EXISTS `player_mounts`") - db.asyncQuery("ALTER TABLE `players` DROP COLUMN `currentmount`, DROP COLUMN `randomizemount`") + if not db.query("DROP TABLE IF EXISTS `player_outfits`") then + return false + end + if not db.query("DROP TABLE IF EXISTS `player_mounts`") then + return false + end + if not db.query("ALTER TABLE `players` DROP COLUMN `currentmount`, DROP COLUMN `randomizemount`") then + return false + end return true end From 904aa8abd0b734fb63db45fe279aa4f3de1efc48 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 25 Dec 2025 02:42:09 -0300 Subject: [PATCH 07/55] refactor(mounts): rename parameter in setWasMounted to prevent shadowing --- data/scripts/systems/outfits/core/mounts.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/scripts/systems/outfits/core/mounts.lua b/data/scripts/systems/outfits/core/mounts.lua index fb18eab270..9129a05b9c 100644 --- a/data/scripts/systems/outfits/core/mounts.lua +++ b/data/scripts/systems/outfits/core/mounts.lua @@ -46,8 +46,8 @@ local wasMounted = {} function Player.getWasMounted(self) return wasMounted[self:getId()] or false end -function Player.setWasMounted(self, wasMounted) - wasMounted[self:getId()] = wasMounted or nil +function Player.setWasMounted(self, value) + wasMounted[self:getId()] = value or nil end function Player.isMounted(self) From 0c5f17f0ff46f1e166c14f981ae89eef1c051a84 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 25 Dec 2025 02:47:00 -0300 Subject: [PATCH 08/55] refactor(outfits): simplify hasOutfit and hasOutfitAddon methods for clarity --- data/scripts/systems/outfits/core/outfits.lua | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/data/scripts/systems/outfits/core/outfits.lua b/data/scripts/systems/outfits/core/outfits.lua index f324b1495c..8734a65849 100644 --- a/data/scripts/systems/outfits/core/outfits.lua +++ b/data/scripts/systems/outfits/core/outfits.lua @@ -13,12 +13,18 @@ function Player.getOutfitAddons(self, lookType) end function Player.hasOutfit(self, lookType, addons) - return self:hasStorageValue(PlayerStorageKeys.outfitsBase + lookType) + local outfitAddons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) + if outfitAddons == nil then + return false + end + if addons == nil or addons == 0 then + return true + end + return outfitAddons & addons == addons end function Player.hasOutfitAddon(self, lookType, addon) - local addons = self:getOutfitAddons(lookType) - return (addons & (1 << (addon - 1))) ~= 0 + return self:getOutfitAddons(lookType) & addon == addon end function Player.removeOutfit(self, lookType) From b0603836450e479af9377c92b541c04c6caaa41b Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 25 Dec 2025 02:48:14 -0300 Subject: [PATCH 09/55] refactor(events): streamline zone change logic for player mounting --- data/scripts/systems/outfits/events.lua | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/data/scripts/systems/outfits/events.lua b/data/scripts/systems/outfits/events.lua index 361b3b85e8..09ae2e29c8 100644 --- a/data/scripts/systems/outfits/events.lua +++ b/data/scripts/systems/outfits/events.lua @@ -6,12 +6,10 @@ function event.onChangeZone(creature, fromZone, toZone) end local player = creature:getPlayer() - if toZone == ZONE_PROTECTION then - if not player:getGroup():getAccess() and player:isMounted() then - player:toggleMount(false) - player:setWasMounted(true) - end - elseif player:getWasMounted() then + if toZone == ZONE_PROTECTION and not player:getGroup():getAccess() and player:isMounted() then + player:toggleMount(false) + player:setWasMounted(true) + elseif fromZone == ZONE_PROTECTION and player:getWasMounted() then player:toggleMount(true) player:setWasMounted(false) end From c437bc6a7b286c6f51a1a2845e9ddfc108875018 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 25 Dec 2025 02:58:23 -0300 Subject: [PATCH 10/55] refactor(set_outfit): ensure player can wear outfit before setting --- data/scripts/systems/outfits/network/set_outfit.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/data/scripts/systems/outfits/network/set_outfit.lua b/data/scripts/systems/outfits/network/set_outfit.lua index 28ed60170c..b7d22121fa 100644 --- a/data/scripts/systems/outfits/network/set_outfit.lua +++ b/data/scripts/systems/outfits/network/set_outfit.lua @@ -33,6 +33,10 @@ function handler.onReceive(player, msg) msg:getU16() -- familiar looktype local randomizeMount = msg:getBool() + if not player:canWearOutfit(outfit.lookType, outfit.lookAddons) or not player:hasMount(outfit.lookMount) then + return + end + player:setOutfit(outfit) player:setRandomizeMount(randomizeMount) elseif outfitType == 1 then -- try outfit from store window @@ -57,6 +61,10 @@ function handler.onReceive(player, msg) local direction = msg:getByte() local isVisible = msg:getBool() + if not player:canWearOutfit(outfit.lookType, outfit.lookAddons) or not player:hasMount(outfit.lookMount) then + return + end + local tile = Tile(position) if tile == nil then return From 8d4111a60f27bc5fd1dbb19a3e84e55dd175342e Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 25 Dec 2025 02:58:57 -0300 Subject: [PATCH 11/55] refactor(iologindata): update SQL queries to remove currentmount --- src/iologindata.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/iologindata.cpp b/src/iologindata.cpp index c7a540281a..0bbd835e89 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`, `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`, `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)))); } From e4c49bfdc8f301fe0de6ab7eefed5b7ecd5292a7 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 25 Dec 2025 02:59:26 -0300 Subject: [PATCH 12/55] refactor(mounts): rename parameter in setWasMounted for clarity --- data/scripts/systems/outfits/core/mounts.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/scripts/systems/outfits/core/mounts.lua b/data/scripts/systems/outfits/core/mounts.lua index 9129a05b9c..8e46abdedc 100644 --- a/data/scripts/systems/outfits/core/mounts.lua +++ b/data/scripts/systems/outfits/core/mounts.lua @@ -46,8 +46,8 @@ local wasMounted = {} function Player.getWasMounted(self) return wasMounted[self:getId()] or false end -function Player.setWasMounted(self, value) - wasMounted[self:getId()] = value or nil +function Player.setWasMounted(self, mounted) + wasMounted[self:getId()] = mounted or nil end function Player.isMounted(self) From 0ec07e4922e2354998f2bf00e934cb871cfc3577 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 25 Dec 2025 03:00:40 -0300 Subject: [PATCH 13/55] refactor(mounts): update toggleMount logic to fix cooldown condition --- data/scripts/systems/outfits/core/mounts.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/scripts/systems/outfits/core/mounts.lua b/data/scripts/systems/outfits/core/mounts.lua index 8e46abdedc..1785e9ecf4 100644 --- a/data/scripts/systems/outfits/core/mounts.lua +++ b/data/scripts/systems/outfits/core/mounts.lua @@ -93,7 +93,7 @@ function Player.dismount(self) end function Player.toggleMount(self, mounted) - if os.mtime() - self:getLastMountToggle() < Outfits.ToggleMountCooldown or not self:getWasMounted() then + if os.mtime() - self:getLastMountToggle() < Outfits.ToggleMountCooldown and not self:getWasMounted() then return false end From 5d03ec1bf540ce009b2dd73a283cf71bfd448e38 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 25 Dec 2025 03:02:26 -0300 Subject: [PATCH 14/55] refactor(podium): fix mount checkbox logic to use lookMount instead of lookType --- data/scripts/systems/outfits/core/windows.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/scripts/systems/outfits/core/windows.lua b/data/scripts/systems/outfits/core/windows.lua index 076de9a86f..b0381d244d 100644 --- a/data/scripts/systems/outfits/core/windows.lua +++ b/data/scripts/systems/outfits/core/windows.lua @@ -197,7 +197,7 @@ function Player.sendPodiumWindow(self, item) msg:addU16(0) msg:addByte(0x05) -- "set outfit" window mode (5 = podium) - msg:addBool((isEmpty and playerOutfit.lookType ~= 0) or podium:hasFlag(PODIUM_SHOW_MOUNT)) -- "mount" checkbox + 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()) From 2a1124ca239b488a8ee5b3069e92cea111bdc957 Mon Sep 17 00:00:00 2001 From: ramon-bernardo Date: Thu, 25 Dec 2025 11:28:18 -0300 Subject: [PATCH 15/55] refactor(general): remove reload, code style and checks --- data/cpplinter.lua | 90 +++++++++---------- data/migrations/37.lua | 1 - data/scripts/events/player.lua | 2 +- data/scripts/systems/outfits/core/mounts.lua | 86 +++++++++--------- data/scripts/systems/outfits/core/outfits.lua | 7 +- data/scripts/systems/outfits/core/windows.lua | 8 +- data/scripts/systems/outfits/data/mounts.lua | 10 +-- data/scripts/systems/outfits/data/outfits.lua | 2 +- data/scripts/systems/outfits/events.lua | 6 +- .../systems/outfits/network/set_outfit.lua | 2 +- data/scripts/talkactions/commands/reload.lua | 3 - src/configmanager.cpp | 1 - src/configmanager.h | 1 - src/const.h | 1 - src/lua/modules/configmanager.cpp | 1 - src/lua/modules/globals.cpp | 1 - vc18/atlas.vcxproj | 5 -- vc18/atlas.vcxproj.filters | 15 ---- 18 files changed, 109 insertions(+), 133 deletions(-) diff --git a/data/cpplinter.lua b/data/cpplinter.lua index 1ed602d428..c693aae07e 100644 --- a/data/cpplinter.lua +++ b/data/cpplinter.lua @@ -1866,14 +1866,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 @@ -2050,44 +2049,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/migrations/37.lua b/data/migrations/37.lua index 07fa1007d4..81818eb364 100644 --- a/data/migrations/37.lua +++ b/data/migrations/37.lua @@ -50,6 +50,5 @@ function onUpdateDatabase() if not db.query("ALTER TABLE `players` DROP COLUMN `currentmount`, DROP COLUMN `randomizemount`") then return false end - return true end diff --git a/data/scripts/events/player.lua b/data/scripts/events/player.lua index 60f3f93d77..b8673e0616 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) diff --git a/data/scripts/systems/outfits/core/mounts.lua b/data/scripts/systems/outfits/core/mounts.lua index 1785e9ecf4..67d67c0c63 100644 --- a/data/scripts/systems/outfits/core/mounts.lua +++ b/data/scripts/systems/outfits/core/mounts.lua @@ -1,3 +1,25 @@ +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 @@ -27,51 +49,17 @@ function Player.getRandomizeMount(self) end function Player.setRandomizeMount(self, randomize) - if not randomize then - self:removeStorageValue(PlayerStorageKeys.randomizeMount) - else + if randomize then self:setStorageValue(PlayerStorageKeys.randomizeMount, 1) + else + self:removeStorageValue(PlayerStorageKeys.randomizeMount) end end -local lastMountToggle = {} -function Player.getLastMountToggle(self) - return lastMountToggle[self:getId()] or 0 -end -function Player.setLastMountToggle(self, time) - lastMountToggle[self:getId()] = time -end - -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 - function Player.isMounted(self) return self:getOutfit().lookMount ~= 0 end -local function getRandomMount(player) - local mounts = Game.getMounts() - - local availableMounts = {} - for _, mount in ipairs(mounts) do - if player:hasMount(mount.id) then - table.insert(availableMounts, mount.id) - end - end - - if #availableMounts == 0 then - return nil - end - - local idx = math.random(1, #availableMounts) - return availableMounts[idx] -end - function Player.mount(self, mount) local outfit = self:getDefaultOutfit() outfit.lookMount = mount.id @@ -92,6 +80,24 @@ function Player.dismount(self) end end +local function getRandomMount(player) + local mounts = Game.getMounts() + + local availableMounts = {} + for _, mount in ipairs(mounts) do + if player:hasMount(mount.id) then + table.insert(availableMounts, mount.id) + end + end + + if #availableMounts == 0 then + return nil + end + + local idx = math.random(1, #availableMounts) + return availableMounts[idx] +end + function Player.toggleMount(self, mounted) if os.mtime() - self:getLastMountToggle() < Outfits.ToggleMountCooldown and not self:getWasMounted() then return false @@ -109,21 +115,21 @@ function Player.toggleMount(self, mounted) end local lookMount = self:getCurrentMount() - if lookMount == nil then + if not lookMount then self:sendOutfitWindow() return false end if self:getRandomizeMount() then lookMount = getRandomMount(self) - if lookMount == nil then + if not lookMount then self:sendOutfitWindow() return false end end local currentMount = Game.getMountByLookType(lookMount) - if currentMount == nil then + if not currentMount then return false end diff --git a/data/scripts/systems/outfits/core/outfits.lua b/data/scripts/systems/outfits/core/outfits.lua index 8734a65849..92a4248fd2 100644 --- a/data/scripts/systems/outfits/core/outfits.lua +++ b/data/scripts/systems/outfits/core/outfits.lua @@ -14,10 +14,11 @@ end function Player.hasOutfit(self, lookType, addons) local outfitAddons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) - if outfitAddons == nil then + if not outfitAddons then return false end - if addons == nil or addons == 0 then + + if not addons or addons == 0 then return true end return outfitAddons & addons == addons @@ -43,7 +44,7 @@ function Player.canWearOutfit(self, lookType, addons) end local outfit = Game.getOutfitByLookType(lookType) - if outfit == nil then + if not outfit then return false end diff --git a/data/scripts/systems/outfits/core/windows.lua b/data/scripts/systems/outfits/core/windows.lua index b0381d244d..7e68892adb 100644 --- a/data/scripts/systems/outfits/core/windows.lua +++ b/data/scripts/systems/outfits/core/windows.lua @@ -104,16 +104,17 @@ function Player.sendOutfitWindow(self) msg:addBool(mounted) msg:addBool(self:getRandomizeMount()) msg:sendToPlayer(self) + msg:delete() end function Player.sendPodiumWindow(self, item) local podium = item:getPodium() - if podium == nil then + if not podium then return end local tile = item:getTile() - if tile == nil then + if not tile then return end @@ -207,6 +208,5 @@ function Player.sendPodiumWindow(self, item) msg:addBool(true) -- "outfit" checkbox, ignored by the client msg:addByte(podium:getDirection()) msg:sendToPlayer(self) + msg:delete() end - -function Player.sendEditPodium(self, item) return self:sendPodiumWindow(item) end diff --git a/data/scripts/systems/outfits/data/mounts.lua b/data/scripts/systems/outfits/data/mounts.lua index 3a5007eee2..cd7c343c75 100644 --- a/data/scripts/systems/outfits/data/mounts.lua +++ b/data/scripts/systems/outfits/data/mounts.lua @@ -212,12 +212,12 @@ local mounts = { function Game.getMounts() local result = {} - for mountId, mountData in pairs(mounts) do + for mountId, mount in pairs(mounts) do table.insert(result, { id = mountId, - name = mountData.name, - speed = mountData.speed, - premium = mountData.premium, + name = mount.name, + speed = mount.speed, + premium = mount.premium, }) end return result @@ -225,7 +225,7 @@ end function Game.getMountByLookType(id) local mount = mounts[id] - if mount == nil then + if not mount then return nil end diff --git a/data/scripts/systems/outfits/data/outfits.lua b/data/scripts/systems/outfits/data/outfits.lua index 8abcaf996f..540ed4beee 100644 --- a/data/scripts/systems/outfits/data/outfits.lua +++ b/data/scripts/systems/outfits/data/outfits.lua @@ -242,7 +242,7 @@ end function Game.getOutfitByLookType(lookType) local outfit = outfits[lookType] - if outfit == nil then + if not outfit then return nil end diff --git a/data/scripts/systems/outfits/events.lua b/data/scripts/systems/outfits/events.lua index 09ae2e29c8..de79325d26 100644 --- a/data/scripts/systems/outfits/events.lua +++ b/data/scripts/systems/outfits/events.lua @@ -1,11 +1,11 @@ local event = Event() -function event.onChangeZone(creature, fromZone, toZone) - if not creature:isPlayer() then +event.onCreatureChangeZone = function(self, fromZone, toZone) + if not self:isPlayer() then return end - local player = creature:getPlayer() + local player = self:getPlayer() if toZone == ZONE_PROTECTION and not player:getGroup():getAccess() and player:isMounted() then player:toggleMount(false) player:setWasMounted(true) diff --git a/data/scripts/systems/outfits/network/set_outfit.lua b/data/scripts/systems/outfits/network/set_outfit.lua index b7d22121fa..777b9a3873 100644 --- a/data/scripts/systems/outfits/network/set_outfit.lua +++ b/data/scripts/systems/outfits/network/set_outfit.lua @@ -66,7 +66,7 @@ function handler.onReceive(player, msg) end local tile = Tile(position) - if tile == nil then + if not tile then return end 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/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/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/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/vc18/atlas.vcxproj b/vc18/atlas.vcxproj index 655aa0eb9e..558246cf40 100644 --- a/vc18/atlas.vcxproj +++ b/vc18/atlas.vcxproj @@ -186,7 +186,6 @@ - @@ -207,7 +206,6 @@ - @@ -216,7 +214,6 @@ Create otpch.h - @@ -304,13 +301,11 @@ - - diff --git a/vc18/atlas.vcxproj.filters b/vc18/atlas.vcxproj.filters index e02a6dd600..abe3f1f15f 100644 --- a/vc18/atlas.vcxproj.filters +++ b/vc18/atlas.vcxproj.filters @@ -200,9 +200,6 @@ Source Files - - Source Files - Source Files @@ -215,9 +212,6 @@ Source Files - - Source Files - Source Files @@ -305,9 +299,6 @@ Source Files\lua\modules - - Source Files\lua\modules - Source Files\lua\modules @@ -556,9 +547,6 @@ Header Files - - Header Files - Header Files @@ -574,9 +562,6 @@ Header Files - - Header Files - Header Files From a18614b7fdc21df65291b5987f46d824235f056b Mon Sep 17 00:00:00 2001 From: ramon-bernardo Date: Thu, 25 Dec 2025 20:42:25 -0300 Subject: [PATCH 16/55] fix(events): self creature and linter --- data/cpplinter.lua | 2 +- data/scripts/systems/outfits/events.lua | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/data/cpplinter.lua b/data/cpplinter.lua index c693aae07e..de2bde9a26 100644 --- a/data/cpplinter.lua +++ b/data/cpplinter.lua @@ -438,7 +438,7 @@ Creature = {} ---@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 diff --git a/data/scripts/systems/outfits/events.lua b/data/scripts/systems/outfits/events.lua index de79325d26..54a0bd5350 100644 --- a/data/scripts/systems/outfits/events.lua +++ b/data/scripts/systems/outfits/events.lua @@ -5,13 +5,12 @@ event.onCreatureChangeZone = function(self, fromZone, toZone) return end - local player = self:getPlayer() - if toZone == ZONE_PROTECTION and not player:getGroup():getAccess() and player:isMounted() then - player:toggleMount(false) - player:setWasMounted(true) - elseif fromZone == ZONE_PROTECTION and player:getWasMounted() then - player:toggleMount(true) - player:setWasMounted(false) + if toZone == ZONE_PROTECTION and not self:getGroup():getAccess() and self:isMounted() then + self:toggleMount(false) + self:setWasMounted(true) + elseif fromZone == ZONE_PROTECTION and self:getWasMounted() then + self:toggleMount(true) + self:setWasMounted(false) end end From 645b99d6022bc248fc6d45675857c235dc0a243f Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Thu, 25 Dec 2025 21:53:49 -0300 Subject: [PATCH 17/55] feat(events): add creature zone change and logout handling for mounts state cleanup --- .../outfits/{events.lua => events/change_zone.lua} | 0 data/scripts/systems/outfits/events/logout.lua | 9 +++++++++ 2 files changed, 9 insertions(+) rename data/scripts/systems/outfits/{events.lua => events/change_zone.lua} (100%) create mode 100644 data/scripts/systems/outfits/events/logout.lua diff --git a/data/scripts/systems/outfits/events.lua b/data/scripts/systems/outfits/events/change_zone.lua similarity index 100% rename from data/scripts/systems/outfits/events.lua rename to data/scripts/systems/outfits/events/change_zone.lua diff --git a/data/scripts/systems/outfits/events/logout.lua b/data/scripts/systems/outfits/events/logout.lua new file mode 100644 index 0000000000..60b7ad588f --- /dev/null +++ b/data/scripts/systems/outfits/events/logout.lua @@ -0,0 +1,9 @@ +local event = CreatureEvent("MountsCleanup") + +function event.onLogout(self, player) + player:setLastMountToggle(nil) + player:setWasMounted(nil) + return true +end + +event:register() From da786a8c89461cf16c94b2da547c8693a64c4cf8 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff <1993083+ranisalt@users.noreply.github.com> Date: Thu, 25 Dec 2025 22:38:36 -0300 Subject: [PATCH 18/55] Update data/scripts/systems/outfits/events/logout.lua Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- data/scripts/systems/outfits/events/logout.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/scripts/systems/outfits/events/logout.lua b/data/scripts/systems/outfits/events/logout.lua index 60b7ad588f..6b33c4f893 100644 --- a/data/scripts/systems/outfits/events/logout.lua +++ b/data/scripts/systems/outfits/events/logout.lua @@ -1,6 +1,6 @@ local event = CreatureEvent("MountsCleanup") -function event.onLogout(self, player) +function event.onLogout(player) player:setLastMountToggle(nil) player:setWasMounted(nil) return true From 4fb04146557bbf433d1cce954f69dc96e324e745 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 26 Dec 2025 01:15:09 -0300 Subject: [PATCH 19/55] feat(commands): add outfit and mount management commands for gamemasters --- .../systems/outfits/commands/add_addon.lua | 43 +++++++++++++++++ .../systems/outfits/commands/add_mount.lua | 32 +++++++++++++ .../systems/outfits/commands/add_outfit.lua | 48 +++++++++++++++++++ .../systems/outfits/commands/remove_addon.lua | 43 +++++++++++++++++ .../systems/outfits/commands/remove_mount.lua | 32 +++++++++++++ .../outfits/commands/remove_outfit.lua | 32 +++++++++++++ data/scripts/systems/outfits/data/mounts.lua | 33 +++++++++++-- data/scripts/systems/outfits/data/outfits.lua | 24 ++++++++++ 8 files changed, 282 insertions(+), 5 deletions(-) create mode 100644 data/scripts/systems/outfits/commands/add_addon.lua create mode 100644 data/scripts/systems/outfits/commands/add_mount.lua create mode 100644 data/scripts/systems/outfits/commands/add_outfit.lua create mode 100644 data/scripts/systems/outfits/commands/remove_addon.lua create mode 100644 data/scripts/systems/outfits/commands/remove_mount.lua create mode 100644 data/scripts/systems/outfits/commands/remove_outfit.lua 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..d87954bb72 --- /dev/null +++ b/data/scripts/systems/outfits/commands/add_addon.lua @@ -0,0 +1,43 @@ +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 player: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 + + return target:addOutfitAddon(outfit.lookType, addon) +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..9cb0c7c8eb --- /dev/null +++ b/data/scripts/systems/outfits/commands/add_mount.lua @@ -0,0 +1,32 @@ +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 + + return target:addMount(mount.lookType) +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..6bb98cf219 --- /dev/null +++ b/data/scripts/systems/outfits/commands/add_outfit.lua @@ -0,0 +1,48 @@ +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 #split < 3 then + return target:addOutfit(outfit.lookType, addons) + end + + local addons = tonumber(split[3]) + if not addons or addons < 0 or addons > 3 then + player:sendCancelMessage("Invalid addons value.") + return false + end + + if not player:hasOutfit(outfit.lookType) then + if not target:addOutfit(outfit.lookType, addons) then + return false + end + end + + if target:hasOutfitAddon(outfit.lookType, addons) then + player:sendCancelMessage("Target already has this outfit with these addons.") + return false + end + + return target:addOutfitAddon(outfit.lookType, addons) +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..7f4cebf347 --- /dev/null +++ b/data/scripts/systems/outfits/commands/remove_addon.lua @@ -0,0 +1,43 @@ +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 player: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 + + return target:removeOutfitAddon(outfit.lookType, addon) +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..f6429e9b47 --- /dev/null +++ b/data/scripts/systems/outfits/commands/remove_mount.lua @@ -0,0 +1,32 @@ +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 + + return target:removeMount(mount.lookType) +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..d82dc2bb9e --- /dev/null +++ b/data/scripts/systems/outfits/commands/remove_outfit.lua @@ -0,0 +1,32 @@ +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 with look type " .. lookType .. " does not exist.") + return false + end + + if not target:hasOutfit(outfit.lookType) then + player:sendCancelMessage("Target does not have this outfit.") + return false + end + + return target:removeOutfit(outfit.lookType) +end + +talkaction:separator(" ") +talkaction:accountType(ACCOUNT_TYPE_GAMEMASTER) +talkaction:register() diff --git a/data/scripts/systems/outfits/data/mounts.lua b/data/scripts/systems/outfits/data/mounts.lua index cd7c343c75..a4c34e97b6 100644 --- a/data/scripts/systems/outfits/data/mounts.lua +++ b/data/scripts/systems/outfits/data/mounts.lua @@ -212,9 +212,9 @@ local mounts = { function Game.getMounts() local result = {} - for mountId, mount in pairs(mounts) do + for lookType, mount in pairs(mounts) do table.insert(result, { - id = mountId, + lookType = lookType, name = mount.name, speed = mount.speed, premium = mount.premium, @@ -223,16 +223,39 @@ function Game.getMounts() return result end -function Game.getMountByLookType(id) - local mount = mounts[id] +function Game.getMountByLookType(lookType) + local mount = mounts[lookType] if not mount then return nil end return { - id = id, + 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) + else + return Game.getMountByName(param) + end +end diff --git a/data/scripts/systems/outfits/data/outfits.lua b/data/scripts/systems/outfits/data/outfits.lua index 540ed4beee..41e400a015 100644 --- a/data/scripts/systems/outfits/data/outfits.lua +++ b/data/scripts/systems/outfits/data/outfits.lua @@ -254,3 +254,27 @@ function Game.getOutfitByLookType(lookType) unlocked = outfit.unlocked, } end + +function Game.getOutfitByName(name, sex) + 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) + else + return Game.getOutfitByName(param, sex) + end +end From 5ac3404f4a5241a2b2d0bcb4c2e66c34a9ddbcf6 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 26 Dec 2025 01:16:22 -0300 Subject: [PATCH 20/55] refactor(mounts): fix mount storage checks and add mount riding validation --- data/lib/core/creature.lua | 4 --- data/scripts/systems/outfits/core/mounts.lua | 29 +++++++++++++++---- data/scripts/systems/outfits/core/outfits.lua | 21 +++++++------- data/scripts/systems/outfits/core/windows.lua | 12 ++++---- .../systems/outfits/network/set_outfit.lua | 6 +++- 5 files changed, 47 insertions(+), 25 deletions(-) diff --git a/data/lib/core/creature.lua b/data/lib/core/creature.lua index 4488c5902e..943e027465 100644 --- a/data/lib/core/creature.lua +++ b/data/lib/core/creature.lua @@ -200,10 +200,6 @@ function Creature.getKillers(self, onlyPlayers) return killers end -function Creature.hasStorageValue(self, key) - return self:getStorageValue(key) ~= nil -end - function Creature.removeStorageValue(self, key) return self:setStorageValue(key, nil) end diff --git a/data/scripts/systems/outfits/core/mounts.lua b/data/scripts/systems/outfits/core/mounts.lua index 67d67c0c63..de8080fe31 100644 --- a/data/scripts/systems/outfits/core/mounts.lua +++ b/data/scripts/systems/outfits/core/mounts.lua @@ -25,7 +25,8 @@ function Player.addMount(self, mountId) end function Player.hasMount(self, mountId) - return self:hasStorageValue(PlayerStorageKeys.mountsBase + mountId) + local value = self:getStorageValue(PlayerStorageKeys.mountsBase + mountId) + return value ~= nil and value ~= -1 end function Player.removeMount(self, mountId) @@ -45,7 +46,8 @@ function Player.getCurrentMount(self) end function Player.getRandomizeMount(self) - return self:hasStorageValue(PlayerStorageKeys.randomizeMount) + local randomizeMount = self:getStorageValue(PlayerStorageKeys.randomizeMount) + return randomizeMount ~= nil and randomizeMount ~= -1 end function Player.setRandomizeMount(self, randomize) @@ -56,13 +58,30 @@ function Player.setRandomizeMount(self, randomize) end end +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 function Player.mount(self, mount) local outfit = self:getDefaultOutfit() - outfit.lookMount = mount.id + outfit.lookMount = mount.lookType self:setOutfit(outfit) self:changeSpeed(mount.speed) @@ -85,8 +104,8 @@ local function getRandomMount(player) local availableMounts = {} for _, mount in ipairs(mounts) do - if player:hasMount(mount.id) then - table.insert(availableMounts, mount.id) + if player:hasMount(mount.lookType) then + table.insert(availableMounts, mount.lookType) end end diff --git a/data/scripts/systems/outfits/core/outfits.lua b/data/scripts/systems/outfits/core/outfits.lua index 92a4248fd2..6b87c7e070 100644 --- a/data/scripts/systems/outfits/core/outfits.lua +++ b/data/scripts/systems/outfits/core/outfits.lua @@ -9,23 +9,28 @@ function Player.addOutfitAddon(self, lookType, addon) end function Player.getOutfitAddons(self, lookType) - return self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) or 0 + local outfitAddons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) + if outfitAddons == nil or outfitAddons == -1 then + return 0 + end + return outfitAddons end function Player.hasOutfit(self, lookType, addons) local outfitAddons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) - if not outfitAddons then + if outfitAddons == nil or outfitAddons == -1 then return false end - if not addons or addons == 0 then + if addons == nil or addons == 0 then return true end - return outfitAddons & addons == addons + + return (outfitAddons & addons) == addons end function Player.hasOutfitAddon(self, lookType, addon) - return self:getOutfitAddons(lookType) & addon == addon + return self:getOutfitAddons(lookType) & addon ~= 0 end function Player.removeOutfit(self, lookType) @@ -56,9 +61,5 @@ function Player.canWearOutfit(self, lookType, addons) return false end - if addons ~= nil and addons ~= 0 and not self:hasOutfitAddon(lookType, addons) then - return false - end - - return true + return addons == nil or addons == 0 or self:hasOutfitAddon(lookType, addons) end diff --git a/data/scripts/systems/outfits/core/windows.lua b/data/scripts/systems/outfits/core/windows.lua index 7e68892adb..5c0824ab1e 100644 --- a/data/scripts/systems/outfits/core/windows.lua +++ b/data/scripts/systems/outfits/core/windows.lua @@ -32,10 +32,12 @@ end local function getAvailableMounts(player) local mounts = Game.getMounts() + local isAccessPlayer = player:getGroup():getAccess() + local availableMounts = {} for _, mount in ipairs(mounts) do - if player:hasMount(mount.id) then - table.insert(availableMounts, { id = mount.id, name = mount.name }) + if isAccessPlayer or player:hasMount(mount.lookType) then + table.insert(availableMounts, { lookType = mount.lookType, name = mount.name }) end end return availableMounts @@ -88,9 +90,9 @@ function Player.sendOutfitWindow(self) msg:addU16(#availableMounts) for _, mount in ipairs(availableMounts) do - msg:addU16(mount.id) + msg:addU16(mount.lookType) msg:addString(mount.name) - msg:addByte(0) -- mode: 0x00 - available, 0x01 store (requires U32 store offerId) + msg:addByte(0) -- mode: 0x00 - available, 0x01 store (requires U32 store offerlookType) end msg:addU16(#availableFamiliars) @@ -189,7 +191,7 @@ function Player.sendPodiumWindow(self, item) -- available mounts msg:addU16(#availableMounts) for _, mount in ipairs(availableMounts) do - msg:addU16(mount.id) + msg:addU16(mount.lookType) msg:addString(mount.name) msg:addByte(0) -- mode: 0x00 - available, 0x01 store (requires U32 store offerId) end diff --git a/data/scripts/systems/outfits/network/set_outfit.lua b/data/scripts/systems/outfits/network/set_outfit.lua index 777b9a3873..1484880b6c 100644 --- a/data/scripts/systems/outfits/network/set_outfit.lua +++ b/data/scripts/systems/outfits/network/set_outfit.lua @@ -33,7 +33,11 @@ function handler.onReceive(player, msg) msg:getU16() -- familiar looktype local randomizeMount = msg:getBool() - if not player:canWearOutfit(outfit.lookType, outfit.lookAddons) or not player:hasMount(outfit.lookMount) then + if not player:canWearOutfit(outfit.lookType, outfit.lookAddons) then + return + end + + if outfit.lookMount ~= 0 and not player:canRideMount(outfit.lookMount) then return end From c071c6b89ac22d75521097b02100697c3ffaf893 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 26 Dec 2025 01:17:35 -0300 Subject: [PATCH 21/55] feat(events): implement creature removal event handling and cleanup --- data/scripts/systems/outfits/events/change_zone.lua | 2 +- data/scripts/systems/outfits/events/cleanup.lua | 13 +++++++++++++ data/scripts/systems/outfits/events/logout.lua | 9 --------- src/game.cpp | 2 ++ 4 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 data/scripts/systems/outfits/events/cleanup.lua delete mode 100644 data/scripts/systems/outfits/events/logout.lua diff --git a/data/scripts/systems/outfits/events/change_zone.lua b/data/scripts/systems/outfits/events/change_zone.lua index 54a0bd5350..810f4882b0 100644 --- a/data/scripts/systems/outfits/events/change_zone.lua +++ b/data/scripts/systems/outfits/events/change_zone.lua @@ -1,6 +1,6 @@ local event = Event() -event.onCreatureChangeZone = function(self, fromZone, toZone) +function event.onCreatureChangeZone(self, fromZone, toZone) if not self:isPlayer() then return end diff --git a/data/scripts/systems/outfits/events/cleanup.lua b/data/scripts/systems/outfits/events/cleanup.lua new file mode 100644 index 0000000000..18f8b2b501 --- /dev/null +++ b/data/scripts/systems/outfits/events/cleanup.lua @@ -0,0 +1,13 @@ +local event = Event() + +function event.onCreatureRemoved(self) + if not self:isPlayer() then + return + end + + self:setLastMountToggle(nil) + self:setWasMounted(nil) + return true +end + +event:register() diff --git a/data/scripts/systems/outfits/events/logout.lua b/data/scripts/systems/outfits/events/logout.lua deleted file mode 100644 index 6b33c4f893..0000000000 --- a/data/scripts/systems/outfits/events/logout.lua +++ /dev/null @@ -1,9 +0,0 @@ -local event = CreatureEvent("MountsCleanup") - -function event.onLogout(player) - player:setLastMountToggle(nil) - player:setWasMounted(nil) - return true -end - -event:register() diff --git a/src/game.cpp b/src/game.cpp index abacf67c05..403c21fb21 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -598,6 +598,8 @@ bool Game::removeCreature(const std::shared_ptr& creature, bool isLogo summon->setSkillLoss(false); removeCreature(summon); } + + tfs::events::creature::onRemoved(creature); return true; } From 36f5f397853358c50e390fadabff7c57a51795c4 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 26 Dec 2025 01:52:07 -0300 Subject: [PATCH 22/55] refactor(mounts): rename and reorganize mount storage keys for clarity --- data/lib/core/storages.lua | 3 ++- data/scripts/systems/outfits/core/mounts.lua | 22 +++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/data/lib/core/storages.lua b/data/lib/core/storages.lua index d51687b5e9..766f5e5255 100644 --- a/data/lib/core/storages.lua +++ b/data/lib/core/storages.lua @@ -50,7 +50,8 @@ PlayerStorageKeys = { bestiaryTrackerBase = 500000, -- Outfits and mounts: - randomizeMount = 60000, + currentMount = 60000, + randomizeMount = 60001, outfitsBase = 600000, mountsBase = 610000, } diff --git a/data/scripts/systems/outfits/core/mounts.lua b/data/scripts/systems/outfits/core/mounts.lua index de8080fe31..4b7d7b41f9 100644 --- a/data/scripts/systems/outfits/core/mounts.lua +++ b/data/scripts/systems/outfits/core/mounts.lua @@ -30,19 +30,26 @@ function Player.hasMount(self, mountId) end function Player.removeMount(self, mountId) - local result = self:removeStorageValue(PlayerStorageKeys.mountsBase + mountId) + local value = self:removeStorageValue(PlayerStorageKeys.mountsBase + mountId) if self:getCurrentMount() == mountId and self:isMounted() then self:dismount() end - return result + return value end function Player.getCurrentMount(self) - local lookType = self:getOutfit().lookMount - if lookType == 0 then + local value = self:getStorageValue(PlayerStorageKeys.currentMount) + if value == nil or value == -1 then return nil end - return lookType + return value +end + +function Player.setCurrentMount(self, mountId) + if mountId ~= nil then + return self:setStorageValue(PlayerStorageKeys.currentMount, mountId) + end + return self:removeStorageValue(PlayerStorageKeys.currentMount) end function Player.getRandomizeMount(self) @@ -52,10 +59,9 @@ end function Player.setRandomizeMount(self, randomize) if randomize then - self:setStorageValue(PlayerStorageKeys.randomizeMount, 1) - else - self:removeStorageValue(PlayerStorageKeys.randomizeMount) + return self:setStorageValue(PlayerStorageKeys.randomizeMount, 1) end + return self:removeStorageValue(PlayerStorageKeys.randomizeMount) end function Player.canRideMount(self, mountId) From 8fbf6760d50f53c3c38fb132db8a34f513570aff Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 26 Dec 2025 02:08:33 -0300 Subject: [PATCH 23/55] refactor(outfits): fix mounted state checks in outfit window and change zone event --- data/scripts/systems/outfits/core/windows.lua | 5 +---- data/scripts/systems/outfits/events/change_zone.lua | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/data/scripts/systems/outfits/core/windows.lua b/data/scripts/systems/outfits/core/windows.lua index 5c0824ab1e..c82fc1aae7 100644 --- a/data/scripts/systems/outfits/core/windows.lua +++ b/data/scripts/systems/outfits/core/windows.lua @@ -59,10 +59,7 @@ function Player.sendOutfitWindow(self) currentOutfit.lookType = availableOutfits[1].lookType end - local mounted = self:isMounted() - if self:getWasMounted() then - mounted = currentOutfit.lookMount ~= 0 - end + local mounted = self:isMounted() or self:getWasMounted() local availableMounts = getAvailableMounts(self) local availableFamiliars = getAvailableFamiliars(self) diff --git a/data/scripts/systems/outfits/events/change_zone.lua b/data/scripts/systems/outfits/events/change_zone.lua index 810f4882b0..01f2873547 100644 --- a/data/scripts/systems/outfits/events/change_zone.lua +++ b/data/scripts/systems/outfits/events/change_zone.lua @@ -6,8 +6,8 @@ function event.onCreatureChangeZone(self, fromZone, toZone) end if toZone == ZONE_PROTECTION and not self:getGroup():getAccess() and self:isMounted() then - self:toggleMount(false) self:setWasMounted(true) + self:toggleMount(false) elseif fromZone == ZONE_PROTECTION and self:getWasMounted() then self:toggleMount(true) self:setWasMounted(false) From 8d5fb83a56050f62820f5fe9230dd98db26ccf22 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 26 Dec 2025 02:11:49 -0300 Subject: [PATCH 24/55] refactor(outfits): fix outfit ownership checks in add and remove addon commands --- .../systems/outfits/commands/add_addon.lua | 2 +- .../systems/outfits/commands/add_outfit.lua | 22 +++---------------- .../systems/outfits/commands/remove_addon.lua | 2 +- .../outfits/commands/remove_outfit.lua | 2 +- 4 files changed, 6 insertions(+), 22 deletions(-) diff --git a/data/scripts/systems/outfits/commands/add_addon.lua b/data/scripts/systems/outfits/commands/add_addon.lua index d87954bb72..645c88b1b3 100644 --- a/data/scripts/systems/outfits/commands/add_addon.lua +++ b/data/scripts/systems/outfits/commands/add_addon.lua @@ -19,7 +19,7 @@ function talkaction.onSay(player, words, param) return false end - if not player:hasOutfit(outfit.lookType) then + if not target:hasOutfit(outfit.lookType) then player:sendCancelMessage("Target does not have this outfit.") return false end diff --git a/data/scripts/systems/outfits/commands/add_outfit.lua b/data/scripts/systems/outfits/commands/add_outfit.lua index 6bb98cf219..18b19cbe89 100644 --- a/data/scripts/systems/outfits/commands/add_outfit.lua +++ b/data/scripts/systems/outfits/commands/add_outfit.lua @@ -19,28 +19,12 @@ function talkaction.onSay(player, words, param) return false end - if #split < 3 then - return target:addOutfit(outfit.lookType, addons) - end - - local addons = tonumber(split[3]) - if not addons or addons < 0 or addons > 3 then - player:sendCancelMessage("Invalid addons value.") - return false - end - - if not player:hasOutfit(outfit.lookType) then - if not target:addOutfit(outfit.lookType, addons) then - return false - end - end - - if target:hasOutfitAddon(outfit.lookType, addons) then - player:sendCancelMessage("Target already has this outfit with these addons.") + if target:hasOutfit(outfit.lookType) then + player:sendCancelMessage("Target already has this outfit.") return false end - return target:addOutfitAddon(outfit.lookType, addons) + return target:addOutfit(outfit.lookType, addons) end talkaction:separator(" ") diff --git a/data/scripts/systems/outfits/commands/remove_addon.lua b/data/scripts/systems/outfits/commands/remove_addon.lua index 7f4cebf347..250f1ea75e 100644 --- a/data/scripts/systems/outfits/commands/remove_addon.lua +++ b/data/scripts/systems/outfits/commands/remove_addon.lua @@ -19,7 +19,7 @@ function talkaction.onSay(player, words, param) return false end - if not player:hasOutfit(outfit.lookType) then + if not target:hasOutfit(outfit.lookType) then player:sendCancelMessage("Target does not have this outfit.") return false end diff --git a/data/scripts/systems/outfits/commands/remove_outfit.lua b/data/scripts/systems/outfits/commands/remove_outfit.lua index d82dc2bb9e..b48a454e03 100644 --- a/data/scripts/systems/outfits/commands/remove_outfit.lua +++ b/data/scripts/systems/outfits/commands/remove_outfit.lua @@ -15,7 +15,7 @@ function talkaction.onSay(player, words, param) local outfit = Game.getOutfit(split[2], target:getSex()) if not outfit then - player:sendCancelMessage("Outfit with look type " .. lookType .. " does not exist.") + player:sendCancelMessage("Outfit " .. split[2] .. " does not exist.") return false end From 7cedd31d6e31c1f7015112ccc04fda6a99ff1a8a Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 26 Dec 2025 02:15:31 -0300 Subject: [PATCH 25/55] refactor(mounts): add mount ownership check in setCurrentMount and validate mount riding in outfit handling --- data/scripts/systems/outfits/core/mounts.lua | 3 +++ data/scripts/systems/outfits/network/set_outfit.lua | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/data/scripts/systems/outfits/core/mounts.lua b/data/scripts/systems/outfits/core/mounts.lua index 4b7d7b41f9..da449fcccd 100644 --- a/data/scripts/systems/outfits/core/mounts.lua +++ b/data/scripts/systems/outfits/core/mounts.lua @@ -47,6 +47,9 @@ end function Player.setCurrentMount(self, mountId) if mountId ~= nil then + if not self:hasMount(mountId) then + return false + end return self:setStorageValue(PlayerStorageKeys.currentMount, mountId) end return self:removeStorageValue(PlayerStorageKeys.currentMount) diff --git a/data/scripts/systems/outfits/network/set_outfit.lua b/data/scripts/systems/outfits/network/set_outfit.lua index 1484880b6c..d65399fc14 100644 --- a/data/scripts/systems/outfits/network/set_outfit.lua +++ b/data/scripts/systems/outfits/network/set_outfit.lua @@ -65,7 +65,11 @@ function handler.onReceive(player, msg) local direction = msg:getByte() local isVisible = msg:getBool() - if not player:canWearOutfit(outfit.lookType, outfit.lookAddons) or not player:hasMount(outfit.lookMount) then + if not player:canWearOutfit(outfit.lookType, outfit.lookAddons) then + return + end + + if outfit.lookMount ~= 0 and not player:canRideMount(outfit.lookMount) then return end From 9479f648dfa9f0cd95b069875471c97ed27872a1 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 26 Dec 2025 03:24:18 -0300 Subject: [PATCH 26/55] refactor(outfits): enhance item validation in set outfit handling --- data/scripts/systems/outfits/network/set_outfit.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/scripts/systems/outfits/network/set_outfit.lua b/data/scripts/systems/outfits/network/set_outfit.lua index d65399fc14..9991b3cd48 100644 --- a/data/scripts/systems/outfits/network/set_outfit.lua +++ b/data/scripts/systems/outfits/network/set_outfit.lua @@ -89,7 +89,7 @@ function handler.onReceive(player, msg) end local it = Game.getItemTypeByClientId(clientId) - if not it or it:getClientId() ~= clientId then + if not it or it:getClientId() ~= clientId or it:getId() ~= item:getId() then return end From 4b164f8e6a4f7fcdd7a3984564ad863e5dd7997c Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 26 Dec 2025 15:05:29 -0300 Subject: [PATCH 27/55] refactor(outfits): fix mount color assignment logic in set outfit handling --- data/scripts/systems/outfits/network/set_outfit.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/scripts/systems/outfits/network/set_outfit.lua b/data/scripts/systems/outfits/network/set_outfit.lua index 9991b3cd48..dbaca1f4c4 100644 --- a/data/scripts/systems/outfits/network/set_outfit.lua +++ b/data/scripts/systems/outfits/network/set_outfit.lua @@ -22,8 +22,8 @@ function handler.onReceive(player, msg) local lookMountLegs = msg:getByte() local lookMountFeet = msg:getByte() - if lookMount ~= 0 then - outfit.lookMount = lookMount + 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 From f7af96b7a763b29306415ca96d4724d5a54ed976 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 26 Dec 2025 15:07:52 -0300 Subject: [PATCH 28/55] refactor(outfits): fix outfit addon check logic in hasOutfitAddon and canWearOutfit functions --- data/scripts/systems/outfits/core/outfits.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/data/scripts/systems/outfits/core/outfits.lua b/data/scripts/systems/outfits/core/outfits.lua index 6b87c7e070..3eaac8d8bd 100644 --- a/data/scripts/systems/outfits/core/outfits.lua +++ b/data/scripts/systems/outfits/core/outfits.lua @@ -30,7 +30,8 @@ function Player.hasOutfit(self, lookType, addons) end function Player.hasOutfitAddon(self, lookType, addon) - return self:getOutfitAddons(lookType) & addon ~= 0 + local addons = self:getOutfitAddons(lookType) + return addons & (1 << (addon - 1)) ~= 0 end function Player.removeOutfit(self, lookType) @@ -61,5 +62,9 @@ function Player.canWearOutfit(self, lookType, addons) return false end - return addons == nil or addons == 0 or self:hasOutfitAddon(lookType, addons) + if addons == nil or addons == 0 then + return true + end + + return self:getOutfitAddons(lookType) & addons == addons end From c878520c8820af0d4679c38698419303b71124b5 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 26 Dec 2025 15:08:36 -0300 Subject: [PATCH 29/55] refactor(outfits): remove unused addons parameter in addOutfit function --- data/scripts/systems/outfits/commands/add_outfit.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/scripts/systems/outfits/commands/add_outfit.lua b/data/scripts/systems/outfits/commands/add_outfit.lua index 18b19cbe89..ee6f97709d 100644 --- a/data/scripts/systems/outfits/commands/add_outfit.lua +++ b/data/scripts/systems/outfits/commands/add_outfit.lua @@ -24,7 +24,7 @@ function talkaction.onSay(player, words, param) return false end - return target:addOutfit(outfit.lookType, addons) + return target:addOutfit(outfit.lookType) end talkaction:separator(" ") From 0d4ea2775036e6af7ffe883d0448b31f3a3a4c8b Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 26 Dec 2025 15:09:52 -0300 Subject: [PATCH 30/55] refactor(podium): fix mount ownership check in onPodiumEdit function --- data/scripts/events/player.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/scripts/events/player.lua b/data/scripts/events/player.lua index b8673e0616..b9d91d401d 100644 --- a/data/scripts/events/player.lua +++ b/data/scripts/events/player.lua @@ -123,7 +123,7 @@ function Player:onPodiumEdit(item, outfit, direction, isVisible) -- reset mount if unable to ride local mount = Game.getMountByLookType(outfit.lookMount) - if not mount or not self:hasMount(mount.id) then + if not mount or not self:hasMount(mount.lookType) then outfit.lookMount = 0 end end From e46e0aad74d2f21bf1882e283a40203a724c5e3f Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 26 Dec 2025 15:22:10 -0300 Subject: [PATCH 31/55] refactor(outfits, mounts): move more outfit and mount management functions --- data/lib/core/player.lua | 16 ------- data/scripts/systems/outfits/core/mounts.lua | 23 ++++++++- data/scripts/systems/outfits/core/outfits.lua | 48 ++++++++++++++----- 3 files changed, 59 insertions(+), 28 deletions(-) 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/scripts/systems/outfits/core/mounts.lua b/data/scripts/systems/outfits/core/mounts.lua index da449fcccd..306f82cba4 100644 --- a/data/scripts/systems/outfits/core/mounts.lua +++ b/data/scripts/systems/outfits/core/mounts.lua @@ -24,6 +24,13 @@ 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 @@ -37,6 +44,16 @@ function Player.removeMount(self, mountId) 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 + function Player.getCurrentMount(self) local value = self:getStorageValue(PlayerStorageKeys.currentMount) if value == nil or value == -1 then @@ -89,11 +106,15 @@ function Player.isMounted(self) end function Player.mount(self, mount) + if not mount or not mount.lookType then + return false + end + local outfit = self:getDefaultOutfit() outfit.lookMount = mount.lookType self:setOutfit(outfit) - self:changeSpeed(mount.speed) + return true end function Player.dismount(self) diff --git a/data/scripts/systems/outfits/core/outfits.lua b/data/scripts/systems/outfits/core/outfits.lua index 3eaac8d8bd..ed047eddaa 100644 --- a/data/scripts/systems/outfits/core/outfits.lua +++ b/data/scripts/systems/outfits/core/outfits.lua @@ -1,13 +1,38 @@ -function Player.addOutfit(self, lookType, addons) - return self:setStorageValue(PlayerStorageKeys.outfitsBase + lookType, addons or 0) +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 + +function Player.addAllOutfits(self) + local outfits = Game.getOutfits(self:getSex()) + for _, outfit in ipairs(outfits) do + self:addOutfit(outfit.lookType) + end end function Player.addOutfitAddon(self, lookType, addon) - local addons = self:getOutfitAddons(lookType) + 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 +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 + function Player.getOutfitAddons(self, lookType) local outfitAddons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) if outfitAddons == nil or outfitAddons == -1 then @@ -16,17 +41,18 @@ function Player.getOutfitAddons(self, lookType) return outfitAddons end -function Player.hasOutfit(self, lookType, addons) - local outfitAddons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) - if outfitAddons == nil or outfitAddons == -1 then - return false - end +function Player.hasOutfit(self, lookType) + local addons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) + return addons ~= nil and addons ~= -1 +end - if addons == nil or addons == 0 then - return true +function Player.hasOutfitAddons(self, lookType, addons) + local currentAddons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) + if currentAddons == nil or currentAddons == -1 then + return false end - return (outfitAddons & addons) == addons + return (currentAddons & addons) == addons end function Player.hasOutfitAddon(self, lookType, addon) From bab15587bfbba4f44abbbe0ff66c238bf0780344 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 26 Dec 2025 15:22:51 -0300 Subject: [PATCH 32/55] refactor(mounts): enhance mount validation to include speed check --- data/scripts/systems/outfits/core/mounts.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/scripts/systems/outfits/core/mounts.lua b/data/scripts/systems/outfits/core/mounts.lua index 306f82cba4..36f44b5e16 100644 --- a/data/scripts/systems/outfits/core/mounts.lua +++ b/data/scripts/systems/outfits/core/mounts.lua @@ -106,7 +106,7 @@ function Player.isMounted(self) end function Player.mount(self, mount) - if not mount or not mount.lookType then + if not mount or not mount.lookType or not mount.speed then return false end From 3b561dbb8d963559bb65c8b4cec539a466ae3f91 Mon Sep 17 00:00:00 2001 From: ramon-bernardo Date: Fri, 26 Dec 2025 21:01:49 -0300 Subject: [PATCH 33/55] fix(mounts): move network, toggle and group bypass --- config.lua.dist | 1 - data/scripts/network/toggle_mount.lua | 8 ----- data/scripts/systems/outfits/config.lua | 1 + data/scripts/systems/outfits/core/mounts.lua | 34 +++++++++++-------- data/scripts/systems/outfits/core/outfits.lua | 14 ++++---- data/scripts/systems/outfits/data/mounts.lua | 3 +- data/scripts/systems/outfits/data/outfits.lua | 3 +- .../systems/outfits/events/change_zone.lua | 2 +- .../systems/outfits/network/set_outfit.lua | 1 + .../systems/outfits/network/toggle_mount.lua | 12 +++++++ 10 files changed, 43 insertions(+), 36 deletions(-) delete mode 100644 data/scripts/network/toggle_mount.lua create mode 100644 data/scripts/systems/outfits/network/toggle_mount.lua 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/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/config.lua b/data/scripts/systems/outfits/config.lua index b3110f74fa..032dd34ed1 100644 --- a/data/scripts/systems/outfits/config.lua +++ b/data/scripts/systems/outfits/config.lua @@ -1,4 +1,5 @@ Outfits = { AllowChangeOutfit = true, + AllowToggleMount = true, ToggleMountCooldown = 3000, -- in milliseconds } diff --git a/data/scripts/systems/outfits/core/mounts.lua b/data/scripts/systems/outfits/core/mounts.lua index 36f44b5e16..f1a8e3e1e8 100644 --- a/data/scripts/systems/outfits/core/mounts.lua +++ b/data/scripts/systems/outfits/core/mounts.lua @@ -33,7 +33,7 @@ end function Player.hasMount(self, mountId) local value = self:getStorageValue(PlayerStorageKeys.mountsBase + mountId) - return value ~= nil and value ~= -1 + return value and value ~= -1 end function Player.removeMount(self, mountId) @@ -49,6 +49,7 @@ function Player.removeAllMounts(self) for _, mount in ipairs(mounts) do self:removeMount(mount.lookType) end + if self:isMounted() then self:dismount() end @@ -56,25 +57,26 @@ end function Player.getCurrentMount(self) local value = self:getStorageValue(PlayerStorageKeys.currentMount) - if value == nil or value == -1 then + if not value or value == -1 then return nil end return value end function Player.setCurrentMount(self, mountId) - if mountId ~= nil then - if not self:hasMount(mountId) then - return false - end - return self:setStorageValue(PlayerStorageKeys.currentMount, mountId) + if not mountId then + return self:removeStorageValue(PlayerStorageKeys.currentMount) + end + + if not self:getGroup():getAccess() and not self:hasMount(mountId) then + return false end - return self:removeStorageValue(PlayerStorageKeys.currentMount) + return self:setStorageValue(PlayerStorageKeys.currentMount, mountId) end function Player.getRandomizeMount(self) local randomizeMount = self:getStorageValue(PlayerStorageKeys.randomizeMount) - return randomizeMount ~= nil and randomizeMount ~= -1 + return randomizeMount and randomizeMount ~= -1 end function Player.setRandomizeMount(self, randomize) @@ -97,7 +99,6 @@ function Player.canRideMount(self, mountId) if mount.premium and not self:isPremium() then return false end - return self:hasMount(mount.lookType) end @@ -124,7 +125,7 @@ function Player.dismount(self) self:setOutfit(outfit) local mount = Game.getMountByLookType(lookMount) - if mount ~= nil then + if mount then self:changeSpeed(-mount.speed) end end @@ -148,8 +149,13 @@ local function getRandomMount(player) end function Player.toggleMount(self, mounted) - if os.mtime() - self:getLastMountToggle() < Outfits.ToggleMountCooldown and not self:getWasMounted() then - return false + if not self:getGroup():getAccess() then + local lastMountToggle = self:getLastMountToggle() + if lastMountToggle and lastMountToggle > 0 then + if os.mtime() - lastMountToggle < Outfits.ToggleMountCooldown and not self:getWasMounted() then + return false + end + end end if mounted then @@ -182,7 +188,7 @@ function Player.toggleMount(self, mounted) return false end - if currentMount.premium and not self:isPremium() then + if not self:getGroup():getAccess() and currentMount.premium and not self:isPremium() then self:sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT) return false end diff --git a/data/scripts/systems/outfits/core/outfits.lua b/data/scripts/systems/outfits/core/outfits.lua index ed047eddaa..30fefe6d20 100644 --- a/data/scripts/systems/outfits/core/outfits.lua +++ b/data/scripts/systems/outfits/core/outfits.lua @@ -1,6 +1,6 @@ function Player.addOutfit(self, lookType) local addons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) - if addons ~= nil and addons ~= -1 then + if addons and addons ~= -1 then return true end return self:setStorageValue(PlayerStorageKeys.outfitsBase + lookType, 0) @@ -15,7 +15,7 @@ end function Player.addOutfitAddon(self, lookType, addon) local addons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) - if addons == nil or addons == -1 then + if not addons or addons == -1 then return false end @@ -35,7 +35,7 @@ end function Player.getOutfitAddons(self, lookType) local outfitAddons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) - if outfitAddons == nil or outfitAddons == -1 then + if not outfitAddons or outfitAddons == -1 then return 0 end return outfitAddons @@ -43,15 +43,14 @@ end function Player.hasOutfit(self, lookType) local addons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) - return addons ~= nil and addons ~= -1 + return addons and addons ~= -1 end function Player.hasOutfitAddons(self, lookType, addons) local currentAddons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) - if currentAddons == nil or currentAddons == -1 then + if not currentAddons or currentAddons == -1 then return false end - return (currentAddons & addons) == addons end @@ -88,9 +87,8 @@ function Player.canWearOutfit(self, lookType, addons) return false end - if addons == nil or addons == 0 then + if not addons or addons == 0 then return true end - return self:getOutfitAddons(lookType) & addons == addons end diff --git a/data/scripts/systems/outfits/data/mounts.lua b/data/scripts/systems/outfits/data/mounts.lua index a4c34e97b6..ae2a023cd3 100644 --- a/data/scripts/systems/outfits/data/mounts.lua +++ b/data/scripts/systems/outfits/data/mounts.lua @@ -255,7 +255,6 @@ function Game.getMount(param) local lookType = tonumber(param) if lookType then return Game.getMountByLookType(lookType) - else - return Game.getMountByName(param) end + return Game.getMountByName(param) end diff --git a/data/scripts/systems/outfits/data/outfits.lua b/data/scripts/systems/outfits/data/outfits.lua index 41e400a015..6a891cb424 100644 --- a/data/scripts/systems/outfits/data/outfits.lua +++ b/data/scripts/systems/outfits/data/outfits.lua @@ -274,7 +274,6 @@ function Game.getOutfit(param, sex) local lookType = tonumber(param) if lookType then return Game.getOutfitByLookType(lookType) - else - return Game.getOutfitByName(param, sex) end + return Game.getOutfitByName(param, sex) end diff --git a/data/scripts/systems/outfits/events/change_zone.lua b/data/scripts/systems/outfits/events/change_zone.lua index 01f2873547..110db7b775 100644 --- a/data/scripts/systems/outfits/events/change_zone.lua +++ b/data/scripts/systems/outfits/events/change_zone.lua @@ -1,6 +1,6 @@ local event = Event() -function event.onCreatureChangeZone(self, fromZone, toZone) +event.onCreatureChangeZone = function(self, fromZone, toZone) if not self:isPlayer() then return end diff --git a/data/scripts/systems/outfits/network/set_outfit.lua b/data/scripts/systems/outfits/network/set_outfit.lua index dbaca1f4c4..f525e0bf42 100644 --- a/data/scripts/systems/outfits/network/set_outfit.lua +++ b/data/scripts/systems/outfits/network/set_outfit.lua @@ -43,6 +43,7 @@ function handler.onReceive(player, msg) player:setOutfit(outfit) player:setRandomizeMount(randomizeMount) + player:setCurrentMount(outfit.lookMount) elseif outfitType == 1 then -- try outfit from store window outfit.lookMount = 0 outfit.lookMountHead = msg:getByte() 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..1a0f51fb0f --- /dev/null +++ b/data/scripts/systems/outfits/network/toggle_mount.lua @@ -0,0 +1,12 @@ +local handler = PacketHandler(0xD4) + +function handler.onReceive(player, msg) + if not Outfits.AllowToggleMount then + return + end + + local mounted = msg:getBool() + player:toggleMount(mounted) +end + +handler:register() From 22e09e2927bf9c73d38aeceea4482c692f0ca1c5 Mon Sep 17 00:00:00 2001 From: ramon-bernardo Date: Fri, 26 Dec 2025 21:10:02 -0300 Subject: [PATCH 34/55] fix(mounts): current mount storage --- data/scripts/systems/outfits/network/set_outfit.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/data/scripts/systems/outfits/network/set_outfit.lua b/data/scripts/systems/outfits/network/set_outfit.lua index f525e0bf42..6419856560 100644 --- a/data/scripts/systems/outfits/network/set_outfit.lua +++ b/data/scripts/systems/outfits/network/set_outfit.lua @@ -37,13 +37,17 @@ function handler.onReceive(player, msg) return end - if outfit.lookMount ~= 0 and not player:canRideMount(outfit.lookMount) then - return + if outfit.lookMount ~= 0 then + if not player:canRideMount(outfit.lookMount) then + player:setCurrentMount(nil) + return + end + + player:setCurrentMount(outfit.lookMount) end player:setOutfit(outfit) player:setRandomizeMount(randomizeMount) - player:setCurrentMount(outfit.lookMount) elseif outfitType == 1 then -- try outfit from store window outfit.lookMount = 0 outfit.lookMountHead = msg:getByte() From cdff977670576671e85a424f1d2c73cfe9903da2 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 26 Dec 2025 23:43:21 -0300 Subject: [PATCH 35/55] refactor(events): remove onCreatureRemoved and add onPlayerLogout event handling --- data/scripts/systems/outfits/events/cleanup.lua | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/data/scripts/systems/outfits/events/cleanup.lua b/data/scripts/systems/outfits/events/cleanup.lua index 18f8b2b501..d325f4d830 100644 --- a/data/scripts/systems/outfits/events/cleanup.lua +++ b/data/scripts/systems/outfits/events/cleanup.lua @@ -1,10 +1,6 @@ local event = Event() -function event.onCreatureRemoved(self) - if not self:isPlayer() then - return - end - +function event.onPlayerLogout(self) self:setLastMountToggle(nil) self:setWasMounted(nil) return true From 77fd03b77901f68a61595af14dd32902528d2d7b Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 26 Dec 2025 23:52:17 -0300 Subject: [PATCH 36/55] refactor(mounts, outfits): revert bad changes --- data/scripts/systems/outfits/core/mounts.lua | 22 +++++++++---------- data/scripts/systems/outfits/core/outfits.lua | 13 ++++++----- .../systems/outfits/events/change_zone.lua | 2 +- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/data/scripts/systems/outfits/core/mounts.lua b/data/scripts/systems/outfits/core/mounts.lua index f1a8e3e1e8..890083b40b 100644 --- a/data/scripts/systems/outfits/core/mounts.lua +++ b/data/scripts/systems/outfits/core/mounts.lua @@ -33,7 +33,7 @@ end function Player.hasMount(self, mountId) local value = self:getStorageValue(PlayerStorageKeys.mountsBase + mountId) - return value and value ~= -1 + return value ~= nil and value ~= -1 end function Player.removeMount(self, mountId) @@ -57,26 +57,27 @@ end function Player.getCurrentMount(self) local value = self:getStorageValue(PlayerStorageKeys.currentMount) - if not value or value == -1 then + if value == nil or value == -1 then return nil end return value end function Player.setCurrentMount(self, mountId) - if not mountId then + 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 and randomizeMount ~= -1 + return randomizeMount ~= nil and randomizeMount ~= -1 end function Player.setRandomizeMount(self, randomize) @@ -99,6 +100,7 @@ function Player.canRideMount(self, mountId) if mount.premium and not self:isPremium() then return false end + return self:hasMount(mount.lookType) end @@ -107,7 +109,7 @@ function Player.isMounted(self) end function Player.mount(self, mount) - if not mount or not mount.lookType or not mount.speed then + if mount == nil or mount.lookType == nil or mount.speed == nil then return false end @@ -125,7 +127,7 @@ function Player.dismount(self) self:setOutfit(outfit) local mount = Game.getMountByLookType(lookMount) - if mount then + if mount ~= nil then self:changeSpeed(-mount.speed) end end @@ -151,10 +153,8 @@ end function Player.toggleMount(self, mounted) if not self:getGroup():getAccess() then local lastMountToggle = self:getLastMountToggle() - if lastMountToggle and lastMountToggle > 0 then - if os.mtime() - lastMountToggle < Outfits.ToggleMountCooldown and not self:getWasMounted() then - return false - end + if os.mtime() - lastMountToggle < Outfits.ToggleMountCooldown and not self:getWasMounted() then + return false end end diff --git a/data/scripts/systems/outfits/core/outfits.lua b/data/scripts/systems/outfits/core/outfits.lua index 30fefe6d20..9bef33eeaa 100644 --- a/data/scripts/systems/outfits/core/outfits.lua +++ b/data/scripts/systems/outfits/core/outfits.lua @@ -1,6 +1,6 @@ function Player.addOutfit(self, lookType) local addons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) - if addons and addons ~= -1 then + if addons ~= nil and addons ~= -1 then return true end return self:setStorageValue(PlayerStorageKeys.outfitsBase + lookType, 0) @@ -15,7 +15,7 @@ end function Player.addOutfitAddon(self, lookType, addon) local addons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) - if not addons or addons == -1 then + if addons == nil or addons == -1 then return false end @@ -35,7 +35,7 @@ end function Player.getOutfitAddons(self, lookType) local outfitAddons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) - if not outfitAddons or outfitAddons == -1 then + if outfitAddons == nil or outfitAddons == -1 then return 0 end return outfitAddons @@ -43,12 +43,12 @@ end function Player.hasOutfit(self, lookType) local addons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) - return addons and addons ~= -1 + return addons ~= nil and addons ~= -1 end function Player.hasOutfitAddons(self, lookType, addons) local currentAddons = self:getStorageValue(PlayerStorageKeys.outfitsBase + lookType) - if not currentAddons or currentAddons == -1 then + if currentAddons == nil or currentAddons == -1 then return false end return (currentAddons & addons) == addons @@ -87,8 +87,9 @@ function Player.canWearOutfit(self, lookType, addons) return false end - if not addons or addons == 0 then + if addons == nil or addons == 0 then return true end + return self:getOutfitAddons(lookType) & addons == addons end diff --git a/data/scripts/systems/outfits/events/change_zone.lua b/data/scripts/systems/outfits/events/change_zone.lua index 110db7b775..01f2873547 100644 --- a/data/scripts/systems/outfits/events/change_zone.lua +++ b/data/scripts/systems/outfits/events/change_zone.lua @@ -1,6 +1,6 @@ local event = Event() -event.onCreatureChangeZone = function(self, fromZone, toZone) +function event.onCreatureChangeZone(self, fromZone, toZone) if not self:isPlayer() then return end From c43e9b8336435d57da6c7297218e619d92816b46 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Sat, 27 Dec 2025 01:01:07 -0300 Subject: [PATCH 37/55] refactor(game): remove onRemoved event call from removeCreature function --- src/game.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/game.cpp b/src/game.cpp index 403c21fb21..d320595cc7 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -599,7 +599,6 @@ bool Game::removeCreature(const std::shared_ptr& creature, bool isLogo removeCreature(summon); } - tfs::events::creature::onRemoved(creature); return true; } From d9d11bf5bf375cccb4828ca5926ff1ee8d7ab5dc Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Sat, 27 Dec 2025 01:49:29 -0300 Subject: [PATCH 38/55] refactor(outfits, mounts): enhance command documentation and clarify functionality --- .../systems/outfits/commands/add_addon.lua | 2 + .../systems/outfits/commands/add_mount.lua | 2 + .../systems/outfits/commands/add_outfit.lua | 2 + .../systems/outfits/commands/remove_addon.lua | 2 + .../systems/outfits/commands/remove_mount.lua | 2 + .../outfits/commands/remove_outfit.lua | 2 + data/scripts/systems/outfits/config.lua | 18 ++- data/scripts/systems/outfits/core/mounts.lua | 30 ++++- data/scripts/systems/outfits/core/outfits.lua | 65 ++++++++-- data/scripts/systems/outfits/core/windows.lua | 111 ++++++++++++------ data/scripts/systems/outfits/data/mounts.lua | 24 +++- data/scripts/systems/outfits/data/outfits.lua | 26 +++- .../systems/outfits/events/change_zone.lua | 10 +- .../systems/outfits/events/cleanup.lua | 3 + .../outfits/network/request_outfit_window.lua | 3 + .../systems/outfits/network/set_outfit.lua | 22 ++++ .../systems/outfits/network/toggle_mount.lua | 7 +- 17 files changed, 266 insertions(+), 65 deletions(-) diff --git a/data/scripts/systems/outfits/commands/add_addon.lua b/data/scripts/systems/outfits/commands/add_addon.lua index 645c88b1b3..86be7683cc 100644 --- a/data/scripts/systems/outfits/commands/add_addon.lua +++ b/data/scripts/systems/outfits/commands/add_addon.lua @@ -1,3 +1,5 @@ +-- /addaddon , , +-- Adds addon 1 or 2 to an owned outfit for the target player. local talkaction = TalkAction("/addaddon") function talkaction.onSay(player, words, param) diff --git a/data/scripts/systems/outfits/commands/add_mount.lua b/data/scripts/systems/outfits/commands/add_mount.lua index 9cb0c7c8eb..152ff299fc 100644 --- a/data/scripts/systems/outfits/commands/add_mount.lua +++ b/data/scripts/systems/outfits/commands/add_mount.lua @@ -1,3 +1,5 @@ +-- /addmount , +-- Grants the mount to the target player if not already owned. local talkaction = TalkAction("/addmount") function talkaction.onSay(player, words, param) diff --git a/data/scripts/systems/outfits/commands/add_outfit.lua b/data/scripts/systems/outfits/commands/add_outfit.lua index ee6f97709d..10eaaa09ee 100644 --- a/data/scripts/systems/outfits/commands/add_outfit.lua +++ b/data/scripts/systems/outfits/commands/add_outfit.lua @@ -1,3 +1,5 @@ +-- /addoutfit , +-- Grants the outfit to the target player if not already owned. local talkaction = TalkAction("/addoutfit") function talkaction.onSay(player, words, param) diff --git a/data/scripts/systems/outfits/commands/remove_addon.lua b/data/scripts/systems/outfits/commands/remove_addon.lua index 250f1ea75e..edcdbc767c 100644 --- a/data/scripts/systems/outfits/commands/remove_addon.lua +++ b/data/scripts/systems/outfits/commands/remove_addon.lua @@ -1,3 +1,5 @@ +-- /removeaddon , , +-- Removes addon 1 or 2 from an owned outfit for the target player. local talkaction = TalkAction("/removeaddon") function talkaction.onSay(player, words, param) diff --git a/data/scripts/systems/outfits/commands/remove_mount.lua b/data/scripts/systems/outfits/commands/remove_mount.lua index f6429e9b47..35a3d2abd8 100644 --- a/data/scripts/systems/outfits/commands/remove_mount.lua +++ b/data/scripts/systems/outfits/commands/remove_mount.lua @@ -1,3 +1,5 @@ +-- /removemount , +-- Removes the mount from the target player; dismounts if currently used. local talkaction = TalkAction("/removemount") function talkaction.onSay(player, words, param) diff --git a/data/scripts/systems/outfits/commands/remove_outfit.lua b/data/scripts/systems/outfits/commands/remove_outfit.lua index b48a454e03..0f862283aa 100644 --- a/data/scripts/systems/outfits/commands/remove_outfit.lua +++ b/data/scripts/systems/outfits/commands/remove_outfit.lua @@ -1,3 +1,5 @@ +-- /removeoutfit , +-- Removes the owned outfit from the target player. local talkaction = TalkAction("/removeoutfit") function talkaction.onSay(player, words, param) diff --git a/data/scripts/systems/outfits/config.lua b/data/scripts/systems/outfits/config.lua index 032dd34ed1..d4cebc5b21 100644 --- a/data/scripts/systems/outfits/config.lua +++ b/data/scripts/systems/outfits/config.lua @@ -1,5 +1,21 @@ +-- 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, - ToggleMountCooldown = 3000, -- in milliseconds + + -- Cooldown (ms) enforced for manual mount toggles (Ctrl+R). Forced zone toggles may bypass this. + ToggleMountCooldown = 3000, } diff --git a/data/scripts/systems/outfits/core/mounts.lua b/data/scripts/systems/outfits/core/mounts.lua index 890083b40b..37fa3b5d1e 100644 --- a/data/scripts/systems/outfits/core/mounts.lua +++ b/data/scripts/systems/outfits/core/mounts.lua @@ -1,3 +1,14 @@ +-- 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) @@ -25,10 +36,10 @@ function Player.addMount(self, mountId) end function Player.addAllMounts(self) - local mounts = Game.getMounts() - for _, mount in ipairs(mounts) do - self:addMount(mount.lookType) - end + local mounts = Game.getMounts() + for _, mount in ipairs(mounts) do + self:addMount(mount.lookType) + end end function Player.hasMount(self, mountId) @@ -55,6 +66,7 @@ function Player.removeAllMounts(self) 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 @@ -63,6 +75,7 @@ function Player.getCurrentMount(self) 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) @@ -87,6 +100,7 @@ function Player.setRandomizeMount(self, randomize) 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 @@ -108,6 +122,7 @@ 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 @@ -120,6 +135,7 @@ function Player.mount(self, mount) 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 @@ -150,6 +166,11 @@ local function getRandomMount(player) 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, protection-zone restriction, premium/ownership rules, and CONDITION_OUTFIT. function Player.toggleMount(self, mounted) if not self:getGroup():getAccess() then local lastMountToggle = self:getLastMountToggle() @@ -212,6 +233,7 @@ function Player.toggleMount(self, mounted) 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 diff --git a/data/scripts/systems/outfits/core/outfits.lua b/data/scripts/systems/outfits/core/outfits.lua index 9bef33eeaa..06744f8f21 100644 --- a/data/scripts/systems/outfits/core/outfits.lua +++ b/data/scripts/systems/outfits/core/outfits.lua @@ -1,3 +1,17 @@ +-- 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 @@ -6,6 +20,9 @@ function Player.addOutfit(self, lookType) 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 @@ -13,6 +30,10 @@ function Player.addAllOutfits(self) 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 @@ -23,16 +44,22 @@ function Player.addOutfitAddon(self, lookType, addon) 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 + 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 @@ -41,11 +68,16 @@ function Player.getOutfitAddons(self, lookType) 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 @@ -54,21 +86,40 @@ function Player.hasOutfitAddons(self, lookType, addons) 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 diff --git a/data/scripts/systems/outfits/core/windows.lua b/data/scripts/systems/outfits/core/windows.lua index c82fc1aae7..6f452caf6a 100644 --- a/data/scripts/systems/outfits/core/windows.lua +++ b/data/scripts/systems/outfits/core/windows.lua @@ -1,3 +1,9 @@ +-- 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) @@ -11,7 +17,7 @@ local function getAvailableOutfits(player) local isAccessPlayer = player:getGroup():getAccess() if isAccessPlayer then - -- add GM outfit for staff members + -- Add GM outfit for staff members. local gamemaster = { name = "Gamemaster", lookType = 75, addons = 0 } table.insert(availableOutfits, gamemaster) end @@ -47,6 +53,19 @@ 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 @@ -59,6 +78,7 @@ function Player.sendOutfitWindow(self) 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) @@ -106,16 +126,31 @@ function Player.sendOutfitWindow(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 podium = item:getPodium() + if not podium then + return + end - local tile = item:getTile() - if not tile then - return - end + local tile = item:getTile() + if not tile then + return + end local it = ItemType(item:getId()) if not it then @@ -123,35 +158,35 @@ function Player.sendPodiumWindow(self, item) 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 #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 diff --git a/data/scripts/systems/outfits/data/mounts.lua b/data/scripts/systems/outfits/data/mounts.lua index ae2a023cd3..3439c71473 100644 --- a/data/scripts/systems/outfits/data/mounts.lua +++ b/data/scripts/systems/outfits/data/mounts.lua @@ -1,3 +1,15 @@ +-- 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 }, @@ -238,17 +250,17 @@ function Game.getMountByLookType(lookType) end function Game.getMountByName(name) - for lookType, mount in pairs(mounts) do - if mount.name:lower() == name:lower() then - return { + 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 + end + return nil end function Game.getMount(param) diff --git a/data/scripts/systems/outfits/data/outfits.lua b/data/scripts/systems/outfits/data/outfits.lua index 6a891cb424..e11f6a3021 100644 --- a/data/scripts/systems/outfits/data/outfits.lua +++ b/data/scripts/systems/outfits/data/outfits.lua @@ -1,3 +1,17 @@ +-- 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 }, @@ -256,18 +270,18 @@ function Game.getOutfitByLookType(lookType) end function Game.getOutfitByName(name, sex) - for lookType, outfit in pairs(outfits) do - if outfit.name:lower() == name:lower() and outfit.sex == sex then - return { + 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 + end + return nil end function Game.getOutfit(param, sex) diff --git a/data/scripts/systems/outfits/events/change_zone.lua b/data/scripts/systems/outfits/events/change_zone.lua index 01f2873547..39161f3af8 100644 --- a/data/scripts/systems/outfits/events/change_zone.lua +++ b/data/scripts/systems/outfits/events/change_zone.lua @@ -1,3 +1,8 @@ +-- 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) @@ -9,8 +14,9 @@ function event.onCreatureChangeZone(self, fromZone, toZone) self:setWasMounted(true) self:toggleMount(false) elseif fromZone == ZONE_PROTECTION and self:getWasMounted() then - self:toggleMount(true) - self:setWasMounted(false) + if self:toggleMount(true) then + self:setWasMounted(false) + end end end diff --git a/data/scripts/systems/outfits/events/cleanup.lua b/data/scripts/systems/outfits/events/cleanup.lua index d325f4d830..10e0d9a29c 100644 --- a/data/scripts/systems/outfits/events/cleanup.lua +++ b/data/scripts/systems/outfits/events/cleanup.lua @@ -1,3 +1,6 @@ +-- 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) diff --git a/data/scripts/systems/outfits/network/request_outfit_window.lua b/data/scripts/systems/outfits/network/request_outfit_window.lua index c46b62d967..713baf5545 100644 --- a/data/scripts/systems/outfits/network/request_outfit_window.lua +++ b/data/scripts/systems/outfits/network/request_outfit_window.lua @@ -1,5 +1,8 @@ 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 diff --git a/data/scripts/systems/outfits/network/set_outfit.lua b/data/scripts/systems/outfits/network/set_outfit.lua index 6419856560..d7e0a5281d 100644 --- a/data/scripts/systems/outfits/network/set_outfit.lua +++ b/data/scripts/systems/outfits/network/set_outfit.lua @@ -1,5 +1,27 @@ 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 diff --git a/data/scripts/systems/outfits/network/toggle_mount.lua b/data/scripts/systems/outfits/network/toggle_mount.lua index 1a0f51fb0f..f0d2df4661 100644 --- a/data/scripts/systems/outfits/network/toggle_mount.lua +++ b/data/scripts/systems/outfits/network/toggle_mount.lua @@ -1,10 +1,15 @@ 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 From 935189604abc7c5b1204bdb8d79606f40148519c Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 2 Jan 2026 18:12:34 -0300 Subject: [PATCH 39/55] refactor(outfits): streamline outfit and mount data handling in network messages --- data/lib/core/network_message.lua | 10 ---------- data/scripts/systems/outfits/core/windows.lua | 12 ++++++------ 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/data/lib/core/network_message.lua b/data/lib/core/network_message.lua index d5a7b100de..5f5d074425 100644 --- a/data/lib/core/network_message.lua +++ b/data/lib/core/network_message.lua @@ -16,7 +16,6 @@ function NetworkMessage:addItemId(itemId) end function NetworkMessage:addOutfit(outfit) - -- outfit self:addU16(outfit.lookType) if outfit.lookType ~= 0 then self:addByte(outfit.lookHead) @@ -27,13 +26,4 @@ function NetworkMessage:addOutfit(outfit) else self:addItemId(outfit.lookTypeEx) end - - -- mount - self:addU16(outfit.lookMount) - if outfit.lookMount ~= 0 then - self:addByte(outfit.lookMountHead) - self:addByte(outfit.lookMountBody) - self:addByte(outfit.lookMountLegs) - self:addByte(outfit.lookMountFeet) - end end diff --git a/data/scripts/systems/outfits/core/windows.lua b/data/scripts/systems/outfits/core/windows.lua index 6f452caf6a..4e102c9721 100644 --- a/data/scripts/systems/outfits/core/windows.lua +++ b/data/scripts/systems/outfits/core/windows.lua @@ -88,12 +88,12 @@ function Player.sendOutfitWindow(self) msg:addByte(0xC8) msg:addOutfit(currentOutfit) - if currentOutfit.lookMount == 0 then - msg:addByte(currentOutfit.lookMountHead) - msg:addByte(currentOutfit.lookMountBody) - msg:addByte(currentOutfit.lookMountLegs) - msg:addByte(currentOutfit.lookMountFeet) - end + + 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 From 2ab053bff50f8b55b81e073cb48e632b2c2a162b Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 2 Jan 2026 18:24:24 -0300 Subject: [PATCH 40/55] refactor(migrations): add migration for currentmount and randomizemount from players table --- data/migrations/37.lua | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/data/migrations/37.lua b/data/migrations/37.lua index 81818eb364..3ae20f4441 100644 --- a/data/migrations/37.lua +++ b/data/migrations/37.lua @@ -28,6 +28,25 @@ function onUpdateDatabase() result.free(resultId) end + -- Migrate currentmount and randomizemount from players table + local resultId = db.storeQuery("SELECT `id`, `currentmount`, `randomizemount` FROM `players` WHERE `currentmount` IS NOT NULL OR `randomizemount` IS NOT NULL") + if resultId then + repeat + local playerId = result.getNumber(resultId, "id") + local currentMount = result.getNumber(resultId, "currentmount") + local randomizeMount = result.getNumber(resultId, "randomizemount") + + if currentMount ~= nil and currentMount > 0 then + table.insert(rows, {playerId = playerId, key = PlayerStorageKeys.currentMount, value = currentMount}) + end + + if randomizeMount ~= nil and randomizeMount > 0 then + table.insert(rows, {playerId = playerId, key = PlayerStorageKeys.randomizeMount, value = randomizeMount}) + end + until not result.next(resultId) + result.free(resultId) + end + if #rows > 0 then local query = "INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES " for i, row in ipairs(rows) do From b0c238a7a0ddccdba847f9efad0975f4d9c72fd9 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 2 Jan 2026 18:27:46 -0300 Subject: [PATCH 41/55] refactor(mounts): improve toggleMount logic to enforce cooldown only when necessary --- data/scripts/systems/outfits/core/mounts.lua | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/data/scripts/systems/outfits/core/mounts.lua b/data/scripts/systems/outfits/core/mounts.lua index 37fa3b5d1e..7af0b88e0e 100644 --- a/data/scripts/systems/outfits/core/mounts.lua +++ b/data/scripts/systems/outfits/core/mounts.lua @@ -170,16 +170,17 @@ end -- 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, protection-zone restriction, premium/ownership rules, and CONDITION_OUTFIT. +-- Enforces cooldown when mounting, protection-zone restriction, premium/ownership rules, and CONDITION_OUTFIT. function Player.toggleMount(self, mounted) - if not self:getGroup():getAccess() then - local lastMountToggle = self:getLastMountToggle() - if os.mtime() - lastMountToggle < Outfits.ToggleMountCooldown and not self:getWasMounted() then - return false - end - end 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 From da5ab24376c5cc0491745fbcaacaa05783eb2b65 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 2 Jan 2026 19:01:38 -0300 Subject: [PATCH 42/55] refactor(migrations): add batch insertion and transaction handling in database update --- data/migrations/37.lua | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/data/migrations/37.lua b/data/migrations/37.lua index 3ae20f4441..4447e6927f 100644 --- a/data/migrations/37.lua +++ b/data/migrations/37.lua @@ -1,6 +1,12 @@ +local BATCH_SIZE = 10000 + function onUpdateDatabase() print("> Updating database to version 38 (revert outfits/mounts to storages)") + if not db.query("START TRANSACTION") then + return false + end + local rows = {} local resultId = db.storeQuery("SELECT `player_id`, `outfit_id`, `addons` FROM `player_outfits`") @@ -48,26 +54,39 @@ function onUpdateDatabase() end if #rows > 0 then - local query = "INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES " - for i, row in ipairs(rows) do - query = query .. string.format("(%d, %d, %d)", row.playerId, row.key, row.value) - if i < #rows then - query = query .. "," + for start = 1, #rows, BATCH_SIZE do + local end_ = math.min(start + BATCH_SIZE - 1, #rows) + + local query = "INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES " + for i = start, end_ do + local row = rows[i] + query = query .. string.format("(%d, %d, %d)", row.playerId, row.key, row.value) + if i < end_ then + query = query .. "," + end end end + if not db.query(query) then + db.query("ROLLBACK") return false end end if not db.query("DROP TABLE IF EXISTS `player_outfits`") then + db.query("ROLLBACK") return false end + if not db.query("DROP TABLE IF EXISTS `player_mounts`") then + db.query("ROLLBACK") return false end + if not db.query("ALTER TABLE `players` DROP COLUMN `currentmount`, DROP COLUMN `randomizemount`") then + db.query("ROLLBACK") return false end - return true + + return db.query("COMMIT") end From 8df1e87fed89456316acb3358a60997adeabf397 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Fri, 2 Jan 2026 19:52:08 -0300 Subject: [PATCH 43/55] refactor(migrations): fix query execution logic in database update function --- data/migrations/37.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/migrations/37.lua b/data/migrations/37.lua index 4447e6927f..e17516bdf6 100644 --- a/data/migrations/37.lua +++ b/data/migrations/37.lua @@ -42,11 +42,11 @@ function onUpdateDatabase() local currentMount = result.getNumber(resultId, "currentmount") local randomizeMount = result.getNumber(resultId, "randomizemount") - if currentMount ~= nil and currentMount > 0 then + if currentMount > 0 then table.insert(rows, {playerId = playerId, key = PlayerStorageKeys.currentMount, value = currentMount}) end - if randomizeMount ~= nil and randomizeMount > 0 then + if randomizeMount > 0 then table.insert(rows, {playerId = playerId, key = PlayerStorageKeys.randomizeMount, value = randomizeMount}) end until not result.next(resultId) @@ -65,11 +65,11 @@ function onUpdateDatabase() query = query .. "," end end - end - if not db.query(query) then - db.query("ROLLBACK") - return false + if not db.query(query) then + db.query("ROLLBACK") + return false + end end end From 1a1fef0249ccc892027d25910c1d04d64fd1504d Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Sat, 3 Jan 2026 03:00:44 -0300 Subject: [PATCH 44/55] refactor(migrations): fix shadowing in database update logic for outfits and mounts migration --- data/migrations/37.lua | 199 +++++++++++++++++++++++------------------ 1 file changed, 111 insertions(+), 88 deletions(-) diff --git a/data/migrations/37.lua b/data/migrations/37.lua index e17516bdf6..4ecef0b9c2 100644 --- a/data/migrations/37.lua +++ b/data/migrations/37.lua @@ -1,92 +1,115 @@ local BATCH_SIZE = 10000 function onUpdateDatabase() - print("> Updating database to version 38 (revert outfits/mounts to storages)") - - if not db.query("START TRANSACTION") then - return false - end - - local rows = {} - - 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 = PlayerStorageKeys.outfitsBase + outfitId - table.insert(rows, {playerId = playerId, key = storageKey, value = addons}) - until not result.next(resultId) - result.free(resultId) - end - - 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 = PlayerStorageKeys.mountsBase + mountId - table.insert(rows, {playerId = playerId, key = storageKey, value = 1}) - until not result.next(resultId) - result.free(resultId) - end - - -- Migrate currentmount and randomizemount from players table - local resultId = db.storeQuery("SELECT `id`, `currentmount`, `randomizemount` FROM `players` WHERE `currentmount` IS NOT NULL OR `randomizemount` IS NOT NULL") - 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 - table.insert(rows, {playerId = playerId, key = PlayerStorageKeys.currentMount, value = currentMount}) - end - - if randomizeMount > 0 then - table.insert(rows, {playerId = playerId, key = PlayerStorageKeys.randomizeMount, value = randomizeMount}) - end - until not result.next(resultId) - result.free(resultId) - end - - if #rows > 0 then - for start = 1, #rows, BATCH_SIZE do - local end_ = math.min(start + BATCH_SIZE - 1, #rows) - - local query = "INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES " - for i = start, end_ do - local row = rows[i] - query = query .. string.format("(%d, %d, %d)", row.playerId, row.key, row.value) - if i < end_ then - query = query .. "," - end - end - - if not db.query(query) then - db.query("ROLLBACK") - return false - end - end - end - - if not db.query("DROP TABLE IF EXISTS `player_outfits`") then - db.query("ROLLBACK") - return false - end - - if not db.query("DROP TABLE IF EXISTS `player_mounts`") then - db.query("ROLLBACK") - return false - end - - if not db.query("ALTER TABLE `players` DROP COLUMN `currentmount`, DROP COLUMN `randomizemount`") then - db.query("ROLLBACK") - return false - end - - return db.query("COMMIT") + print("> Updating database to version 38 (revert outfits/mounts to storages)") + + if not db.query("START TRANSACTION") then + return false + end + + local rows = {} + + 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 = PlayerStorageKeys.outfitsBase + outfitId + table.insert(rows, { + playerId = playerId, + key = storageKey, + value = 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 = PlayerStorageKeys.mountsBase + mountId + table.insert(rows, { + playerId = playerId, + key = storageKey, + value = 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` IS NOT NULL OR `randomizemount` IS NOT NULL") + 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 + table.insert(rows, { + playerId = playerId, + key = PlayerStorageKeys.currentMount, + value = currentMount + }) + end + + if randomizeMount > 0 then + table.insert(rows, { + playerId = playerId, + key = PlayerStorageKeys.randomizeMount, + value = randomizeMount + }) + end + until not result.next(resultId) + result.free(resultId) + end + end + + if #rows > 0 then + for start = 1, #rows, BATCH_SIZE do + local end_ = math.min(start + BATCH_SIZE - 1, #rows) + + local query = "INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES " + for i = start, end_ do + local row = rows[i] + query = query .. string.format("(%d, %d, %d)", row.playerId, row.key, row.value) + if i < end_ then + query = query .. "," + end + end + + if not db.query(query) then + db.query("ROLLBACK") + return false + end + end + end + + if not db.query("DROP TABLE IF EXISTS `player_outfits`") then + db.query("ROLLBACK") + return false + end + + if not db.query("DROP TABLE IF EXISTS `player_mounts`") then + db.query("ROLLBACK") + return false + end + + if not db.query("ALTER TABLE `players` DROP COLUMN `currentmount`, DROP COLUMN `randomizemount`") then + db.query("ROLLBACK") + return false + end + + return db.query("COMMIT") end From f7be8256ed9ca2a327c633128387948465d0d464 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Sat, 3 Jan 2026 03:07:01 -0300 Subject: [PATCH 45/55] refactor(migrations): enhance transaction handling and batch insertion in database update --- data/migrations/37.lua | 18 ++++++------------ data/scripts/systems/outfits/core/mounts.lua | 2 -- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/data/migrations/37.lua b/data/migrations/37.lua index 4ecef0b9c2..339b5bb960 100644 --- a/data/migrations/37.lua +++ b/data/migrations/37.lua @@ -3,7 +3,8 @@ local BATCH_SIZE = 10000 function onUpdateDatabase() print("> Updating database to version 38 (revert outfits/mounts to storages)") - if not db.query("START TRANSACTION") then + local tx = DBTransaction() + if not tx.begin() then return false end @@ -80,36 +81,29 @@ function onUpdateDatabase() for start = 1, #rows, BATCH_SIZE do local end_ = math.min(start + BATCH_SIZE - 1, #rows) - local query = "INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES " + local query = DBInsert("INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES ") for i = start, end_ do local row = rows[i] - query = query .. string.format("(%d, %d, %d)", row.playerId, row.key, row.value) - if i < end_ then - query = query .. "," - end + query:addRow(string.format("%d, %d, %d", row.playerId, row.key, row.value)) end - if not db.query(query) then - db.query("ROLLBACK") + if not query:execute() then return false end end end if not db.query("DROP TABLE IF EXISTS `player_outfits`") then - db.query("ROLLBACK") return false end if not db.query("DROP TABLE IF EXISTS `player_mounts`") then - db.query("ROLLBACK") return false end if not db.query("ALTER TABLE `players` DROP COLUMN `currentmount`, DROP COLUMN `randomizemount`") then - db.query("ROLLBACK") return false end - return db.query("COMMIT") + return tx.commit() end diff --git a/data/scripts/systems/outfits/core/mounts.lua b/data/scripts/systems/outfits/core/mounts.lua index 7af0b88e0e..8b37e0fe55 100644 --- a/data/scripts/systems/outfits/core/mounts.lua +++ b/data/scripts/systems/outfits/core/mounts.lua @@ -8,7 +8,6 @@ -- 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) @@ -172,7 +171,6 @@ end -- - 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() From d6dc2cd856288012f61a1ff29d1ecebc962bda28 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Sat, 3 Jan 2026 03:11:18 -0300 Subject: [PATCH 46/55] refactor(commands): enhance feedback for outfit and mount management commands --- .../systems/outfits/commands/add_addon.lua | 83 +++++++++++-------- .../systems/outfits/commands/add_mount.lua | 60 ++++++++------ .../systems/outfits/commands/add_outfit.lua | 60 ++++++++------ .../systems/outfits/commands/remove_addon.lua | 83 +++++++++++-------- .../systems/outfits/commands/remove_mount.lua | 60 ++++++++------ .../outfits/commands/remove_outfit.lua | 60 ++++++++------ data/scripts/systems/outfits/config.lua | 3 + 7 files changed, 243 insertions(+), 166 deletions(-) diff --git a/data/scripts/systems/outfits/commands/add_addon.lua b/data/scripts/systems/outfits/commands/add_addon.lua index 86be7683cc..4e22bcab46 100644 --- a/data/scripts/systems/outfits/commands/add_addon.lua +++ b/data/scripts/systems/outfits/commands/add_addon.lua @@ -3,41 +3,54 @@ 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 - - return target:addOutfitAddon(outfit.lookType, addon) + 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(" ") diff --git a/data/scripts/systems/outfits/commands/add_mount.lua b/data/scripts/systems/outfits/commands/add_mount.lua index 152ff299fc..0f8f0ee645 100644 --- a/data/scripts/systems/outfits/commands/add_mount.lua +++ b/data/scripts/systems/outfits/commands/add_mount.lua @@ -3,30 +3,42 @@ 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 - - return target:addMount(mount.lookType) + 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(" ") diff --git a/data/scripts/systems/outfits/commands/add_outfit.lua b/data/scripts/systems/outfits/commands/add_outfit.lua index 10eaaa09ee..46f4c765fc 100644 --- a/data/scripts/systems/outfits/commands/add_outfit.lua +++ b/data/scripts/systems/outfits/commands/add_outfit.lua @@ -3,30 +3,42 @@ 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 - - return target:addOutfit(outfit.lookType) + 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(" ") diff --git a/data/scripts/systems/outfits/commands/remove_addon.lua b/data/scripts/systems/outfits/commands/remove_addon.lua index edcdbc767c..84354c6a51 100644 --- a/data/scripts/systems/outfits/commands/remove_addon.lua +++ b/data/scripts/systems/outfits/commands/remove_addon.lua @@ -3,41 +3,54 @@ 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 - - return target:removeOutfitAddon(outfit.lookType, addon) + 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(" ") diff --git a/data/scripts/systems/outfits/commands/remove_mount.lua b/data/scripts/systems/outfits/commands/remove_mount.lua index 35a3d2abd8..bb58fc1a2c 100644 --- a/data/scripts/systems/outfits/commands/remove_mount.lua +++ b/data/scripts/systems/outfits/commands/remove_mount.lua @@ -3,30 +3,42 @@ 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 - - return target:removeMount(mount.lookType) + 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(" ") diff --git a/data/scripts/systems/outfits/commands/remove_outfit.lua b/data/scripts/systems/outfits/commands/remove_outfit.lua index 0f862283aa..dce2f8c77f 100644 --- a/data/scripts/systems/outfits/commands/remove_outfit.lua +++ b/data/scripts/systems/outfits/commands/remove_outfit.lua @@ -3,30 +3,42 @@ 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 - - return target:removeOutfit(outfit.lookType) + 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(" ") diff --git a/data/scripts/systems/outfits/config.lua b/data/scripts/systems/outfits/config.lua index d4cebc5b21..e8ae3abacc 100644 --- a/data/scripts/systems/outfits/config.lua +++ b/data/scripts/systems/outfits/config.lua @@ -18,4 +18,7 @@ Outfits = { -- 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 } From 86f0450e4f335957cc6374985a15de5f45517fd4 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Sat, 3 Jan 2026 03:16:34 -0300 Subject: [PATCH 47/55] refactor(outfits): standardize indentation and formatting in outfit handling scripts --- data/scripts/systems/outfits/core/outfits.lua | 1 - data/scripts/systems/outfits/core/windows.lua | 404 ++-- data/scripts/systems/outfits/data/mounts.lua | 1326 +++++++++--- data/scripts/systems/outfits/data/outfits.lua | 1844 ++++++++++++++--- .../outfits/network/request_outfit_window.lua | 8 +- .../systems/outfits/network/set_outfit.lua | 204 +- .../systems/outfits/network/toggle_mount.lua | 10 +- 7 files changed, 2984 insertions(+), 813 deletions(-) diff --git a/data/scripts/systems/outfits/core/outfits.lua b/data/scripts/systems/outfits/core/outfits.lua index 06744f8f21..e1a9472a18 100644 --- a/data/scripts/systems/outfits/core/outfits.lua +++ b/data/scripts/systems/outfits/core/outfits.lua @@ -9,7 +9,6 @@ -- - 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) diff --git a/data/scripts/systems/outfits/core/windows.lua b/data/scripts/systems/outfits/core/windows.lua index 4e102c9721..da37cccd80 100644 --- a/data/scripts/systems/outfits/core/windows.lua +++ b/data/scripts/systems/outfits/core/windows.lua @@ -3,54 +3,68 @@ -- 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 + if not outfit.unlocked then + return player:hasOutfit(outfit.lookType) + end - return not outfit.premium or player:isPremium() + 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 + 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 + 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 {} + return {} end -- player:sendOutfitWindow() @@ -67,63 +81,63 @@ end -- - 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() + 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) @@ -142,105 +156,105 @@ end -- - 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() + 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 index 3439c71473..7e3a1f5101 100644 --- a/data/scripts/systems/outfits/data/mounts.lua +++ b/data/scripts/systems/outfits/data/mounts.lua @@ -11,262 +11,1098 @@ -- - 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 }, + [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.getMounts() - local result = {} - for lookType, mount in pairs(mounts) do - table.insert(result, { - lookType = lookType, - name = mount.name, - speed = mount.speed, - premium = mount.premium, - }) - end - return result + local result = {} + for lookType, mount in pairs(mounts) do + table.insert(result, { + lookType = lookType, + name = mount.name, + speed = mount.speed, + premium = mount.premium + }) + end + return result end function Game.getMountByLookType(lookType) - local mount = mounts[lookType] - if not mount then - return nil - end + local mount = mounts[lookType] + if not mount then + return nil + end - return { - lookType = lookType, - name = mount.name, - speed = mount.speed, - premium = mount.premium, - } + 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 + 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) + local lookType = tonumber(param) + if lookType then + return Game.getMountByLookType(lookType) + end + return Game.getMountByName(param) end diff --git a/data/scripts/systems/outfits/data/outfits.lua b/data/scripts/systems/outfits/data/outfits.lua index e11f6a3021..c8b4396043 100644 --- a/data/scripts/systems/outfits/data/outfits.lua +++ b/data/scripts/systems/outfits/data/outfits.lua @@ -14,280 +14,1600 @@ -- - 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 }, + [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 }, + -- 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.getOutfits(sex) - local result = {} - for lookType, outfit in pairs(outfits) do - if outfit.sex == sex and outfit.enabled then - table.insert(result, { - lookType = lookType, - name = outfit.name, - sex = outfit.sex, - premium = outfit.premium, - unlocked = outfit.unlocked, - }) - end - end - return result + local result = {} + for lookType, outfit in pairs(outfits) do + if outfit.sex == sex and outfit.enabled then + table.insert(result, { + lookType = lookType, + name = outfit.name, + sex = outfit.sex, + premium = outfit.premium, + unlocked = outfit.unlocked + }) + end + end + return result end function Game.getOutfitByLookType(lookType) - local outfit = outfits[lookType] - if not outfit then - return nil - end + 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, - } + return { + lookType = lookType, + name = outfit.name, + sex = outfit.sex, + premium = outfit.premium, + unlocked = outfit.unlocked + } end function Game.getOutfitByName(name, sex) - 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 + 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) + local lookType = tonumber(param) + if lookType then + return Game.getOutfitByLookType(lookType) + end + return Game.getOutfitByName(param, sex) end diff --git a/data/scripts/systems/outfits/network/request_outfit_window.lua b/data/scripts/systems/outfits/network/request_outfit_window.lua index 713baf5545..8f3b9f700c 100644 --- a/data/scripts/systems/outfits/network/request_outfit_window.lua +++ b/data/scripts/systems/outfits/network/request_outfit_window.lua @@ -4,11 +4,11 @@ local handler = PacketHandler(0xD2) -- 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 + if not Outfits.AllowChangeOutfit then + return + end - player:sendOutfitWindow() + 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 index d7e0a5281d..0e645921fc 100644 --- a/data/scripts/systems/outfits/network/set_outfit.lua +++ b/data/scripts/systems/outfits/network/set_outfit.lua @@ -23,107 +23,109 @@ local handler = PacketHandler(0xD3) -- - 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) - 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() - - -- open store? - 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 - - local itemPosition = item:getPosition() - if itemPosition.stackpos ~= stackpos 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 + 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() + + -- open store? + 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 + + local itemPosition = item:getPosition() + if itemPosition.stackpos ~= stackpos 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 index f0d2df4661..393da585a3 100644 --- a/data/scripts/systems/outfits/network/toggle_mount.lua +++ b/data/scripts/systems/outfits/network/toggle_mount.lua @@ -6,12 +6,12 @@ local handler = PacketHandler(0xD4) -- - 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 + if not Outfits.AllowToggleMount then + return + end - local mounted = msg:getBool() - player:toggleMount(mounted) + local mounted = msg:getBool() + player:toggleMount(mounted) end handler:register() From 2714970524a2ccdd92d380a09a4d0a8e9188de53 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Sat, 3 Jan 2026 03:28:06 -0300 Subject: [PATCH 48/55] refactor(outfits, mounts): optimize outfit and mount retrieval with caching --- data/scripts/systems/outfits/data/mounts.lua | 30 +++++++------- data/scripts/systems/outfits/data/outfits.lua | 39 +++++++++++-------- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/data/scripts/systems/outfits/data/mounts.lua b/data/scripts/systems/outfits/data/mounts.lua index 7e3a1f5101..9c8c83154a 100644 --- a/data/scripts/systems/outfits/data/mounts.lua +++ b/data/scripts/systems/outfits/data/mounts.lua @@ -1058,19 +1058,6 @@ local mounts = { } } -function Game.getMounts() - local result = {} - for lookType, mount in pairs(mounts) do - table.insert(result, { - lookType = lookType, - name = mount.name, - speed = mount.speed, - premium = mount.premium - }) - end - return result -end - function Game.getMountByLookType(lookType) local mount = mounts[lookType] if not mount then @@ -1106,3 +1093,20 @@ function Game.getMount(param) 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 index c8b4396043..b9c340962e 100644 --- a/data/scripts/systems/outfits/data/outfits.lua +++ b/data/scripts/systems/outfits/data/outfits.lua @@ -1558,22 +1558,6 @@ local outfits = { } } -function Game.getOutfits(sex) - local result = {} - for lookType, outfit in pairs(outfits) do - if outfit.sex == sex and outfit.enabled then - table.insert(result, { - lookType = lookType, - name = outfit.name, - sex = outfit.sex, - premium = outfit.premium, - unlocked = outfit.unlocked - }) - end - end - return result -end - function Game.getOutfitByLookType(lookType) local outfit = outfits[lookType] if not outfit then @@ -1611,3 +1595,26 @@ function Game.getOutfit(param, sex) 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 From 7065921869b35ec961b7ab6846247bf38ecdedb9 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Sat, 3 Jan 2026 03:29:20 -0300 Subject: [PATCH 49/55] refactor(outfits): add TODO for implementing store outfit preview window --- data/scripts/systems/outfits/network/set_outfit.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/scripts/systems/outfits/network/set_outfit.lua b/data/scripts/systems/outfits/network/set_outfit.lua index 0e645921fc..23c03e0612 100644 --- a/data/scripts/systems/outfits/network/set_outfit.lua +++ b/data/scripts/systems/outfits/network/set_outfit.lua @@ -79,7 +79,7 @@ function handler.onReceive(player, msg) outfit.lookMountLegs = msg:getByte() outfit.lookMountFeet = msg:getByte() - -- open store? + -- TODO: Implement store outfit preview window elseif outfitType == 2 then -- set podium outfit local position = msg:getPosition() local clientId = msg:getU16() From cf87b0d5d3b248a0a50706507d0146f2011b817a Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Sat, 3 Jan 2026 12:12:21 -0300 Subject: [PATCH 50/55] refactor(outfits): improve item position verification in outfit handling --- data/scripts/systems/outfits/network/set_outfit.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data/scripts/systems/outfits/network/set_outfit.lua b/data/scripts/systems/outfits/network/set_outfit.lua index 23c03e0612..8ed7ca02ff 100644 --- a/data/scripts/systems/outfits/network/set_outfit.lua +++ b/data/scripts/systems/outfits/network/set_outfit.lua @@ -112,8 +112,9 @@ function handler.onReceive(player, msg) return end - local itemPosition = item:getPosition() - if itemPosition.stackpos ~= stackpos then + -- Verify item is at expected stack position + local thingAtPos = tile:getThing(stackpos) + if not thingAtPos or thingAtPos ~= item then return end From 66cc1a1adc0c49bfc923bc0676c4b0ab1be0ef99 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Sat, 3 Jan 2026 12:19:51 -0300 Subject: [PATCH 51/55] refactor(outfits): add type check for outfit name in getOutfitByName function --- data/scripts/systems/outfits/data/outfits.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/scripts/systems/outfits/data/outfits.lua b/data/scripts/systems/outfits/data/outfits.lua index b9c340962e..a358a558ee 100644 --- a/data/scripts/systems/outfits/data/outfits.lua +++ b/data/scripts/systems/outfits/data/outfits.lua @@ -1574,6 +1574,10 @@ function Game.getOutfitByLookType(lookType) 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 { From 424976382eec2ba0fd907dcd838576b17b3059a8 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Sat, 3 Jan 2026 12:27:52 -0300 Subject: [PATCH 52/55] refactor(storages, migrations): update storage key definitions for outfits and mounts --- data/lib/core/storages.lua | 1 - data/migrations/37.lua | 14 ++++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/data/lib/core/storages.lua b/data/lib/core/storages.lua index 766f5e5255..03ff985b5f 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 = { diff --git a/data/migrations/37.lua b/data/migrations/37.lua index 339b5bb960..7c0fc4a1ce 100644 --- a/data/migrations/37.lua +++ b/data/migrations/37.lua @@ -1,5 +1,11 @@ local BATCH_SIZE = 10000 +-- 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() print("> Updating database to version 38 (revert outfits/mounts to storages)") @@ -18,7 +24,7 @@ function onUpdateDatabase() local outfitId = result.getNumber(resultId, "outfit_id") local addons = result.getNumber(resultId, "addons") - local storageKey = PlayerStorageKeys.outfitsBase + outfitId + local storageKey = OUTFITS_BASE + outfitId table.insert(rows, { playerId = playerId, key = storageKey, @@ -36,7 +42,7 @@ function onUpdateDatabase() local playerId = result.getNumber(resultId, "player_id") local mountId = result.getNumber(resultId, "mount_id") - local storageKey = PlayerStorageKeys.mountsBase + mountId + local storageKey = MOUNTS_BASE + mountId table.insert(rows, { playerId = playerId, key = storageKey, @@ -60,7 +66,7 @@ function onUpdateDatabase() if currentMount > 0 then table.insert(rows, { playerId = playerId, - key = PlayerStorageKeys.currentMount, + key = CURRENT_MOUNT, value = currentMount }) end @@ -68,7 +74,7 @@ function onUpdateDatabase() if randomizeMount > 0 then table.insert(rows, { playerId = playerId, - key = PlayerStorageKeys.randomizeMount, + key = RANDOMIZE_MOUNT, value = randomizeMount }) end From dcfa9d6d8eb2438c57f468837c16a062d000d190 Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Sat, 3 Jan 2026 12:50:57 -0300 Subject: [PATCH 53/55] refactor(migrations): simplify storage collection in onUpdateDatabase --- data/migrations/37.lua | 44 +++++++----------------------------------- 1 file changed, 7 insertions(+), 37 deletions(-) diff --git a/data/migrations/37.lua b/data/migrations/37.lua index 7c0fc4a1ce..3599bbaf2c 100644 --- a/data/migrations/37.lua +++ b/data/migrations/37.lua @@ -1,5 +1,3 @@ -local BATCH_SIZE = 10000 - -- must match PlayerStorageKeys in storages.lua, change accordingly if modified local CURRENT_MOUNT = 60000 local RANDOMIZE_MOUNT = 60001 @@ -14,7 +12,7 @@ function onUpdateDatabase() return false end - local rows = {} + 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`") @@ -25,11 +23,7 @@ function onUpdateDatabase() local addons = result.getNumber(resultId, "addons") local storageKey = OUTFITS_BASE + outfitId - table.insert(rows, { - playerId = playerId, - key = storageKey, - value = addons - }) + query:addRow(string.format("%d, %d, %d", playerId, storageKey, addons)) until not result.next(resultId) result.free(resultId) end @@ -43,11 +37,7 @@ function onUpdateDatabase() local mountId = result.getNumber(resultId, "mount_id") local storageKey = MOUNTS_BASE + mountId - table.insert(rows, { - playerId = playerId, - key = storageKey, - value = 1 - }) + query:addRow(string.format("%d, %d, %d", playerId, storageKey, 1)) until not result.next(resultId) result.free(resultId) end @@ -64,39 +54,19 @@ function onUpdateDatabase() local randomizeMount = result.getNumber(resultId, "randomizemount") if currentMount > 0 then - table.insert(rows, { - playerId = playerId, - key = CURRENT_MOUNT, - value = currentMount - }) + query:addRow(string.format("%d, %d, %d", playerId, CURRENT_MOUNT, currentMount)) end if randomizeMount > 0 then - table.insert(rows, { - playerId = playerId, - key = RANDOMIZE_MOUNT, - value = randomizeMount - }) + query:addRow(string.format("%d, %d, %d", playerId, RANDOMIZE_MOUNT, randomizeMount)) end until not result.next(resultId) result.free(resultId) end end - if #rows > 0 then - for start = 1, #rows, BATCH_SIZE do - local end_ = math.min(start + BATCH_SIZE - 1, #rows) - - local query = DBInsert("INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES ") - for i = start, end_ do - local row = rows[i] - query:addRow(string.format("%d, %d, %d", row.playerId, row.key, row.value)) - end - - if not query:execute() then - return false - end - end + if not query:execute() then + return false end if not db.query("DROP TABLE IF EXISTS `player_outfits`") then From e757a4d56ff403d8c0aa374490ad09ce3a0daa8c Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Sat, 3 Jan 2026 13:14:18 -0300 Subject: [PATCH 54/55] refactor(migrations): ensure transaction rollback on query failures in onUpdateDatabase --- data/migrations/37.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/migrations/37.lua b/data/migrations/37.lua index 3599bbaf2c..28a7bae3ec 100644 --- a/data/migrations/37.lua +++ b/data/migrations/37.lua @@ -66,18 +66,22 @@ function onUpdateDatabase() 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 From 661e6c5636928cf14e62a9907231196d703f246b Mon Sep 17 00:00:00 2001 From: Ranieri Althoff Date: Sat, 3 Jan 2026 13:55:23 -0300 Subject: [PATCH 55/55] refactor(migrations): update query condition for migrating current and randomized mounts --- data/migrations/37.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/migrations/37.lua b/data/migrations/37.lua index 28a7bae3ec..eea226154e 100644 --- a/data/migrations/37.lua +++ b/data/migrations/37.lua @@ -46,7 +46,7 @@ function onUpdateDatabase() -- Migrate currentmount and randomizemount from players table do local resultId = db.storeQuery( - "SELECT `id`, `currentmount`, `randomizemount` FROM `players` WHERE `currentmount` IS NOT NULL OR `randomizemount` IS NOT NULL") + "SELECT `id`, `currentmount`, `randomizemount` FROM `players` WHERE `currentmount` > 0 OR `randomizemount` > 0") if resultId then repeat local playerId = result.getNumber(resultId, "id")