diff --git a/README.md b/README.md
index 586d3f7615..900b02b810 100644
--- a/README.md
+++ b/README.md
@@ -535,7 +535,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) |
@@ -547,9 +546,9 @@ Have found a bug? Please create an issue in our [bug tracker](https://github.com
| Protocol / version | Description | Required Feature | Compatibility |
|--------------------- |----------------------------- |----------------------------------------------------- |--------------- |
-| TFS
(7.72) | Downgrade nekiro /
Nostalrius | [force-new-walking-formula: true](https://github.com/mehah/otclient/blob/cf7badda978de88cb3724615688e3d9da2ff4207/data/setup.otml#L21)
[item-ticks-per-frame: 75](https://github.com/mehah/otclient/blob/cf7badda978de88cb3724615688e3d9da2ff4207/data/setup.otml#L32) | ✅ |
-| TFS 0.4
(8.6) | Fir3element | [item-ticks-per-frame: 75](https://github.com/mehah/otclient/blob/cf7badda978de88cb3724615688e3d9da2ff4207/data/setup.otml#L32) | ✅ |
-| TFS 1.5
(8.0 / 8.60) | Downgrade nekiro /
MillhioreBT | [force-new-walking-formula: true](https://github.com/mehah/otclient/blob/cf7badda978de88cb3724615688e3d9da2ff4207/data/setup.otml#L21)
[item-ticks-per-frame: 75](https://github.com/mehah/otclient/blob/cf7badda978de88cb3724615688e3d9da2ff4207/data/setup.otml#L32) | ✅ |
+| TFS
(7.72) | Downgrade nekiro /
Nostalrius | [force-new-walking-formula: true](https://github.com/mehah/otclient/blob/cf7badda978de88cb3724615688e3d9da2ff4207/data/setup.otml#L21)
[item-ticks-per-frame: 500](https://github.com/mehah/otclient/blob/cf7badda978de88cb3724615688e3d9da2ff4207/data/setup.otml#L32) | ✅ |
+| TFS 0.4
(8.6) | Fir3element | [item-ticks-per-frame: 500](https://github.com/mehah/otclient/blob/cf7badda978de88cb3724615688e3d9da2ff4207/data/setup.otml#L32) | ✅ |
+| TFS 1.5
(8.0 / 8.60) | Downgrade nekiro /
MillhioreBT | [force-new-walking-formula: true](https://github.com/mehah/otclient/blob/cf7badda978de88cb3724615688e3d9da2ff4207/data/setup.otml#L21)
[item-ticks-per-frame: 500](https://github.com/mehah/otclient/blob/cf7badda978de88cb3724615688e3d9da2ff4207/data/setup.otml#L32) | ✅ |
| TFS 1.4.2
(10.98) | Release Otland | | ✅ |
| TFS 1.6
(13.10) | Main repo
otland (2024) | [See wiki](https://github.com/mehah/otclient/wiki/Tutorial-to-Use-OTC-in-TFS-main) | ✅ |
| Canary 13.21 | OpenTibiaBr | [Assets , Enable HTTP login and port 80](https://docs.opentibiabr.com/opentibiabr/projects/otclient-redemption#how-to-connect-on-canary-with-otclient-redemption) | ✅ |
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 0000000000..8af3f94e24
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 ecb2779ff2..eea0cc5a1a 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 a3968c2ebb..90c18586fa 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 520fa93e29..a2ba2a007f 100644
--- a/modules/game_features/features.lua
+++ b/modules/game_features/features.lua
@@ -206,6 +206,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 b13eac7425..7286264b6f 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,38 @@ 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 +541,7 @@ function destroy()
showFloorCheck = nil
showOutfitCheck = nil
showMountCheck = nil
+ showFamiliarCheck = nil
showWingsCheck = nil
showAuraCheck = nil
showShaderCheck = nil
@@ -527,6 +565,7 @@ function destroy()
currentOutfit = {},
outfits = {},
mounts = {},
+ familiars = {},
wings = {},
auras = {},
shaders = {},
@@ -590,7 +629,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 +698,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 +781,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 +877,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 +890,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 +934,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 +955,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 +987,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 +1297,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 +1556,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 +1568,21 @@ 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,11 @@ function accept()
settings.presets[settings.currentPreset].mounted = isMountedChecked
end
end
-
+ if g_game.getFeature(GamePlayerFamiliars) then
+ 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 0cfd3c6a1a..d84d40c384 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 af91860b51..43f544d6fd 100644
--- a/modules/gamelib/const.lua
+++ b/modules/gamelib/const.lua
@@ -215,6 +215,7 @@ GameForgeConvergence = 119
GameAllowCustomBotScripts = 120
GameColorizedLootValue = 121
GameAllowPreWalk = 122
+GamePlayerFamiliars = 123
TextColors = {
red = '#f55e5e', -- '#c83200'
diff --git a/src/client/const.h b/src/client/const.h
index 7ce187f836..b4b924f56c 100644
--- a/src/client/const.h
+++ b/src/client/const.h
@@ -550,7 +550,8 @@ namespace Otc
GameForgeConvergence = 119,
GameAllowCustomBotScripts = 120,
GameColorizedLootValue = 121,
- GameAllowPreWalk = 122,
+ GameAllowPreWalk = 122,
+ GamePlayerFamiliars = 123,
LastGameFeature
};
diff --git a/src/client/game.cpp b/src/client/game.cpp
index 9176852901..db6d9f4e45 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 751e6992df..cf93f58e98 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 9afc2a8eea..4ab801f041 100644
--- a/src/client/luavaluecasts_client.cpp
+++ b/src/client/luavaluecasts_client.cpp
@@ -46,6 +46,20 @@ int push_luavalue(const Outfit& outfit)
g_lua.pushInteger(outfit.getMount());
g_lua.setField("mount");
}
+ if (g_game.getFeature(Otc::GamePlayerFamiliars)) {
+ g_lua.pushInteger(outfit.getFamiliar());
+ g_lua.setField("familiar");
+ }
+ 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;
}
@@ -74,6 +88,10 @@ bool luavalue_cast(const int index, Outfit& outfit)
g_lua.getField("mount", index);
outfit.setMount(g_lua.popInteger());
}
+ if (g_game.getFeature(Otc::GamePlayerFamiliars)) {
+ g_lua.getField("familiar", index);
+ outfit.setFamiliar(g_lua.popInteger());
+ }
if (g_game.getFeature(Otc::GameWingsAurasEffectsShader)) {
g_lua.getField("wings", index);
outfit.setWing(g_lua.popInteger());
diff --git a/src/client/outfit.cpp b/src/client/outfit.cpp
index f62bd5359a..8f94bba387 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 623c65e000..2bed8231d9 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 44efc3248f..b8276ba168 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 d8ae7daa01..c0ba839b8c 100644
--- a/src/client/protocolgame.h
+++ b/src/client/protocolgame.h
@@ -200,6 +200,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 ec5c54a2e4..f702d9d469 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;
@@ -2575,7 +2578,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();
@@ -2583,15 +2586,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;
@@ -2605,40 +2608,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
@@ -2679,7 +2686,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)
@@ -3227,7 +3234,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);
@@ -3466,10 +3473,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);
@@ -5327,6 +5338,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 61e9615259..558a41f1a8 100644
--- a/src/client/protocolgamesend.cpp
+++ b/src/client/protocolgamesend.cpp
@@ -781,8 +781,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)) {