From 7cb25c64a3c2286c37f0572b93b8f1cf55f3343b Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Tue, 23 Dec 2025 22:26:01 -0300 Subject: [PATCH 1/7] init --- modules/game_features/features.lua | 1 + modules/game_interface/interface.otmod | 1 + modules/game_outfit/outfit.lua | 89 ++++----- modules/game_paperdolls/configs/demon.lua | 36 ++++ modules/game_paperdolls/lib.lua | 142 +++++++++++++ modules/game_paperdolls/paperdolls.lua | 80 ++++++++ modules/game_paperdolls/paperdolls.otmod | 9 + modules/game_paperdolls/server/creature.cpp | 47 +++++ modules/game_paperdolls/server/creature.h | 50 +++++ modules/game_paperdolls/server/enums.h | 12 ++ modules/game_paperdolls/server/luascript.cpp | 189 ++++++++++++++++++ modules/game_paperdolls/server/luascript.h | 25 +++ modules/game_paperdolls/server/player.h | 13 ++ .../game_paperdolls/server/protocolgame.cpp | 46 +++++ modules/game_paperdolls/server/protocolgame.h | 5 + modules/game_paperdolls/setup.lua | 54 +++++ modules/gamelib/const.lua | 3 +- src/client/client.cpp | 2 + src/client/const.h | 1 + src/client/creature.cpp | 97 ++++++++- src/client/creature.h | 16 ++ src/client/declarations.h | 2 + src/client/luafunctions.cpp | 45 +++++ src/client/paperdoll.cpp | 112 +++++++++++ src/client/paperdoll.h | 132 ++++++++++++ src/client/paperdollmanager.cpp | 62 ++++++ src/client/paperdollmanager.h | 40 ++++ src/client/protocolcodes.h | 2 + src/client/protocolgame.h | 5 + src/client/protocolgameparse.cpp | 95 ++++++++- vc17/otclient.vcxproj | 4 + 31 files changed, 1364 insertions(+), 53 deletions(-) create mode 100644 modules/game_paperdolls/configs/demon.lua create mode 100644 modules/game_paperdolls/lib.lua create mode 100644 modules/game_paperdolls/paperdolls.lua create mode 100644 modules/game_paperdolls/paperdolls.otmod create mode 100644 modules/game_paperdolls/server/creature.cpp create mode 100644 modules/game_paperdolls/server/creature.h create mode 100644 modules/game_paperdolls/server/enums.h create mode 100644 modules/game_paperdolls/server/luascript.cpp create mode 100644 modules/game_paperdolls/server/luascript.h create mode 100644 modules/game_paperdolls/server/player.h create mode 100644 modules/game_paperdolls/server/protocolgame.cpp create mode 100644 modules/game_paperdolls/server/protocolgame.h create mode 100644 modules/game_paperdolls/setup.lua create mode 100644 src/client/paperdoll.cpp create mode 100644 src/client/paperdoll.h create mode 100644 src/client/paperdollmanager.cpp create mode 100644 src/client/paperdollmanager.h diff --git a/modules/game_features/features.lua b/modules/game_features/features.lua index 4419ce6aae..a73a4a1384 100644 --- a/modules/game_features/features.lua +++ b/modules/game_features/features.lua @@ -4,6 +4,7 @@ controller:registerEvents(g_game, { -- g_game.enableFeature(GameKeepUnawareTiles) -- g_game.enableFeature(GameNegativeOffset) -- g_game.enableFeature(GameWingsAurasEffectsShader) + -- g_game.enableFeature(GameCreaturePaperdoll) -- g_game.enableFeature(GameAllowCustomBotScripts) g_game.enableFeature(GameFormatCreatureName) diff --git a/modules/game_interface/interface.otmod b/modules/game_interface/interface.otmod index d3fadec5e1..cfdd191ac0 100644 --- a/modules/game_interface/interface.otmod +++ b/modules/game_interface/interface.otmod @@ -40,6 +40,7 @@ Module - game_unjustifiedpoints - game_shaders - game_attachedeffects + - game_paperdolls - game_stash - game_healthcircle - game_shop diff --git a/modules/game_outfit/outfit.lua b/modules/game_outfit/outfit.lua index eb79540c22..8c6c0f4989 100644 --- a/modules/game_outfit/outfit.lua +++ b/modules/game_outfit/outfit.lua @@ -1,4 +1,4 @@ -local statesOutft ={ +local statesOutft = { available = 0, store = 1, goldenOutfitTooltip = 2 @@ -63,7 +63,7 @@ end local function attachEffectIfValid(UICreature, value) local creature = UICreature:getCreature() - if checkPresetsValidity({value}) then + if checkPresetsValidity({ value }) then if creature then creature:attachEffect(g_attachedEffects.getById(value)) end @@ -72,7 +72,7 @@ end local function attachOrDetachEffect(Id, attach) local creature = previewCreature:getCreature() - if checkPresetsValidity({Id}) then + if checkPresetsValidity({ Id }) then if creature then if attach then if not creature:getAttachedEffectById(Id) then @@ -108,7 +108,6 @@ local function showSelectionList(data, tempValue, tempField, onSelectCallback) end if data and #data > 0 then for _, itemData in ipairs(data) do - local button = g_ui.createWidget("SelectionButton", window.selectionList) button:setId(tostring(itemData[1])) @@ -117,10 +116,9 @@ local function showSelectionList(data, tempValue, tempField, onSelectCallback) button.outfit:setOutfit({ type = modules.game_attachedeffects.thingId(itemData[1]) }) - + button.outfit:setMarginBottom(15) button.outfit:setCenter(true) - elseif Category == 2 then button.outfit:setOutfit(previewCreature:getCreature():getOutfit()) button.outfit:getCreature():attachEffect(g_attachedEffects.getById(itemData[1])) @@ -149,7 +147,8 @@ local function showSelectionList(data, tempValue, tempField, onSelectCallback) window.listSearch:show() end -local AppearanceData = {"preset", "outfit", "mount", "familiar", "wings", "aura", "effects", "shader", "healthBar", "title"} +local AppearanceData = { "preset", "outfit", "mount", "familiar", "wings", "aura", "effects", "shader", "healthBar", + "title" } function init() connect(g_game, { @@ -172,7 +171,7 @@ function onOutfitChange(creature, outfit, oldOutfit) end function onMovementChange(checkBox, checked) - local walkingSpeed = checked and 1000 or 0 + local walkingSpeed = checked and 1000 or 0 local mainCreature = previewCreature:getCreature() if mainCreature then @@ -490,22 +489,22 @@ function create(player, outfitList, creatureMount, mountList, familiarList, wing window.preview.options.showFamiliar:setVisible(g_game.getFeature(GamePlayerFamiliars)) window.appearance.settings.familiar:setVisible(g_game.getFeature(GamePlayerFamiliars)) - + local checks = { - {window.preview.options.showWings, ServerData.wings}, - {window.preview.options.showAura, ServerData.auras}, - {window.preview.options.showShader, ServerData.shaders}, - {window.preview.options.showBars, ServerData.healthBars}, - {window.preview.options.showEffects, ServerData.effects}, - {window.preview.options.showTitle, ServerData.title}, - {window.preview.options.showFamiliar, ServerData.familiars}, - {window.appearance.settings.familiar, ServerData.familiars}, - {window.appearance.settings.wings, ServerData.wings}, - {window.appearance.settings.aura, ServerData.auras}, - {window.appearance.settings.shader, ServerData.shaders}, - {window.appearance.settings.healthBar, ServerData.healthBars}, - {window.appearance.settings.effects, ServerData.effects}, - {window.appearance.settings.title, ServerData.title}, + { window.preview.options.showWings, ServerData.wings }, + { window.preview.options.showAura, ServerData.auras }, + { window.preview.options.showShader, ServerData.shaders }, + { window.preview.options.showBars, ServerData.healthBars }, + { window.preview.options.showEffects, ServerData.effects }, + { window.preview.options.showTitle, ServerData.title }, + { window.preview.options.showFamiliar, ServerData.familiars }, + { window.appearance.settings.familiar, ServerData.familiars }, + { window.appearance.settings.wings, ServerData.wings }, + { window.appearance.settings.aura, ServerData.auras }, + { window.appearance.settings.shader, ServerData.shaders }, + { window.appearance.settings.healthBar, ServerData.healthBars }, + { window.appearance.settings.effects, ServerData.effects }, + { window.appearance.settings.title, ServerData.title }, } for _, check in ipairs(checks) do @@ -671,7 +670,6 @@ function deletePreset() updateAppearanceText("aura", "None") updateAppearanceText("wings", "None") updateAppearanceText("effects", "None") - end function savePreset() @@ -706,7 +704,7 @@ function savePreset() attachEffectIfValid(window.presetsList[presetId].creature, lastSelectAura) attachEffectIfValid(window.presetsList[presetId].creature, lastSelectEffects) attachEffectIfValid(window.presetsList[presetId].creature, lastSelectWings) - local presets = {lastSelectAura, lastSelectEffects, lastSelectWings} + local presets = { lastSelectAura, lastSelectEffects, lastSelectWings } local hasValidAE = checkPresetsValidity(presets) local thingType = g_things.getThingType(tempOutfit.type, ThingCategoryCreature) @@ -822,7 +820,7 @@ function showPresets() attachEffectIfValid(presetWidget.creature, preset.effects) attachEffectIfValid(presetWidget.creature, preset.wings) - local presets = {preset.auras, preset.effects, preset.wings} + local presets = { preset.auras, preset.effects, preset.wings } local hasValidAE = checkPresetsValidity(presets) local thingType = g_things.getThingType(tempOutfit.type, ThingCategoryCreature) @@ -843,7 +841,6 @@ function showPresets() if presetId == settings.currentPreset then focused = presetId - end end end @@ -952,7 +949,7 @@ function showMounts() if tempOutfit.mount == mountData[1] then focused = mountData[1] end - + local state = mountData[3] if state then button.state = state @@ -1005,11 +1002,11 @@ function showFamiliars() button.outfit:setOutfit({ type = familiarData[1] }) - + button.name:setText(familiarData[2]) button.outfit:setCenter(true) - + if tempOutfit.familiar == familiarData[1] then focused = familiarData[1] end @@ -1073,13 +1070,12 @@ function showShaders() }) button.outfit:setCenter(true) - + button.outfit:getCreature():setShader(shaderData[2]) button.name:setText(shaderData[2]) if tempOutfit.shaders == shaderData[2] then - focused = shaderData[2] end end @@ -1205,9 +1201,7 @@ function showTitle() end function onPresetSelect(list, focusedChild, unfocusedChild, reason) - if focusedChild then - local presetId = tonumber(focusedChild:getId()) local preset = settings.presets[presetId] tempOutfit = table.copy(preset.outfit) @@ -1236,7 +1230,7 @@ function onPresetSelect(list, focusedChild, unfocusedChild, reason) updateAppearanceText("shader", preset.shaders or "Outfit - Default") updateAppearanceText("effects", modules.game_attachedeffects.getName(preset.effects)) end - + previewCreature:getCreature():clearAttachedEffects() if settings.showEffects and preset.effects then @@ -1275,7 +1269,6 @@ function onPresetSelect(list, focusedChild, unfocusedChild, reason) lastSelectWings = preset.wings lastSelectEffects = preset.effects lastSelectShader = preset.shaders - end end @@ -1361,7 +1354,7 @@ function onAuraSelect(list, focusedChild, unfocusedChild, reason) if focusedChild then local auraType = tonumber(focusedChild:getId()) - if checkPresetsValidity({auraType}) then + if checkPresetsValidity({ auraType }) then previewCreature:getCreature():attachEffect(g_attachedEffects.getById(auraType)) lastSelectAura = auraType tempOutfit.auras = auraType @@ -1388,8 +1381,7 @@ function onWingsSelect(list, focusedChild, unfocusedChild, reason) if focusedChild then local wingsType = tonumber(focusedChild:getId()) - if checkPresetsValidity({wingsType}) then - + if checkPresetsValidity({ wingsType }) then previewCreature:getCreature():attachEffect(g_attachedEffects.getById(wingsType)) lastSelectWings = wingsType tempOutfit.wings = wingsType @@ -1419,7 +1411,7 @@ function onShaderSelect(list, focusedChild, unfocusedChild, reason) showShaderCheck:setChecked(true) showShaderCheck.onCheckChange = onShowShaderChange end - + lastSelectShader = shaderType tempOutfit.shaders = shaderType creature:setShader(shaderType) @@ -1467,7 +1459,7 @@ function onEffectBarSelect(list, focusedChild, unfocusedChild, reason) if focusedChild then local effect_id = tonumber(focusedChild:getId()) - if checkPresetsValidity({effect_id}) then + if checkPresetsValidity({ effect_id }) then previewCreature:getCreature():attachEffect(g_attachedEffects.getById(effect_id)) lastSelectEffects = effect_id tempOutfit.effects = effect_id @@ -1691,18 +1683,24 @@ function updatePreview() previewCreature:setOutfit(previewOutfit) previewCreature:getCreature():setDirection(direction) + for _, paperdoll in ipairs(g_game.getLocalPlayer():getPaperdolls()) do + if paperdoll:canDrawOnUI() then + local clone = paperdoll:clone() + previewCreature:getCreature():attachPaperdoll(clone) + end + end end function rotate(value) if not previewCreature then return end - + local creature = previewCreature:getCreature() if not creature then return end - + local direction = previewCreature:getDirection() direction = direction + value @@ -1739,7 +1737,6 @@ function onFilterOnlyMine(self, checked) end) end - function onFilterSearch() addEvent(function() local searchText = window.listSearch.search:getText():lower():trim() @@ -1768,7 +1765,7 @@ function saveSettings() local writeStatus, writeError = pcall(function() return g_resources.writeFileContents(settingsFile, "[]") end) - + if not writeStatus then g_logger.debug("Could not create outfit settings file during logout: " .. tostring(writeError)) return @@ -1803,7 +1800,7 @@ function saveSettings() local writeStatus, writeError = pcall(function() return g_resources.writeFileContents(settingsFile, json.encode(fullSettings)) end) - + if not writeStatus then g_logger.debug("Could not save outfit settings during logout: " .. tostring(writeError)) end diff --git a/modules/game_paperdolls/configs/demon.lua b/modules/game_paperdolls/configs/demon.lua new file mode 100644 index 0000000000..37f00513d6 --- /dev/null +++ b/modules/game_paperdolls/configs/demon.lua @@ -0,0 +1,36 @@ +--[[ + registerThingConfig(thingId, thingType) + set(attachedEffectId, config) +]] +-- +local c = PaperdollManager.registerThingConfig(35) + +c:set(1, { + sizeFactor = 1.7, + dirOffset = { + [North] = { 0, 0 }, + [East] = { 0, 5 }, + [South] = { 5, 0 }, + [West] = { 10, 5 } + } +}) + +c:set(2, { + sizeFactor = 1.7, + dirOffset = { + [North] = { 0, 20, false }, + [East] = { -15, 5 }, + [South] = { 5, -15 }, + [West] = { 5, -15 } + } +}) + +c:set(3, { + sizeFactor = 1.7, + dirOffset = { + [North] = { 0, -10 }, + [East] = { -15, -3 }, + [South] = { 0, -10 }, + [West] = { -10, -5 } + } +}) diff --git a/modules/game_paperdolls/lib.lua b/modules/game_paperdolls/lib.lua new file mode 100644 index 0000000000..9374351191 --- /dev/null +++ b/modules/game_paperdolls/lib.lua @@ -0,0 +1,142 @@ +local __OBJECTS = {} +local __THING_CONFIG = {} + +local executeConfig = function(paperdoll, config) + if not config then + return + end + + local x = 0 + local y = 0 + local onTop = config.onTop or true + + if config.speed then + paperdoll:setSpeed(config.speed) + end + + if config.drawOnUI == false then + paperdoll:setCanDrawOnUI(false) + end + + if config.shader then + paperdoll:setShader(config.shader) + end + + if config.priority then + paperdoll:setPriority(config.priority) + end + + if config.opacity ~= nil and config.opacity < 1.0 then + paperdoll:setOpacity(config.opacity) + end + + if config.onlyAddon then + paperdoll:setOnlyAddon(config.onlyAddon) + end + + if config.addon then + paperdoll:setAddon(config.addon) + end + + if config.size then + paperdoll:setSize({ + width = config.size[1], + height = config.size[2] + }) + end + + if config.offset then + x = config.offset[1] or 0 + y = config.offset[2] or 0 + onTop = config.offset[3] or false + end + + if x ~= 0 or y ~= 0 then + paperdoll:setOffset(x, y) + end + + if onTop ~= nil then + paperdoll:setOnTop(onTop) + end + + if config.dirOffset then + for dir, offset in pairs(config.dirOffset) do + local _x = offset[1] or x + local _y = offset[2] or y + local _onTop = offset[3] or onTop + + if type(x) == 'boolean' then -- onTop Config + paperdoll:setOnTopByDir(dir, _x) + else + paperdoll:setDirOffset(dir, _x, _y, _onTop) + end + end + end +end + +PaperdollManager = { + get = function(id) + return __OBJECTS[id] + end, + register = function(id, name, thingId, config) + local paperdoll = g_paperdolls.register(id, thingId) + if paperdoll == nil then + return + end + + executeConfig(paperdoll, config) + config.isThingConfig = false + + __OBJECTS[id] = { + id = id, + name = name, + thingId = thingId, + config = config + } + end, + registerThingConfig = function(thingId) + if __THING_CONFIG[thingId] == nil then + __THING_CONFIG[thingId] = {} + end + + local thingConfig = __THING_CONFIG[thingId] + + local methods = { + set = function(self, id, config) + local paperdoll = PaperdollManager.get(id) + if paperdoll == nil then + return + end + + local __config = table.recursivecopy(paperdoll.config) + table.merge(__config, config) + + thingConfig[id] = __config + + __config.isThingConfig = true + if config.onAttach then + __config.__onAttach = paperdoll.config.onAttach + end + + if config.onDetach then + __config.__onDetach = paperdoll.config.onDetach + end + end + } + + return methods + end, + getConfig = function(id, thingId) + local config = __THING_CONFIG[thingId] + if config then + config = config[id] + if config then + return config + end + end + return __OBJECTS[id].config + end, + executeThingConfig = function(effect, thingId) + executeConfig(effect, PaperdollManager.getConfig(effect:getId(), thingId)) + end +} diff --git a/modules/game_paperdolls/paperdolls.lua b/modules/game_paperdolls/paperdolls.lua new file mode 100644 index 0000000000..c92e47d5ab --- /dev/null +++ b/modules/game_paperdolls/paperdolls.lua @@ -0,0 +1,80 @@ +--[[ + register(id, name, thingId, config) + config = { + drawOnUI, priority, onlyAddon, addon, + shader, bounce, fixed, sizeFactor, + color, headColor, bodyColor, legsColor, feetColor, + useMountPattern, showOnMount + offset{x, y, onTop}, dirOffset[dir]{x, y, onTop}, + mountOffset{x, y, onTop}, mountDirOffset[dir]{x, y, onTop}, + onAttach, onDetach + } +]] +-- + +PaperdollManager.register(1, 'Armadura', 512, { + priority = 1, + addon = 1, + onlyAddon = true, + onAttach = function(paperdoll, creature) + print('onAttach: ', paperdoll:getId(), creature:getName()) + end, + onDetach = function(paperdoll, creature) + print('onDetach: ', paperdoll:getId(), creature:getName()) + end +}) + +PaperdollManager.register(2, 'Weapons/shield', 512, { + priority = 2, + addon = 2, + onlyAddon = true +}) + +PaperdollManager.register(3, 'Peitoral', 367, { + priority = 3, + addon = 1, + onlyAddon = true +}) + +PaperdollManager.register(4, 'Akuma Aura', 664, { + priority = 4, + addon = 1, + bounce = true, + fixed = true, + onlyAddon = true +}) + +PaperdollManager.register(5, 'Mochila', 136, { + priority = 5, + addon = 1, + color = 77, + onlyAddon = true +}) + +PaperdollManager.register(1990, 'wings1990', 136, { + priority = 5, + onlyAddon = true, + addon = 2, + onTop = true, + bounce = true, + fixed = false, + useMountPattern = true, + dirOffset = { + [North] = { 0, 0, true }, + [East] = { 0, 0, true }, -- x+ esquerda, y+ = cima + [South] = { 0, 0, true }, + [West] = { 0, 0, true } -- x- = esquerda, y+ = cima + + -- x,y (x+ = esquerda, y+ = baixo) + } +}) + +PaperdollManager.register(130, 'wings130', 136, { + priority = 4, + onlyAddon = true, + addon = 1, + onTop = true, + bounce = false, + fixed = false, + useMountPattern = true +}) diff --git a/modules/game_paperdolls/paperdolls.otmod b/modules/game_paperdolls/paperdolls.otmod new file mode 100644 index 0000000000..ba2e733b9a --- /dev/null +++ b/modules/game_paperdolls/paperdolls.otmod @@ -0,0 +1,9 @@ +Module + name: game_paperdolls + description: Attached Paperdolls System + author: Mehah + website: + scripts: [lib, setup, paperdolls, configs/demon] + sandboxed: true + @onLoad: controller:init() + @onUnload: controller:terminate() \ No newline at end of file diff --git a/modules/game_paperdolls/server/creature.cpp b/modules/game_paperdolls/server/creature.cpp new file mode 100644 index 0000000000..1f99fdc22b --- /dev/null +++ b/modules/game_paperdolls/server/creature.cpp @@ -0,0 +1,47 @@ +// paperdolls +void Creature::addPaperdoll(const Paperdoll_t& p) { + m_paperdolls.push_back(p); + + SpectatorVec spectators; + g_game.map.getSpectators(spectators, position, true, true); + + for (const auto spectator : spectators) { + spectator->getPlayer()->sendAttachedPaperdoll(this, p); + } +} + +bool Creature::removePaperdollById(uint16_t id) { + const auto it = std::find_if(m_paperdolls.begin(), m_paperdolls.end(), + [id](const Paperdoll_t& obj) { return obj.id == id; }); + + if (it == m_paperdolls.end()) + return false; + + SpectatorVec spectators; + g_game.map.getSpectators(spectators, position, true, true); + + for (const auto spectator : spectators) { + spectator->getPlayer()->sendDetachPaperdoll(this, *it, false); + } + + m_paperdolls.erase(it); + return true; +} + +bool Creature::removePaperdollBySlot(uint8_t slot) { + const auto it = std::find_if(m_paperdolls.begin(), m_paperdolls.end(), + [slot](const Paperdoll_t& obj) { return obj.slot == slot; }); + + if (it == m_paperdolls.end()) + return false; + + SpectatorVec spectators; + g_game.map.getSpectators(spectators, position, true, true); + + for (const auto spectator : spectators) { + spectator->getPlayer()->sendDetachPaperdoll(this, *it, true); + } + + m_paperdolls.erase(it); + return true; +} diff --git a/modules/game_paperdolls/server/creature.h b/modules/game_paperdolls/server/creature.h new file mode 100644 index 0000000000..6e3c49b229 --- /dev/null +++ b/modules/game_paperdolls/server/creature.h @@ -0,0 +1,50 @@ +public: + // paperdolls + void addPaperdoll(const Paperdoll_t& p); + const std::vector& getPaperdolls() const { + return m_paperdolls; + } + void setPaperdoll(const Paperdoll_t& p) { + removePaperdollBySlot(p.slot); + addPaperdoll(p); + } + bool hasPaperdollById(uint16_t id) const { + for (const auto& p : m_paperdolls) { + if (p.id == id) + return true; + } + return false; + } + bool hasPaperdollBySlot(uint8_t slot) const { + for (const auto& p : m_paperdolls) { + if (p.slot == slot) + return true; + } + return false; + } + + Paperdoll_t getPaperdollById(uint16_t id) const { + const auto it = std::find_if(m_paperdolls.begin(), m_paperdolls.end(), + [id](const Paperdoll_t& obj) { return obj.id == id; }); + + if (it == m_paperdolls.end()) + return { UINT16_MAX }; + + return *it; + } + Paperdoll_t getPaperdollBySlot(uint8_t slot) const { + const auto it = std::find_if(m_paperdolls.begin(), m_paperdolls.end(), + [slot](const Paperdoll_t& obj) { return obj.slot == slot; }); + + if (it == m_paperdolls.end()) + return { UINT16_MAX }; + + return *it; + } + + bool removePaperdollById(uint16_t id); + bool removePaperdollBySlot(uint8_t slot); + +private: + // paperdoll + std::vector m_paperdolls; \ No newline at end of file diff --git a/modules/game_paperdolls/server/enums.h b/modules/game_paperdolls/server/enums.h new file mode 100644 index 0000000000..d2f25e9006 --- /dev/null +++ b/modules/game_paperdolls/server/enums.h @@ -0,0 +1,12 @@ +// paperdoll +struct Paperdoll_t +{ + uint16_t id{ 0 }; + uint8_t slot{ 255 }; + uint8_t color{ 0 }; + uint8_t head{ 0 }; + uint8_t body{ 0 }; + uint8_t legs{ 0 }; + uint8_t feet{ 0 }; + std::string shader; +}; diff --git a/modules/game_paperdolls/server/luascript.cpp b/modules/game_paperdolls/server/luascript.cpp new file mode 100644 index 0000000000..e21ae3563d --- /dev/null +++ b/modules/game_paperdolls/server/luascript.cpp @@ -0,0 +1,189 @@ +void LuaScriptInterface::registerFunctions() { + // . + // . + // . + + // add at the end +// Paperdoll + registerMethod("Creature", "addPaperdoll", LuaScriptInterface::luaCreatureAddPaperdoll); + registerMethod("Creature", "getPaperdolls", LuaScriptInterface::luaCreatureGetPaperdolls); + registerMethod("Creature", "setPaperdoll", LuaScriptInterface::luaCreatureSetPaperdoll); + registerMethod("Creature", "hasPaperdollById", LuaScriptInterface::luaCreatureHasPaperdollById); + registerMethod("Creature", "hasPaperdollBySlot", LuaScriptInterface::luaCreatureHasPaperdollBySlot); + registerMethod("Creature", "getPaperdollById", LuaScriptInterface::luaCreatureGetPaperdollById); + registerMethod("Creature", "getPaperdollBySlot", LuaScriptInterface::luaCreatureGetPaperdollBySlot); + registerMethod("Creature", "removePaperdollById", LuaScriptInterface::luaCreatureRemovePaperdollById); + registerMethod("Creature", "removePaperdollBySlot", LuaScriptInterface::luaCreatureRemovePaperdollBySlot); +} + +// Paperdolls +Paperdoll_t LuaScriptInterface::getPaperdoll(lua_State* L, int32_t arg) +{ + Paperdoll_t o; + + o.id = getField(L, arg, "id"); + o.slot = getField(L, arg, "slot", 255); + o.color = getField(L, arg, "color", 0); + o.head = getField(L, arg, "head", 0); + o.body = getField(L, arg, "body", 0); + o.legs = getField(L, arg, "legs", 0); + o.feet = getField(L, arg, "feet", 0); + o.shader = getFieldString(L, arg, "shader"); + + lua_pop(L, 8); + return o; +} + +void LuaScriptInterface::pushPaperdoll(lua_State* L, const Paperdoll_t& paperdoll) +{ + lua_createtable(L, 0, 8); + setField(L, "id", paperdoll.id); + setField(L, "slot", paperdoll.slot); + setField(L, "color", paperdoll.color); + setField(L, "head", paperdoll.head); + setField(L, "body", paperdoll.body); + setField(L, "legs", paperdoll.legs); + setField(L, "feet", paperdoll.feet); + setField(L, "shader", paperdoll.shader); + setMetatable(L, -1, "Paperdoll"); +} + +int LuaScriptInterface::luaCreatureAddPaperdoll(lua_State* L) +{ + // creature:addPaperdoll(paperdoll) + Creature* creature = getUserdata(L, 1); + if (creature) { + creature->addPaperdoll(getPaperdoll(L, 2)); + pushBoolean(L, true); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetPaperdolls(lua_State* L) +{ + // creature:getPaperdolls() + const auto creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + lua_createtable(L, creature->getPaperdolls().size(), 0); + + int index = 0; + for (const auto& paperdoll : creature->getPaperdolls()) { + pushPaperdoll(L, paperdoll); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaCreatureSetPaperdoll(lua_State* L) +{ + // creature:setPaperdoll(paperdoll) + Creature* creature = getUserdata(L, 1); + if (creature) { + creature->setPaperdoll(getPaperdoll(L, 2)); + pushBoolean(L, true); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureHasPaperdollById(lua_State* L) +{ + // creature:hasPaperdollById(id) + const Creature* creature = getUserdata(L, 1); + if (creature) { + uint16_t id = getNumber(L, 2); + pushBoolean(L, creature->hasPaperdollById(id)); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureHasPaperdollBySlot(lua_State* L) +{ + // creature:hasPaperdollBySlot(slot) + const Creature* creature = getUserdata(L, 1); + if (creature) { + uint8_t slot = getNumber(L, 2); + pushBoolean(L, creature->hasPaperdollBySlot(slot)); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetPaperdollById(lua_State* L) +{ + // creature:getPaperdollById() + const Creature* creature = getUserdata(L, 1); + if (creature) { + uint16_t id = getNumber(L, 2); + const auto& paperdoll = creature->getPaperdollById(id); + if (paperdoll.id < UINT16_MAX) + pushPaperdoll(L, creature->getPaperdollById(id)); + else + lua_pushnil(L); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetPaperdollBySlot(lua_State* L) +{ + // creature:getPaperdollBySlot() + const Creature* creature = getUserdata(L, 1); + if (creature) { + uint8_t slot = getNumber(L, 2); + const auto& paperdoll = creature->getPaperdollBySlot(slot); + if (paperdoll.id < UINT16_MAX) + pushPaperdoll(L, creature->getPaperdollBySlot(slot)); + else + lua_pushnil(L); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureRemovePaperdollById(lua_State* L) +{ + // creature:removePaperdollById(id) + Creature* creature = getUserdata(L, 1); + if (creature) { + uint16_t id = getNumber(L, 2); + pushBoolean(L, creature->removePaperdollById(id)); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureRemovePaperdollBySlot(lua_State* L) +{ + // creature:removePaperdollBySlot(slot) + Creature* creature = getUserdata(L, 1); + if (creature) { + uint8_t slot = getNumber(L, 2); + pushBoolean(L, creature->removePaperdollBySlot(slot)); + } + else { + lua_pushnil(L); + } + return 1; +} + diff --git a/modules/game_paperdolls/server/luascript.h b/modules/game_paperdolls/server/luascript.h new file mode 100644 index 0000000000..9fe88da454 --- /dev/null +++ b/modules/game_paperdolls/server/luascript.h @@ -0,0 +1,25 @@ +// put inside class LuaScriptInterface +public: + template + static T getField(lua_State* L, int32_t arg, const std::string& key, T defaultValue) + { + lua_getfield(L, arg, key.c_str()); + if (lua_isnil(L, -1) == 1) + return defaultValue; + + return getNumber(L, -1); + } + + // Paperdoll + static Paperdoll_t getPaperdoll(lua_State* L, int32_t arg); + static void pushPaperdoll(lua_State* L, const Paperdoll_t& paperdoll); + + static int luaCreatureAddPaperdoll(lua_State* L); + static int luaCreatureGetPaperdolls(lua_State* L); + static int luaCreatureSetPaperdoll(lua_State* L); + static int luaCreatureHasPaperdollById(lua_State* L); + static int luaCreatureHasPaperdollBySlot(lua_State* L); + static int luaCreatureGetPaperdollById(lua_State* L); + static int luaCreatureGetPaperdollBySlot(lua_State* L); + static int luaCreatureRemovePaperdollById(lua_State* L); + static int luaCreatureRemovePaperdollBySlot(lua_State* L); \ No newline at end of file diff --git a/modules/game_paperdolls/server/player.h b/modules/game_paperdolls/server/player.h new file mode 100644 index 0000000000..43e938bf3a --- /dev/null +++ b/modules/game_paperdolls/server/player.h @@ -0,0 +1,13 @@ +// paperdolls +public: + void sendAttachedPaperdoll(const Creature* creature, const Paperdoll_t& paperdoll) { + if (client) { + client->sendAttachedPaperdoll(creature, paperdoll); + } + } + + void sendDetachPaperdoll(const Creature* creature, const Paperdoll_t& paperdoll, bool bySlot) { + if (client) { + client->sendDetachPaperdoll(creature, paperdoll, bySlot); + } + } \ No newline at end of file diff --git a/modules/game_paperdolls/server/protocolgame.cpp b/modules/game_paperdolls/server/protocolgame.cpp new file mode 100644 index 0000000000..a030273327 --- /dev/null +++ b/modules/game_paperdolls/server/protocolgame.cpp @@ -0,0 +1,46 @@ + +// find this method +void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bool known, uint32_t remove) { + + // . + // . + // . + + // Add after msg.addByte(player->canWalkthroughEx(creature) ? 0x00 : 0x01); + + // Paperdolls + { + msg.addByte(static_cast(creature->getPaperdolls().size())); + for (const auto& paperdoll : creature->getPaperdolls()) + addPaperdoll(msg, paperdoll); + } + +} + +void ProtocolGame::addPaperdoll(NetworkMessage& msg, const Paperdoll_t& paperdoll) { + msg.add(paperdoll.id); + msg.add(paperdoll.slot); + msg.add(paperdoll.color); + msg.add(paperdoll.head); + msg.add(paperdoll.body); + msg.add(paperdoll.legs); + msg.add(paperdoll.feet); + msg.addString(paperdoll.shader); +} + +void ProtocolGame::sendAttachedPaperdoll(const Creature* creature, const Paperdoll_t& paperdoll) { + NetworkMessage msg; + msg.addByte(0x3C); + msg.add(creature->getID()); + addPaperdoll(msg, paperdoll); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendDetachPaperdoll(const Creature* creature, const Paperdoll_t& paperdoll, bool bySlot) { + NetworkMessage msg; + msg.addByte(0x3D); + msg.add(creature->getID()); + msg.add(static_cast(bySlot)); + msg.add(bySlot ? paperdoll.slot : paperdoll.id); + writeToOutputBuffer(msg); +} \ No newline at end of file diff --git a/modules/game_paperdolls/server/protocolgame.h b/modules/game_paperdolls/server/protocolgame.h new file mode 100644 index 0000000000..ed1e0fec69 --- /dev/null +++ b/modules/game_paperdolls/server/protocolgame.h @@ -0,0 +1,5 @@ + // paperdoll +private: + void addPaperdoll(NetworkMessage& msg, const Paperdoll_t& paperdoll); + void sendAttachedPaperdoll(const Creature* creature, const Paperdoll_t& paperdoll); + void sendDetachPaperdoll(const Creature* creature, const Paperdoll_t& paperdoll, bool bySlot); \ No newline at end of file diff --git a/modules/game_paperdolls/setup.lua b/modules/game_paperdolls/setup.lua new file mode 100644 index 0000000000..528f85cc22 --- /dev/null +++ b/modules/game_paperdolls/setup.lua @@ -0,0 +1,54 @@ +controller = Controller:new() + +function controller:onGameStart() + -- g_game.getLocalPlayer():attachPaperdoll(g_paperdolls.getById(1)) + -- g_game.getLocalPlayer():attachPaperdoll(g_paperdolls.getById(2)) + -- g_game.getLocalPlayer():attachPaperdoll(g_paperdolls.getById(3)) + -- g_game.getLocalPlayer():attachPaperdoll(g_paperdolls.getById(4)) +end + +function controller:onGameEnd() + -- g_game.getLocalPlayer():clearPaperdolls() +end + +function controller:onTerminate() + g_paperdolls.clear() +end + +local function onAttach(paperdoll, owner) + local outfitType = owner:getOutfit().type + local config = PaperdollManager.getConfig(paperdoll:getId(), outfitType) + + if config.isThingConfig then + PaperdollManager.executeThingConfig(paperdoll, outfitType) + end + + if config.onAttach then + config.onAttach(paperdoll, owner, config.__onAttach) + end +end + +local function onDetach(paperdoll, oldOwner) + local config = PaperdollManager.getConfig(paperdoll:getId(), oldOwner:getOutfit().type) + + if config.onDetach then + config.onDetach(paperdoll, oldOwner, config.__onDetach) + end +end + +local function onOutfitChange(creature, outfit, oldOutfit) + for _i, paperdoll in pairs(creature:getPaperdolls()) do + PaperdollManager.executeThingConfig(paperdoll, outfit.type) + end +end + +controller:registerEvents(LocalPlayer, { + onOutfitChange = onOutfitChange +}) +controller:registerEvents(Creature, { + onOutfitChange = onOutfitChange +}) +controller:registerEvents(Paperdoll, { + onAttach = onAttach, + onDetach = onDetach +}) diff --git a/modules/gamelib/const.lua b/modules/gamelib/const.lua index e08cfa79c3..3943151ffa 100644 --- a/modules/gamelib/const.lua +++ b/modules/gamelib/const.lua @@ -220,6 +220,7 @@ GameTileAddThingWithStackpos = 124 GameMapCache = 125 GameForgeSkillStats = 126 GameCharacterSkillStats = 127 +GameCreaturePaperdoll = 128 TextColors = { red = '#f55e5e', -- '#c83200' @@ -475,7 +476,7 @@ ExperienceRate = { PriceTypeEnum = { Market = 0, - Leader = 1 + Leader = 1 } -- Analyzer constants diff --git a/src/client/client.cpp b/src/client/client.cpp index f8c8238f6d..af4682fc52 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -38,6 +38,7 @@ #ifdef FRAMEWORK_EDITOR #include "creatures.h" #endif +#include "paperdollmanager.h" Client g_client; @@ -70,6 +71,7 @@ void Client::terminate() g_sprites.terminate(); g_spriteAppearances.terminate(); g_shaders.terminate(); + g_paperdolls.clear(); g_gameConfig.terminate(); } diff --git a/src/client/const.h b/src/client/const.h index 7e62d06dac..472b7b19e2 100644 --- a/src/client/const.h +++ b/src/client/const.h @@ -566,6 +566,7 @@ namespace Otc GameMapCache = 125, GameForgeSkillStats = 126, GameCharacterSkillStats = 127, + GameCreaturePaperdoll = 128, LastGameFeature }; diff --git a/src/client/creature.cpp b/src/client/creature.cpp index c84d9ccbb3..4ad4fe4029 100644 --- a/src/client/creature.cpp +++ b/src/client/creature.cpp @@ -36,6 +36,7 @@ #include "thingtype.h" #include "thingtypemanager.h" #include "tile.h" +#include "paperdoll.h" #include "framework/core/clock.h" #include "framework/core/eventdispatcher.h" #include "framework/core/scheduledevent.h" @@ -119,6 +120,9 @@ void Creature::drawLight(const Point& dest, LightView* lightView) { } drawAttachedLightEffect(dest + m_walkOffset * g_drawPool.getScaleFactor(), lightView); + + for (const auto& paperdoll : m_paperdolls) + paperdoll->drawLight(dest, m_outfit.hasMount(), lightView); } void Creature::draw(const Rect& destRect, const uint8_t size, const bool center) @@ -313,6 +317,11 @@ void Creature::internalDraw(Point dest, const Color& color) drawAttachedEffect(dest, nullptr, false); // On Bottom if (!isHided()) { + const int animationPhase = getCurrentAnimationPhase(); + + for (const auto& paperdoll : m_paperdolls) + paperdoll->draw(dest, animationPhase, m_outfit.hasMount(), false, true); + // outfit is a real creature if (m_outfit.isCreature()) { if (m_outfit.hasMount()) { @@ -339,7 +348,6 @@ void Creature::internalDraw(Point dest, const Color& color) } const auto& datType = getThingType(); - const int animationPhase = getCurrentAnimationPhase(); const bool useFramebuffer = !replaceColorShader && hasShader() && g_shaders.getShaderById(m_shaderId)->useFramebuffer(); const auto& drawCreature = [&](const Point& dest) { @@ -379,6 +387,9 @@ void Creature::internalDraw(Point dest, const Color& color) g_drawPool.resetShaderProgram(); } else drawCreature(dest); + for (const auto& paperdoll : m_paperdolls) + paperdoll->draw(dest, animationPhase, m_outfit.hasMount(), true, true); + // outfit is a creature imitating an item or the invisible effect } else { int animationPhases = getThingType()->getAnimationPhases(); @@ -814,6 +825,7 @@ void Creature::setDirection(const Otc::Direction direction) m_numPatternX = direction; setAttachedEffectDirection(static_cast(m_numPatternX)); + setPaperdollsDirection(static_cast(m_numPatternX)); } void Creature::setOutfit(const Outfit& outfit, bool fireEvent) @@ -1310,4 +1322,87 @@ std::string Creature::getText() bool Creature::canShoot(int distance) { return getTile() ? getTile()->canShoot(distance) : false; +} + +bool Creature::hasPaperdoll(uint16_t id) { + for (const auto& pd : m_paperdolls) { + if (pd->m_id == id) + return true; + } + + return false; +} + +void Creature::attachPaperdoll(const PaperdollPtr& obj) { + if (!obj) return; + + obj->m_direction = getDirection(); + + uint_fast8_t i = 0; + for (const auto& pd : m_paperdolls) { + if (obj->m_priority < pd->m_priority) + break; + ++i; + } + + m_paperdolls.insert(m_paperdolls.begin() + i, obj); + + g_dispatcher.addEvent([paperdoll = obj, self = static_self_cast()] { + paperdoll->callLuaField("onAttach", self->asLuaObject()); + }); +} + +bool Creature::detachPaperdollById(uint16_t id) { + const auto it = std::find_if(m_paperdolls.begin(), m_paperdolls.end(), + [id](const PaperdollPtr& obj) { return obj->getId() == id; }); + + if (it == m_paperdolls.end()) + return false; + + onDetachPaperdoll(*it); + m_paperdolls.erase(it); + + return true; +} + +bool Creature::detachPaperdollByPriority(uint8_t priority) { + bool finded = false; + for (auto it = m_paperdolls.begin(); it != m_paperdolls.end();) { + const auto& obj = *it; + if (obj->getPriority() == priority) { + onDetachPaperdoll(obj); + it = m_paperdolls.erase(it); + finded = true; + } else ++it; + } + + return finded; +} + +void Creature::onDetachPaperdoll(const PaperdollPtr& paperdoll) { + paperdoll->callLuaField("onDetach", asLuaObject()); +} + +void Creature::clearPaperdolls() { + for (const auto& e : m_paperdolls) + onDetachPaperdoll(e); + m_paperdolls.clear(); +} + +PaperdollPtr Creature::getPaperdollById(uint16_t id) { + const auto it = std::find_if(m_paperdolls.begin(), m_paperdolls.end(), + [id](const PaperdollPtr& obj) { return obj->getId() == id; }); + + if (it == m_paperdolls.end()) + return nullptr; + + return *it; +} + +void Creature::setPaperdollsDirection(Otc::Direction dir) const +{ + for (const auto& paperdoll : m_paperdolls) { + if (paperdoll->m_thingType) + paperdoll->m_direction = dir; + } } \ No newline at end of file diff --git a/src/client/creature.h b/src/client/creature.h index 97741a0f1c..8a5339bf9d 100644 --- a/src/client/creature.h +++ b/src/client/creature.h @@ -202,6 +202,17 @@ minHeight, void setVocation(uint8_t vocation) { m_vocation = vocation; } uint8_t getVocation() { return m_vocation; } + void attachPaperdoll(const PaperdollPtr& obj); + void clearPaperdolls(); + bool hasPaperdoll(uint16_t id); + + bool detachPaperdollById(uint16_t id); + bool detachPaperdollByPriority(uint8_t priority); + + PaperdollPtr getPaperdollById(uint16_t id); + + const std::vector& getPaperdolls() { return m_paperdolls; }; + protected: virtual void terminateWalk(); virtual void onWalking() {}; @@ -211,6 +222,9 @@ minHeight, void setOldPositionSilently(const Position& pos) { m_oldPosition = pos; } void setRemovedSilently(const bool removed) { m_removed = removed; } + void onDetachPaperdoll(const PaperdollPtr& paperdoll); + void setPaperdollsDirection(Otc::Direction dir) const; + ThingType* getThingType() const override; ThingType* getMountThingType() const; @@ -261,6 +275,8 @@ minHeight, CachedText numberText; }; + std::vector m_paperdolls; + UIWidgetPtr m_widgetInformation; TilePtr m_walkingTile; diff --git a/src/client/declarations.h b/src/client/declarations.h index 0c39a73a1a..d7b17e1f29 100644 --- a/src/client/declarations.h +++ b/src/client/declarations.h @@ -58,6 +58,7 @@ class ItemType; class TileBlock; class AttachedEffect; class AttachableObject; +class Paperdoll; #ifdef FRAMEWORK_EDITOR class House; @@ -85,6 +86,7 @@ using ThingTypePtr = std::shared_ptr; using ItemTypePtr = std::shared_ptr; using AttachedEffectPtr = std::shared_ptr; using AttachableObjectPtr = std::shared_ptr; +using PaperdollPtr = std::shared_ptr; #ifdef FRAMEWORK_EDITOR using HousePtr = std::shared_ptr; diff --git a/src/client/luafunctions.cpp b/src/client/luafunctions.cpp index f88d0dadc8..cf0df1bf7c 100644 --- a/src/client/luafunctions.cpp +++ b/src/client/luafunctions.cpp @@ -57,6 +57,8 @@ #include "uiminimap.h" #include "uiprogressrect.h" #include "uisprite.h" +#include "paperdoll.h" +#include "paperdollmanager.h" #ifdef FRAMEWORK_EDITOR #include "houses.h" @@ -425,6 +427,12 @@ void Client::registerLuaFunctions() g_lua.bindSingletonFunction("g_attachedEffects", "remove", &AttachedEffectManager::remove, &g_attachedEffects); g_lua.bindSingletonFunction("g_attachedEffects", "clear", &AttachedEffectManager::clear, &g_attachedEffects); + g_lua.registerSingletonClass("g_paperdolls"); + g_lua.bindSingletonFunction("g_paperdolls", "getById", &PaperdollManager::getById, &g_paperdolls); + g_lua.bindSingletonFunction("g_paperdolls", "register", &PaperdollManager::set, &g_paperdolls); + g_lua.bindSingletonFunction("g_paperdolls", "remove", &PaperdollManager::remove, &g_paperdolls); + g_lua.bindSingletonFunction("g_paperdolls", "clear", &PaperdollManager::clear, &g_paperdolls); + g_lua.bindGlobalFunction("getOutfitColor", Outfit::getColor); g_lua.bindGlobalFunction("getAngleFromPos", Position::getAngleFromPositions); g_lua.bindGlobalFunction("getDirectionFromPos", Position::getDirectionFromPositions); @@ -636,6 +644,12 @@ void Client::registerLuaFunctions() g_lua.bindClassMemberFunction("isFullHealth", &Creature::isFullHealth); g_lua.bindClassMemberFunction("isCovered", &Creature::isCovered); + g_lua.bindClassMemberFunction("getPaperdolls", &Creature::getPaperdolls); + g_lua.bindClassMemberFunction("attachPaperdoll", &Creature::attachPaperdoll); + g_lua.bindClassMemberFunction("detachPaperdollById", &Creature::detachPaperdollById); + g_lua.bindClassMemberFunction("getPaperdollById", &Creature::getPaperdollById); + g_lua.bindClassMemberFunction("clearPaperdolls", &Creature::clearPaperdolls); + g_lua.bindClassMemberFunction("setText", &Creature::setText); g_lua.bindClassMemberFunction("getText", &Creature::getText); g_lua.bindClassMemberFunction("clearText", &Creature::clearText); @@ -830,6 +844,37 @@ void Client::registerLuaFunctions() g_lua.bindClassMemberFunction("getDirection", &AttachedEffect::getDirection); g_lua.bindClassMemberFunction("move", &AttachedEffect::move); + g_lua.registerClass(); + g_lua.bindClassMemberFunction("clone", &Paperdoll::clone); + g_lua.bindClassMemberFunction("getId", &Paperdoll::getId); + g_lua.bindClassMemberFunction("getSpeed", &Paperdoll::getSpeed); + g_lua.bindClassMemberFunction("setOnTop", &Paperdoll::setOnTop); + g_lua.bindClassMemberFunction("setSpeed", &Paperdoll::setSpeed); + g_lua.bindClassMemberFunction("setOpacity", &Paperdoll::setOpacity); + g_lua.bindClassMemberFunction("setOffset", &Paperdoll::setOffset); + g_lua.bindClassMemberFunction("setDirOffset", &Paperdoll::setDirOffset); + g_lua.bindClassMemberFunction("setOnTopByDir", &Paperdoll::setOnTopByDir); + g_lua.bindClassMemberFunction("setShader", &Paperdoll::setShader); + g_lua.bindClassMemberFunction("setSize", &Paperdoll::setSize); + g_lua.bindClassMemberFunction("setPriority", &Paperdoll::setPriority); + g_lua.bindClassMemberFunction("canDrawOnUI", &Paperdoll::canDrawOnUI); + g_lua.bindClassMemberFunction("setCanDrawOnUI", &Paperdoll::setCanDrawOnUI); + g_lua.bindClassMemberFunction("setOnlyAddon", &Paperdoll::setOnlyAddon); + g_lua.bindClassMemberFunction("setAddons", &Paperdoll::setAddons); + g_lua.bindClassMemberFunction("hasAddon", &Paperdoll::hasAddon); + g_lua.bindClassMemberFunction("setAddon", &Paperdoll::setAddon); + g_lua.bindClassMemberFunction("removeAddon", &Paperdoll::removeAddon); + g_lua.bindClassMemberFunction("setColor", &Paperdoll::setColor); + g_lua.bindClassMemberFunction("setHeadColor", &Paperdoll::setHeadColor); + g_lua.bindClassMemberFunction("setBodyColor", &Paperdoll::setBodyColor); + g_lua.bindClassMemberFunction("setLegsColor", &Paperdoll::setLegsColor); + g_lua.bindClassMemberFunction("setFeetColor", &Paperdoll::setFeetColor); + g_lua.bindClassMemberFunction("getHeadColor", &Paperdoll::getHeadColor); + g_lua.bindClassMemberFunction("getBodyColor", &Paperdoll::getBodyColor); + g_lua.bindClassMemberFunction("getLegsColor", &Paperdoll::getLegsColor); + g_lua.bindClassMemberFunction("getFeetColor", &Paperdoll::getFeetColor); + g_lua.bindClassMemberFunction("setColorByOutfit", &Paperdoll::setColorByOutfit); + g_lua.registerClass(); g_lua.bindClassStaticFunction("create", [] { return std::make_shared(); }); g_lua.bindClassMemberFunction("addMessage", &StaticText::addMessage); diff --git a/src/client/paperdoll.cpp b/src/client/paperdoll.cpp new file mode 100644 index 0000000000..59239e0e6d --- /dev/null +++ b/src/client/paperdoll.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2010-2022 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "paperdoll.h" +#include "animator.h" +#include "thingtype.h" + +#include +#include +#include +#include + +PaperdollPtr Paperdoll::clone() +{ + auto obj = std::make_shared(); + *(obj.get()) = *this; + + return obj; +} + +void Paperdoll::draw(const Point& dest, uint16_t animationPhase, bool mount, bool isOnTop, bool drawThings, LightView* lightView) { + if (!m_thingType) + return; + + const auto& dirControl = m_offsetDirections[m_direction]; + if (dirControl.onTop != isOnTop) + return; + + if (!m_canDrawOnUI && g_drawPool.getCurrentType() == DrawPoolType::FOREGROUND) + return; + + if (m_shader) g_drawPool.setShaderProgram(m_shader, true); + if (m_opacity < 100) g_drawPool.setOpacity(getOpacity(), true); + + const auto& point = dest - (dirControl.offset * g_drawPool.getScaleFactor()); + const int animation = animationPhase == 0 ? getCurrentAnimationPhase() : animationPhase; + + if (!drawThings) { + m_thingType->draw(point, 0, m_direction, 0, 0, animation, Color::white, false, lightView); + } else { + if (!m_onlyAddon) + m_thingType->draw(point, 0, m_direction, 0, 0, animation, Color::white, drawThings, lightView); + + for (int yPattern = 0; yPattern < m_thingType->getNumPatternY(); ++yPattern) { + if (yPattern == 0 && m_onlyAddon) + continue; + + // continue if we dont have this addon + if (yPattern > 0 && !(m_addons & (1 << (yPattern - 1)))) + continue; + + if (m_shader) + g_drawPool.setShaderProgram(m_shader, true); + + m_thingType->draw(point, 0, m_direction, yPattern, static_cast(mount), animation, Color::white); + + if (m_thingType->getLayers() > 1) { + g_drawPool.setCompositionMode(CompositionMode::MULTIPLY); + m_thingType->draw(dest, SpriteMaskYellow, m_direction, yPattern, static_cast(mount), animationPhase, getHeadColor()); + m_thingType->draw(dest, SpriteMaskRed, m_direction, yPattern, static_cast(mount), animationPhase, getBodyColor()); + m_thingType->draw(dest, SpriteMaskGreen, m_direction, yPattern, static_cast(mount), animationPhase, getLegsColor()); + m_thingType->draw(dest, SpriteMaskBlue, m_direction, yPattern, static_cast(mount), animationPhase, getFeetColor()); + g_drawPool.resetCompositionMode(); + } + } + } +} + +void Paperdoll::drawLight(const Point& dest, bool mount, LightView* lightView) { + if (!lightView) return; + + const auto& dirControl = m_offsetDirections[m_direction]; + draw(dest, 0, mount, dirControl.onTop, false, lightView); +} + +int Paperdoll::getCurrentAnimationPhase() +{ + const auto* animator = m_thingType->getIdleAnimator(); + if (!animator && m_thingType->isAnimateAlways()) + animator = m_thingType->getAnimator(); + + if (animator) + return animator->getPhaseAt(m_animationTimer, getSpeed()); + + if (m_thingType->isCreature() && m_thingType->isAnimateAlways()) { + const int ticksPerFrame = std::round(1000 / m_thingType->getAnimationPhases()) / getSpeed(); + return (g_clock.millis() % (static_cast(ticksPerFrame) * m_thingType->getAnimationPhases())) / ticksPerFrame; + } + + return 0; +} + +void Paperdoll::setShader(const std::string_view name) { m_shader = g_shaders.getShader(name); } \ No newline at end of file diff --git a/src/client/paperdoll.h b/src/client/paperdoll.h new file mode 100644 index 0000000000..bf439e8b9c --- /dev/null +++ b/src/client/paperdoll.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2010-2022 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include "outfit.h" +#include "declarations.h" +#include +#include +#include + +class Paperdoll : public LuaObject +{ +public: + void draw(const Point& /*dest*/, uint16_t animationPhase, bool mount, bool isOnTop, bool drawThings, LightView* = nullptr); + void drawLight(const Point& /*dest*/, bool mount, LightView*); + + uint16_t getId() { return m_id; } + + PaperdollPtr clone(); + + float getSpeed() { return m_speed / 100.f; } + void setSpeed(float speed) { m_speed = speed * 100u; } + + float getOpacity() { return m_opacity / 100.f; } + void setOpacity(float opacity) { m_opacity = opacity * 100u; } + + Size getSize() { return m_size; } + void setSize(const Size& s) { m_size = s; } + + bool getOnlyAddon() { return m_onlyAddon; } + void setOnlyAddon(bool s) { m_onlyAddon = s; } + + uint32_t getAddons() { return m_addons; } + void setAddons(uint32_t addons) { m_addons = addons; } + + uint8_t getPriority() { return m_priority; } + void setPriority(uint8_t priority) { m_priority = priority; } + + uint32_t hasAddon(uint32_t addon) { return (m_addons & addon) == addon; } + void setAddon(uint32_t addon) { m_addons |= addon; } + void removeAddon(uint32_t addon) { m_addons &= ~addon; } + + void setOnTop(bool onTop) { for (auto& control : m_offsetDirections) control.onTop = onTop; } + void setOffset(int16_t x, int16_t y) { for (auto& control : m_offsetDirections) control.offset = { x, y }; } + void setOnTopByDir(Otc::Direction direction, bool onTop) { m_offsetDirections[direction].onTop = onTop; } + + void setDirOffset(Otc::Direction direction, int8_t x, int8_t y, bool onTop = false) { m_offsetDirections[direction] = { onTop, {x, y} }; } + void setShader(const std::string_view name); + void setCanDrawOnUI(bool canDraw) { m_canDrawOnUI = canDraw; } + bool canDrawOnUI() { return m_canDrawOnUI; } + + void setColor(uint8_t c) { + m_head = c; + m_body = c; + m_legs = c; + m_feet = c; + } + + void setHeadColor(uint8_t c) { m_head = c; } + void setBodyColor(uint8_t c) { m_body = c; } + void setLegsColor(uint8_t c) { m_legs = c; } + void setFeetColor(uint8_t c) { m_feet = c; } + + uint8_t getHeadColor() { return m_head; } + uint8_t getBodyColor() { return m_body; } + uint8_t getLegsColor() { return m_legs; } + uint8_t getFeetColor() { return m_feet; } + + void setColorByOutfit(const Outfit& outfit) { + m_head = outfit.getHead(); + m_body = outfit.getBody(); + m_legs = outfit.getLegs(); + m_feet = outfit.getFeet(); + } + +private: + int getCurrentAnimationPhase(); + + struct DirControl + { + bool onTop{ true }; + Point offset; + }; + + uint8_t m_priority{ 1 }; + uint8_t m_head{ 0 }, m_body{ 0 }, m_legs{ 0 }, m_feet{ 0 }; + + uint8_t m_speed{ 100 }; + uint8_t m_opacity{ 100 }; + uint16_t m_id{ 0 }; + uint16_t m_thingId{ 0 }; + uint32_t m_addons{ 0 }; + + Timer m_timer; + + bool m_onlyAddon{ false }; + bool m_canDrawOnUI{ true }; + + ThingType* m_thingType{ nullptr }; + + Size m_size; + Timer m_animationTimer; + + Otc::Direction m_direction{ Otc::North }; + + std::array m_offsetDirections; + + PainterShaderProgramPtr m_shader; + + friend class Creature; + friend class PaperdollManager; +}; diff --git a/src/client/paperdollmanager.cpp b/src/client/paperdollmanager.cpp new file mode 100644 index 0000000000..795f4b1758 --- /dev/null +++ b/src/client/paperdollmanager.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2010-2022 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "paperdollmanager.h" +#include "paperdoll.h" +#include "thingtypemanager.h" + +PaperdollManager g_paperdolls; + +PaperdollPtr PaperdollManager::getById(uint16_t id) { + const auto it = m_paperdolls.find(id); + if (it == m_paperdolls.end()) { + g_logger.error(std::format("PaperdollManager::getById(%d): not found.", id)); + return nullptr; + } + + const auto& obj = (*it).second; + if (obj->m_thingId > 0 && obj->m_thingType == nullptr) { + if (!g_things.isValidDatId(obj->m_thingId, ThingCategoryCreature)) { + g_logger.error(std::format("PaperdollManager::getById(%d): invalid thing with id %d.", id, obj->m_thingId)); + return nullptr; + } + + obj->m_thingType = g_things.getThingType(obj->m_thingId, ThingCategoryCreature).get(); + } + + return obj; +} + +PaperdollPtr PaperdollManager::set(uint16_t id, uint16_t thingId) { + const auto it = m_paperdolls.find(id); + if (it != m_paperdolls.end()) { + g_logger.error(std::format("PaperdollManager::register(%d, %d): has already been registered.", id, thingId)); + return nullptr; + } + + const auto& obj = std::make_shared(); + obj->m_id = id; + obj->m_thingId = thingId; + + m_paperdolls.emplace(id, obj); + return obj; +} \ No newline at end of file diff --git a/src/client/paperdollmanager.h b/src/client/paperdollmanager.h new file mode 100644 index 0000000000..74a6af363f --- /dev/null +++ b/src/client/paperdollmanager.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010-2022 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include "declarations.h" + +class PaperdollManager +{ +public: + PaperdollPtr set(uint16_t id, uint16_t thingId); + PaperdollPtr getById(uint16_t id); + + void remove(uint16_t id) { m_paperdolls.erase(id); } + void clear() { m_paperdolls.clear(); } + +private: + stdext::map m_paperdolls; +}; + +extern PaperdollManager g_paperdolls; diff --git a/src/client/protocolcodes.h b/src/client/protocolcodes.h index a1ff5e381a..4a60b1e0ce 100644 --- a/src/client/protocolcodes.h +++ b/src/client/protocolcodes.h @@ -77,6 +77,8 @@ namespace Proto GameServerCreatureShader = 54, GameServerMapShader = 55, GameServerCreatureTyping = 56, + GameServerAttachedPaperdoll = 60, + GameServerDetachPaperdoll = 61, GameServerFeatures = 67, GameServerFloorDescription = 75, diff --git a/src/client/protocolgame.h b/src/client/protocolgame.h index 38d9cc7a6b..3ed8202462 100644 --- a/src/client/protocolgame.h +++ b/src/client/protocolgame.h @@ -366,6 +366,9 @@ class ProtocolGame final : public Protocol void parseCreatureShader(const InputMessagePtr& msg); void parseMapShader(const InputMessagePtr& msg); + void parseAttachedPaperdoll(const InputMessagePtr& msg); + void parseDetachPaperdoll(const InputMessagePtr& msg); + MarketOffer readMarketOffer(const InputMessagePtr& msg, uint8_t action, uint16_t var); Imbuement getImbuementInfo(const InputMessagePtr& msg); @@ -385,6 +388,8 @@ class ProtocolGame final : public Protocol Position getPosition(const InputMessagePtr& msg); private: + PaperdollPtr getPaperdoll(const InputMessagePtr& msg) const; + bool m_enableSendExtendedOpcode{ false }; bool m_gameInitialized{ false }; bool m_mapKnown{ false }; diff --git a/src/client/protocolgameparse.cpp b/src/client/protocolgameparse.cpp index 713c4baf48..759a221500 100644 --- a/src/client/protocolgameparse.cpp +++ b/src/client/protocolgameparse.cpp @@ -38,6 +38,8 @@ #include "thingtypemanager.h" #include "framework/core/eventdispatcher.h" #include "framework/net/inputmessage.h" +#include "paperdollmanager.h" +#include "paperdoll.h" void ProtocolGame::parseMessage(const InputMessagePtr& msg) { @@ -147,6 +149,12 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg) case Proto::GameServerCreatureTyping: parseCreatureTyping(msg); break; + case Proto::GameServerAttachedPaperdoll: + parseAttachedPaperdoll(msg); + break; + case Proto::GameServerDetachPaperdoll: + parseDetachPaperdoll(msg); + break; case Proto::GameServerFeatures: parseFeatures(msg); break; @@ -447,7 +455,7 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg) case Proto::GameServerLootContainers: parseLootContainers(msg); break; - case Proto::GameServerVirtue: + case Proto::GameServerVirtue: parseVirtue(msg); break; case Proto::GameServerCyclopediaHouseAuctionMessage: @@ -3885,6 +3893,15 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type) cons unpass = static_cast(msg->getU8()); } + if (g_game.getFeature(Otc::GameCreaturePaperdoll)) { + uint8_t size = msg->getU8(); + for (uint8_t i = 0; i < size; ++i) { + const auto& paperdoll = getPaperdoll(msg); + if (creature) + creature->attachPaperdoll(paperdoll); + } + } + std::string shader; if (g_game.getFeature(Otc::GameCreatureShader)) { shader = msg->getString(); @@ -5649,7 +5666,6 @@ void ProtocolGame::parseImbuementWindow(const InputMessagePtr& msg) const uint32_t unknown = msg->getU32(); g_lua.callGlobalField("g_game", "onOpenImbuementWindow", itemId, unknown); - } else if (windowType == Otc::IMBUEMENT_WINDOW_SELECT_ITEM) { const uint16_t itemId = msg->getU16(); const auto& item = Item::create(itemId); @@ -5697,7 +5713,6 @@ void ProtocolGame::parseImbuementWindow(const InputMessagePtr& msg) } g_lua.callGlobalField("g_game", "onImbuementItem", itemId, slot, activeSlots, imbuements, neededItemsList); - } else if (windowType == Otc::IMBUEMENT_WINDOW_SCROLL) { msg->getU8(); // unknown byte msg->getU8(); // unknown byte @@ -6250,7 +6265,77 @@ void ProtocolGame::parseWeaponProficiencyInfo(const InputMessagePtr& msg) const uint8_t size = msg->getU8(); for (auto j = 0; j < size; ++j) { - msg->getU8(); // proficiencyLevel - msg->getU8(); // perkPosition + msg->getU8(); // proficiencyLevel + msg->getU8(); // perkPosition + } +} + +void ProtocolGame::parseAttachedPaperdoll(const InputMessagePtr& msg) { + const uint32_t id = msg->getU32(); + const auto& paperdoll = getPaperdoll(msg); + + const auto& creature = g_map.getCreatureById(id); + if (!creature) { + g_logger.traceError(std::format("could not get creature with id %d", id)); + return; } + + if (creature->hasPaperdoll(paperdoll->getId())) + return; + + creature->attachPaperdoll(paperdoll); } +void ProtocolGame::parseDetachPaperdoll(const InputMessagePtr& msg) { + const uint32_t id = msg->getU32(); + const bool bySlot = msg->getU8(); + const uint16_t idOrSlot = msg->getU16(); + + const auto& creature = g_map.getCreatureById(id); + if (!creature) { + g_logger.traceError(std::format("could not get creature with id %d", id)); + return; + } + + if (bySlot) + creature->detachPaperdollByPriority(idOrSlot); + else + creature->detachPaperdollById(idOrSlot); +} + +PaperdollPtr ProtocolGame::getPaperdoll(const InputMessagePtr& msg) const { + uint16_t id = msg->getU16(); + uint8_t slot = msg->getU8(); + uint8_t color = msg->getU8(); + uint8_t head = msg->getU8(); + uint8_t body = msg->getU8(); + uint8_t legs = msg->getU8(); + uint8_t feet = msg->getU8(); + const auto& shader = msg->getString(); + + auto paperdoll = g_paperdolls.getById(id); + if (!paperdoll) return nullptr; + + paperdoll = paperdoll->clone(); + if (slot != UINT8_MAX) + paperdoll->setPriority(slot); + + if (color != 0) + paperdoll->setColor(color); + + if (head != 0) + paperdoll->setHeadColor(head); + + if (body != 0) + paperdoll->setBodyColor(body); + + if (legs != 0) + paperdoll->setLegsColor(legs); + + if (feet != 0) + paperdoll->setFeetColor(feet); + + if (!shader.empty()) + paperdoll->setShader(shader); + + return paperdoll; +} \ No newline at end of file diff --git a/vc17/otclient.vcxproj b/vc17/otclient.vcxproj index 8335eea36e..c263b49405 100644 --- a/vc17/otclient.vcxproj +++ b/vc17/otclient.vcxproj @@ -332,6 +332,8 @@ + + false Create @@ -511,6 +513,8 @@ + + From 6c224df9d0f330059598f3b701b28b0da90e5b5c Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Tue, 23 Dec 2025 22:43:20 -0300 Subject: [PATCH 2/7] update --- modules/game_paperdolls/lib.lua | 87 +++++++++++++++++++++++++++------ src/client/luafunctions.cpp | 9 +++- src/client/paperdoll.cpp | 52 ++++++++++++++++++-- src/client/paperdoll.h | 31 +++++++++--- 4 files changed, 153 insertions(+), 26 deletions(-) diff --git a/modules/game_paperdolls/lib.lua b/modules/game_paperdolls/lib.lua index 9374351191..8e1f38ab4c 100644 --- a/modules/game_paperdolls/lib.lua +++ b/modules/game_paperdolls/lib.lua @@ -8,12 +8,17 @@ local executeConfig = function(paperdoll, config) local x = 0 local y = 0 - local onTop = config.onTop or true + local onTop = true + if config.onTop ~= nil then + onTop = config.onTop + end if config.speed then paperdoll:setSpeed(config.speed) end + paperdoll:reset() + if config.drawOnUI == false then paperdoll:setCanDrawOnUI(false) end @@ -38,21 +43,49 @@ local executeConfig = function(paperdoll, config) paperdoll:setAddon(config.addon) end - if config.size then - paperdoll:setSize({ - width = config.size[1], - height = config.size[2] - }) + if config.sizeFactor then + paperdoll:setSizeFactor(config.sizeFactor) + end + + if config.color then + paperdoll:setColor(config.color) + end + + if config.headColor then + paperdoll:setHeadColor(config.headColor) + end + + if config.bodyColor then + paperdoll:setBodyColor(config.bodyColor) + end + + if config.legsColor then + paperdoll:setLegsColor(config.legsColor) + end + + if config.feetColor then + paperdoll:setFeetColor(config.feetColor) + end + + if config.useMountPattern ~= nil then + paperdoll:setUseMountPattern(config.useMountPattern) + end + + if config.showOnMount ~= nil then + paperdoll:setShowOnMount(config.showOnMount) end if config.offset then x = config.offset[1] or 0 y = config.offset[2] or 0 - onTop = config.offset[3] or false - end + local _onTop = config.offset[3] + if _onTop == nil then _onTop = onTop end + + onTop = _onTop - if x ~= 0 or y ~= 0 then - paperdoll:setOffset(x, y) + if x ~= 0 or y ~= 0 then + paperdoll:setOffset(x, y) + end end if onTop ~= nil then @@ -63,15 +96,40 @@ local executeConfig = function(paperdoll, config) for dir, offset in pairs(config.dirOffset) do local _x = offset[1] or x local _y = offset[2] or y - local _onTop = offset[3] or onTop + local _onTop = offset[3] + if _onTop == nil then _onTop = onTop end - if type(x) == 'boolean' then -- onTop Config + if type(_x) == 'boolean' then -- onTop Config paperdoll:setOnTopByDir(dir, _x) else paperdoll:setDirOffset(dir, _x, _y, _onTop) end end end + + if config.mountOffset then + x = config.mountOffset[1] or 0 + y = config.mountOffset[2] or 0 + + if x ~= 0 or y ~= 0 then + paperdoll:setMountOffset(x, y) + end + end + + if config.mountDirOffset then + for dir, offset in pairs(config.mountDirOffset) do + local _x = offset[1] or x + local _y = offset[2] or y + local _onTop = offset[3] + if _onTop == nil then _onTop = onTop end + + if type(_x) == 'boolean' then -- onTop Config + paperdoll:setMountOnTopByDir(dir, _x) + else + paperdoll:setMountDirOffset(dir, _x, _y, _onTop) + end + end + end end PaperdollManager = { @@ -134,9 +192,10 @@ PaperdollManager = { return config end end + return __OBJECTS[id].config end, - executeThingConfig = function(effect, thingId) - executeConfig(effect, PaperdollManager.getConfig(effect:getId(), thingId)) + executeThingConfig = function(paperdoll, thingId) + executeConfig(paperdoll, PaperdollManager.getConfig(paperdoll:getId(), thingId)) end } diff --git a/src/client/luafunctions.cpp b/src/client/luafunctions.cpp index cf0df1bf7c..58fbf056f3 100644 --- a/src/client/luafunctions.cpp +++ b/src/client/luafunctions.cpp @@ -855,7 +855,7 @@ void Client::registerLuaFunctions() g_lua.bindClassMemberFunction("setDirOffset", &Paperdoll::setDirOffset); g_lua.bindClassMemberFunction("setOnTopByDir", &Paperdoll::setOnTopByDir); g_lua.bindClassMemberFunction("setShader", &Paperdoll::setShader); - g_lua.bindClassMemberFunction("setSize", &Paperdoll::setSize); + g_lua.bindClassMemberFunction("setSizeFactor", &Paperdoll::setSizeFactor); g_lua.bindClassMemberFunction("setPriority", &Paperdoll::setPriority); g_lua.bindClassMemberFunction("canDrawOnUI", &Paperdoll::canDrawOnUI); g_lua.bindClassMemberFunction("setCanDrawOnUI", &Paperdoll::setCanDrawOnUI); @@ -864,6 +864,7 @@ void Client::registerLuaFunctions() g_lua.bindClassMemberFunction("hasAddon", &Paperdoll::hasAddon); g_lua.bindClassMemberFunction("setAddon", &Paperdoll::setAddon); g_lua.bindClassMemberFunction("removeAddon", &Paperdoll::removeAddon); + g_lua.bindClassMemberFunction("reset", &Paperdoll::reset); g_lua.bindClassMemberFunction("setColor", &Paperdoll::setColor); g_lua.bindClassMemberFunction("setHeadColor", &Paperdoll::setHeadColor); g_lua.bindClassMemberFunction("setBodyColor", &Paperdoll::setBodyColor); @@ -875,6 +876,12 @@ void Client::registerLuaFunctions() g_lua.bindClassMemberFunction("getFeetColor", &Paperdoll::getFeetColor); g_lua.bindClassMemberFunction("setColorByOutfit", &Paperdoll::setColorByOutfit); + g_lua.bindClassMemberFunction("setMountOffset", &Paperdoll::setMountOffset); + g_lua.bindClassMemberFunction("setMountOnTopByDir", &Paperdoll::setMountOnTopByDir); + g_lua.bindClassMemberFunction("setMountDirOffset", &Paperdoll::setMountDirOffset); + g_lua.bindClassMemberFunction("setUseMountPattern", &Paperdoll::setUseMountPattern); + g_lua.bindClassMemberFunction("setShowOnMount", &Paperdoll::setShowOnMount); + g_lua.registerClass(); g_lua.bindClassStaticFunction("create", [] { return std::make_shared(); }); g_lua.bindClassMemberFunction("addMessage", &StaticText::addMessage); diff --git a/src/client/paperdoll.cpp b/src/client/paperdoll.cpp index 59239e0e6d..0318dc9c40 100644 --- a/src/client/paperdoll.cpp +++ b/src/client/paperdoll.cpp @@ -41,10 +41,16 @@ void Paperdoll::draw(const Point& dest, uint16_t animationPhase, bool mount, boo if (!m_thingType) return; - const auto& dirControl = m_offsetDirections[m_direction]; + if (mount && !m_showOnMount) + return; + + const auto& dirControl = m_offsetDirections[mount][m_direction]; if (dirControl.onTop != isOnTop) return; + if (!m_useMountPattern) + mount = false; + if (!m_canDrawOnUI && g_drawPool.getCurrentType() == DrawPoolType::FOREGROUND) return; @@ -54,6 +60,10 @@ void Paperdoll::draw(const Point& dest, uint16_t animationPhase, bool mount, boo const auto& point = dest - (dirControl.offset * g_drawPool.getScaleFactor()); const int animation = animationPhase == 0 ? getCurrentAnimationPhase() : animationPhase; + const auto oldScaleFactor = g_drawPool.getScaleFactor(); + + g_drawPool.setScaleFactor(m_sizeFactor); + if (!drawThings) { m_thingType->draw(point, 0, m_direction, 0, 0, animation, Color::white, false, lightView); } else { @@ -83,12 +93,14 @@ void Paperdoll::draw(const Point& dest, uint16_t animationPhase, bool mount, boo } } } + + g_drawPool.setScaleFactor(oldScaleFactor); } void Paperdoll::drawLight(const Point& dest, bool mount, LightView* lightView) { if (!lightView) return; - const auto& dirControl = m_offsetDirections[m_direction]; + const auto& dirControl = m_offsetDirections[mount][m_direction]; draw(dest, 0, mount, dirControl.onTop, false, lightView); } @@ -109,4 +121,38 @@ int Paperdoll::getCurrentAnimationPhase() return 0; } -void Paperdoll::setShader(const std::string_view name) { m_shader = g_shaders.getShader(name); } \ No newline at end of file +void Paperdoll::setShader(const std::string_view name) { m_shader = g_shaders.getShader(name); } + +void Paperdoll::reset() { + m_onlyAddon = false; + m_canDrawOnUI = true; + + m_addons = 0; + m_sizeFactor = 1.f; + for (auto& pattern : m_offsetDirections) + pattern.fill(DirControl()); +} + +void Paperdoll::setOnTop(bool onTop) { + for (auto& pattern : m_offsetDirections) + for (auto& control : pattern) + control.onTop = onTop; +} + +void Paperdoll::setOffset(int16_t x, int16_t y) { + for (auto& control : m_offsetDirections[0]) + control.offset = { x, y }; +} + +void Paperdoll::setOnTopByDir(Otc::Direction direction, bool onTop) { + m_offsetDirections[0][direction].onTop = onTop; +} + +void Paperdoll::setMountOffset(int16_t x, int16_t y) { + for (auto& control : m_offsetDirections[1]) + control.offset = { x, y }; +} + +void Paperdoll::setMountOnTopByDir(Otc::Direction direction, bool onTop) { + m_offsetDirections[1][direction].onTop = onTop; +} \ No newline at end of file diff --git a/src/client/paperdoll.h b/src/client/paperdoll.h index bf439e8b9c..a5de0a1546 100644 --- a/src/client/paperdoll.h +++ b/src/client/paperdoll.h @@ -44,8 +44,8 @@ class Paperdoll : public LuaObject float getOpacity() { return m_opacity / 100.f; } void setOpacity(float opacity) { m_opacity = opacity * 100u; } - Size getSize() { return m_size; } - void setSize(const Size& s) { m_size = s; } + float getSizeFactor() { return m_sizeFactor; } + void setSizeFactor(const float s) { m_sizeFactor = s; } bool getOnlyAddon() { return m_onlyAddon; } void setOnlyAddon(bool s) { m_onlyAddon = s; } @@ -60,11 +60,22 @@ class Paperdoll : public LuaObject void setAddon(uint32_t addon) { m_addons |= addon; } void removeAddon(uint32_t addon) { m_addons &= ~addon; } - void setOnTop(bool onTop) { for (auto& control : m_offsetDirections) control.onTop = onTop; } - void setOffset(int16_t x, int16_t y) { for (auto& control : m_offsetDirections) control.offset = { x, y }; } - void setOnTopByDir(Otc::Direction direction, bool onTop) { m_offsetDirections[direction].onTop = onTop; } + void setOnTop(bool onTop); + void setOffset(int16_t x, int16_t y); + void setOnTopByDir(Otc::Direction direction, bool onTop); + + void setMountOffset(int16_t x, int16_t y); + void setMountOnTopByDir(Otc::Direction direction, bool onTop); + + void setUseMountPattern(bool b) { m_useMountPattern = b; } + bool isUsingMountPattern() { return m_useMountPattern; } + + void setShowOnMount(bool b) { m_showOnMount = b; } + bool isShowingOnMount() { return m_showOnMount; } + + void setDirOffset(Otc::Direction direction, int8_t x, int8_t y, bool onTop = true) { m_offsetDirections[0][direction] = { onTop, {x, y} }; } + void setMountDirOffset(Otc::Direction direction, int8_t x, int8_t y, bool onTop = true) { m_offsetDirections[1][direction] = { onTop, {x, y} }; } - void setDirOffset(Otc::Direction direction, int8_t x, int8_t y, bool onTop = false) { m_offsetDirections[direction] = { onTop, {x, y} }; } void setShader(const std::string_view name); void setCanDrawOnUI(bool canDraw) { m_canDrawOnUI = canDraw; } bool canDrawOnUI() { return m_canDrawOnUI; } @@ -93,6 +104,8 @@ class Paperdoll : public LuaObject m_feet = outfit.getFeet(); } + void reset(); + private: int getCurrentAnimationPhase(); @@ -115,15 +128,17 @@ class Paperdoll : public LuaObject bool m_onlyAddon{ false }; bool m_canDrawOnUI{ true }; + bool m_useMountPattern{ false }; + bool m_showOnMount{ true }; ThingType* m_thingType{ nullptr }; - Size m_size; + float m_sizeFactor{ 1.0 }; Timer m_animationTimer; Otc::Direction m_direction{ Otc::North }; - std::array m_offsetDirections; + std::array m_offsetDirections[2]; PainterShaderProgramPtr m_shader; From 2e4d2a1d9cd456a6339070471a54fbd4e00844a6 Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Tue, 23 Dec 2025 22:48:01 -0300 Subject: [PATCH 3/7] fix: Highlight --- src/client/creature.cpp | 4 ++-- src/client/paperdoll.cpp | 10 +++++----- src/client/paperdoll.h | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/client/creature.cpp b/src/client/creature.cpp index 4ad4fe4029..55fa27812c 100644 --- a/src/client/creature.cpp +++ b/src/client/creature.cpp @@ -320,7 +320,7 @@ void Creature::internalDraw(Point dest, const Color& color) const int animationPhase = getCurrentAnimationPhase(); for (const auto& paperdoll : m_paperdolls) - paperdoll->draw(dest, animationPhase, m_outfit.hasMount(), false, true); + paperdoll->draw(dest, animationPhase, m_outfit.hasMount(), false, true, color); // outfit is a real creature if (m_outfit.isCreature()) { @@ -388,7 +388,7 @@ void Creature::internalDraw(Point dest, const Color& color) } else drawCreature(dest); for (const auto& paperdoll : m_paperdolls) - paperdoll->draw(dest, animationPhase, m_outfit.hasMount(), true, true); + paperdoll->draw(dest, animationPhase, m_outfit.hasMount(), true, true, color); // outfit is a creature imitating an item or the invisible effect } else { diff --git a/src/client/paperdoll.cpp b/src/client/paperdoll.cpp index 0318dc9c40..31275af946 100644 --- a/src/client/paperdoll.cpp +++ b/src/client/paperdoll.cpp @@ -37,7 +37,7 @@ PaperdollPtr Paperdoll::clone() return obj; } -void Paperdoll::draw(const Point& dest, uint16_t animationPhase, bool mount, bool isOnTop, bool drawThings, LightView* lightView) { +void Paperdoll::draw(const Point& dest, uint16_t animationPhase, bool mount, bool isOnTop, bool drawThings, const Color& color, LightView* lightView) { if (!m_thingType) return; @@ -65,10 +65,10 @@ void Paperdoll::draw(const Point& dest, uint16_t animationPhase, bool mount, boo g_drawPool.setScaleFactor(m_sizeFactor); if (!drawThings) { - m_thingType->draw(point, 0, m_direction, 0, 0, animation, Color::white, false, lightView); + m_thingType->draw(point, 0, m_direction, 0, 0, animation, color, false, lightView); } else { if (!m_onlyAddon) - m_thingType->draw(point, 0, m_direction, 0, 0, animation, Color::white, drawThings, lightView); + m_thingType->draw(point, 0, m_direction, 0, 0, animation, color, drawThings, lightView); for (int yPattern = 0; yPattern < m_thingType->getNumPatternY(); ++yPattern) { if (yPattern == 0 && m_onlyAddon) @@ -81,7 +81,7 @@ void Paperdoll::draw(const Point& dest, uint16_t animationPhase, bool mount, boo if (m_shader) g_drawPool.setShaderProgram(m_shader, true); - m_thingType->draw(point, 0, m_direction, yPattern, static_cast(mount), animation, Color::white); + m_thingType->draw(point, 0, m_direction, yPattern, static_cast(mount), animation, color); if (m_thingType->getLayers() > 1) { g_drawPool.setCompositionMode(CompositionMode::MULTIPLY); @@ -101,7 +101,7 @@ void Paperdoll::drawLight(const Point& dest, bool mount, LightView* lightView) { if (!lightView) return; const auto& dirControl = m_offsetDirections[mount][m_direction]; - draw(dest, 0, mount, dirControl.onTop, false, lightView); + draw(dest, 0, mount, dirControl.onTop, false, Color::white, lightView); } int Paperdoll::getCurrentAnimationPhase() diff --git a/src/client/paperdoll.h b/src/client/paperdoll.h index a5de0a1546..71d83b3047 100644 --- a/src/client/paperdoll.h +++ b/src/client/paperdoll.h @@ -31,7 +31,7 @@ class Paperdoll : public LuaObject { public: - void draw(const Point& /*dest*/, uint16_t animationPhase, bool mount, bool isOnTop, bool drawThings, LightView* = nullptr); + void draw(const Point& /*dest*/, uint16_t animationPhase, bool mount, bool isOnTop, bool drawThings, const Color& color, LightView* = nullptr); void drawLight(const Point& /*dest*/, bool mount, LightView*); uint16_t getId() { return m_id; } From 08246f7fea4a4065fbbda16afdc7607d6bb375ca Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Tue, 23 Dec 2025 22:49:26 -0300 Subject: [PATCH 4/7] Update CMakeLists.txt --- src/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 207e78cb75..6e14d51ceb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -288,6 +288,8 @@ set(SOURCE_FILES client/protocolgame.cpp client/protocolgameparse.cpp client/protocolgamesend.cpp + client/paperdoll.cpp + client/paperdollmanager.cpp client/spriteappearances.cpp client/spritemanager.cpp client/statictext.cpp From 74f0b9a43fb9cbdb60795f86eb98b350d9ced4a5 Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Tue, 23 Dec 2025 22:58:37 -0300 Subject: [PATCH 5/7] Update demon.lua --- modules/game_paperdolls/configs/demon.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/game_paperdolls/configs/demon.lua b/modules/game_paperdolls/configs/demon.lua index 37f00513d6..1812ced0dc 100644 --- a/modules/game_paperdolls/configs/demon.lua +++ b/modules/game_paperdolls/configs/demon.lua @@ -1,6 +1,6 @@ --[[ registerThingConfig(thingId, thingType) - set(attachedEffectId, config) + set(paperdollId, config) ]] -- local c = PaperdollManager.registerThingConfig(35) From 262f8091335f54551cd400c67633b992fe6369ed Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Tue, 23 Dec 2025 23:11:22 -0300 Subject: [PATCH 6/7] cleanup --- modules/game_paperdolls/paperdolls.lua | 32 +------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/modules/game_paperdolls/paperdolls.lua b/modules/game_paperdolls/paperdolls.lua index c92e47d5ab..3c602ff907 100644 --- a/modules/game_paperdolls/paperdolls.lua +++ b/modules/game_paperdolls/paperdolls.lua @@ -2,7 +2,7 @@ register(id, name, thingId, config) config = { drawOnUI, priority, onlyAddon, addon, - shader, bounce, fixed, sizeFactor, + shader, sizeFactor, color, headColor, bodyColor, legsColor, feetColor, useMountPattern, showOnMount offset{x, y, onTop}, dirOffset[dir]{x, y, onTop}, @@ -39,8 +39,6 @@ PaperdollManager.register(3, 'Peitoral', 367, { PaperdollManager.register(4, 'Akuma Aura', 664, { priority = 4, addon = 1, - bounce = true, - fixed = true, onlyAddon = true }) @@ -50,31 +48,3 @@ PaperdollManager.register(5, 'Mochila', 136, { color = 77, onlyAddon = true }) - -PaperdollManager.register(1990, 'wings1990', 136, { - priority = 5, - onlyAddon = true, - addon = 2, - onTop = true, - bounce = true, - fixed = false, - useMountPattern = true, - dirOffset = { - [North] = { 0, 0, true }, - [East] = { 0, 0, true }, -- x+ esquerda, y+ = cima - [South] = { 0, 0, true }, - [West] = { 0, 0, true } -- x- = esquerda, y+ = cima - - -- x,y (x+ = esquerda, y+ = baixo) - } -}) - -PaperdollManager.register(130, 'wings130', 136, { - priority = 4, - onlyAddon = true, - addon = 1, - onTop = true, - bounce = false, - fixed = false, - useMountPattern = true -}) From ed3ecde4cf833c8479bd0d95c07d87fe50626b36 Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Wed, 24 Dec 2025 13:29:13 -0300 Subject: [PATCH 7/7] update --- src/client/paperdoll.cpp | 2 +- src/client/paperdoll.h | 2 +- src/client/paperdollmanager.cpp | 2 +- src/client/paperdollmanager.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/paperdoll.cpp b/src/client/paperdoll.cpp index 31275af946..3a981bf329 100644 --- a/src/client/paperdoll.cpp +++ b/src/client/paperdoll.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2022 OTClient + * Copyright (c) 2010-2025 OTClient * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/client/paperdoll.h b/src/client/paperdoll.h index 71d83b3047..861049887a 100644 --- a/src/client/paperdoll.h +++ b/src/client/paperdoll.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2022 OTClient + * Copyright (c) 2010-2025 OTClient * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/client/paperdollmanager.cpp b/src/client/paperdollmanager.cpp index 795f4b1758..548363a585 100644 --- a/src/client/paperdollmanager.cpp +++ b/src/client/paperdollmanager.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2022 OTClient + * Copyright (c) 2010-2025 OTClient * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/client/paperdollmanager.h b/src/client/paperdollmanager.h index 74a6af363f..4afefc3339 100644 --- a/src/client/paperdollmanager.h +++ b/src/client/paperdollmanager.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2022 OTClient + * Copyright (c) 2010-2025 OTClient * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal