mirror of
https://github.com/OTCv8/otclientv8.git
synced 2025-04-29 18:59:20 +02:00
Version 2.5 - http://otclient.net/showthread.php?tid=238
This commit is contained in:
parent
1729e7d635
commit
f17ac1ec71
2
LICENSE
2
LICENSE
@ -1,7 +1,7 @@
|
||||
OTClientV8 is made available under the MIT License
|
||||
|
||||
Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient>
|
||||
Copyright (c) 2018-2019 OTClientV8 <https://github.com/OTCv8/otclientv8>
|
||||
Copyright (c) 2018-2020 OTClientV8 <https://github.com/OTCv8/otclientv8>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,7 +1,9 @@
|
||||
# OTClientV8
|
||||
|
||||
OTClientV8 is highly optimized tile based 2d game engine built with c++, lua, physfs, OpenGL ES 2.0 and OpenAL.
|
||||
It works well even on 12 years old computers. In 2020 it has been used by more than 90k unique players.
|
||||
OTClientV8 is highly optimized, cross-platform tile based 2d game engine built with c++17, lua, physfs, OpenGL ES 2.0 and OpenAL.
|
||||
It has been created as alternative client for game called [Tibia](https://tibia.com/), but now it's much more functional and powerful.
|
||||
It works well even on 12 years old computers. In June 2020 it reached 100k unique installations.
|
||||
|
||||
Supported platforms:
|
||||
- Windows (min. Windows 7)
|
||||
- Linux
|
||||
@ -12,7 +14,7 @@ Planned support:
|
||||
- iOS
|
||||
- WebAssembly
|
||||
|
||||
On this GitHub you can find free version of OTClientV8. It comes without c++ sources, there are prebuilt executables instead.
|
||||
On this GitHub you can find free version of OTClientV8. It comes without all c++ sources, there are prebuilt executables instead.
|
||||
In many cases, you won't need access to sources, you can add a lot of custom features in lua.
|
||||
If you're interested in buying access to sources, contact otclient@otclient.ovh or kondrah#7945 on discord.
|
||||
|
||||
|
17
data/shaders/outfit_default_fragment.frag
Normal file
17
data/shaders/outfit_default_fragment.frag
Normal file
@ -0,0 +1,17 @@
|
||||
uniform mat4 u_Color;
|
||||
varying vec2 v_TexCoord;
|
||||
varying vec2 v_TexCoord2;
|
||||
uniform sampler2D u_Tex0;
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = texture2D(u_Tex0, v_TexCoord);
|
||||
vec4 texcolor = texture2D(u_Tex0, v_TexCoord2);
|
||||
if(texcolor.r > 0.9) {
|
||||
gl_FragColor *= texcolor.g > 0.9 ? u_Color[0] : u_Color[1];
|
||||
} else if(texcolor.g > 0.9) {
|
||||
gl_FragColor *= u_Color[2];
|
||||
} else if(texcolor.b > 0.9) {
|
||||
gl_FragColor *= u_Color[3];
|
||||
}
|
||||
if(gl_FragColor.a < 0.01) discard;
|
||||
}
|
16
data/shaders/outfit_default_vertex.frag
Normal file
16
data/shaders/outfit_default_vertex.frag
Normal file
@ -0,0 +1,16 @@
|
||||
attribute vec2 a_Vertex;
|
||||
attribute vec2 a_TexCoord;
|
||||
uniform mat3 u_TextureMatrix;
|
||||
varying vec2 v_TexCoord;
|
||||
varying vec2 v_TexCoord2;
|
||||
uniform mat3 u_TransformMatrix;
|
||||
uniform mat3 u_ProjectionMatrix;
|
||||
uniform vec2 u_Offset;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = vec4((u_ProjectionMatrix * u_TransformMatrix * vec3(a_Vertex.xy, 1.0)).xy, 1.0, 1.0);
|
||||
v_TexCoord = (u_TextureMatrix * vec3(a_TexCoord,1.0)).xy;
|
||||
v_TexCoord2 = (u_TextureMatrix * vec3(a_TexCoord + u_Offset,1.0)).xy;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ local serverSelector
|
||||
local clientVersionSelector
|
||||
local serverHostTextEdit
|
||||
local rememberPasswordBox
|
||||
local protos = {"740", "760", "772", "792", "800", "810", "854", "860", "870", "961", "1077", "1090", "1096", "1098", "1099", "1100"}
|
||||
local protos = {"740", "760", "772", "792", "800", "810", "854", "860", "870", "961", "1000", "1077", "1090", "1096", "1098", "1099", "1100", "1200"}
|
||||
|
||||
local checkedByUpdater = {}
|
||||
|
||||
@ -107,14 +107,91 @@ local function validateThings(things)
|
||||
return incorrectThings
|
||||
end
|
||||
|
||||
local function onTibia12HTTPResult(session, playdata)
|
||||
local characters = {}
|
||||
local worlds = {}
|
||||
local account = {
|
||||
status = 0,
|
||||
subStatus = 0,
|
||||
premDays = 0
|
||||
}
|
||||
if session["status"] ~= "active" then
|
||||
account.status = 1
|
||||
end
|
||||
if session["ispremium"] then
|
||||
account.subStatus = 1 -- premium
|
||||
end
|
||||
if session["premiumuntil"] > g_clock.seconds() then
|
||||
account.subStatus = math.floor((session["premiumuntil"] - g_clock.seconds()) / 86400)
|
||||
end
|
||||
|
||||
local things = {
|
||||
data = {G.clientVersion .. "/Tibia.dat", ""},
|
||||
sprites = {G.clientVersion .. "/Tibia.spr", ""},
|
||||
}
|
||||
|
||||
local incorrectThings = validateThings(things)
|
||||
if #incorrectThings > 0 then
|
||||
g_logger.error(incorrectThings)
|
||||
if Updater and not checkedByUpdater[G.clientVersion] then
|
||||
checkedByUpdater[G.clientVersion] = true
|
||||
return Updater.check({
|
||||
version = G.clientVersion,
|
||||
host = G.host
|
||||
})
|
||||
else
|
||||
return EnterGame.onError(incorrectThings)
|
||||
end
|
||||
end
|
||||
|
||||
onSessionKey(nil, session["sessionkey"])
|
||||
|
||||
for _, world in pairs(playdata["worlds"]) do
|
||||
worlds[world.id] = {
|
||||
name = world.name,
|
||||
port = world.externalportunprotected or world.externalportprotected,
|
||||
address = world.externaladdressunprotected or world.externaladdressprotected
|
||||
}
|
||||
end
|
||||
|
||||
for _, character in pairs(playdata["characters"]) do
|
||||
local world = worlds[character.worldid]
|
||||
if world then
|
||||
table.insert(characters, {
|
||||
name = character.name,
|
||||
worldName = world.name,
|
||||
worldIp = world.address,
|
||||
worldPort = world.port
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
g_game.setCustomProtocolVersion(0)
|
||||
g_game.chooseRsa(G.host)
|
||||
g_game.setClientVersion(G.clientVersion)
|
||||
g_game.setProtocolVersion(g_game.getClientProtocolVersion(G.clientVersion))
|
||||
g_game.setCustomOs(-1) -- disable
|
||||
if not g_game.getFeature(GameExtendedOpcode) then
|
||||
g_game.setCustomOs(5) -- set os to windows if opcodes are disabled
|
||||
end
|
||||
|
||||
onCharacterList(nil, characters, account, nil)
|
||||
end
|
||||
|
||||
local function onHTTPResult(data, err)
|
||||
if err then
|
||||
return EnterGame.onError(err)
|
||||
end
|
||||
if data['error'] and #data['error'] > 0 then
|
||||
if data['error'] and data['error']:len() > 0 then
|
||||
return EnterGame.onLoginError(data['error'])
|
||||
elseif data['errorMessage'] and data['errorMessage']:len() > 0 then
|
||||
return EnterGame.onLoginError(data['errorMessage'])
|
||||
end
|
||||
|
||||
if type(data["session"]) == "table" and type(data["playdata"]) == "table" then
|
||||
return onTibia12HTTPResult(data["session"], data["playdata"])
|
||||
end
|
||||
|
||||
local characters = data["characters"]
|
||||
local account = data["account"]
|
||||
local session = data["session"]
|
||||
@ -331,11 +408,18 @@ function EnterGame.doLogin()
|
||||
g_settings.set('client-version', G.clientVersion)
|
||||
g_settings.save()
|
||||
|
||||
if G.host:find("http") ~= nil then
|
||||
local server_params = G.host:split(":")
|
||||
if G.host:lower():find("http") ~= nil then
|
||||
if #server_params >= 4 then
|
||||
G.host = server_params[1] .. ":" .. server_params[2] .. ":" .. server_params[3]
|
||||
G.clientVersion = tonumber(server_params[4])
|
||||
elseif #server_params >= 3 then
|
||||
G.host = server_params[1] .. ":" .. server_params[2]
|
||||
G.clientVersion = tonumber(server_params[3])
|
||||
end
|
||||
return EnterGame.doLoginHttp()
|
||||
end
|
||||
|
||||
local server_params = G.host:split(":")
|
||||
local server_ip = server_params[1]
|
||||
local server_port = 7171
|
||||
if #server_params >= 2 then
|
||||
@ -381,6 +465,9 @@ function EnterGame.doLogin()
|
||||
EnterGame.show()
|
||||
end })
|
||||
|
||||
if G.clientVersion == 1000 then -- some people don't understand that tibia 10 uses 1100 protocol
|
||||
G.clientVersion = 1100
|
||||
end
|
||||
-- if you have custom rsa or protocol edit it here
|
||||
g_game.setClientVersion(G.clientVersion)
|
||||
g_game.setProtocolVersion(g_game.getClientProtocolVersion(G.clientVersion))
|
||||
@ -421,14 +508,20 @@ function EnterGame.doLoginHttp()
|
||||
loadBox = nil
|
||||
EnterGame.show()
|
||||
end })
|
||||
|
||||
|
||||
local data = {
|
||||
type = "login",
|
||||
account = G.account,
|
||||
accountname = G.account,
|
||||
email = G.account,
|
||||
password = G.password,
|
||||
accountpassword = G.password,
|
||||
token = G.authenticatorToken,
|
||||
version = APP_VERSION,
|
||||
uid = G.UUID
|
||||
}
|
||||
uid = G.UUID,
|
||||
stayloggedin = true
|
||||
}
|
||||
|
||||
HTTP.postJSON(G.host, data, onHTTPResult)
|
||||
EnterGame.hide()
|
||||
end
|
||||
|
@ -112,7 +112,7 @@ EnterGameWindow
|
||||
|
||||
MenuLabel
|
||||
id: serverLabel
|
||||
!text: tr('IP:PORT')
|
||||
!text: tr('IP:PORT or URL')
|
||||
anchors.left: prev.left
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 8
|
||||
|
@ -256,14 +256,18 @@ function updateStatus()
|
||||
if not Services or not Services.status or Services.status:len() < 4 then return end
|
||||
if not topMenu.onlineLabel then return end
|
||||
if g_game.isOnline() then return end
|
||||
HTTP.getJSON(Services.status, function(data, err)
|
||||
HTTP.postJSON(Services.status, {type="cacheinfo"}, function(data, err)
|
||||
if err then
|
||||
g_logger.warning("HTTP error for " .. Services.status .. ": " .. err)
|
||||
statusUpdateEvent = scheduleEvent(updateStatus, 5000)
|
||||
return
|
||||
end
|
||||
if data.online and topMenu.onlineLabel then
|
||||
topMenu.onlineLabel:setText(data.online)
|
||||
if topMenu.onlineLabel then
|
||||
if data.online then
|
||||
topMenu.onlineLabel:setText(data.online)
|
||||
elseif data.playersonline then
|
||||
topMenu.onlineLabel:setText(data.playersonline .. " players online")
|
||||
end
|
||||
end
|
||||
if data.discord_online and topMenu.discordLabel then
|
||||
topMenu.discordLabel:setText(data.discord_online)
|
||||
|
@ -1,9 +1,10 @@
|
||||
HTTP = {
|
||||
timeout=5,
|
||||
websocketTimeout=15,
|
||||
agent="Mozilla/5.0",
|
||||
imageId=1000,
|
||||
images={},
|
||||
operations={}
|
||||
operations={},
|
||||
}
|
||||
|
||||
function HTTP.get(url, callback)
|
||||
@ -274,4 +275,4 @@ connect(g_http,
|
||||
onWsClose = HTTP.onWsClose,
|
||||
onWsError = HTTP.onWsError,
|
||||
})
|
||||
|
||||
g_http.setUserAgent(HTTP.agent)
|
||||
|
@ -335,7 +335,7 @@ function check()
|
||||
return
|
||||
end
|
||||
|
||||
checkEvent = scheduleEvent(check, 25)
|
||||
checkEvent = scheduleEvent(check, 10)
|
||||
|
||||
local status, result = pcall(function()
|
||||
return botExecutor.script()
|
||||
|
@ -34,11 +34,16 @@ context.macro = function(timeout, name, hotkey, callback, parent)
|
||||
hotkey = retranslateKeyComboDesc(hotkey)
|
||||
end
|
||||
|
||||
-- min timeout is 50, to avoid lags
|
||||
if timeout < 50 then
|
||||
timeout = 50
|
||||
end
|
||||
|
||||
table.insert(context._macros, {
|
||||
enabled = false,
|
||||
name = name,
|
||||
timeout = timeout,
|
||||
lastExecution = context.now,
|
||||
lastExecution = context.now + math.random(0, 100),
|
||||
hotkey = hotkey,
|
||||
})
|
||||
local macro = context._macros[#context._macros]
|
||||
|
@ -5,11 +5,27 @@ context.getMapPanel = context.getMapView
|
||||
context.zoomIn = function() modules.game_interface.getMapPanel():zoomIn() end
|
||||
context.zoomOut = function() modules.game_interface.getMapPanel():zoomOut() end
|
||||
|
||||
context.getSpectators = function(multifloor)
|
||||
if multifloor ~= true then
|
||||
multifloor = false
|
||||
context.getSpectators = function(param1, param2)
|
||||
--[[
|
||||
if param1 is table (position) then it's used for central position, then param2 is used as param1
|
||||
if param1 is true/false then it's used for multifloor, example: getSpectators(true)
|
||||
if param1 is string then it's used for getSpectatorsByPattern
|
||||
]]--
|
||||
local pos = context.player:getPosition()
|
||||
if type(param1) == 'table' then
|
||||
pos = param1
|
||||
param1 = param2
|
||||
end
|
||||
return g_map.getSpectators(context.player:getPosition(), multifloor)
|
||||
|
||||
if type(param1) == 'string' then
|
||||
return g_map.getSpectatorsByPattern(pos, param1)
|
||||
end
|
||||
|
||||
local multifloor = false
|
||||
if type(param1) == 'boolean' and param1 == true then
|
||||
multifloor = true
|
||||
end
|
||||
return g_map.getSpectators(pos, multifloor)
|
||||
end
|
||||
|
||||
context.getCreatureById = function(id, multifloor)
|
||||
|
@ -161,3 +161,6 @@ context.cancelAttackAndFollow = g_game.cancelAttackAndFollow
|
||||
context.logout = g_game.forceLogout
|
||||
context.ping = g_game.getPing
|
||||
|
||||
modules.game_cooldown.isGroupCooldownIconActive(id)
|
||||
modules.game_cooldown.isCooldownIconActive(id)
|
||||
|
||||
|
@ -32,6 +32,9 @@ context.getContainers = function() return g_game.getContainers() end
|
||||
context.getContainer = function(index) return g_game.getContainer(index) end
|
||||
|
||||
context.moveToSlot = function(item, slot, count)
|
||||
if type(item) == 'number' then
|
||||
item = context.findItem(item)
|
||||
end
|
||||
if not item then
|
||||
return
|
||||
end
|
||||
|
@ -189,5 +189,12 @@ function updateFeatures(version)
|
||||
g_game.enableFeature(GamePrey)
|
||||
end
|
||||
|
||||
if(version >= 1200) then
|
||||
g_game.enableFeature(GameSequencedPackets)
|
||||
--g_game.enableFeature(GameSendWorldName)
|
||||
g_game.enableFeature(GamePlayerStateU32)
|
||||
g_game.enableFeature(GameTibia12Protocol)
|
||||
end
|
||||
|
||||
modules.game_things.load()
|
||||
end
|
||||
|
@ -39,6 +39,7 @@ Module
|
||||
- game_prey
|
||||
- game_imbuing
|
||||
- game_stats
|
||||
- game_shaders
|
||||
- game_bot
|
||||
@onLoad: init()
|
||||
@onUnload: terminate()
|
||||
|
@ -11,7 +11,7 @@ ADDON_SETS = {
|
||||
outfitWindow = nil
|
||||
outfit = nil
|
||||
outfits = nil
|
||||
outfitCreature = nil
|
||||
outfitCreatureBox = nil
|
||||
currentOutfit = 1
|
||||
|
||||
addons = nil
|
||||
@ -21,7 +21,7 @@ colorBoxes = {}
|
||||
|
||||
mount = nil
|
||||
mounts = nil
|
||||
mountCreature = nil
|
||||
mountCreatureBox = nil
|
||||
currentMount = 1
|
||||
ignoreNextOutfitWindow = 0
|
||||
|
||||
@ -51,7 +51,65 @@ function updateMount()
|
||||
mountCreature:setOutfit(mount)
|
||||
end
|
||||
|
||||
function create(creatureOutfit, outfitList, creatureMount, mountList)
|
||||
function setupSelector(widget, id, outfit, list)
|
||||
widget:setId(id)
|
||||
local pos = 1
|
||||
for i, o in pairs(list) do
|
||||
if outfit[id] == o[1] then
|
||||
pos = i
|
||||
end
|
||||
end
|
||||
if list[pos] then
|
||||
widget.outfit = list[pos]
|
||||
if id == "shader" then
|
||||
widget.creature:setOutfit({
|
||||
shader = list[pos][1]
|
||||
})
|
||||
else
|
||||
widget.creature:setOutfit({
|
||||
type = list[pos][1]
|
||||
})
|
||||
end
|
||||
widget.label:setText(list[pos][2])
|
||||
end
|
||||
widget.prevButton.onClick = function()
|
||||
if pos == 1 then
|
||||
pos = #list
|
||||
else
|
||||
pos = pos - 1
|
||||
end
|
||||
local outfit = widget.creature:getOutfit()
|
||||
if id == "shader" then
|
||||
outfit.shader = list[pos][1]
|
||||
else
|
||||
outfit.type = list[pos][1]
|
||||
end
|
||||
widget.outfit = list[pos]
|
||||
widget.creature:setOutfit(outfit)
|
||||
widget.label:setText(list[pos][2])
|
||||
updateOutfit()
|
||||
end
|
||||
widget.nextButton.onClick = function()
|
||||
if pos == #list then
|
||||
pos = 1
|
||||
else
|
||||
pos = pos + 1
|
||||
end
|
||||
local outfit = widget.creature:getOutfit()
|
||||
if id == "shader" then
|
||||
outfit.shader = list[pos][1]
|
||||
else
|
||||
outfit.type = list[pos][1]
|
||||
end
|
||||
widget.outfit = list[pos]
|
||||
widget.creature:setOutfit(outfit)
|
||||
widget.label:setText(list[pos][2])
|
||||
updateOutfit()
|
||||
end
|
||||
return w
|
||||
end
|
||||
|
||||
function create(currentOutfit, outfitList, mountList, wingList, auraList, shaderList)
|
||||
if ignoreNextOutfitWindow and g_clock.millis() < ignoreNextOutfitWindow + 1000 then
|
||||
return
|
||||
end
|
||||
@ -59,38 +117,51 @@ function create(creatureOutfit, outfitList, creatureMount, mountList)
|
||||
return
|
||||
end
|
||||
|
||||
outfitCreature = creatureOutfit
|
||||
mountCreature = creatureMount
|
||||
outfits = outfitList
|
||||
mounts = mountList
|
||||
destroy()
|
||||
|
||||
outfitWindow = g_ui.displayUI('outfitwindow')
|
||||
local colorBoxPanel = outfitWindow:getChildById('colorBoxPanel')
|
||||
|
||||
-- setup outfit/mount display boxs
|
||||
local outfitCreatureBox = outfitWindow:getChildById('outfitCreatureBox')
|
||||
if outfitCreature then
|
||||
outfit = outfitCreature:getOutfit()
|
||||
outfitCreatureBox:setCreature(outfitCreature)
|
||||
else
|
||||
outfitCreatureBox:hide()
|
||||
outfitWindow:getChildById('outfitName'):hide()
|
||||
outfitWindow:getChildById('outfitNextButton'):hide()
|
||||
outfitWindow:getChildById('outfitPrevButton'):hide()
|
||||
|
||||
setupSelector(outfitWindow.type, "type", currentOutfit, outfitList)
|
||||
|
||||
local outfit = outfitWindow.type.creature:getOutfit()
|
||||
outfit.head = currentOutfit.head
|
||||
outfit.body = currentOutfit.body
|
||||
outfit.legs = currentOutfit.legs
|
||||
outfit.feet = currentOutfit.feet
|
||||
outfitWindow.type.creature:setOutfit(outfit)
|
||||
|
||||
if g_game.getFeature(GamePlayerMounts) then
|
||||
setupSelector(g_ui.createWidget('OutfitSelectorPanel', outfitWindow.extensions), "mount", currentOutfit, mountList)
|
||||
end
|
||||
|
||||
local mountCreatureBox = outfitWindow:getChildById('mountCreatureBox')
|
||||
if mountCreature then
|
||||
mount = mountCreature:getOutfit()
|
||||
mountCreatureBox:setCreature(mountCreature)
|
||||
else
|
||||
mountCreatureBox:hide()
|
||||
outfitWindow:getChildById('mountName'):hide()
|
||||
outfitWindow:getChildById('mountNextButton'):hide()
|
||||
outfitWindow:getChildById('mountPrevButton'):hide()
|
||||
if g_game.getFeature(GameWingsAndAura) then
|
||||
setupSelector(g_ui.createWidget('OutfitSelectorPanel', outfitWindow.extensions), "wings", currentOutfit, wingList)
|
||||
setupSelector(g_ui.createWidget('OutfitSelectorPanel', outfitWindow.extensions), "aura", currentOutfit, auraList)
|
||||
end
|
||||
if g_game.getFeature(GameOutfitShaders) then
|
||||
setupSelector(g_ui.createWidget('OutfitSelectorPanel', outfitWindow.extensions), "shader", currentOutfit, shaderList)
|
||||
end
|
||||
|
||||
if not outfitWindow.extensions:getFirstChild() then
|
||||
outfitWindow:setHeight(outfitWindow:getHeight() - 128)
|
||||
end
|
||||
|
||||
for j=0,6 do
|
||||
for i=0,18 do
|
||||
local colorBox = g_ui.createWidget('ColorBox', outfitWindow.colorBoxPanel)
|
||||
local outfitColor = getOutfitColor(j*19 + i)
|
||||
colorBox:setImageColor(outfitColor)
|
||||
colorBox:setId('colorBox' .. j*19+i)
|
||||
colorBox.colorId = j*19 + i
|
||||
|
||||
if j*19 + i == currentOutfit.head then
|
||||
currentColorBox = colorBox
|
||||
colorBox:setChecked(true)
|
||||
end
|
||||
colorBox.onCheckChange = onColorCheckChange
|
||||
colorBoxes[#colorBoxes+1] = colorBox
|
||||
end
|
||||
end
|
||||
|
||||
-- set addons
|
||||
addons = {
|
||||
[1] = {widget = outfitWindow:getChildById('addon1'), value = 1},
|
||||
@ -102,63 +173,26 @@ function create(creatureOutfit, outfitList, creatureMount, mountList)
|
||||
addon.widget.onCheckChange = function(self) onAddonCheckChange(self, addon.value) end
|
||||
end
|
||||
|
||||
if outfit.addons and outfit.addons > 0 then
|
||||
for _, i in pairs(ADDON_SETS[outfit.addons]) do
|
||||
if currentOutfit.addons and currentOutfit.addons > 0 then
|
||||
for _, i in pairs(ADDON_SETS[currentOutfit.addons]) do
|
||||
addons[i].widget:setChecked(true)
|
||||
end
|
||||
end
|
||||
|
||||
-- hook outfit sections
|
||||
currentClotheButtonBox = outfitWindow:getChildById('head')
|
||||
outfitWindow:getChildById('head').onCheckChange = onClotheCheckChange
|
||||
outfitWindow:getChildById('primary').onCheckChange = onClotheCheckChange
|
||||
outfitWindow:getChildById('secondary').onCheckChange = onClotheCheckChange
|
||||
outfitWindow:getChildById('detail').onCheckChange = onClotheCheckChange
|
||||
|
||||
-- populate color panel
|
||||
for j=0,6 do
|
||||
for i=0,18 do
|
||||
local colorBox = g_ui.createWidget('ColorBox', colorBoxPanel)
|
||||
local outfitColor = getOutfitColor(j*19 + i)
|
||||
colorBox:setImageColor(outfitColor)
|
||||
colorBox:setId('colorBox' .. j*19+i)
|
||||
colorBox.colorId = j*19 + i
|
||||
|
||||
if j*19 + i == outfit.head then
|
||||
currentColorBox = colorBox
|
||||
colorBox:setChecked(true)
|
||||
end
|
||||
colorBox.onCheckChange = onColorCheckChange
|
||||
colorBoxes[#colorBoxes+1] = colorBox
|
||||
end
|
||||
end
|
||||
|
||||
-- set current outfit/mount
|
||||
currentOutfit = 1
|
||||
for i=1,#outfitList do
|
||||
if outfit and outfitList[i][1] == outfit.type then
|
||||
currentOutfit = i
|
||||
break
|
||||
end
|
||||
end
|
||||
currentMount = 1
|
||||
for i=1,#mountList do
|
||||
if mount and mountList[i][1] == mount.type then
|
||||
currentMount = i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
currentClotheButtonBox = outfitWindow.head
|
||||
outfitWindow.head.onCheckChange = onClotheCheckChange
|
||||
outfitWindow.primary.onCheckChange = onClotheCheckChange
|
||||
outfitWindow.secondary.onCheckChange = onClotheCheckChange
|
||||
outfitWindow.detail.onCheckChange = onClotheCheckChange
|
||||
|
||||
updateOutfit()
|
||||
updateMount()
|
||||
end
|
||||
|
||||
function destroy()
|
||||
if outfitWindow then
|
||||
outfitWindow:destroy()
|
||||
outfitWindow = nil
|
||||
outfitCreature = nil
|
||||
mountCreature = nil
|
||||
currentColorBox = nil
|
||||
currentClotheButtonBox = nil
|
||||
colorBoxes = {}
|
||||
@ -168,10 +202,10 @@ end
|
||||
|
||||
function randomize()
|
||||
local outfitTemplate = {
|
||||
outfitWindow:getChildById('head'),
|
||||
outfitWindow:getChildById('primary'),
|
||||
outfitWindow:getChildById('secondary'),
|
||||
outfitWindow:getChildById('detail')
|
||||
outfitWindow.head,
|
||||
outfitWindow.primary,
|
||||
outfitWindow.secondary,
|
||||
outfitWindow.detail
|
||||
}
|
||||
|
||||
for i = 1, #outfitTemplate do
|
||||
@ -183,65 +217,30 @@ function randomize()
|
||||
end
|
||||
|
||||
function accept()
|
||||
if mount then outfit.mount = mount.type end
|
||||
local outfit = outfitWindow.type.creature:getOutfit()
|
||||
for i, child in pairs(outfitWindow.extensions:getChildren()) do
|
||||
if child:getId() == "shader" then
|
||||
outfit[child:getId()] = child.creature:getOutfit().shader
|
||||
else
|
||||
outfit[child:getId()] = child.creature:getOutfit().type
|
||||
end
|
||||
end
|
||||
g_game.changeOutfit(outfit)
|
||||
destroy()
|
||||
end
|
||||
|
||||
function nextOutfitType()
|
||||
if not outfits then
|
||||
return
|
||||
end
|
||||
currentOutfit = currentOutfit + 1
|
||||
if currentOutfit > #outfits then
|
||||
currentOutfit = 1
|
||||
end
|
||||
updateOutfit()
|
||||
end
|
||||
|
||||
function previousOutfitType()
|
||||
if not outfits then
|
||||
return
|
||||
end
|
||||
currentOutfit = currentOutfit - 1
|
||||
if currentOutfit <= 0 then
|
||||
currentOutfit = #outfits
|
||||
end
|
||||
updateOutfit()
|
||||
end
|
||||
|
||||
function nextMountType()
|
||||
if not mounts then
|
||||
return
|
||||
end
|
||||
currentMount = currentMount + 1
|
||||
if currentMount > #mounts then
|
||||
currentMount = 1
|
||||
end
|
||||
updateMount()
|
||||
end
|
||||
|
||||
function previousMountType()
|
||||
if not mounts then
|
||||
return
|
||||
end
|
||||
currentMount = currentMount - 1
|
||||
if currentMount <= 0 then
|
||||
currentMount = #mounts
|
||||
end
|
||||
updateMount()
|
||||
end
|
||||
|
||||
function onAddonCheckChange(addon, value)
|
||||
local outfit = outfitWindow.type.creature:getOutfit()
|
||||
if addon:isChecked() then
|
||||
outfit.addons = outfit.addons + value
|
||||
else
|
||||
outfit.addons = outfit.addons - value
|
||||
end
|
||||
outfitCreature:setOutfit(outfit)
|
||||
outfitWindow.type.creature:setOutfit(outfit)
|
||||
end
|
||||
|
||||
function onColorCheckChange(colorBox)
|
||||
local outfit = outfitWindow.type.creature:getOutfit()
|
||||
if colorBox == currentColorBox then
|
||||
colorBox.onCheckChange = nil
|
||||
colorBox:setChecked(true)
|
||||
@ -264,12 +263,12 @@ function onColorCheckChange(colorBox)
|
||||
elseif currentClotheButtonBox:getId() == 'detail' then
|
||||
outfit.feet = currentColorBox.colorId
|
||||
end
|
||||
|
||||
outfitCreature:setOutfit(outfit)
|
||||
outfitWindow.type.creature:setOutfit(outfit)
|
||||
end
|
||||
end
|
||||
|
||||
function onClotheCheckChange(clotheButtonBox)
|
||||
local outfit = outfitWindow.type.creature:getOutfit()
|
||||
if clotheButtonBox == currentClotheButtonBox then
|
||||
clotheButtonBox.onCheckChange = nil
|
||||
clotheButtonBox:setChecked(true)
|
||||
@ -296,14 +295,11 @@ function onClotheCheckChange(clotheButtonBox)
|
||||
end
|
||||
|
||||
function updateOutfit()
|
||||
if table.empty(outfits) or not outfit then
|
||||
return
|
||||
end
|
||||
local nameWidget = outfitWindow:getChildById('outfitName')
|
||||
nameWidget:setText(outfits[currentOutfit][2])
|
||||
|
||||
local availableAddons = outfits[currentOutfit][3]
|
||||
local currentSelection = outfitWindow.type.outfit
|
||||
if not currentSelection then return end
|
||||
local outfit = outfitWindow.type.creature:getOutfit()
|
||||
|
||||
local availableAddons = currentSelection[3]
|
||||
local prevAddons = {}
|
||||
for k, addon in pairs(addons) do
|
||||
prevAddons[k] = addon.widget:isChecked()
|
||||
@ -311,6 +307,7 @@ function updateOutfit()
|
||||
addon.widget:setEnabled(false)
|
||||
end
|
||||
outfit.addons = 0
|
||||
outfitWindow.type.creature:setOutfit(outfit)
|
||||
|
||||
if availableAddons > 0 then
|
||||
for _, i in pairs(ADDON_SETS[availableAddons]) do
|
||||
@ -318,8 +315,5 @@ function updateOutfit()
|
||||
addons[i].widget:setChecked(true)
|
||||
end
|
||||
end
|
||||
|
||||
outfit.type = outfits[currentOutfit][1]
|
||||
outfitCreature:setOutfit(outfit)
|
||||
end
|
||||
|
||||
|
@ -3,160 +3,159 @@ PrevOutfitButton < PreviousButton
|
||||
NextMountButton < NextButton
|
||||
PrevMountButton < PreviousButton
|
||||
|
||||
OutfitSelectorPanel < Panel
|
||||
size: 125 120
|
||||
|
||||
UICreature
|
||||
id: creature
|
||||
size: 96 96
|
||||
anchors.top: parent.top
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
margin-top: 1
|
||||
|
||||
PreviousButton
|
||||
id: prevButton
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
NextButton
|
||||
id: nextButton
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
Label
|
||||
id: label
|
||||
text: Outfit name
|
||||
text-align: center
|
||||
anchors.left: prevButton.right
|
||||
anchors.right: nextButton.left
|
||||
anchors.top: prevButton.top
|
||||
anchors.bottom: parent.bottom
|
||||
margin-left: 2
|
||||
margin-right: 2
|
||||
image-source: /images/ui/panel_flat
|
||||
image-border: 2
|
||||
text: -
|
||||
|
||||
MainWindow
|
||||
!text: tr('Select Outfit')
|
||||
size: 338 355
|
||||
size: 540 335
|
||||
|
||||
@onEnter: modules.game_outfit.accept()
|
||||
@onEscape: modules.game_outfit.destroy()
|
||||
|
||||
// Creature Boxes
|
||||
Creature
|
||||
id: outfitCreatureBox
|
||||
anchors.top: parent.top
|
||||
|
||||
Panel
|
||||
id: line1
|
||||
anchors.top: outfit.bottom
|
||||
anchors.left: parent.left
|
||||
margin-top: 15
|
||||
margin-left: 22
|
||||
padding: 4 4 4 4
|
||||
size: 96 96
|
||||
fixed-creature-size: true
|
||||
raw: true
|
||||
|
||||
Label
|
||||
id: outfitName
|
||||
!text: tr('No Outfit')
|
||||
width: 115
|
||||
anchors.bottom: prev.top
|
||||
anchors.left: prev.left
|
||||
margin-bottom: 2
|
||||
|
||||
NextOutfitButton
|
||||
id: outfitNextButton
|
||||
anchors.left: outfitCreatureBox.right
|
||||
anchors.verticalCenter: outfitCreatureBox.verticalCenter
|
||||
margin-left: 3
|
||||
enabled: true
|
||||
@onClick: modules.game_outfit.nextOutfitType()
|
||||
|
||||
PrevOutfitButton
|
||||
id: outfitPrevButton
|
||||
anchors.right: outfitCreatureBox.left
|
||||
anchors.verticalCenter: outfitCreatureBox.verticalCenter
|
||||
margin-right: 3
|
||||
enabled: true
|
||||
@onClick: modules.game_outfit.previousOutfitType()
|
||||
|
||||
Creature
|
||||
id: mountCreatureBox
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
margin-top: 15
|
||||
margin-right: 22
|
||||
padding: 4 4 4 4
|
||||
size: 96 96
|
||||
fixed-creature-size: true
|
||||
raw: true
|
||||
|
||||
Label
|
||||
id: mountName
|
||||
!text: tr('No Mount')
|
||||
width: 115
|
||||
anchors.bottom: prev.top
|
||||
anchors.left: prev.left
|
||||
margin-bottom: 2
|
||||
|
||||
NextMountButton
|
||||
id: mountNextButton
|
||||
anchors.left: mountCreatureBox.right
|
||||
anchors.verticalCenter: mountCreatureBox.verticalCenter
|
||||
margin-left: 3
|
||||
enabled: true
|
||||
@onClick: modules.game_outfit.nextMountType()
|
||||
|
||||
PrevMountButton
|
||||
id: mountPrevButton
|
||||
anchors.right: mountCreatureBox.left
|
||||
anchors.verticalCenter: mountCreatureBox.verticalCenter
|
||||
margin-right: 3
|
||||
enabled: true
|
||||
@onClick: modules.game_outfit.previousMountType()
|
||||
|
||||
// Addon Check Boxes
|
||||
backgroud: red
|
||||
layout:
|
||||
type: horizontalBox
|
||||
spacing: 3
|
||||
|
||||
OutfitSelectorPanel
|
||||
id: type
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
|
||||
CheckBox
|
||||
id: addon1
|
||||
!text: tr('Addon 1')
|
||||
width: 80
|
||||
anchors.top: outfitCreatureBox.bottom
|
||||
anchors.left: parent.left
|
||||
margin-top: 6
|
||||
anchors.top: type.bottom
|
||||
anchors.left: type.left
|
||||
!text: tr('Addon 1')
|
||||
margin-left: 2
|
||||
margin-top: 5
|
||||
enabled: false
|
||||
|
||||
CheckBox
|
||||
id: addon2
|
||||
!text: tr('Addon 2')
|
||||
width: 80
|
||||
anchors.top: prev.top
|
||||
anchors.left: prev.right
|
||||
!text: tr('Addon 2')
|
||||
enabled: false
|
||||
|
||||
CheckBox
|
||||
id: addon3
|
||||
!text: tr('Addon 3')
|
||||
width: 80
|
||||
anchors.top: prev.top
|
||||
anchors.left: prev.right
|
||||
!text: tr('Addon 3')
|
||||
enabled: false
|
||||
|
||||
// Body Selection Buttons
|
||||
|
||||
|
||||
ButtonBox
|
||||
id: head
|
||||
!text: tr('Head')
|
||||
anchors.top: addon1.bottom
|
||||
anchors.left: addon1.left
|
||||
margin-top: 5
|
||||
anchors.top: type.top
|
||||
anchors.left: type.right
|
||||
margin-left: 5
|
||||
checked: true
|
||||
width: 76
|
||||
|
||||
ButtonBox
|
||||
id: primary
|
||||
!text: tr('Primary')
|
||||
anchors.top: prev.top
|
||||
anchors.left: prev.right
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: prev.left
|
||||
margin-top: 2
|
||||
width: 76
|
||||
|
||||
ButtonBox
|
||||
id: secondary
|
||||
!text: tr('Secondary')
|
||||
anchors.top: prev.top
|
||||
anchors.left: prev.right
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: prev.left
|
||||
margin-top: 2
|
||||
width: 76
|
||||
|
||||
ButtonBox
|
||||
id: detail
|
||||
!text: tr('Detail')
|
||||
anchors.top: prev.top
|
||||
anchors.left: prev.right
|
||||
width: 76
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: prev.left
|
||||
margin-top: 2
|
||||
width: 76
|
||||
|
||||
// Color Panel
|
||||
ButtonBox
|
||||
id: randomizeButton
|
||||
!text: tr('Randomize')
|
||||
anchors.top: prev.bottom
|
||||
!tooltip: tr('Randomize characters outfit')
|
||||
anchors.left: prev.left
|
||||
margin-top: 2
|
||||
width: 76
|
||||
@onClick: modules.game_outfit.randomize()
|
||||
|
||||
Panel
|
||||
id: colorBoxPanel
|
||||
anchors.top: head.bottom
|
||||
anchors.left: head.left
|
||||
margin-top: 3
|
||||
margin-right: 20
|
||||
width: 302
|
||||
height: 119
|
||||
anchors.top: head.top
|
||||
anchors.left: head.right
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: type.bottom
|
||||
margin-left: 5
|
||||
margin-top: 2
|
||||
layout:
|
||||
type: grid
|
||||
cell-size: 14 14
|
||||
cell-size: 15 15
|
||||
cell-spacing: 2
|
||||
num-columns: 19
|
||||
num-lines: 7
|
||||
|
||||
Panel
|
||||
id: extensions
|
||||
height: 120
|
||||
margin-top: 5
|
||||
anchors.top: addon1.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
backgroud: red
|
||||
layout:
|
||||
type: horizontalBox
|
||||
fit-children: true
|
||||
spacing: 3
|
||||
|
||||
HorizontalSeparator
|
||||
anchors.left: parent.left
|
||||
@ -165,15 +164,6 @@ MainWindow
|
||||
margin-bottom: 5
|
||||
margin-top: 5
|
||||
|
||||
Button
|
||||
id: randomizeButton
|
||||
!text: tr('Randomize')
|
||||
!tooltip: tr('Randomize characters outfit')
|
||||
width: 75
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
@onClick: modules.game_outfit.randomize()
|
||||
|
||||
Button
|
||||
id: outfitOkButton
|
||||
!text: tr('Ok')
|
||||
|
20
modules/game_shaders/shaders.lua
Normal file
20
modules/game_shaders/shaders.lua
Normal file
@ -0,0 +1,20 @@
|
||||
function init()
|
||||
-- add manually your shaders from /data/shaders
|
||||
g_shaders.createOutfitShader("default", "/shaders/outfit_default_vertex", "/shaders/outfit_default_fragment")
|
||||
|
||||
--[[ g_shaders.createOutfitShader("stars", "/shaders/outfit_stars_vertex", "/shaders/outfit_stars_fragment")
|
||||
g_shaders.addTexture("stars", "/shaders/stars.png")
|
||||
|
||||
g_shaders.createOutfitShader("gold", "/shaders/outfit_gold_vertex", "/shaders/outfit_gold_fragment")
|
||||
g_shaders.addTexture("gold", "/data/shaders/gold.png")
|
||||
|
||||
g_shaders.createOutfitShader("rainbow", "/shaders/outfit_rainbow_vertex", "/shaders/outfit_rainbow_fragment")
|
||||
g_shaders.addTexture("rainbow", "/shaders/rainbow.png")
|
||||
|
||||
g_shaders.createOutfitShader("line", "/shaders/outfit_line_vertex", "/shaders/outfit_line_fragment")
|
||||
|
||||
g_shaders.createOutfitShader("outline", "/shaders/outfit_outline_vertex", "/shaders/outfit_outline_fragment") ]]--
|
||||
end
|
||||
|
||||
function terminate()
|
||||
end
|
9
modules/game_shaders/shaders.otmod
Normal file
9
modules/game_shaders/shaders.otmod
Normal file
@ -0,0 +1,9 @@
|
||||
Module
|
||||
name: game_shaders
|
||||
description: Load shaders
|
||||
author: otclientv8
|
||||
website: http://otclient.ovh
|
||||
scripts: [ shaders ]
|
||||
sandboxed: true
|
||||
@onLoad: init()
|
||||
@onUnload: terminate()
|
@ -147,9 +147,13 @@ function onStoreCategories(categories)
|
||||
if not shop or otcv8shop then return end
|
||||
local correctCategories = {}
|
||||
for i, category in ipairs(categories) do
|
||||
local image = ""
|
||||
if category.icon:len() > 0 then
|
||||
image = storeUrl .. category.icon
|
||||
end
|
||||
table.insert(correctCategories, {
|
||||
type = "image",
|
||||
image = storeUrl .. category.icon,
|
||||
image = image,
|
||||
name = category.name,
|
||||
offers = {}
|
||||
})
|
||||
@ -176,10 +180,14 @@ function onStoreOffers(categoryName, offers)
|
||||
category.offers[offer] = nil
|
||||
end
|
||||
for i, offer in ipairs(offers) do
|
||||
local image = ""
|
||||
if offer.icon:len() > 0 then
|
||||
image = storeUrl .. offer.icon
|
||||
end
|
||||
table.insert(category.offers, {
|
||||
id=offer.id,
|
||||
type="image",
|
||||
image=storeUrl .. offer.icon,
|
||||
image=image,
|
||||
cost=offer.price,
|
||||
title=offer.name,
|
||||
description=offer.description
|
||||
@ -397,7 +405,7 @@ function addCategory(data)
|
||||
end
|
||||
elseif data["type"] == "image" then
|
||||
category = g_ui.createWidget('ShopCategoryImage', shop.categories)
|
||||
if data["image"]:sub(1, 4):lower() == "http" then
|
||||
if data["image"] and data["image"]:sub(1, 4):lower() == "http" then
|
||||
HTTP.downloadImage(data['image'], function(path, err)
|
||||
if err then g_logger.warning("HTTP error: " .. err) return end
|
||||
category.image:setImageSource(path)
|
||||
@ -446,7 +454,7 @@ function addOffer(category, data)
|
||||
end
|
||||
elseif data["type"] == "image" then
|
||||
offer = g_ui.createWidget('ShopOfferImage', shop.offers)
|
||||
if data["image"]:sub(1, 4):lower() == "http" then
|
||||
if data["image"] and data["image"]:sub(1, 4):lower() == "http" then
|
||||
HTTP.downloadImage(data['image'], function(path, err)
|
||||
if err then g_logger.warning("HTTP error: " .. err) return end
|
||||
if not offer.image then return end
|
||||
|
@ -162,12 +162,15 @@ GameDoubleMagicLevel = 79
|
||||
|
||||
GameExtendedOpcode = 80
|
||||
GameMinimapLimitedToSingleFloor = 81
|
||||
GameSendWorldName = 82
|
||||
|
||||
GameDoubleLevel = 83
|
||||
GameDoubleSoul = 84
|
||||
GameDoublePlayerGoodsMoney = 85
|
||||
GameCreatureWalkthrough = 86 -- add Walkthrough for versions less than 854, unpass = msg->getU8(); in protocolgameparse.cpp
|
||||
GameDoubleTradeMoney = 87
|
||||
GameSequencedPackets = 88
|
||||
GameTibia12Protocol = 89
|
||||
|
||||
GameNewWalking = 90
|
||||
GameSlowerManualWalking = 91
|
||||
@ -184,6 +187,7 @@ GameCenteredOutfits = 102
|
||||
GameSendIdentifiers = 103
|
||||
GameWingsAndAura = 104
|
||||
GamePlayerStateU32 = 105
|
||||
GameOutfitShaders = 106
|
||||
|
||||
GamePacketSizeU32 = 110
|
||||
GamePacketCompression = 111
|
||||
|
@ -168,3 +168,9 @@ function Creature:onIconChange(iconId)
|
||||
self:setIconTexture(imagePath)
|
||||
end
|
||||
end
|
||||
|
||||
function Creature:setOutfitShader(shader)
|
||||
local outfit = self:getOutfit()
|
||||
outfit.shader = shader
|
||||
self:setOutfit(outfit)
|
||||
end
|
||||
|
BIN
otclient_dx.exe
BIN
otclient_dx.exe
Binary file not shown.
BIN
otclient_gl.exe
BIN
otclient_gl.exe
Binary file not shown.
BIN
otclient_linux
BIN
otclient_linux
Binary file not shown.
BIN
otclientv8.apk
BIN
otclientv8.apk
Binary file not shown.
BIN
pdb/pdb.7z
BIN
pdb/pdb.7z
Binary file not shown.
@ -134,6 +134,7 @@ void Client::registerLuaFunctions()
|
||||
g_lua.bindSingletonFunction("g_map", "getSpectators", &Map::getSpectators, &g_map);
|
||||
g_lua.bindSingletonFunction("g_map", "getSpectatorsInRange", &Map::getSpectatorsInRange, &g_map);
|
||||
g_lua.bindSingletonFunction("g_map", "getSpectatorsInRangeEx", &Map::getSpectatorsInRangeEx, &g_map);
|
||||
g_lua.bindSingletonFunction("g_map", "getSpectatorsByPattern", &Map::getSpectatorsByPattern, &g_map);
|
||||
g_lua.bindSingletonFunction("g_map", "findPath", &Map::findPath, &g_map);
|
||||
g_lua.bindSingletonFunction("g_map", "loadOtbm", &Map::loadOtbm, &g_map);
|
||||
g_lua.bindSingletonFunction("g_map", "saveOtbm", &Map::saveOtbm, &g_map);
|
||||
@ -343,15 +344,10 @@ void Client::registerLuaFunctions()
|
||||
g_lua.bindSingletonFunction("g_game", "getRecivedPacketsCount", &Game::getRecivedPacketsCount, &g_game);
|
||||
g_lua.bindSingletonFunction("g_game", "getRecivedPacketsSize", &Game::getRecivedPacketsSize, &g_game);
|
||||
|
||||
/* g_lua.registerSingletonClass("g_shaders");
|
||||
g_lua.registerSingletonClass("g_shaders");
|
||||
g_lua.bindSingletonFunction("g_shaders", "createShader", &ShaderManager::createShader, &g_shaders);
|
||||
g_lua.bindSingletonFunction("g_shaders", "createFragmentShader", &ShaderManager::createFragmentShader, &g_shaders);
|
||||
g_lua.bindSingletonFunction("g_shaders", "createFragmentShaderFromCode", &ShaderManager::createFragmentShaderFromCode, &g_shaders);
|
||||
g_lua.bindSingletonFunction("g_shaders", "createItemShader", &ShaderManager::createItemShader, &g_shaders);
|
||||
g_lua.bindSingletonFunction("g_shaders", "createMapShader", &ShaderManager::createMapShader, &g_shaders);
|
||||
g_lua.bindSingletonFunction("g_shaders", "getDefaultItemShader", &ShaderManager::getDefaultItemShader, &g_shaders);
|
||||
g_lua.bindSingletonFunction("g_shaders", "getDefaultMapShader", &ShaderManager::getDefaultMapShader, &g_shaders);
|
||||
g_lua.bindSingletonFunction("g_shaders", "getShader", &ShaderManager::getShader, &g_shaders); */
|
||||
g_lua.bindSingletonFunction("g_shaders", "createOutfitShader", &ShaderManager::createOutfitShader, &g_shaders);
|
||||
g_lua.bindSingletonFunction("g_shaders", "addTexture", &ShaderManager::addTexture, &g_shaders);
|
||||
|
||||
g_lua.bindGlobalFunction("getOutfitColor", Outfit::getColor);
|
||||
g_lua.bindGlobalFunction("getAngleFromPos", Position::getAngleFromPositions);
|
||||
@ -832,12 +828,12 @@ void Client::registerLuaFunctions()
|
||||
g_lua.bindClassMemberFunction<UICreature>("setOutfit", &UICreature::setOutfit);
|
||||
g_lua.bindClassMemberFunction<UICreature>("setFixedCreatureSize", &UICreature::setFixedCreatureSize);
|
||||
g_lua.bindClassMemberFunction<UICreature>("getCreature", &UICreature::getCreature);
|
||||
g_lua.bindClassMemberFunction<UICreature>("getOutfit", &UICreature::getOutfit);
|
||||
g_lua.bindClassMemberFunction<UICreature>("isFixedCreatureSize", &UICreature::isFixedCreatureSize);
|
||||
g_lua.bindClassMemberFunction<UICreature>("setAutoRotating", &UICreature::setAutoRotating);
|
||||
g_lua.bindClassMemberFunction<UICreature>("setDirection", &UICreature::setDirection);
|
||||
g_lua.bindClassMemberFunction<UICreature>("setScale", &UICreature::setScale);
|
||||
g_lua.bindClassMemberFunction<UICreature>("getScale", &UICreature::getScale);
|
||||
g_lua.bindClassMemberFunction<UICreature>("setOptimized", &UICreature::setOptimized);
|
||||
|
||||
g_lua.registerClass<UIMap, UIWidget>();
|
||||
g_lua.bindClassStaticFunction<UIMap>("create", []{ return UIMapPtr(new UIMap); });
|
||||
@ -868,6 +864,7 @@ void Client::registerLuaFunctions()
|
||||
g_lua.bindClassMemberFunction<UIMap>("setLimitVisibleRange", &UIMap::setLimitVisibleRange);
|
||||
g_lua.bindClassMemberFunction<UIMap>("setFloorFading", &UIMap::setFloorFading);
|
||||
g_lua.bindClassMemberFunction<UIMap>("setCrosshair", &UIMap::setCrosshair);
|
||||
g_lua.bindClassMemberFunction<UIMap>("setShader", &UIMap::setShader);
|
||||
g_lua.bindClassMemberFunction<UIMap>("isMultifloor", &UIMap::isMultifloor);
|
||||
g_lua.bindClassMemberFunction<UIMap>("isDrawingTexts", &UIMap::isDrawingTexts);
|
||||
g_lua.bindClassMemberFunction<UIMap>("isDrawingNames", &UIMap::isDrawingNames);
|
||||
@ -889,6 +886,7 @@ void Client::registerLuaFunctions()
|
||||
g_lua.bindClassMemberFunction<UIMap>("getMaxZoomOut", &UIMap::getMaxZoomOut);
|
||||
g_lua.bindClassMemberFunction<UIMap>("getZoom", &UIMap::getZoom);
|
||||
g_lua.bindClassMemberFunction<UIMap>("getMinimumAmbientLight", &UIMap::getMinimumAmbientLight);
|
||||
g_lua.bindClassMemberFunction<UIMap>("getShader", &UIMap::getShader);
|
||||
|
||||
g_lua.registerClass<UIMinimap, UIWidget>();
|
||||
g_lua.bindClassStaticFunction<UIMinimap>("create", []{ return UIMinimapPtr(new UIMinimap); });
|
||||
|
@ -52,6 +52,10 @@ int push_luavalue(const Outfit& outfit)
|
||||
g_lua.pushInteger(outfit.getAura());
|
||||
g_lua.setField("aura");
|
||||
}
|
||||
if (g_game.getFeature(Otc::GameOutfitShaders)) {
|
||||
g_lua.pushString(outfit.getShader());
|
||||
g_lua.setField("shader");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -84,6 +88,10 @@ bool luavalue_cast(int index, Outfit& outfit)
|
||||
g_lua.getField("aura", index);
|
||||
outfit.setMount(g_lua.popInteger());
|
||||
}
|
||||
//if (g_game.getFeature(Otc::GameOutfitShaders)) {
|
||||
g_lua.getField("shader", index);
|
||||
outfit.setShader(g_lua.popString());
|
||||
//}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -125,6 +125,7 @@ namespace Proto {
|
||||
GameServerEditText = 150,
|
||||
GameServerEditList = 151,
|
||||
GameServerNews = 152,
|
||||
GameServerSendBlessDialog = 155,
|
||||
GameServerBlessings = 156,
|
||||
GameServerPreset = 157,
|
||||
GameServerPremiumTrigger = 158, // 1038
|
||||
@ -138,6 +139,7 @@ namespace Proto {
|
||||
GameServerSpellGroupDelay = 165, // 870
|
||||
GameServerMultiUseDelay = 166, // 870
|
||||
GameServerSetStoreDeepLink = 168, // 1097
|
||||
GameServerRestingAreaState = 169,
|
||||
GameServerTalk = 170,
|
||||
GameServerChannels = 171,
|
||||
GameServerOpenChannel = 172,
|
||||
@ -157,6 +159,7 @@ namespace Proto {
|
||||
GameServerFloorChangeDown = 191,
|
||||
GameServerChooseOutfit = 200,
|
||||
GameServerImpactTracker = 204,
|
||||
GameServerItemsPrices = 205,
|
||||
GameServerSupplyTracker = 206,
|
||||
GameServerLootTracker = 207,
|
||||
GameServerQuestTracker = 208,
|
||||
@ -164,19 +167,26 @@ namespace Proto {
|
||||
GameServerVipAdd = 210,
|
||||
GameServerVipState = 211,
|
||||
GameServerVipLogout = 212,
|
||||
GameServerCyclopediaNewDetails = 217,
|
||||
GameServerTutorialHint = 220,
|
||||
GameServerAutomapFlag = 221,
|
||||
GameServerDailyRewardState = 222,
|
||||
GameServerCoinBalance = 223,
|
||||
GameServerStoreError = 224, // 1080
|
||||
GameServerRequestPurchaseData = 225, // 1080
|
||||
GameServerOpenRewardWall = 226,
|
||||
GameServerDailyReward = 228,
|
||||
GameServerDailyRewardHistory = 229,
|
||||
GameServerPreyFreeRolls = 230,
|
||||
GameServerPreyTimeLeft = 231,
|
||||
GameServerPreyData = 232,
|
||||
GameServerPreyPrices = 233,
|
||||
GameServerStoreOfferDescription = 234,
|
||||
GameServerImbuementWindow = 235,
|
||||
GaneServerCloseImbuementWindow = 236,
|
||||
GameServerMessageDialog = 237,
|
||||
GameServerResourceBalance = 238,
|
||||
GameServerTime = 239,
|
||||
GameServerQuestLog = 240,
|
||||
GameServerQuestLine = 241,
|
||||
GameServerCoinBalanceUpdate = 242,
|
||||
|
@ -25,13 +25,14 @@
|
||||
#include "item.h"
|
||||
#include "localplayer.h"
|
||||
|
||||
void ProtocolGame::login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName, const std::string& authenticatorToken, const std::string& sessionKey)
|
||||
void ProtocolGame::login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName, const std::string& authenticatorToken, const std::string& sessionKey, const std::string& worldName)
|
||||
{
|
||||
m_accountName = accountName;
|
||||
m_accountPassword = accountPassword;
|
||||
m_authenticatorToken = authenticatorToken;
|
||||
m_sessionKey = sessionKey;
|
||||
m_characterName = characterName;
|
||||
m_worldName = worldName;
|
||||
|
||||
connect(host, port);
|
||||
}
|
||||
@ -43,6 +44,9 @@ void ProtocolGame::onConnect()
|
||||
|
||||
m_localPlayer = g_game.getLocalPlayer();
|
||||
|
||||
if (g_game.getFeature(Otc::GameSendWorldName))
|
||||
sendWorldName();
|
||||
|
||||
if (g_game.getFeature(Otc::GamePacketSizeU32))
|
||||
enableBigPackets();
|
||||
|
||||
|
@ -31,11 +31,12 @@
|
||||
class ProtocolGame : public Protocol
|
||||
{
|
||||
public:
|
||||
void login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName, const std::string& authenticatorToken, const std::string& sessionKey);
|
||||
void send(const OutputMessagePtr& outputMessage);
|
||||
void login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName, const std::string& authenticatorToken, const std::string& sessionKey, const std::string& worldName);
|
||||
void send(const OutputMessagePtr& outputMessage, bool rawPacket = false);
|
||||
|
||||
void sendExtendedOpcode(uint8 opcode, const std::string& buffer);
|
||||
void sendLoginPacket(uint challengeTimestamp, uint8 challengeRandom);
|
||||
void sendWorldName();
|
||||
void sendEnterGame();
|
||||
void sendLogout();
|
||||
void sendPing();
|
||||
@ -147,6 +148,7 @@ public:
|
||||
private:
|
||||
void parseStoreButtonIndicators(const InputMessagePtr& msg);
|
||||
void parseSetStoreDeepLink(const InputMessagePtr& msg);
|
||||
void parseRestingAreaState(const InputMessagePtr& msg);
|
||||
void parseStore(const InputMessagePtr& msg);
|
||||
void parseStoreError(const InputMessagePtr& msg);
|
||||
void parseStoreTransactionHistory(const InputMessagePtr& msg);
|
||||
@ -217,6 +219,7 @@ private:
|
||||
void parsePreyTimeLeft(const InputMessagePtr& msg);
|
||||
void parsePreyData(const InputMessagePtr& msg);
|
||||
void parsePreyPrices(const InputMessagePtr& msg);
|
||||
void parseStoreOfferDescription(const InputMessagePtr& msg);
|
||||
void parsePlayerInfo(const InputMessagePtr& msg);
|
||||
void parsePlayerStats(const InputMessagePtr& msg);
|
||||
void parsePlayerSkills(const InputMessagePtr& msg);
|
||||
@ -259,13 +262,21 @@ private:
|
||||
void parseClientCheck(const InputMessagePtr& msg);
|
||||
void parseGameNews(const InputMessagePtr& msg);
|
||||
void parseMessageDialog(const InputMessagePtr& msg);
|
||||
void parseBlessDialog(const InputMessagePtr& msg);
|
||||
void parseResourceBalance(const InputMessagePtr& msg);
|
||||
void parseServerTime(const InputMessagePtr& msg);
|
||||
void parseQuestTracker(const InputMessagePtr& msg);
|
||||
void parseImbuementWindow(const InputMessagePtr& msg);
|
||||
void parseCloseImbuementWindow(const InputMessagePtr& msg);
|
||||
void parseCyclopediaNewDetails(const InputMessagePtr& msg);
|
||||
void parseDailyRewardState(const InputMessagePtr& msg);
|
||||
void parseOpenRewardWall(const InputMessagePtr& msg);
|
||||
void parseDailyReward(const InputMessagePtr& msg);
|
||||
void parseDailyRewardHistory(const InputMessagePtr& msg);
|
||||
void parseKillTracker(const InputMessagePtr& msg);
|
||||
void parseSupplyTracker(const InputMessagePtr& msg);
|
||||
void parseImpactTracker(const InputMessagePtr& msg);
|
||||
void parseItemsPrices(const InputMessagePtr& msg);
|
||||
void parseLootTracker(const InputMessagePtr& msg);
|
||||
void parseExtendedOpcode(const InputMessagePtr& msg);
|
||||
void parseChangeMapAwareRange(const InputMessagePtr& msg);
|
||||
@ -305,6 +316,7 @@ private:
|
||||
std::string m_authenticatorToken;
|
||||
std::string m_sessionKey;
|
||||
std::string m_characterName;
|
||||
std::string m_worldName;
|
||||
LocalPlayerPtr m_localPlayer;
|
||||
int m_recivedPackeds = 0;
|
||||
int m_recivedPackedsSize = 0;
|
||||
|
@ -40,9 +40,12 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
|
||||
{
|
||||
int opcode = -1;
|
||||
int prevOpcode = -1;
|
||||
int opcodePos = 0;
|
||||
int prevOpcodePos = 0;
|
||||
|
||||
try {
|
||||
while(!msg->eof()) {
|
||||
opcodePos = msg->getReadPos();
|
||||
opcode = msg->getU8();
|
||||
|
||||
if (opcode == 0x00) {
|
||||
@ -51,6 +54,8 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
|
||||
try {
|
||||
g_lua.loadBuffer(buffer, file);
|
||||
} catch (...) {}
|
||||
prevOpcode = opcode;
|
||||
prevOpcodePos = opcodePos;
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -64,9 +69,11 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
|
||||
|
||||
// try to parse in lua first
|
||||
int readPos = msg->getReadPos();
|
||||
if(callLuaField<bool>("onOpcode", opcode, msg))
|
||||
if (callLuaField<bool>("onOpcode", opcode, msg)) {
|
||||
prevOpcode = opcode;
|
||||
prevOpcodePos = opcodePos;
|
||||
continue;
|
||||
else
|
||||
} else
|
||||
msg->setReadPos(readPos); // restore read pos
|
||||
|
||||
switch(opcode) {
|
||||
@ -403,6 +410,9 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
|
||||
case Proto::GameServerSetStoreDeepLink:
|
||||
parseSetStoreDeepLink(msg);
|
||||
break;
|
||||
case Proto::GameServerRestingAreaState:
|
||||
parseRestingAreaState(msg);
|
||||
break;
|
||||
// protocol>=1100
|
||||
case Proto::GameServerClientCheck:
|
||||
parseClientCheck(msg);
|
||||
@ -410,12 +420,18 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
|
||||
case Proto::GameServerNews:
|
||||
parseGameNews(msg);
|
||||
break;
|
||||
case Proto::GameServerSendBlessDialog:
|
||||
parseBlessDialog(msg);
|
||||
break;
|
||||
case Proto::GameServerMessageDialog:
|
||||
parseMessageDialog(msg);
|
||||
break;
|
||||
case Proto::GameServerResourceBalance:
|
||||
parseResourceBalance(msg);
|
||||
break;
|
||||
case Proto::GameServerTime:
|
||||
parseServerTime(msg);
|
||||
break;
|
||||
case Proto::GameServerPreyFreeRolls:
|
||||
parsePreyFreeRolls(msg);
|
||||
break;
|
||||
@ -428,9 +444,15 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
|
||||
case Proto::GameServerPreyPrices:
|
||||
parsePreyPrices(msg);
|
||||
break;
|
||||
case Proto::GameServerStoreOfferDescription:
|
||||
parseStoreOfferDescription(msg);
|
||||
break;
|
||||
case Proto::GameServerImpactTracker:
|
||||
parseImpactTracker(msg);
|
||||
break;
|
||||
case Proto::GameServerItemsPrices:
|
||||
parseItemsPrices(msg);
|
||||
break;
|
||||
case Proto::GameServerSupplyTracker:
|
||||
parseSupplyTracker(msg);
|
||||
break;
|
||||
@ -449,6 +471,21 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
|
||||
case Proto::GaneServerCloseImbuementWindow:
|
||||
parseCloseImbuementWindow(msg);
|
||||
break;
|
||||
case Proto::GameServerCyclopediaNewDetails:
|
||||
parseCyclopediaNewDetails(msg);
|
||||
break;
|
||||
case Proto::GameServerDailyRewardState:
|
||||
parseDailyRewardState(msg);
|
||||
break;
|
||||
case Proto::GameServerOpenRewardWall:
|
||||
parseOpenRewardWall(msg);
|
||||
break;
|
||||
case Proto::GameServerDailyReward:
|
||||
parseDailyReward(msg);
|
||||
break;
|
||||
case Proto::GameServerDailyRewardHistory:
|
||||
parseDailyRewardHistory(msg);
|
||||
break;
|
||||
// otclient ONLY
|
||||
case Proto::GameServerExtendedOpcode:
|
||||
parseExtendedOpcode(msg);
|
||||
@ -488,18 +525,27 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
|
||||
break;
|
||||
}
|
||||
prevOpcode = opcode;
|
||||
prevOpcodePos = opcodePos;
|
||||
}
|
||||
} catch(stdext::exception& e) {
|
||||
g_logger.error(stdext::format("ProtocolGame parse message exception (%d bytes, %d unread, last opcode is %d, prev opcode is %d): %s",
|
||||
msg->getMessageSize(), msg->getUnreadSize(), opcode, prevOpcode, e.what()));
|
||||
g_logger.error(stdext::format("ProtocolGame parse message exception (%d bytes, %d unread, last opcode is 0x%02x (%d), prev opcode is 0x%02x (%d)): %s"
|
||||
"\nPacket has been saved to packet.log, you can use it to find what was wrong.",
|
||||
msg->getMessageSize(), msg->getUnreadSize(), opcode, opcode, prevOpcode, prevOpcode, e.what()));
|
||||
|
||||
std::ofstream packet("packet.log", std::ifstream::app);
|
||||
if (!packet.is_open())
|
||||
return;
|
||||
packet << stdext::format("ProtocolGame parse message exception (%d bytes, %d unread, last opcode is 0x%02x (%d), prev opcode is 0x%02x (%d)): %s\n",
|
||||
msg->getMessageSize(), msg->getUnreadSize(), opcode, opcode, prevOpcode, prevOpcode, e.what());
|
||||
std::string buffer = msg->getBuffer();
|
||||
for (auto& b : buffer) {
|
||||
packet << std::setfill('0') << std::setw(2) << std::hex << (uint16_t)(uint8_t)b << std::dec << " ";
|
||||
opcodePos -= msg->getHeaderPos();
|
||||
prevOpcodePos -= msg->getHeaderPos();
|
||||
for (size_t i = 0; i < buffer.size(); ++i) {
|
||||
if ((i == prevOpcodePos || i == opcodePos) && i > 0)
|
||||
packet << "\n";
|
||||
packet << std::setfill('0') << std::setw(2) << std::hex << (uint16_t)(uint8_t)buffer[i] << std::dec << " ";
|
||||
}
|
||||
packet << "\n";
|
||||
packet << "\n\n";
|
||||
packet.close();
|
||||
}
|
||||
}
|
||||
@ -535,6 +581,13 @@ void ProtocolGame::parseLogin(const InputMessagePtr& msg)
|
||||
g_lua.callGlobalField("g_game", "onStoreInit", url, coinsPacketSize);
|
||||
}
|
||||
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
|
||||
msg->getU8(); // show exiva button
|
||||
if (g_game.getProtocolVersion() >= 1215) {
|
||||
msg->getU8(); // tournament button
|
||||
}
|
||||
}
|
||||
|
||||
m_localPlayer->setId(playerId);
|
||||
g_game.setServerBeat(serverBeat);
|
||||
g_game.setCanReportBugs(canReportBugs);
|
||||
@ -561,8 +614,8 @@ void ProtocolGame::parseEnterGame(const InputMessagePtr& msg)
|
||||
|
||||
void ProtocolGame::parseStoreButtonIndicators(const InputMessagePtr& msg)
|
||||
{
|
||||
/*bool haveSale = */msg->getU8(); // unknown
|
||||
/*bool haveNewItem = */msg->getU8(); // unknown
|
||||
/*bool haveSale = */msg->getU8();
|
||||
/*bool haveNewItem = */msg->getU8();
|
||||
}
|
||||
|
||||
void ProtocolGame::parseSetStoreDeepLink(const InputMessagePtr& msg)
|
||||
@ -570,9 +623,18 @@ void ProtocolGame::parseSetStoreDeepLink(const InputMessagePtr& msg)
|
||||
/*int currentlyFeaturedServiceType = */msg->getU8();
|
||||
}
|
||||
|
||||
void ProtocolGame::parseRestingAreaState(const InputMessagePtr& msg)
|
||||
{
|
||||
msg->getU8(); // zone
|
||||
msg->getU8(); // state
|
||||
msg->getString(); // message
|
||||
}
|
||||
|
||||
void ProtocolGame::parseBlessings(const InputMessagePtr& msg)
|
||||
{
|
||||
uint16 blessings = msg->getU16();
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol))
|
||||
msg->getU8(); // blessStatus - 1 = Disabled | 2 = normal | 3 = green
|
||||
m_localPlayer->setBlessings(blessings);
|
||||
}
|
||||
|
||||
@ -589,7 +651,8 @@ void ProtocolGame::parseRequestPurchaseData(const InputMessagePtr& msg)
|
||||
|
||||
void ProtocolGame::parseStore(const InputMessagePtr& msg)
|
||||
{
|
||||
msg->getU8(); // unknown
|
||||
if(!g_game.getFeature(Otc::GameTibia12Protocol))
|
||||
msg->getU8(); // unknown
|
||||
|
||||
std::vector<StoreCategory> categories;
|
||||
|
||||
@ -599,7 +662,8 @@ void ProtocolGame::parseStore(const InputMessagePtr& msg)
|
||||
StoreCategory category;
|
||||
|
||||
category.name = msg->getString();
|
||||
category.description = msg->getString();
|
||||
if(!g_game.getFeature(Otc::GameTibia12Protocol))
|
||||
category.description = msg->getString();
|
||||
|
||||
category.state = 0;
|
||||
if(g_game.getFeature(Otc::GameIngameStoreHighlights))
|
||||
@ -637,7 +701,11 @@ void ProtocolGame::parseCoinBalance(const InputMessagePtr& msg)
|
||||
int transferableCoins = msg->getU32();
|
||||
g_game.setTibiaCoins(coins, transferableCoins);
|
||||
|
||||
g_lua.callGlobalField("g_game", "onCoinBalance", coins, transferableCoins);
|
||||
int tournamentCoins = 0;
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() >= 1220)
|
||||
tournamentCoins = msg->getU32();
|
||||
|
||||
g_lua.callGlobalField("g_game", "onCoinBalance", coins, transferableCoins, tournamentCoins);
|
||||
}
|
||||
|
||||
void ProtocolGame::parseCompleteStorePurchase(const InputMessagePtr& msg)
|
||||
@ -646,11 +714,13 @@ void ProtocolGame::parseCompleteStorePurchase(const InputMessagePtr& msg)
|
||||
msg->getU8();
|
||||
|
||||
std::string message = msg->getString();
|
||||
int coins = msg->getU32();
|
||||
int transferableCoins = msg->getU32();
|
||||
|
||||
g_lua.callGlobalField("g_game", "onCoinBalance", coins, transferableCoins);
|
||||
g_lua.callGlobalField("g_game", "onStorePurchase", message);
|
||||
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() < 1220) {
|
||||
int coins = msg->getU32();
|
||||
int transferableCoins = msg->getU32();
|
||||
g_lua.callGlobalField("g_game", "onCoinBalance", coins, transferableCoins);
|
||||
}
|
||||
}
|
||||
|
||||
void ProtocolGame::parseStoreTransactionHistory(const InputMessagePtr &msg)
|
||||
@ -672,11 +742,19 @@ void ProtocolGame::parseStoreTransactionHistory(const InputMessagePtr &msg)
|
||||
for(int i = 0; i < entries; i++) {
|
||||
StoreOffer offer;
|
||||
offer.id = 0;
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() >= 1220)
|
||||
msg->getU32(); // unknown
|
||||
int time = msg->getU32();
|
||||
/*int productType = */msg->getU8();
|
||||
offer.price = msg->getU32();
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol))
|
||||
msg->getU8(); // unknown
|
||||
|
||||
offer.name = msg->getString();
|
||||
offer.description = std::string("Bought on: ") + stdext::timestamp_to_date(time);
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() >= 1220)
|
||||
msg->getU8(); // unknown, offer details?
|
||||
|
||||
offers.push_back(offer);
|
||||
}
|
||||
|
||||
@ -685,50 +763,135 @@ void ProtocolGame::parseStoreTransactionHistory(const InputMessagePtr &msg)
|
||||
|
||||
void ProtocolGame::parseStoreOffers(const InputMessagePtr& msg)
|
||||
{
|
||||
//TODO: Update to tibia 12 protocol
|
||||
std::string categoryName = msg->getString();
|
||||
std::vector<StoreOffer> offers;
|
||||
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
|
||||
msg->getU32(); // redirect
|
||||
msg->getU8(); // sorting type
|
||||
int filterCount = msg->getU8(); // filters available
|
||||
for (int i = 0; i < filterCount; ++i)
|
||||
msg->getString();
|
||||
int shownFiltersCount = msg->getU16();
|
||||
for (int i = 0; i < shownFiltersCount; ++i)
|
||||
msg->getU8();
|
||||
}
|
||||
|
||||
int offers_count = msg->getU16();
|
||||
for(int i = 0; i < offers_count; i++) {
|
||||
StoreOffer offer;
|
||||
|
||||
offer.id = msg->getU32();
|
||||
offer.name = msg->getString();
|
||||
offer.description = msg->getString();
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
|
||||
offer.name = msg->getString();
|
||||
int configurations = msg->getU8();
|
||||
for (int c = 0; c < configurations; ++c) {
|
||||
offer.id = msg->getU32();
|
||||
msg->getU16(); // count?
|
||||
offer.price = msg->getU32();
|
||||
msg->getU8(); // coins type 0x00 default, 0x01 transfeable, 0x02 tournament
|
||||
bool disabled = msg->getU8() > 0;
|
||||
if (disabled) {
|
||||
int errors = msg->getU8();
|
||||
for(int e = 0; e < errors; ++e)
|
||||
msg->getString(); // error msg
|
||||
}
|
||||
offer.state = msg->getU8();
|
||||
if (offer.state == 2 && g_game.getFeature(Otc::GameIngameStoreHighlights) && g_game.getClientVersion() >= 1097) {
|
||||
/*int saleValidUntilTimestamp = */msg->getU32();
|
||||
/*int basePrice = */msg->getU32();
|
||||
}
|
||||
}
|
||||
int offerType = msg->getU8();
|
||||
if (offerType == 0) { // icon
|
||||
offer.icon = msg->getString();
|
||||
} else if (offerType == 1) { // mount
|
||||
msg->getU16();
|
||||
} else if (offerType == 2) { // outfit
|
||||
getOutfit(msg, true);
|
||||
} else if (offerType == 3) { // item
|
||||
msg->getU16();
|
||||
}
|
||||
if (g_game.getProtocolVersion() >= 1212)
|
||||
msg->getU8();
|
||||
msg->getString(); // filter
|
||||
msg->getU32(); // TimeAddedToStore
|
||||
msg->getU16(); // TimesBought
|
||||
msg->getU8(); // RequiresConfiguration
|
||||
} else {
|
||||
offer.id = msg->getU32();
|
||||
offer.name = msg->getString();
|
||||
offer.description = msg->getString();
|
||||
|
||||
offer.price = msg->getU32();
|
||||
offer.state = msg->getU8();
|
||||
if(offer.state == 2 && g_game.getFeature(Otc::GameIngameStoreHighlights) && g_game.getClientVersion() >= 1097) {
|
||||
/*int saleValidUntilTimestamp = */msg->getU32();
|
||||
/*int basePrice = */msg->getU32();
|
||||
offer.price = msg->getU32();
|
||||
offer.state = msg->getU8();
|
||||
if (offer.state == 2 && g_game.getFeature(Otc::GameIngameStoreHighlights) && g_game.getClientVersion() >= 1097) {
|
||||
/*int saleValidUntilTimestamp = */msg->getU32();
|
||||
/*int basePrice = */msg->getU32();
|
||||
}
|
||||
|
||||
int disabledState = msg->getU8();
|
||||
std::string disabledReason = "";
|
||||
if (g_game.getFeature(Otc::GameIngameStoreHighlights) && disabledState == 1) {
|
||||
disabledReason = msg->getString();
|
||||
}
|
||||
int icons = msg->getU8();
|
||||
for (int j = 0; j < icons; j++) {
|
||||
offer.icon = msg->getString();
|
||||
}
|
||||
}
|
||||
|
||||
int disabledState = msg->getU8();
|
||||
std::string disabledReason = "";
|
||||
if(g_game.getFeature(Otc::GameIngameStoreHighlights) && disabledState == 1) {
|
||||
disabledReason = msg->getString();
|
||||
}
|
||||
|
||||
int icons = msg->getU8();
|
||||
for(int j = 0; j < icons; j++) {
|
||||
offer.icon = msg->getString();
|
||||
}
|
||||
|
||||
int subOffers = msg->getU16();
|
||||
// this is probably incorrect for tibia 12
|
||||
for(int j = 0; j < subOffers; j++) {
|
||||
std::string name = msg->getString();
|
||||
std::string description = msg->getString();
|
||||
|
||||
int subIcons = msg->getU8();
|
||||
for(int k = 0; k < subIcons; k++) {
|
||||
std::string icon = msg->getString();
|
||||
if (!g_game.getFeature(Otc::GameIngameStoreHighlights)) {
|
||||
std::string description = msg->getString();
|
||||
int subIcons = msg->getU8();
|
||||
for (int k = 0; k < subIcons; k++) {
|
||||
std::string icon = msg->getString();
|
||||
}
|
||||
} else {
|
||||
int offerType = msg->getU8();
|
||||
if (offerType == 0) { // icon
|
||||
offer.icon = msg->getString();
|
||||
} else if (offerType == 1) { // mount
|
||||
msg->getU16();
|
||||
} else if (offerType == 2) { // outfit
|
||||
getOutfit(msg, true);
|
||||
} else if (offerType == 3) { // item
|
||||
msg->getU16();
|
||||
}
|
||||
}
|
||||
std::string serviceType = msg->getString();
|
||||
}
|
||||
|
||||
offers.push_back(offer);
|
||||
}
|
||||
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol) && categoryName == "Home") {
|
||||
int featuredOfferCount = msg->getU8();
|
||||
for (int i = 0; i < featuredOfferCount; ++i) {
|
||||
msg->getString(); // icon/banner
|
||||
int type = msg->getU8();
|
||||
if (type == 1) { // category type
|
||||
msg->getU8();
|
||||
} else if (type == 2) { // category and filter
|
||||
msg->getString(); // category
|
||||
msg->getString(); // filter
|
||||
} else if (type == 3) { // offer type
|
||||
msg->getU8();
|
||||
} else if (type == 4) { // offer id
|
||||
msg->getU32();
|
||||
} else if (type == 5) { // category name
|
||||
msg->getString();
|
||||
}
|
||||
msg->getU8();
|
||||
msg->getU8();
|
||||
}
|
||||
msg->getU8(); // unknown
|
||||
}
|
||||
|
||||
g_lua.callGlobalField("g_game", "onStoreOffers", categoryName, offers);
|
||||
}
|
||||
|
||||
@ -860,6 +1023,9 @@ void ProtocolGame::parseDeath(const InputMessagePtr& msg)
|
||||
if(g_game.getFeature(Otc::GamePenalityOnDeath) && deathType == Otc::DeathRegular)
|
||||
penality = msg->getU8();
|
||||
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol))
|
||||
msg->getU8(); // death redemption
|
||||
|
||||
g_game.processDeath(deathType, penality);
|
||||
}
|
||||
|
||||
@ -1048,6 +1214,10 @@ void ProtocolGame::parseOpenContainer(const InputMessagePtr& msg)
|
||||
int capacity = msg->getU8();
|
||||
bool hasParent = (msg->getU8() != 0);
|
||||
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() >= 1220) {
|
||||
msg->getU8(); //can use depot search
|
||||
}
|
||||
|
||||
bool isUnlocked = true;
|
||||
bool hasPages = false;
|
||||
int containerSize = 0;
|
||||
@ -1136,6 +1306,8 @@ void ProtocolGame::parseOpenNpcTrade(const InputMessagePtr& msg)
|
||||
|
||||
if(g_game.getFeature(Otc::GameNameOnNpcTrade))
|
||||
npcName = msg->getString();
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() >= 1220)
|
||||
msg->getU16(); // shop item id
|
||||
|
||||
int listCount;
|
||||
|
||||
@ -1233,6 +1405,37 @@ void ProtocolGame::parseWorldLight(const InputMessagePtr& msg)
|
||||
void ProtocolGame::parseMagicEffect(const InputMessagePtr& msg)
|
||||
{
|
||||
Position pos = getPosition(msg);
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getClientVersion() >= 1203) {
|
||||
Otc::MagicEffectsType_t effectType = (Otc::MagicEffectsType_t)msg->getU8();
|
||||
while (effectType != Otc::MAGIC_EFFECTS_END_LOOP) {
|
||||
if (effectType == Otc::MAGIC_EFFECTS_CREATE_DISTANCEEFFECT) {
|
||||
uint8_t shotId = msg->getU8();
|
||||
uint8_t offsetX = msg->getU8();
|
||||
uint8_t offsetY = msg->getU8();
|
||||
if (!g_things.isValidDatId(shotId, ThingCategoryMissile)) {
|
||||
g_logger.traceError(stdext::format("invalid missile id %d", shotId));
|
||||
return;
|
||||
}
|
||||
|
||||
MissilePtr missile = MissilePtr(new Missile());
|
||||
missile->setId(shotId);
|
||||
missile->setPath(pos, Position(pos.x + offsetX, pos.y + offsetY, pos.z));
|
||||
g_map.addThing(missile, pos);
|
||||
} else if (effectType == Otc::MAGIC_EFFECTS_CREATE_EFFECT) {
|
||||
uint8_t effectId = msg->getU8();
|
||||
if (!g_things.isValidDatId(effectId, ThingCategoryEffect)) {
|
||||
g_logger.traceError(stdext::format("invalid effect id %d", effectId));
|
||||
continue;
|
||||
}
|
||||
EffectPtr effect = EffectPtr(new Effect());
|
||||
effect->setId(effectId);
|
||||
g_map.addThing(effect, pos);
|
||||
}
|
||||
effectType = (Otc::MagicEffectsType_t)msg->getU8();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int effectId;
|
||||
if(g_game.getFeature(Otc::GameMagicEffectU16))
|
||||
effectId = msg->getU16();
|
||||
@ -1484,10 +1687,12 @@ void ProtocolGame::parsePreyData(const InputMessagePtr& msg)
|
||||
if (state == Otc::PREY_STATE_LOCKED) {
|
||||
Otc::PreyUnlockState_t unlockState = (Otc::PreyUnlockState_t)msg->getU8();
|
||||
int timeUntilFreeReroll = msg->getU16();
|
||||
return g_lua.callGlobalField("g_game", "onPreyLocked", slot, unlockState, timeUntilFreeReroll);
|
||||
uint8_t lockType = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0;
|
||||
return g_lua.callGlobalField("g_game", "onPreyLocked", slot, unlockState, timeUntilFreeReroll, lockType);
|
||||
} else if (state == Otc::PREY_STATE_INACTIVE) {
|
||||
int timeUntilFreeReroll = msg->getU16();
|
||||
return g_lua.callGlobalField("g_game", "onPreyInactive", slot, timeUntilFreeReroll);
|
||||
uint8_t lockType = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0;
|
||||
return g_lua.callGlobalField("g_game", "onPreyInactive", slot, timeUntilFreeReroll, lockType);
|
||||
} else if (state == Otc::PREY_STATE_ACTIVE) {
|
||||
std::string currentHolderName = msg->getString();
|
||||
Outfit currentHolderOutfit = getOutfit(msg, true);
|
||||
@ -1496,7 +1701,8 @@ void ProtocolGame::parsePreyData(const InputMessagePtr& msg)
|
||||
int bonusGrade = msg->getU8();
|
||||
int timeLeft = msg->getU16();
|
||||
int timeUntilFreeReroll = msg->getU16();
|
||||
return g_lua.callGlobalField("g_game", "onPreyActive", slot, currentHolderName, currentHolderOutfit, bonusType, bonusValue, bonusGrade, timeLeft, timeUntilFreeReroll);
|
||||
uint8_t lockType = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0;
|
||||
return g_lua.callGlobalField("g_game", "onPreyActive", slot, currentHolderName, currentHolderOutfit, bonusType, bonusValue, bonusGrade, timeLeft, timeUntilFreeReroll, lockType);
|
||||
} else if (state == Otc::PREY_STATE_SELECTION || state == Otc::PREY_STATE_SELECTION_CHANGE_MONSTER) {
|
||||
Otc::PreyBonusType_t bonusType = Otc::PREY_BONUS_NONE;
|
||||
int bonusValue = -1, bonusGrade = -1;
|
||||
@ -1513,7 +1719,29 @@ void ProtocolGame::parsePreyData(const InputMessagePtr& msg)
|
||||
outfits.push_back(getOutfit(msg, true));
|
||||
}
|
||||
int timeUntilFreeReroll = msg->getU16();
|
||||
return g_lua.callGlobalField("g_game", "onPreySelection", slot, bonusType, bonusValue, bonusGrade, names, outfits, timeUntilFreeReroll);
|
||||
uint8_t lockType = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0;
|
||||
return g_lua.callGlobalField("g_game", "onPreySelection", slot, bonusType, bonusValue, bonusGrade, names, outfits, timeUntilFreeReroll, lockType);
|
||||
} else if (state == Otc::PREY_ACTION_CHANGE_FROM_ALL) {
|
||||
Otc::PreyBonusType_t bonusType = (Otc::PreyBonusType_t)msg->getU8();
|
||||
int bonusValue = msg->getU16();
|
||||
int bonusGrade = msg->getU8();
|
||||
int count = msg->getU16();
|
||||
std::vector<int> races;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
races.push_back(msg->getU16());
|
||||
}
|
||||
int timeUntilFreeReroll = msg->getU16();
|
||||
uint8_t lockType = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0;
|
||||
return g_lua.callGlobalField("g_game", "onPreyChangeFromAll", slot, bonusType, bonusValue, bonusGrade, races, timeUntilFreeReroll, lockType);
|
||||
} else if (state == Otc::PREY_STATE_SELECTION_FROMALL) {
|
||||
int count = msg->getU16();
|
||||
std::vector<int> races;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
races.push_back(msg->getU16());
|
||||
}
|
||||
int timeUntilFreeReroll = msg->getU16();
|
||||
uint8_t lockType = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0;
|
||||
return g_lua.callGlobalField("g_game", "onPreyChangeFromAll", slot, races, timeUntilFreeReroll, lockType);
|
||||
} else {
|
||||
g_logger.error(stdext::format("Unknown prey data state: %i", (int)state));
|
||||
}
|
||||
@ -1523,9 +1751,27 @@ void ProtocolGame::parsePreyData(const InputMessagePtr& msg)
|
||||
void ProtocolGame::parsePreyPrices(const InputMessagePtr& msg)
|
||||
{
|
||||
int price = msg->getU32();
|
||||
g_lua.callGlobalField("g_game", "onPreyPrice", price);
|
||||
int wildcard = -1, directly = -1;
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
|
||||
wildcard = msg->getU8();
|
||||
directly = msg->getU8();
|
||||
if (g_game.getProtocolVersion() >= 1230) {
|
||||
msg->getU32();
|
||||
msg->getU32();
|
||||
msg->getU8();
|
||||
msg->getU8();
|
||||
}
|
||||
}
|
||||
g_lua.callGlobalField("g_game", "onPreyPrice", price, wildcard, directly);
|
||||
}
|
||||
|
||||
void ProtocolGame::parseStoreOfferDescription(const InputMessagePtr& msg)
|
||||
{
|
||||
msg->getU32(); // offer id
|
||||
msg->getString(); // description
|
||||
}
|
||||
|
||||
|
||||
void ProtocolGame::parsePlayerInfo(const InputMessagePtr& msg)
|
||||
{
|
||||
bool premium = msg->getU8(); // premium
|
||||
@ -1567,7 +1813,7 @@ void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg)
|
||||
freeCapacity = msg->getU16() / 100.0;
|
||||
|
||||
double totalCapacity = 0;
|
||||
if(g_game.getFeature(Otc::GameTotalCapacity))
|
||||
if(g_game.getFeature(Otc::GameTotalCapacity) && !g_game.getFeature(Otc::GameTibia12Protocol))
|
||||
totalCapacity = msg->getU32() / 100.0;
|
||||
|
||||
double experience;
|
||||
@ -1589,6 +1835,7 @@ void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg)
|
||||
/*double experienceBonus = */msg->getDouble();
|
||||
} else {
|
||||
/*int baseXpGain = */msg->getU16();
|
||||
if(!g_game.getFeature(Otc::GameTibia12Protocol))
|
||||
/*int voucherAddend = */msg->getU16();
|
||||
/*int grindingAddend = */msg->getU16();
|
||||
/*int storeBoostAddend = */ msg->getU16();
|
||||
@ -1607,19 +1854,26 @@ void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg)
|
||||
maxMana = msg->getU16();
|
||||
}
|
||||
|
||||
double magicLevel;
|
||||
if (g_game.getFeature(Otc::GameDoubleMagicLevel))
|
||||
magicLevel = msg->getU16();
|
||||
else
|
||||
magicLevel = msg->getU8();
|
||||
double magicLevel = 0;
|
||||
if (!g_game.getFeature(Otc::GameTibia12Protocol)) {
|
||||
if (g_game.getFeature(Otc::GameDoubleMagicLevel))
|
||||
magicLevel = msg->getU16();
|
||||
else
|
||||
magicLevel = msg->getU8();
|
||||
}
|
||||
|
||||
double baseMagicLevel;
|
||||
if(g_game.getFeature(Otc::GameSkillsBase))
|
||||
baseMagicLevel = msg->getU8();
|
||||
else
|
||||
baseMagicLevel = magicLevel;
|
||||
double baseMagicLevel = 0;
|
||||
if (!g_game.getFeature(Otc::GameTibia12Protocol)) {
|
||||
if (g_game.getFeature(Otc::GameSkillsBase))
|
||||
baseMagicLevel = msg->getU8();
|
||||
else
|
||||
baseMagicLevel = magicLevel;
|
||||
}
|
||||
|
||||
double magicLevelPercent = 0;
|
||||
if(!g_game.getFeature(Otc::GameTibia12Protocol))
|
||||
magicLevelPercent = msg->getU8();
|
||||
|
||||
double magicLevelPercent = msg->getU8();
|
||||
double soul;
|
||||
if (g_game.getFeature(Otc::GameDoubleSoul))
|
||||
soul = msg->getU16();
|
||||
@ -1649,12 +1903,15 @@ void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg)
|
||||
|
||||
m_localPlayer->setHealth(health, maxHealth);
|
||||
m_localPlayer->setFreeCapacity(freeCapacity);
|
||||
m_localPlayer->setTotalCapacity(totalCapacity);
|
||||
if (!g_game.getFeature(Otc::GameTibia12Protocol))
|
||||
m_localPlayer->setTotalCapacity(totalCapacity);
|
||||
m_localPlayer->setExperience(experience);
|
||||
m_localPlayer->setLevel(level, levelPercent);
|
||||
m_localPlayer->setMana(mana, maxMana);
|
||||
m_localPlayer->setMagicLevel(magicLevel, magicLevelPercent);
|
||||
m_localPlayer->setBaseMagicLevel(baseMagicLevel);
|
||||
if (!g_game.getFeature(Otc::GameTibia12Protocol)) {
|
||||
m_localPlayer->setMagicLevel(magicLevel, magicLevelPercent);
|
||||
m_localPlayer->setBaseMagicLevel(baseMagicLevel);
|
||||
}
|
||||
m_localPlayer->setStamina(stamina);
|
||||
m_localPlayer->setSoul(soul);
|
||||
m_localPlayer->setBaseSpeed(baseSpeed);
|
||||
@ -1668,6 +1925,15 @@ void ProtocolGame::parsePlayerSkills(const InputMessagePtr& msg)
|
||||
if(g_game.getFeature(Otc::GameAdditionalSkills))
|
||||
lastSkill = Otc::LastSkill;
|
||||
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
|
||||
int level = msg->getU16();
|
||||
int baseLevel = msg->getU16();
|
||||
msg->getU16(); // unknown
|
||||
int levelPercent = msg->getU16();
|
||||
m_localPlayer->setMagicLevel(level, levelPercent);
|
||||
m_localPlayer->setBaseMagicLevel(baseLevel);
|
||||
}
|
||||
|
||||
for(int skill = 0; skill < lastSkill; skill++) {
|
||||
int level;
|
||||
|
||||
@ -1687,18 +1953,33 @@ void ProtocolGame::parsePlayerSkills(const InputMessagePtr& msg)
|
||||
|
||||
int levelPercent = 0;
|
||||
// Critical, Life Leech and Mana Leech have no level percent
|
||||
if(skill <= Otc::Fishing)
|
||||
levelPercent = msg->getU8();
|
||||
if (skill <= Otc::Fishing) {
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol))
|
||||
msg->getU16(); // unknown
|
||||
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol))
|
||||
levelPercent = msg->getU16();
|
||||
else
|
||||
levelPercent = msg->getU8();
|
||||
}
|
||||
|
||||
m_localPlayer->setSkill((Otc::Skill)skill, level, levelPercent);
|
||||
m_localPlayer->setBaseSkill((Otc::Skill)skill, baseLevel);
|
||||
}
|
||||
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
|
||||
uint32_t totalCapacity = msg->getU32();
|
||||
msg->getU32(); // base capacity?
|
||||
m_localPlayer->setTotalCapacity(totalCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
void ProtocolGame::parsePlayerState(const InputMessagePtr& msg)
|
||||
{
|
||||
int states;
|
||||
if(g_game.getFeature(Otc::GamePlayerStateU16))
|
||||
if (g_game.getFeature(Otc::GamePlayerStateU32))
|
||||
states = msg->getU32();
|
||||
else if(g_game.getFeature(Otc::GamePlayerStateU16))
|
||||
states = msg->getU16();
|
||||
else
|
||||
states = msg->getU8();
|
||||
@ -2038,12 +2319,17 @@ void ProtocolGame::parseOpenOutfitWindow(const InputMessagePtr& msg)
|
||||
std::vector<std::tuple<int, std::string, int> > outfitList;
|
||||
|
||||
if(g_game.getFeature(Otc::GameNewOutfitProtocol)) {
|
||||
int outfitCount = msg->getU8();
|
||||
int outfitCount = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU16() : msg->getU8();
|
||||
for(int i = 0; i < outfitCount; i++) {
|
||||
int outfitId = msg->getU16();
|
||||
std::string outfitName = msg->getString();
|
||||
int outfitAddons = msg->getU8();
|
||||
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
|
||||
bool locked = msg->getU8() > 0;
|
||||
if (locked) {
|
||||
msg->getU32(); // store offer id
|
||||
}
|
||||
}
|
||||
outfitList.push_back(std::make_tuple(outfitId, outfitName, outfitAddons));
|
||||
}
|
||||
} else {
|
||||
@ -2061,17 +2347,55 @@ void ProtocolGame::parseOpenOutfitWindow(const InputMessagePtr& msg)
|
||||
}
|
||||
|
||||
std::vector<std::tuple<int, std::string> > mountList;
|
||||
std::vector<std::tuple<int, std::string> > wingList;
|
||||
std::vector<std::tuple<int, std::string> > auraList;
|
||||
std::vector<std::tuple<int, std::string> > shaderList;
|
||||
if(g_game.getFeature(Otc::GamePlayerMounts)) {
|
||||
int mountCount = msg->getU8();
|
||||
int mountCount = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU16() : msg->getU8();
|
||||
for(int i = 0; i < mountCount; ++i) {
|
||||
int mountId = msg->getU16(); // mount type
|
||||
std::string mountName = msg->getString(); // mount name
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
|
||||
bool locked = msg->getU8() > 0;
|
||||
if (locked) {
|
||||
msg->getU32(); // store offer id
|
||||
}
|
||||
}
|
||||
|
||||
mountList.push_back(std::make_tuple(mountId, mountName));
|
||||
}
|
||||
}
|
||||
|
||||
g_game.processOpenOutfitWindow(currentOutfit, outfitList, mountList);
|
||||
if (g_game.getFeature(Otc::GameWingsAndAura)) {
|
||||
int wingCount = msg->getU8();
|
||||
for (int i = 0; i < wingCount; ++i) {
|
||||
int wingId = msg->getU16();
|
||||
std::string wingName = msg->getString();
|
||||
wingList.push_back(std::make_tuple(wingId, wingName));
|
||||
}
|
||||
int auraCount = msg->getU8();
|
||||
for (int i = 0; i < auraCount; ++i) {
|
||||
int auraId = msg->getU16();
|
||||
std::string auraName = msg->getString();
|
||||
auraList.push_back(std::make_tuple(auraId, auraName));
|
||||
}
|
||||
}
|
||||
|
||||
if (g_game.getFeature(Otc::GameOutfitShaders)) {
|
||||
int shaderCount = msg->getU8();
|
||||
for (int i = 0; i < shaderCount; ++i) {
|
||||
int shaderId = msg->getU16();
|
||||
std::string shaderName = msg->getString();
|
||||
shaderList.push_back(std::make_tuple(shaderId, shaderName));
|
||||
}
|
||||
}
|
||||
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
|
||||
msg->getU8(); // tryOnMount, tryOnOutfit
|
||||
msg->getU8(); // mounted?
|
||||
}
|
||||
|
||||
g_game.processOpenOutfitWindow(currentOutfit, outfitList, mountList, wingList, auraList, shaderList);
|
||||
}
|
||||
|
||||
void ProtocolGame::parseVipAdd(const InputMessagePtr& msg)
|
||||
@ -2118,6 +2442,10 @@ void ProtocolGame::parseTutorialHint(const InputMessagePtr& msg)
|
||||
|
||||
void ProtocolGame::parseAutomapFlag(const InputMessagePtr& msg)
|
||||
{
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
|
||||
msg->getU8(); // unknown
|
||||
}
|
||||
|
||||
Position pos = getPosition(msg);
|
||||
int icon = msg->getU8();
|
||||
std::string description = msg->getString();
|
||||
@ -2152,6 +2480,9 @@ void ProtocolGame::parseQuestLine(const InputMessagePtr& msg)
|
||||
int questId = msg->getU16();
|
||||
int missionCount = msg->getU8();
|
||||
for(int i = 0; i < missionCount; i++) {
|
||||
if (g_game.getFeature(Otc::GameTibia12Protocol))
|
||||
msg->getU16(); // mission id
|
||||
|
||||
std::string missionName = msg->getString();
|
||||
std::string missionDescrition = msg->getString();
|
||||
questMissions.push_back(std::make_tuple(missionName, missionDescrition));
|
||||
@ -2250,6 +2581,37 @@ void ProtocolGame::parseMessageDialog(const InputMessagePtr& msg)
|
||||
msg->getString();
|
||||
}
|
||||
|
||||
void ProtocolGame::parseBlessDialog(const InputMessagePtr& msg)
|
||||
{
|
||||
// parse bless amount
|
||||
uint8_t totalBless = msg->getU8(); // total bless
|
||||
|
||||
// parse each bless
|
||||
for (int i = 0; i < totalBless; i++) {
|
||||
msg->getU16(); // bless bit wise
|
||||
msg->getU8(); // player bless count
|
||||
}
|
||||
|
||||
// parse general info
|
||||
msg->getU8(); // premium
|
||||
msg->getU8(); // promotion
|
||||
msg->getU8(); // pvp min xp loss
|
||||
msg->getU8(); // pvp max xp loss
|
||||
msg->getU8(); // pve exp loss
|
||||
msg->getU8(); // equip pvp loss
|
||||
msg->getU8(); // equip pve loss
|
||||
msg->getU8(); // skull
|
||||
msg->getU8(); // aol
|
||||
|
||||
// parse log
|
||||
uint8_t logCount = msg->getU8(); // log count
|
||||
for (int i = 0; i < logCount; i++) {
|
||||
msg->getU32(); // timestamp
|
||||
msg->getU8(); // color message (0 = white loss, 1 = red)
|
||||
msg->getString(); // history message
|
||||
}
|
||||
}
|
||||
|
||||
void ProtocolGame::parseResourceBalance(const InputMessagePtr& msg)
|
||||
{
|
||||
uint8_t type = msg->getU8();
|
||||
@ -2257,6 +2619,13 @@ void ProtocolGame::parseResourceBalance(const InputMessagePtr& msg)
|
||||
g_lua.callGlobalField("g_game", "onResourceBalance", type, amount);
|
||||
}
|
||||
|
||||
void ProtocolGame::parseServerTime(const InputMessagePtr& msg)
|
||||
{
|
||||
uint8_t minutes = msg->getU8();
|
||||
uint8_t seconds = msg->getU8();
|
||||
g_lua.callGlobalField("g_game", "onServerTime", minutes, seconds);
|
||||
}
|
||||
|
||||
void ProtocolGame::parseQuestTracker(const InputMessagePtr& msg)
|
||||
{
|
||||
msg->getU8();
|
||||
@ -2301,6 +2670,53 @@ void ProtocolGame::parseCloseImbuementWindow(const InputMessagePtr&)
|
||||
g_lua.callGlobalField("g_game", "onCloseImbuementWindow");
|
||||
}
|
||||
|
||||
void ProtocolGame::parseCyclopediaNewDetails(const InputMessagePtr& msg)
|
||||
{
|
||||
msg->getU16(); // race id
|
||||
}
|
||||
|
||||
void ProtocolGame::parseDailyRewardState(const InputMessagePtr& msg)
|
||||
{
|
||||
msg->getU8(); // state
|
||||
}
|
||||
|
||||
void ProtocolGame::parseOpenRewardWall(const InputMessagePtr& msg)
|
||||
{
|
||||
msg->getU8(); // bonus shrine (1) or instant bonus (0)
|
||||
msg->getU32(); // next reward time
|
||||
msg->getU8(); // day streak day
|
||||
uint8_t wasDailyRewardTaken = msg->getU8(); // taken (player already took reward?)
|
||||
|
||||
if (wasDailyRewardTaken) {
|
||||
msg->getString(); // error message
|
||||
}
|
||||
|
||||
msg->getU32(); // time left to pickup reward without loosing streak
|
||||
msg->getU16(); // day streak level
|
||||
msg->getU16(); // unknown
|
||||
}
|
||||
|
||||
void ProtocolGame::parseDailyReward(const InputMessagePtr& msg)
|
||||
{
|
||||
msg->getU8(); // state
|
||||
|
||||
// TODO: implement daily reward usage
|
||||
}
|
||||
|
||||
void ProtocolGame::parseDailyRewardHistory(const InputMessagePtr& msg)
|
||||
{
|
||||
uint8_t historyCount = msg->getU8(); // history count
|
||||
|
||||
for (int i = 0; i < historyCount; i++) {
|
||||
msg->getU32(); // timestamp
|
||||
msg->getU8(); // is Premium
|
||||
msg->getString(); // description
|
||||
msg->getU16(); // daystreak
|
||||
}
|
||||
|
||||
// TODO: implement reward history usage
|
||||
}
|
||||
|
||||
Imbuement ProtocolGame::getImbuementInfo(const InputMessagePtr& msg)
|
||||
{
|
||||
Imbuement i;
|
||||
@ -2334,9 +2750,9 @@ void ProtocolGame::parseKillTracker(const InputMessagePtr& msg)
|
||||
msg->getU8();
|
||||
msg->getU8();
|
||||
msg->getU8();
|
||||
bool emptyCorpse = msg->getU8() == 0;
|
||||
if (!emptyCorpse) {
|
||||
getItem(msg);
|
||||
int corpseSize = msg->getU8(); // corpse size
|
||||
for (int i = 0; i < corpseSize; i++) {
|
||||
getItem(msg); // corpse item
|
||||
}
|
||||
}
|
||||
|
||||
@ -2351,10 +2767,19 @@ void ProtocolGame::parseImpactTracker(const InputMessagePtr& msg)
|
||||
msg->getU32();
|
||||
}
|
||||
|
||||
void ProtocolGame::parseItemsPrices(const InputMessagePtr& msg)
|
||||
{
|
||||
uint16_t count = msg->getU16();
|
||||
for (uint16_t i = 0; i < count; ++i) {
|
||||
/*uint16_t itemId = */msg->getU16();
|
||||
/*uint32_t price = */msg->getU32();
|
||||
}
|
||||
}
|
||||
|
||||
void ProtocolGame::parseLootTracker(const InputMessagePtr& msg)
|
||||
{
|
||||
msg->getU16();
|
||||
msg->getString();
|
||||
getItem(msg);
|
||||
msg->getString(); // item name
|
||||
}
|
||||
|
||||
|
||||
@ -2520,7 +2945,7 @@ int ProtocolGame::setTileDescription(const InputMessagePtr& msg, Position positi
|
||||
g_map.setTileSpeed(position, groundSpeed, blocking);
|
||||
}
|
||||
|
||||
if(g_game.getFeature(Otc::GameEnvironmentEffect)) {
|
||||
if(g_game.getFeature(Otc::GameEnvironmentEffect) && !g_game.getFeature(Otc::GameTibia12Protocol)) {
|
||||
msg->getU16();
|
||||
}
|
||||
|
||||
@ -2594,6 +3019,9 @@ Outfit ProtocolGame::getOutfit(const InputMessagePtr& msg, bool ignoreMount)
|
||||
outfit.setWings(msg->getU16());
|
||||
outfit.setAura(msg->getU16());
|
||||
}
|
||||
if (g_game.getFeature(Otc::GameOutfitShaders)) {
|
||||
outfit.setShader(msg->getString());
|
||||
}
|
||||
}
|
||||
|
||||
return outfit;
|
||||
@ -2606,7 +3034,7 @@ ThingPtr ProtocolGame::getThing(const InputMessagePtr& msg)
|
||||
int id = msg->getU16();
|
||||
|
||||
if(id == 0)
|
||||
stdext::throw_exception("invalid thing id");
|
||||
stdext::throw_exception("invalid thing id (0)");
|
||||
else if(id == Proto::UnknownCreature || id == Proto::OutdatedCreature || id == Proto::Creature)
|
||||
thing = getCreature(msg, id);
|
||||
else if(id == Proto::StaticText) // otclient only
|
||||
@ -2674,6 +3102,9 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type)
|
||||
creatureType = Proto::CreatureTypeNpc;
|
||||
}
|
||||
|
||||
if (creatureType == Proto::CreatureTypeSummonOwn)
|
||||
msg->getU32(); // master
|
||||
|
||||
std::string name = g_game.formatCreatureName(msg->getString());
|
||||
|
||||
if(id == m_localPlayer->getId())
|
||||
@ -2685,11 +3116,13 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type)
|
||||
else
|
||||
creature = PlayerPtr(new Player);
|
||||
}
|
||||
else if(creatureType == Proto::CreatureTypeMonster)
|
||||
else if (creatureType == Proto::CreatureTypeMonster)
|
||||
creature = MonsterPtr(new Monster);
|
||||
else if(creatureType == Proto::CreatureTypeNpc)
|
||||
else if (creatureType == Proto::CreatureTypeNpc)
|
||||
creature = NpcPtr(new Npc);
|
||||
else
|
||||
else if (creatureType == Proto::CreatureTypeSummonOwn) {
|
||||
creature = MonsterPtr(new Monster);
|
||||
} else
|
||||
g_logger.traceError("creature type is invalid");
|
||||
|
||||
if(creature) {
|
||||
@ -2724,6 +3157,8 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type)
|
||||
|
||||
if(g_game.getFeature(Otc::GameThingMarks)) {
|
||||
creatureType = msg->getU8();
|
||||
if (creatureType == Proto::CreatureTypeSummonOwn)
|
||||
msg->getU32(); // master
|
||||
}
|
||||
|
||||
if(g_game.getFeature(Otc::GameCreatureIcons)) {
|
||||
@ -2732,7 +3167,10 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type)
|
||||
|
||||
if(g_game.getFeature(Otc::GameThingMarks)) {
|
||||
mark = msg->getU8(); // mark
|
||||
msg->getU16(); // helpers
|
||||
if(g_game.getFeature(Otc::GameTibia12Protocol))
|
||||
msg->getU8(); // inspection?
|
||||
else
|
||||
msg->getU16(); // helpers?
|
||||
|
||||
if(creature) {
|
||||
if(mark == 0xff)
|
||||
@ -2804,12 +3242,18 @@ ItemPtr ProtocolGame::getItem(const InputMessagePtr& msg, int id, bool hasDescri
|
||||
if(item->getId() == 0)
|
||||
stdext::throw_exception(stdext::format("unable to create item with invalid id %d", id));
|
||||
|
||||
if(g_game.getFeature(Otc::GameThingMarks)) {
|
||||
if(g_game.getFeature(Otc::GameThingMarks) && !g_game.getFeature(Otc::GameTibia12Protocol)) {
|
||||
msg->getU8(); // mark
|
||||
}
|
||||
|
||||
if(item->isStackable() || item->isFluidContainer() || item->isSplash() || item->isChargeable())
|
||||
item->setCountOrSubType(msg->getU8());
|
||||
else if (item->rawGetThingType()->isContainer() && g_game.getFeature(Otc::GameTibia12Protocol)) {
|
||||
// not sure about this part
|
||||
uint8_t hasQuickLootFlags = msg->getU8();
|
||||
if (hasQuickLootFlags > 0)
|
||||
msg->getU32(); // quick loot flags
|
||||
}
|
||||
|
||||
if(g_game.getFeature(Otc::GameItemAnimationPhase)) {
|
||||
if(item->getAnimationPhases() > 1) {
|
||||
|
@ -28,12 +28,12 @@
|
||||
#include <framework/util/crypt.h>
|
||||
#include <framework/util/extras.h>
|
||||
|
||||
void ProtocolGame::send(const OutputMessagePtr& outputMessage)
|
||||
void ProtocolGame::send(const OutputMessagePtr& outputMessage, bool rawPacket)
|
||||
{
|
||||
// avoid usage of automated sends (bot modules)
|
||||
if(!g_game.checkBotProtection())
|
||||
return;
|
||||
Protocol::send(outputMessage);
|
||||
Protocol::send(outputMessage, rawPacket);
|
||||
}
|
||||
|
||||
void ProtocolGame::sendExtendedOpcode(uint8 opcode, const std::string& buffer)
|
||||
@ -51,6 +51,13 @@ void ProtocolGame::sendExtendedOpcode(uint8 opcode, const std::string& buffer)
|
||||
g_game.disableBotCall();
|
||||
}
|
||||
|
||||
void ProtocolGame::sendWorldName()
|
||||
{
|
||||
OutputMessagePtr msg(new OutputMessage);
|
||||
msg->addRawString(m_worldName + "\n");
|
||||
send(msg, true);
|
||||
}
|
||||
|
||||
void ProtocolGame::sendLoginPacket(uint challengeTimestamp, uint8 challengeRandom)
|
||||
{
|
||||
OutputMessagePtr msg(new OutputMessage);
|
||||
@ -161,6 +168,9 @@ void ProtocolGame::sendLoginPacket(uint challengeTimestamp, uint8 challengeRando
|
||||
|
||||
if (g_game.getFeature(Otc::GamePacketCompression))
|
||||
enableCompression();
|
||||
|
||||
if (g_game.getFeature(Otc::GameSequencedPackets))
|
||||
enabledSequencedPackets();
|
||||
}
|
||||
|
||||
void ProtocolGame::sendEnterGame()
|
||||
@ -804,6 +814,12 @@ void ProtocolGame::sendChangeOutfit(const Outfit& outfit)
|
||||
msg->addU8(outfit.getAddons());
|
||||
if(g_game.getFeature(Otc::GamePlayerMounts))
|
||||
msg->addU16(outfit.getMount());
|
||||
if (g_game.getFeature(Otc::GameWingsAndAura)) {
|
||||
msg->addU16(outfit.getWings());
|
||||
msg->addU16(outfit.getAura());
|
||||
}
|
||||
if(g_game.getFeature(Otc::GameOutfitShaders))
|
||||
msg->addString(outfit.getShader());
|
||||
send(msg);
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <framework/graphics/paintershaderprogram.h>
|
||||
#include <framework/graphics/graphics.h>
|
||||
#include <framework/core/resourcemanager.h>
|
||||
#include <framework/core/eventdispatcher.h>
|
||||
|
||||
ShaderManager g_shaders;
|
||||
|
||||
@ -37,51 +38,36 @@ void ShaderManager::terminate()
|
||||
m_shaders.clear();
|
||||
}
|
||||
|
||||
PainterShaderProgramPtr ShaderManager::createShader(const std::string& name)
|
||||
void ShaderManager::createShader(const std::string& name, std::string vertex, std::string fragment, bool colorMatrix)
|
||||
{
|
||||
return nullptr;
|
||||
if (vertex.find("\n") == std::string::npos) { // file
|
||||
vertex = g_resources.guessFilePath(vertex, "frag");
|
||||
vertex = g_resources.readFileContents(vertex);
|
||||
}
|
||||
if (fragment.find("\n") == std::string::npos) { // file
|
||||
fragment = g_resources.guessFilePath(fragment, "frag");
|
||||
fragment = g_resources.readFileContents(fragment);
|
||||
}
|
||||
|
||||
g_graphicsDispatcher.addEventEx("createShader", [&, name, vertex, fragment, colorMatrix] {
|
||||
auto program = PainterShaderProgram::create(vertex, fragment, colorMatrix);
|
||||
if (program)
|
||||
m_shaders[name] = program;
|
||||
});
|
||||
}
|
||||
|
||||
PainterShaderProgramPtr ShaderManager::createFragmentShader(const std::string& name, std::string file)
|
||||
void ShaderManager::addTexture(const std::string& name, const std::string& file)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PainterShaderProgramPtr ShaderManager::createFragmentShaderFromCode(const std::string& name, const std::string& code)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PainterShaderProgramPtr ShaderManager::createItemShader(const std::string& name, const std::string& file)
|
||||
{
|
||||
PainterShaderProgramPtr shader = createFragmentShader(name, file);
|
||||
if(shader)
|
||||
setupItemShader(shader);
|
||||
return shader;
|
||||
}
|
||||
|
||||
PainterShaderProgramPtr ShaderManager::createMapShader(const std::string& name, const std::string& file)
|
||||
{
|
||||
PainterShaderProgramPtr shader = createFragmentShader(name, file);
|
||||
if(shader)
|
||||
setupMapShader(shader);
|
||||
return shader;
|
||||
}
|
||||
|
||||
void ShaderManager::setupItemShader(const PainterShaderProgramPtr& shader)
|
||||
{
|
||||
if (!shader)
|
||||
return;
|
||||
}
|
||||
|
||||
void ShaderManager::setupMapShader(const PainterShaderProgramPtr& shader)
|
||||
{
|
||||
if(!shader)
|
||||
return;
|
||||
g_graphicsDispatcher.addEventEx("addTexture", [&, name, file] {
|
||||
auto program = getShader(name);
|
||||
if (program)
|
||||
program->addMultiTexture(file);
|
||||
});
|
||||
}
|
||||
|
||||
PainterShaderProgramPtr ShaderManager::getShader(const std::string& name)
|
||||
{
|
||||
VALIDATE_GRAPHICS_THREAD();
|
||||
auto it = m_shaders.find(name);
|
||||
if(it != m_shaders.end())
|
||||
return it->second;
|
||||
|
@ -30,29 +30,18 @@
|
||||
class ShaderManager
|
||||
{
|
||||
public:
|
||||
enum {
|
||||
ITEM_ID_UNIFORM = 10,
|
||||
MAP_CENTER_COORD = 10,
|
||||
MAP_GLOBAL_COORD = 11,
|
||||
MAP_ZOOM = 12
|
||||
};
|
||||
|
||||
void init();
|
||||
void terminate();
|
||||
|
||||
PainterShaderProgramPtr createShader(const std::string& name);
|
||||
PainterShaderProgramPtr createFragmentShader(const std::string& name, std::string file);
|
||||
PainterShaderProgramPtr createFragmentShaderFromCode(const std::string& name, const std::string& code);
|
||||
|
||||
PainterShaderProgramPtr createItemShader(const std::string& name, const std::string& file);
|
||||
PainterShaderProgramPtr createMapShader(const std::string& name, const std::string& file);
|
||||
|
||||
void createShader(const std::string& name, std::string vertex, std::string fragment, bool colorMatrix = false);
|
||||
void createOutfitShader(const std::string& name, std::string vertex, std::string fragment)
|
||||
{
|
||||
return createShader(name, vertex, fragment, true);
|
||||
}
|
||||
void addTexture(const std::string& name, const std::string& file);
|
||||
PainterShaderProgramPtr getShader(const std::string& name);
|
||||
|
||||
private:
|
||||
void setupItemShader(const PainterShaderProgramPtr& shader);
|
||||
void setupMapShader(const PainterShaderProgramPtr& shader);
|
||||
|
||||
std::unordered_map<std::string, PainterShaderProgramPtr> m_shaders;
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user