diff --git a/README.md b/README.md index 27a7425a1..50cf96fa5 100644 --- a/README.md +++ b/README.md @@ -536,7 +536,6 @@ Have found a bug? Please create an issue in our [bug tracker](https://github.com | TO-DO list | Status | PR | |----------------------- |----------------------------------- |------ | | Android compatibility | ![](https://geps.dev/progress/50) | [Branch](https://github.com/mehah/otclient/tree/mobile-working) | -| Familiar outfit | ![](https://geps.dev/progress/30) | [#39](https://github.com/Nottinghster/otclient/pull/39) | | wheel of destiny | ![](https://geps.dev/progress/1) | None | | Forge | ![](https://geps.dev/progress/1) | None | | Analyzer | ![](https://geps.dev/progress/10) | [#802](https://github.com/mehah/otclient/pull/802) | diff --git a/modules/game_store/images/button-blue-down.png b/data/images/ui/button-blue-qt.png similarity index 100% rename from modules/game_store/images/button-blue-down.png rename to data/images/ui/button-blue-qt.png diff --git a/data/images/ui/button-grey-qt.png b/data/images/ui/button-grey-qt.png new file mode 100644 index 000000000..8af3f94e2 Binary files /dev/null and b/data/images/ui/button-grey-qt.png differ diff --git a/data/styles/40-outfitwindow.otui b/data/styles/40-outfitwindow.otui index ecb2779ff..eea0cc5a1 100644 --- a/data/styles/40-outfitwindow.otui +++ b/data/styles/40-outfitwindow.otui @@ -45,7 +45,7 @@ PresetButton < Panel $focus: border: 1 white - image-color: #ffffff + UICreature id: creature @@ -95,20 +95,15 @@ PresetButton < Panel text: Save SelectionButton < Panel - image-source: /images/ui/button - image-color: #dfdfdf - image-clip: 0 0 22 23 + image-source: /images/ui/button-grey-qt + image-clip: 0 0 98 40 image-border: 3 border: 1 alpha focusable: true phantom: false - - $hover: - image-color: #ffffff - $focus: border: 1 white - image-color: #ffffff + UICreature id: outfit @@ -195,6 +190,28 @@ OutfitWindow < MainWindow anchors.right: parent.right text: Show Mount + FlatPanel + id: showFamiliar + height: 22 + padding: 5 + @onSetup: | + if not g_game.getFeature(GamePlayerFamiliars) then + self:hide() + self:setHeight(0) + self:setPadding(0) + end + + CheckBox + id: check + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: parent.right + text: Show Familiar + @onSetup: | + if not g_game.getFeature(GamePlayerFamiliars) then + self:hide() + end + FlatPanel id: showWings height: 22 @@ -287,7 +304,16 @@ OutfitWindow < MainWindow type: grid cell-size: 64 64 flow: true - + + UICreature + id: UIfamiliar + anchors.centerIn: parent + size: 256 256 + @onSetup: | + if not g_game.getFeature(GamePlayerFamiliars) then + self:hide() + end + UICreature id: creature anchors.centerIn: parent @@ -499,7 +525,40 @@ OutfitWindow < MainWindow anchors.bottom: parent.bottom anchors.left: prev.right anchors.right: parent.right - + + Panel + id: familiar + height: 20 + @onSetup: | + if not g_game.getFeature(GamePlayerFamiliars) then + self:hide() + self:setHeight(0) + end + + CheckBox + id: check + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + image-source: /images/ui/outfits/checkbox_round + text-offset: 15 0 + text: Familiar: + width: 84 + @onSetup: | + if not g_game.getFeature(GamePlayerFamiliars) then + self:hide() + end + + FlatPanel + id: name + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: prev.right + anchors.right: parent.right + @onSetup: | + if not g_game.getFeature(GamePlayerFamiliars) then + self:hide() + end + Panel id: wings height: 20 @@ -590,7 +649,7 @@ OutfitWindow < MainWindow anchors.left: parent.left image-source: /images/ui/outfits/checkbox_round text-offset: 15 0 - text: Effects Bar: + text: Effects: width: 84 FlatPanel @@ -600,8 +659,6 @@ OutfitWindow < MainWindow anchors.left: prev.right anchors.right: parent.right - - Panel id: title height: 20 @@ -715,12 +772,20 @@ OutfitWindow < MainWindow margin-left: 5 height: 50 text: Filter + + QtCheckBox + id: onlyMine + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + text: "Only mine" + width: 84 TextEdit id: search - anchors.fill: parent + anchors.left: prev.right + anchors.right: parent.right placeholder: Search by name - placeholder-color: #00000077 + placeholder-color: #c0c0c0c0 ScrollablePanel id: presetsList diff --git a/modules/game_attachedeffects/attachedeffects.lua b/modules/game_attachedeffects/attachedeffects.lua index a3968c2eb..90c18586f 100644 --- a/modules/game_attachedeffects/attachedeffects.lua +++ b/modules/game_attachedeffects/attachedeffects.lua @@ -58,7 +58,6 @@ function controller:onTerminate() g_attachedEffects.clear() end --- @ note: sorry, I couldn't find any other way to do it function getCategory(id) return AttachedEffectManager.get(id).thingCategory end @@ -84,5 +83,3 @@ function thingId(id) return "None" end end - --- @ diff --git a/modules/game_features/features.lua b/modules/game_features/features.lua index fcd0f702f..6040095ef 100644 --- a/modules/game_features/features.lua +++ b/modules/game_features/features.lua @@ -205,6 +205,7 @@ controller:registerEvents(g_game, { end if version >= 1281 then + g_game.enableFeature(GamePlayerFamiliars) g_game.disableFeature(GameEnvironmentEffect) g_game.disableFeature(GameItemAnimationPhase) end diff --git a/modules/game_outfit/outfit.lua b/modules/game_outfit/outfit.lua index b13eac742..a092a98ce 100644 --- a/modules/game_outfit/outfit.lua +++ b/modules/game_outfit/outfit.lua @@ -3,6 +3,12 @@ local opcodeSystem = { id = 213 } +local statesOutft ={ + available = 0, + store = 1, + goldenOutfitTooltip = 2 +} + local window = nil local appearanceGroup = nil local colorModeGroup = nil @@ -19,7 +25,7 @@ local showShaderCheck = nil local showBarsCheck = nil local showTitleCheck = nil local showEffectsCheck = nil - +local showFamiliarCheck = nil local colorBoxes = {} local currentColorBox = nil @@ -33,6 +39,7 @@ local ServerData = { currentOutfit = {}, outfits = {}, mounts = {}, + familiars = {}, wings = {}, auras = {}, shaders = {}, @@ -140,7 +147,7 @@ local function showSelectionList(data, tempValue, tempField, onSelectCallback) window.listSearch:show() end -local AppearanceData = {"preset", "outfit", "mount", "wings", "aura", "effects", "shader", "healthBar", "title"} +local AppearanceData = {"preset", "outfit", "mount", "familiar", "wings", "aura", "effects", "shader", "healthBar", "title"} function init() if opcodeSystem.enable then @@ -178,10 +185,18 @@ function terminate() end function onMovementChange(checkBox, checked) - if checked == true then - previewCreature:getCreature():setStaticWalking(1000) - else - previewCreature:getCreature():setStaticWalking(0) + local walkingSpeed = checked and 1000 or 0 + + local mainCreature = previewCreature:getCreature() + if mainCreature then + mainCreature:setStaticWalking(walkingSpeed) + end + + if g_game.getFeature(GamePlayerFamiliars) then + local familiarCreature = previewFamiliar:getCreature() + if familiarCreature then + familiarCreature:setStaticWalking(walkingSpeed) + end end settings.movement = checked @@ -245,9 +260,15 @@ function onShowMountChange(checkBox, checked) updatePreview() end +function onShowFamiliarChange(checkBox, checked) + settings.showFamiliar = checked + updatePreview() +end + function onShowOutfitChange(checkBox, checked) settings.showOutfit = checked showMountCheck:setEnabled(settings.showOutfit) + showFamiliarCheck:setEnabled(settings.showOutfit) showWingsCheck:setEnabled(settings.showOutfit) showAuraCheck:setEnabled(settings.showOutfit) showShaderCheck:setEnabled(settings.showOutfit) @@ -290,6 +311,7 @@ local PreviewOptions = { ["showFloor"] = onShowFloorChange, ["showOutfit"] = onShowOutfitChange, ["showMount"] = onShowMountChange, + ["showFamiliar"] = onShowFamiliarChange, ["showWings"] = onShowWingsChange, ["showAura"] = onShowAuraChange, ["showShader"] = onShowShaderChange, @@ -298,7 +320,7 @@ local PreviewOptions = { ["showEffects"] = onShowEffectsChange } -function create(player, outfitList, creatureMount, mountList, wingsList, auraList, effectsList, shaderList) +function create(player, outfitList, creatureMount, mountList, familiarList, wingsList, auraList, effectsList, shaderList) if ignoreNextOutfitWindow and g_clock.millis() < ignoreNextOutfitWindow + 1000 then return end @@ -312,11 +334,11 @@ function create(player, outfitList, creatureMount, mountList, wingsList, auraLis end loadSettings() - ServerData = { currentOutfit = currentOutfit, outfits = outfitList, mounts = mountList, + familiars = familiarList, wings = wingsList, auras = auraList, effects = effectsList, @@ -329,29 +351,6 @@ function create(player, outfitList, creatureMount, mountList, wingsList, auraLis window = g_ui.displayUI("outfitwindow") - 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.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 - local widget, data = check[1], check[2] - if not table.empty(data) then - widget:setVisible(true) - else - widget:setVisible(false) - end - end - floor = window.preview.panel.floor for i = 1, floorTiles * floorTiles do g_ui.createWidget("FloorTile", floor) @@ -365,6 +364,10 @@ function create(player, outfitList, creatureMount, mountList, wingsList, auraLis previewCreature = window.preview.panel.creature previewCreature:setCreatureSize(200) previewCreature:setCenter(true) + + previewFamiliar = window.preview.panel.UIfamiliar + previewFamiliar:setCreatureSize(200) + previewFamiliar:setCenter(true) -- previewCreature:setBorderColor('red') -- previewCreature:setBorderWidth(2) @@ -415,6 +418,7 @@ function create(player, outfitList, creatureMount, mountList, wingsList, auraLis showFloorCheck = window.preview.options.showFloor.check showOutfitCheck = window.preview.options.showOutfit.check showMountCheck = window.preview.options.showMount.check + showFamiliarCheck = window.preview.options.showFamiliar.check showWingsCheck = window.preview.options.showWings.check showAuraCheck = window.preview.options.showAura.check showShaderCheck = window.preview.options.showShader.check @@ -432,6 +436,7 @@ function create(player, outfitList, creatureMount, mountList, wingsList, auraLis if not settings.showOutfit then showMountCheck:setEnabled(false) + showFamiliarCheck:setEnabled(false) showWingsCheck:setEnabled(false) showAuraCheck:setEnabled(false) showShaderCheck:setEnabled(false) @@ -442,6 +447,7 @@ function create(player, outfitList, creatureMount, mountList, wingsList, auraLis showOutfitCheck:setChecked(settings.showOutfit) showMountCheck:setChecked(settings.showMount) + showFamiliarCheck:setChecked(settings.showFamiliar) showWingsCheck:setChecked(settings.showWings) showAuraCheck:setChecked(settings.showAura) showShaderCheck:setChecked(settings.showShader) @@ -472,6 +478,7 @@ function create(player, outfitList, creatureMount, mountList, wingsList, auraLis appearanceGroup:addWidget(window.appearance.settings.preset.check) appearanceGroup:addWidget(window.appearance.settings.outfit.check) appearanceGroup:addWidget(window.appearance.settings.mount.check) + appearanceGroup:addWidget(window.appearance.settings.familiar.check) appearanceGroup:addWidget(window.appearance.settings.aura.check) appearanceGroup:addWidget(window.appearance.settings.wings.check) appearanceGroup:addWidget(window.appearance.settings.shader.check) @@ -493,8 +500,37 @@ function create(player, outfitList, creatureMount, mountList, wingsList, auraLis window.preview.options.showMount:setVisible(g_game.getFeature(GamePlayerMounts)) window.configure.mount:setVisible(g_game.getFeature(GamePlayerMounts)) window.appearance.settings.mount:setVisible(g_game.getFeature(GamePlayerMounts)) - window.listSearch.search.onKeyPress = onFilterSearch + + 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}} + + for _, check in ipairs(checks) do + local widget, data = check[1], check[2] + if not table.empty(data) then + widget:setVisible(true) + else + widget:setVisible(false) + end + end previewCreature:getCreature():setDirection(2) + window.listSearch.search.onKeyPress = onFilterSearch + window.listSearch.onlyMine.onCheckChange = onFilterOnlyMine end function destroy() @@ -504,6 +540,7 @@ function destroy() showFloorCheck = nil showOutfitCheck = nil showMountCheck = nil + showFamiliarCheck = nil showWingsCheck = nil showAuraCheck = nil showShaderCheck = nil @@ -527,6 +564,7 @@ function destroy() currentOutfit = {}, outfits = {}, mounts = {}, + familiars = {}, wings = {}, auras = {}, shaders = {}, @@ -590,7 +628,8 @@ function newPreset() effects = "None", wings = "None", shader = "None", - mounted = window.configure.mount.check:isChecked() + mounted = window.configure.mount.check:isChecked(), + familiar = "None" } presetWidget:focus() @@ -658,7 +697,7 @@ function savePreset() settings.presets[presetId].outfit = outfitCopy settings.presets[presetId].mounted = window.configure.mount.check:isChecked() - + settings.presets[presetId].familiar = tempOutfit.familiar or 0 settings.presets[presetId].shader = "Outfit - Default" settings.presets[presetId].auras = lastSelectAura or "None" settings.presets[presetId].effects = lastSelectEffects or "None" @@ -741,7 +780,8 @@ function onAppearanceChange(widget, selectedWidget) showOutfits() elseif id == "mount" then showMounts() - -- numbers + elseif id == "familiar" then + showFamiliars() elseif id == "aura" then showSelectionList(ServerData.auras, tempOutfit.auras, "aura", onAuraSelect) elseif id == "wings" then @@ -836,6 +876,7 @@ function showOutfits() outfit.type = outfitData[1] outfit.addons = outfitData[3] outfit.mount = 0 + outfit.familiar = 0 outfit.auras = 0 outfit.wings = 0 outfit.shader = "Outfit - Default" @@ -848,8 +889,14 @@ function showOutfits() if thingType:getRealSize() > 0 then button.outfit:setCreatureSize(thingType:getRealSize() + 32) end - --button.outfit:setBorderColor('red') - --button.outfit:setBorderWidth(2) + + local state = outfitData[4] + if state then + button.state = state + if state ~= statesOutft.available then + button:setImageSource("/images/ui/button-blue-qt") + end + end button.name:setText(outfitData[2]) if tempOutfit.type == outfitData[1] then @@ -886,6 +933,7 @@ function showMounts() local button = g_ui.createWidget("SelectionButton", window.selectionList) button:setId(0) button.name:setText("None") + button.state = 0 focused = 0 for _, mountData in ipairs(ServerData.mounts) do @@ -906,6 +954,14 @@ function showMounts() if tempOutfit.mount == mountData[1] then focused = mountData[1] end + + local state = mountData[3] + if state then + button.state = state + if state ~= statesOutft.available then + button:setImageSource("/images/ui/button-blue-qt") + end + end end if #ServerData.mounts == 1 then @@ -930,6 +986,53 @@ function showMounts() window.listSearch:show() end +function showFamiliars() + window.presetsList:hide() + window.presetsScroll:hide() + window.presetButtons:hide() + + window.selectionList.onChildFocusChange = nil + window.selectionList:destroyChildren() + + local focused = nil + + local button = g_ui.createWidget("SelectionButton", window.selectionList) + button:setId(0) + button.name:setText("None") + focused = 0 + for _, familiarData in ipairs(ServerData.familiars) do + local button = g_ui.createWidget("SelectionButton", window.selectionList) + button:setId(familiarData[1]) + + button.outfit:setOutfit({ + type = familiarData[1] + }) + + button.name:setText(familiarData[2]) + if tempOutfit.familiar == familiarData[1] then + focused = familiarData[1] + end + end + + if #ServerData.familiars == 1 then + window.selectionList:focusChild(nil) + end + + if focused then + local w = window.selectionList[focused] + w:focus() + window.selectionList:ensureChildVisible(w, { + x = 0, + y = 196 + }) + end + + window.selectionList.onChildFocusChange = onFamiliarSelect + window.selectionList:show() + window.selectionScroll:show() + window.listSearch:show() +end + function showShaders() window.presetsList:hide() window.presetsScroll:hide() @@ -1193,6 +1296,34 @@ function onMountSelect(list, focusedChild, unfocusedChild, reason) end end +function onFamiliarSelect(list, focusedChild, unfocusedChild, reason) + if focusedChild then + local familiarType = tonumber(focusedChild:getId()) + + tempOutfit.familiar = familiarType + + deselectPreset() + + previewFamiliar:setOutfit({ + type = familiarType + }) + + updatePreview() + + if settings.showFamiliar and g_game.getFeature(GamePlayerFamiliars) and familiarType > 0 then + previewCreature:setMarginRight(50) + previewFamiliar:setCreatureSize(200) + previewFamiliar:setCenter(true) + previewFamiliar:setMarginLeft(70) + else + previewCreature:setMarginRight(0) + previewFamiliar:setMarginLeft(0) + end + + updateAppearanceText("familiar", focusedChild.name:getText()) + end +end + function onAuraSelect(list, focusedChild, unfocusedChild, reason) local auraName = window.appearance.settings["aura"].name:getText() if auraName ~= "None" then @@ -1424,17 +1555,6 @@ end function updatePreview() local direction = previewCreature:getDirection() - - --[[ - without c++ - g_lua.bindClassMemberFunction("getDirection", &UICreature::getDirection); - - local direction = previewCreature:getCreature():getDirection() -> Not work - local direction= previewCreature:getDirection() ->not work - print(g_game.getLocalPlayer():getCreature():getDirection()) -> - - ]] - local previewOutfit = table.copy(tempOutfit) if not settings.showOutfit then @@ -1447,6 +1567,22 @@ function updatePreview() previewOutfit.mount = 0 end + if not settings.showFamiliar then + previewOutfit.familiar = 0 + previewCreature:setMarginRight(0) + previewFamiliar:setMarginLeft(0) + previewFamiliar:setVisible(settings.showFamiliar) + else + if previewOutfit.familiar and previewOutfit.familiar > 0 then + previewFamiliar:setVisible(true) + previewCreature:setMarginRight(50) + previewFamiliar:setCreatureSize(200) + previewFamiliar:setCenter(true) + previewFamiliar:setMarginLeft(70) + end + + end + if settings.showAura then attachOrDetachEffect(lastSelectAura, true) else @@ -1530,10 +1666,27 @@ function rotate(value) end previewCreature:getCreature():setDirection(direction) - + if g_game.getFeature(GamePlayerFamiliars) then + previewFamiliar:getCreature():setDirection(direction) + end floor:setMargin(0) end +function onFilterOnlyMine(self, checked) + addEvent(function() + local children = window.selectionList:getChildren() + for _, child in ipairs(children) do + if checked and (not child.state or child.state ~= 0) then + window.selectionList:focusChild(nil) + child:hide() + else + child:show() + end + end + end) +end + + function onFilterSearch() addEvent(function() local searchText = window.listSearch.search:getText():lower():trim() @@ -1618,6 +1771,7 @@ function loadDefaultSettings() showFloor = true, showOutfit = true, showMount = true, + showFamiliar = true, showWings = true, showAura = true, showShader = true, @@ -1660,7 +1814,12 @@ function accept() settings.presets[settings.currentPreset].mounted = isMountedChecked end end - + if g_game.getFeature(GamePlayerFamiliars) then + local player = g_game.getLocalPlayer() + if settings.currentPreset > 0 then + settings.presets[settings.currentPreset].familiar = window.configure.familiar.check:isChecked() + end + end g_game.changeOutfit(tempOutfit) if opcodeSystem.enable then sendAction("changeOutfit", { diff --git a/modules/game_store/style/ui.otui b/modules/game_store/style/ui.otui index 0cfd3c6a1..d84d40c38 100644 --- a/modules/game_store/style/ui.otui +++ b/modules/game_store/style/ui.otui @@ -288,7 +288,7 @@ confirmarSHOP < UIMessageBox anchors.left: Box.right anchors.top: content.bottom margin-left:16 - image-source: /game_store/images/button-blue-down + image-source: /images/ui/button-blue-qt image-border: 1 text-wrap: true image-clip: 0 0 98 40 diff --git a/modules/gamelib/const.lua b/modules/gamelib/const.lua index 5acb080ba..230d59bf0 100644 --- a/modules/gamelib/const.lua +++ b/modules/gamelib/const.lua @@ -214,6 +214,7 @@ GameWingsAurasEffectsShader = 118 GameForgeConvergence = 119 GameAllowCustomBotScripts = 120 GameColorizedLootValue = 121 +GamePlayerFamiliars = 122 TextColors = { red = '#f55e5e', -- '#c83200' diff --git a/src/client/const.h b/src/client/const.h index 8ce0f766f..93a614152 100644 --- a/src/client/const.h +++ b/src/client/const.h @@ -550,6 +550,7 @@ namespace Otc GameForgeConvergence = 119, GameAllowCustomBotScripts = 120, GameColorizedLootValue = 121, + GamePlayerFamiliars = 122, LastGameFeature }; diff --git a/src/client/game.cpp b/src/client/game.cpp index f48776f17..f15cbbf95 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -404,8 +404,9 @@ void Game::processRemoveAutomapFlag(const Position& pos, const uint8_t icon, con g_lua.callGlobalField("g_game", "onRemoveAutomapFlag", pos, icon, message); } -void Game::processOpenOutfitWindow(const Outfit& currentOutfit, const std::vector>& outfitList, - const std::vector>& mountList, +void Game::processOpenOutfitWindow(const Outfit& currentOutfit, const std::vector>& outfitList, + const std::vector>& mountList, + const std::vector>& familiarList, const std::vector>& wingsList, const std::vector>& aurasList, const std::vector>& effectList, @@ -430,7 +431,13 @@ void Game::processOpenOutfitWindow(const Outfit& currentOutfit, const std::vecto virtualMountCreature->setOutfit(mountOutfit); } - g_lua.callGlobalField("g_game", "onOpenOutfitWindow", virtualOutfitCreature, outfitList, virtualMountCreature, mountList, wingsList, aurasList, effectList, shaderList); + if (getFeature(Otc::GamePlayerFamiliars)) { + Outfit familiarOutfit; + familiarOutfit.setId(currentOutfit.getFamiliar()); + familiarOutfit.setCategory(ThingCategoryCreature); + } + + g_lua.callGlobalField("g_game", "onOpenOutfitWindow", virtualOutfitCreature, outfitList, virtualMountCreature, mountList, familiarList, wingsList, aurasList, effectList, shaderList); } void Game::processOpenNpcTrade(const std::vector>& items) diff --git a/src/client/game.h b/src/client/game.h index 9df762b91..a15ffd1ab 100644 --- a/src/client/game.h +++ b/src/client/game.h @@ -473,8 +473,9 @@ class Game static void processRemoveAutomapFlag(const Position& pos, uint8_t icon, std::string_view message); // outfit - void processOpenOutfitWindow(const Outfit& currentOutfit, const std::vector>& outfitList, - const std::vector>& mountList, + void processOpenOutfitWindow(const Outfit& currentOutfit, const std::vector>& outfitList, + const std::vector>& mountList, + const std::vector>& familiarList, const std::vector>& wingsList, const std::vector>& aurasList, const std::vector>& effectsList, diff --git a/src/client/luavaluecasts_client.cpp b/src/client/luavaluecasts_client.cpp index 9d8fc04f8..b21ffec66 100644 --- a/src/client/luavaluecasts_client.cpp +++ b/src/client/luavaluecasts_client.cpp @@ -46,6 +46,16 @@ int push_luavalue(const Outfit& outfit) g_lua.pushInteger(outfit.getMount()); g_lua.setField("mount"); } + if (g_game.getFeature(Otc::GameWingsAurasEffectsShader)) { + g_lua.pushInteger(outfit.getWing()); + g_lua.setField("wings"); + g_lua.pushInteger(outfit.getEffect()); + g_lua.setField("effects"); + g_lua.pushInteger(outfit.getAura()); + g_lua.setField("auras"); + g_lua.pushString(outfit.getShader()); + g_lua.setField("shaders"); + } return 1; } diff --git a/src/client/outfit.cpp b/src/client/outfit.cpp index f62bd5359..8f94bba38 100644 --- a/src/client/outfit.cpp +++ b/src/client/outfit.cpp @@ -118,6 +118,7 @@ void Outfit::resetClothes() setLegs(0); setFeet(0); setMount(0); + setFamiliar(0); setWing(0); setAura(0); setEffect(0); diff --git a/src/client/outfit.h b/src/client/outfit.h index eadef3104..9b426ec8f 100644 --- a/src/client/outfit.h +++ b/src/client/outfit.h @@ -39,6 +39,7 @@ class Outfit void setId(const uint16_t id) { m_id = id; } void setAuxId(const uint16_t id) { m_auxId = id; } void setMount(const uint16_t mount) { m_mount = mount; } + void setFamiliar(const uint16_t familiar) { m_familiar = familiar; } void setWing(const uint16_t Wing) { m_wing = Wing; } void setAura(const uint16_t Aura) { m_aura = Aura; } void setEffect(const uint16_t Effect) { m_effect = Effect; } @@ -58,6 +59,7 @@ class Outfit uint16_t getId() const { return m_id; } uint16_t getAuxId() const { return m_auxId; } uint16_t getMount() const { return m_mount; } + uint16_t getFamiliar() const { return m_familiar; } uint16_t getWing() const { return m_wing; } uint16_t getAura() const { return m_aura; } uint16_t getEffect() const { return m_effect; } @@ -94,6 +96,7 @@ class Outfit m_feet == other.m_feet && m_addons == other.m_addons && m_mount == other.m_mount && + m_familiar == other.m_familiar && m_wing == other.m_wing && m_aura == other.m_aura && m_effect == other.m_effect && @@ -109,6 +112,7 @@ class Outfit uint16_t m_id{ 0 }; uint16_t m_auxId{ 0 }; uint16_t m_mount{ 0 }; + uint16_t m_familiar{ 0 }; uint16_t m_wing{ 0 }; uint16_t m_aura{ 0 }; uint16_t m_effect{ 0 }; diff --git a/src/client/protocolcodes.h b/src/client/protocolcodes.h index 44efc3248..b8276ba16 100644 --- a/src/client/protocolcodes.h +++ b/src/client/protocolcodes.h @@ -77,6 +77,7 @@ namespace Proto GameServerCreatureShader = 54, GameServerMapShader = 55, GameServerCreatureTyping = 56, + GameServerFeatures = 67, GameServerFloorDescription = 75, // original tibia ONLY diff --git a/src/client/protocolgame.h b/src/client/protocolgame.h index 84099490c..7a9fa8a4e 100644 --- a/src/client/protocolgame.h +++ b/src/client/protocolgame.h @@ -201,6 +201,7 @@ class ProtocolGame final : public Protocol void parseFloorDescription(const InputMessagePtr& msg); void parseMapDescription(const InputMessagePtr& msg); void parseCreatureTyping(const InputMessagePtr& msg); + void parseFeatures(const InputMessagePtr& msg); void parseMapMoveNorth(const InputMessagePtr& msg); void parseMapMoveEast(const InputMessagePtr& msg); void parseMapMoveSouth(const InputMessagePtr& msg); diff --git a/src/client/protocolgameparse.cpp b/src/client/protocolgameparse.cpp index 4604e0fe3..bd2f08482 100644 --- a/src/client/protocolgameparse.cpp +++ b/src/client/protocolgameparse.cpp @@ -144,6 +144,9 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg) case Proto::GameServerCreatureTyping: parseCreatureTyping(msg); break; + case Proto::GameServerFeatures: + parseFeatures(msg); + break; case Proto::GameServerFloorDescription: parseFloorDescription(msg); break; @@ -2567,7 +2570,7 @@ void ProtocolGame::parseOpenOutfitWindow(const InputMessagePtr& msg) const msg->getU16(); // current familiar looktype } - std::vector> outfitList; + std::vector> outfitList; if (g_game.getFeature(Otc::GameNewOutfitProtocol)) { const uint16_t outfitCount = g_game.getClientVersion() >= 1281 ? msg->getU16() : msg->getU8(); @@ -2575,15 +2578,15 @@ void ProtocolGame::parseOpenOutfitWindow(const InputMessagePtr& msg) const const uint16_t outfitId = msg->getU16(); const auto& outfitName = msg->getString(); const uint8_t outfitAddons = msg->getU8(); - + uint8_t outfitMode = 0; if (g_game.getClientVersion() >= 1281) { - const uint8_t outfitMode = msg->getU8(); // mode: 0x00 - available, 0x01 store (requires U32 store offerId), 0x02 golden outfit tooltip (hardcoded) + outfitMode = msg->getU8(); // mode: 0x00 - available, 0x01 store (requires U32 store offerId), 0x02 golden outfit tooltip (hardcoded) if (outfitMode == 1) { msg->getU32(); } } - outfitList.emplace_back(outfitId, outfitName, outfitAddons); + outfitList.emplace_back(outfitId, outfitName, outfitAddons, outfitMode); } } else { uint16_t outfitStart; @@ -2597,40 +2600,44 @@ void ProtocolGame::parseOpenOutfitWindow(const InputMessagePtr& msg) const } for (auto i = outfitStart; i <= outfitEnd; ++i) { - outfitList.emplace_back(i, "", 0); + outfitList.emplace_back(i, "", 0, 0); } } - std::vector> mountList; + std::vector> mountList; if (g_game.getFeature(Otc::GamePlayerMounts)) { const uint16_t mountCount = g_game.getClientVersion() >= 1281 ? msg->getU16() : msg->getU8(); for (auto i = 0; i < mountCount; ++i) { const uint16_t mountId = msg->getU16(); // mount type const auto& mountName = msg->getString(); // mount name - + uint8_t mountMode = 0; if (g_game.getClientVersion() >= 1281) { - const uint8_t mountMode = msg->getU8(); // mode: 0x00 - available, 0x01 store (requires U32 store offerId) + mountMode = msg->getU8(); // mode: 0x00 - available, 0x01 store (requires U32 store offerId) if (mountMode == 1) { msg->getU32(); } } - mountList.emplace_back(mountId, mountName); + mountList.emplace_back(mountId, mountName, mountMode); } } - if (g_game.getClientVersion() >= 1281) { + std::vector > familiarList; + if (g_game.getFeature(Otc::GamePlayerFamiliars)) { const uint16_t familiarCount = msg->getU16(); for (auto i = 0; i < familiarCount; ++i) { - msg->getU16(); // familiar lookType - msg->getString(); // familiar name + const uint16_t familiarLookType = msg->getU16(); // familiar lookType + const auto& familiarName = msg->getString(); // familiar name const uint8_t familiarMode = msg->getU8(); // 0x00 // mode: 0x00 - available, 0x01 store (requires U32 store offerId) if (familiarMode == 1) { msg->getU32(); } + familiarList.emplace_back(familiarLookType, familiarName); } + } + if (g_game.getClientVersion() >= 1281) { msg->getU8(); // Try outfit mode (?) msg->getU8(); // (bool) mounted msg->getU8(); // (bool) randomize mount @@ -2671,7 +2678,7 @@ void ProtocolGame::parseOpenOutfitWindow(const InputMessagePtr& msg) const } } - g_game.processOpenOutfitWindow(currentOutfit, outfitList, mountList, wingList, auraList, effectList, shaderList); + g_game.processOpenOutfitWindow(currentOutfit, outfitList, mountList, familiarList, wingList, auraList, effectList, shaderList); } void ProtocolGame::parseKillTracker(const InputMessagePtr& msg) @@ -3219,7 +3226,7 @@ Outfit ProtocolGame::getOutfit(const InputMessagePtr& msg, const bool parseMount outfit.setMount(mount); } - if (g_game.getFeature(Otc::GameWingsAurasEffectsShader)) { + if (g_game.getFeature(Otc::GameWingsAurasEffectsShader) && parseMount) { const uint16_t wings = msg->getU16(); outfit.setWing(wings); @@ -3458,10 +3465,14 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type) cons creature->setMasterId(masterId); creature->setShader(shader); creature->clearTemporaryAttachedEffects(); + std::unordered_set currentAttachedEffectIds; + for (const auto& attachedEffect : creature->getAttachedEffects()) { + currentAttachedEffectIds.insert(attachedEffect->getId()); + } for (const auto effectId : attachedEffectList) { const auto& effect = g_attachedEffects.getById(effectId); - if (effect) { + if (effect && currentAttachedEffectIds.find(effectId) == currentAttachedEffectIds.end()) { const auto& clonedEffect = effect->clone(); clonedEffect->setPermanent(false); creature->attachEffect(clonedEffect); @@ -5319,6 +5330,20 @@ void ProtocolGame::parseCreatureTyping(const InputMessagePtr& msg) creature->setTyping(typing); } +void ProtocolGame::parseFeatures(const InputMessagePtr& msg) +{ + const uint16_t features = msg->getU16(); + for (auto i = 0; i < features; ++i) { + const auto feature = static_cast(msg->getU8()); + const auto enabled = static_cast(msg->getU8()); + if (enabled) { + g_game.enableFeature(feature); + } else { + g_game.disableFeature(feature); + } + } +} + void ProtocolGame::parseHighscores(const InputMessagePtr& msg) { const bool isEmpty = static_cast(msg->getU8()); diff --git a/src/client/protocolgamesend.cpp b/src/client/protocolgamesend.cpp index 9a93dd66a..3e3aaf617 100644 --- a/src/client/protocolgamesend.cpp +++ b/src/client/protocolgamesend.cpp @@ -790,8 +790,11 @@ void ProtocolGame::sendChangeOutfit(const Outfit& outfit) msg->addU8(outfit.hasMount()); } + if (g_game.getFeature(Otc::GamePlayerFamiliars)) { + msg->addU16(outfit.getFamiliar()); //familiars + } + if (g_game.getClientVersion() >= 1281) { - msg->addU16(0x00); //familiars msg->addU8(0x00); //randomizeMount } if (g_game.getFeature(Otc::GameWingsAurasEffectsShader)) {