This commit is contained in:
OTCv8 2020-07-12 01:24:25 +02:00
parent 1729e7d635
commit f17ac1ec71
36 changed files with 1082 additions and 416 deletions

View File

@ -1,7 +1,7 @@
OTClientV8 is made available under the MIT License OTClientV8 is made available under the MIT License
Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient> 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,7 +1,9 @@
# OTClientV8 # OTClientV8
OTClientV8 is highly optimized tile based 2d game engine built with c++, lua, physfs, OpenGL ES 2.0 and OpenAL. OTClientV8 is highly optimized, cross-platform tile based 2d game engine built with c++17, 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. 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: Supported platforms:
- Windows (min. Windows 7) - Windows (min. Windows 7)
- Linux - Linux
@ -12,7 +14,7 @@ Planned support:
- iOS - iOS
- WebAssembly - 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. 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. If you're interested in buying access to sources, contact otclient@otclient.ovh or kondrah#7945 on discord.

View 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;
}

View 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;
}

View File

@ -15,7 +15,7 @@ local serverSelector
local clientVersionSelector local clientVersionSelector
local serverHostTextEdit local serverHostTextEdit
local rememberPasswordBox 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 = {} local checkedByUpdater = {}
@ -107,12 +107,89 @@ local function validateThings(things)
return incorrectThings return incorrectThings
end 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) local function onHTTPResult(data, err)
if err then if err then
return EnterGame.onError(err) return EnterGame.onError(err)
end end
if data['error'] and #data['error'] > 0 then if data['error'] and data['error']:len() > 0 then
return EnterGame.onLoginError(data['error']) 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 end
local characters = data["characters"] local characters = data["characters"]
@ -331,11 +408,18 @@ function EnterGame.doLogin()
g_settings.set('client-version', G.clientVersion) g_settings.set('client-version', G.clientVersion)
g_settings.save() 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() return EnterGame.doLoginHttp()
end end
local server_params = G.host:split(":")
local server_ip = server_params[1] local server_ip = server_params[1]
local server_port = 7171 local server_port = 7171
if #server_params >= 2 then if #server_params >= 2 then
@ -381,6 +465,9 @@ function EnterGame.doLogin()
EnterGame.show() EnterGame.show()
end }) 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 -- if you have custom rsa or protocol edit it here
g_game.setClientVersion(G.clientVersion) g_game.setClientVersion(G.clientVersion)
g_game.setProtocolVersion(g_game.getClientProtocolVersion(G.clientVersion)) g_game.setProtocolVersion(g_game.getClientProtocolVersion(G.clientVersion))
@ -423,12 +510,18 @@ function EnterGame.doLoginHttp()
end }) end })
local data = { local data = {
type = "login",
account = G.account, account = G.account,
accountname = G.account,
email = G.account,
password = G.password, password = G.password,
accountpassword = G.password,
token = G.authenticatorToken, token = G.authenticatorToken,
version = APP_VERSION, version = APP_VERSION,
uid = G.UUID uid = G.UUID,
stayloggedin = true
} }
HTTP.postJSON(G.host, data, onHTTPResult) HTTP.postJSON(G.host, data, onHTTPResult)
EnterGame.hide() EnterGame.hide()
end end

View File

@ -112,7 +112,7 @@ EnterGameWindow
MenuLabel MenuLabel
id: serverLabel id: serverLabel
!text: tr('IP:PORT') !text: tr('IP:PORT or URL')
anchors.left: prev.left anchors.left: prev.left
anchors.top: prev.bottom anchors.top: prev.bottom
margin-top: 8 margin-top: 8

View File

@ -256,14 +256,18 @@ function updateStatus()
if not Services or not Services.status or Services.status:len() < 4 then return end if not Services or not Services.status or Services.status:len() < 4 then return end
if not topMenu.onlineLabel then return end if not topMenu.onlineLabel then return end
if g_game.isOnline() 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 if err then
g_logger.warning("HTTP error for " .. Services.status .. ": " .. err) g_logger.warning("HTTP error for " .. Services.status .. ": " .. err)
statusUpdateEvent = scheduleEvent(updateStatus, 5000) statusUpdateEvent = scheduleEvent(updateStatus, 5000)
return return
end end
if data.online and topMenu.onlineLabel then if topMenu.onlineLabel then
topMenu.onlineLabel:setText(data.online) if data.online then
topMenu.onlineLabel:setText(data.online)
elseif data.playersonline then
topMenu.onlineLabel:setText(data.playersonline .. " players online")
end
end end
if data.discord_online and topMenu.discordLabel then if data.discord_online and topMenu.discordLabel then
topMenu.discordLabel:setText(data.discord_online) topMenu.discordLabel:setText(data.discord_online)

View File

@ -1,9 +1,10 @@
HTTP = { HTTP = {
timeout=5, timeout=5,
websocketTimeout=15, websocketTimeout=15,
agent="Mozilla/5.0",
imageId=1000, imageId=1000,
images={}, images={},
operations={} operations={},
} }
function HTTP.get(url, callback) function HTTP.get(url, callback)
@ -274,4 +275,4 @@ connect(g_http,
onWsClose = HTTP.onWsClose, onWsClose = HTTP.onWsClose,
onWsError = HTTP.onWsError, onWsError = HTTP.onWsError,
}) })
g_http.setUserAgent(HTTP.agent)

View File

@ -335,7 +335,7 @@ function check()
return return
end end
checkEvent = scheduleEvent(check, 25) checkEvent = scheduleEvent(check, 10)
local status, result = pcall(function() local status, result = pcall(function()
return botExecutor.script() return botExecutor.script()

View File

@ -34,11 +34,16 @@ context.macro = function(timeout, name, hotkey, callback, parent)
hotkey = retranslateKeyComboDesc(hotkey) hotkey = retranslateKeyComboDesc(hotkey)
end end
-- min timeout is 50, to avoid lags
if timeout < 50 then
timeout = 50
end
table.insert(context._macros, { table.insert(context._macros, {
enabled = false, enabled = false,
name = name, name = name,
timeout = timeout, timeout = timeout,
lastExecution = context.now, lastExecution = context.now + math.random(0, 100),
hotkey = hotkey, hotkey = hotkey,
}) })
local macro = context._macros[#context._macros] local macro = context._macros[#context._macros]

View File

@ -5,11 +5,27 @@ context.getMapPanel = context.getMapView
context.zoomIn = function() modules.game_interface.getMapPanel():zoomIn() end context.zoomIn = function() modules.game_interface.getMapPanel():zoomIn() end
context.zoomOut = function() modules.game_interface.getMapPanel():zoomOut() end context.zoomOut = function() modules.game_interface.getMapPanel():zoomOut() end
context.getSpectators = function(multifloor) context.getSpectators = function(param1, param2)
if multifloor ~= true then --[[
multifloor = false 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 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 end
context.getCreatureById = function(id, multifloor) context.getCreatureById = function(id, multifloor)

View File

@ -161,3 +161,6 @@ context.cancelAttackAndFollow = g_game.cancelAttackAndFollow
context.logout = g_game.forceLogout context.logout = g_game.forceLogout
context.ping = g_game.getPing context.ping = g_game.getPing
modules.game_cooldown.isGroupCooldownIconActive(id)
modules.game_cooldown.isCooldownIconActive(id)

View File

@ -32,6 +32,9 @@ context.getContainers = function() return g_game.getContainers() end
context.getContainer = function(index) return g_game.getContainer(index) end context.getContainer = function(index) return g_game.getContainer(index) end
context.moveToSlot = function(item, slot, count) context.moveToSlot = function(item, slot, count)
if type(item) == 'number' then
item = context.findItem(item)
end
if not item then if not item then
return return
end end

View File

@ -189,5 +189,12 @@ function updateFeatures(version)
g_game.enableFeature(GamePrey) g_game.enableFeature(GamePrey)
end 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() modules.game_things.load()
end end

View File

@ -39,6 +39,7 @@ Module
- game_prey - game_prey
- game_imbuing - game_imbuing
- game_stats - game_stats
- game_shaders
- game_bot - game_bot
@onLoad: init() @onLoad: init()
@onUnload: terminate() @onUnload: terminate()

View File

@ -11,7 +11,7 @@ ADDON_SETS = {
outfitWindow = nil outfitWindow = nil
outfit = nil outfit = nil
outfits = nil outfits = nil
outfitCreature = nil outfitCreatureBox = nil
currentOutfit = 1 currentOutfit = 1
addons = nil addons = nil
@ -21,7 +21,7 @@ colorBoxes = {}
mount = nil mount = nil
mounts = nil mounts = nil
mountCreature = nil mountCreatureBox = nil
currentMount = 1 currentMount = 1
ignoreNextOutfitWindow = 0 ignoreNextOutfitWindow = 0
@ -51,7 +51,65 @@ function updateMount()
mountCreature:setOutfit(mount) mountCreature:setOutfit(mount)
end 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 if ignoreNextOutfitWindow and g_clock.millis() < ignoreNextOutfitWindow + 1000 then
return return
end end
@ -59,36 +117,49 @@ function create(creatureOutfit, outfitList, creatureMount, mountList)
return return
end end
outfitCreature = creatureOutfit
mountCreature = creatureMount
outfits = outfitList
mounts = mountList
destroy() destroy()
outfitWindow = g_ui.displayUI('outfitwindow') outfitWindow = g_ui.displayUI('outfitwindow')
local colorBoxPanel = outfitWindow:getChildById('colorBoxPanel')
-- setup outfit/mount display boxs setupSelector(outfitWindow.type, "type", currentOutfit, outfitList)
local outfitCreatureBox = outfitWindow:getChildById('outfitCreatureBox')
if outfitCreature then local outfit = outfitWindow.type.creature:getOutfit()
outfit = outfitCreature:getOutfit() outfit.head = currentOutfit.head
outfitCreatureBox:setCreature(outfitCreature) outfit.body = currentOutfit.body
else outfit.legs = currentOutfit.legs
outfitCreatureBox:hide() outfit.feet = currentOutfit.feet
outfitWindow:getChildById('outfitName'):hide() outfitWindow.type.creature:setOutfit(outfit)
outfitWindow:getChildById('outfitNextButton'):hide()
outfitWindow:getChildById('outfitPrevButton'):hide() if g_game.getFeature(GamePlayerMounts) then
setupSelector(g_ui.createWidget('OutfitSelectorPanel', outfitWindow.extensions), "mount", currentOutfit, mountList)
end
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 end
local mountCreatureBox = outfitWindow:getChildById('mountCreatureBox') if not outfitWindow.extensions:getFirstChild() then
if mountCreature then outfitWindow:setHeight(outfitWindow:getHeight() - 128)
mount = mountCreature:getOutfit() end
mountCreatureBox:setCreature(mountCreature)
else for j=0,6 do
mountCreatureBox:hide() for i=0,18 do
outfitWindow:getChildById('mountName'):hide() local colorBox = g_ui.createWidget('ColorBox', outfitWindow.colorBoxPanel)
outfitWindow:getChildById('mountNextButton'):hide() local outfitColor = getOutfitColor(j*19 + i)
outfitWindow:getChildById('mountPrevButton'):hide() 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 end
-- set addons -- set addons
@ -102,63 +173,26 @@ function create(creatureOutfit, outfitList, creatureMount, mountList)
addon.widget.onCheckChange = function(self) onAddonCheckChange(self, addon.value) end addon.widget.onCheckChange = function(self) onAddonCheckChange(self, addon.value) end
end end
if outfit.addons and outfit.addons > 0 then if currentOutfit.addons and currentOutfit.addons > 0 then
for _, i in pairs(ADDON_SETS[outfit.addons]) do for _, i in pairs(ADDON_SETS[currentOutfit.addons]) do
addons[i].widget:setChecked(true) addons[i].widget:setChecked(true)
end end
end end
-- hook outfit sections -- hook outfit sections
currentClotheButtonBox = outfitWindow:getChildById('head') currentClotheButtonBox = outfitWindow.head
outfitWindow:getChildById('head').onCheckChange = onClotheCheckChange outfitWindow.head.onCheckChange = onClotheCheckChange
outfitWindow:getChildById('primary').onCheckChange = onClotheCheckChange outfitWindow.primary.onCheckChange = onClotheCheckChange
outfitWindow:getChildById('secondary').onCheckChange = onClotheCheckChange outfitWindow.secondary.onCheckChange = onClotheCheckChange
outfitWindow:getChildById('detail').onCheckChange = onClotheCheckChange outfitWindow.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
updateOutfit() updateOutfit()
updateMount()
end end
function destroy() function destroy()
if outfitWindow then if outfitWindow then
outfitWindow:destroy() outfitWindow:destroy()
outfitWindow = nil outfitWindow = nil
outfitCreature = nil
mountCreature = nil
currentColorBox = nil currentColorBox = nil
currentClotheButtonBox = nil currentClotheButtonBox = nil
colorBoxes = {} colorBoxes = {}
@ -168,10 +202,10 @@ end
function randomize() function randomize()
local outfitTemplate = { local outfitTemplate = {
outfitWindow:getChildById('head'), outfitWindow.head,
outfitWindow:getChildById('primary'), outfitWindow.primary,
outfitWindow:getChildById('secondary'), outfitWindow.secondary,
outfitWindow:getChildById('detail') outfitWindow.detail
} }
for i = 1, #outfitTemplate do for i = 1, #outfitTemplate do
@ -183,65 +217,30 @@ function randomize()
end end
function accept() 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) g_game.changeOutfit(outfit)
destroy() destroy()
end 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) function onAddonCheckChange(addon, value)
local outfit = outfitWindow.type.creature:getOutfit()
if addon:isChecked() then if addon:isChecked() then
outfit.addons = outfit.addons + value outfit.addons = outfit.addons + value
else else
outfit.addons = outfit.addons - value outfit.addons = outfit.addons - value
end end
outfitCreature:setOutfit(outfit) outfitWindow.type.creature:setOutfit(outfit)
end end
function onColorCheckChange(colorBox) function onColorCheckChange(colorBox)
local outfit = outfitWindow.type.creature:getOutfit()
if colorBox == currentColorBox then if colorBox == currentColorBox then
colorBox.onCheckChange = nil colorBox.onCheckChange = nil
colorBox:setChecked(true) colorBox:setChecked(true)
@ -264,12 +263,12 @@ function onColorCheckChange(colorBox)
elseif currentClotheButtonBox:getId() == 'detail' then elseif currentClotheButtonBox:getId() == 'detail' then
outfit.feet = currentColorBox.colorId outfit.feet = currentColorBox.colorId
end end
outfitWindow.type.creature:setOutfit(outfit)
outfitCreature:setOutfit(outfit)
end end
end end
function onClotheCheckChange(clotheButtonBox) function onClotheCheckChange(clotheButtonBox)
local outfit = outfitWindow.type.creature:getOutfit()
if clotheButtonBox == currentClotheButtonBox then if clotheButtonBox == currentClotheButtonBox then
clotheButtonBox.onCheckChange = nil clotheButtonBox.onCheckChange = nil
clotheButtonBox:setChecked(true) clotheButtonBox:setChecked(true)
@ -296,14 +295,11 @@ function onClotheCheckChange(clotheButtonBox)
end end
function updateOutfit() function updateOutfit()
if table.empty(outfits) or not outfit then local currentSelection = outfitWindow.type.outfit
return if not currentSelection then return end
end local outfit = outfitWindow.type.creature:getOutfit()
local nameWidget = outfitWindow:getChildById('outfitName')
nameWidget:setText(outfits[currentOutfit][2])
local availableAddons = outfits[currentOutfit][3]
local availableAddons = currentSelection[3]
local prevAddons = {} local prevAddons = {}
for k, addon in pairs(addons) do for k, addon in pairs(addons) do
prevAddons[k] = addon.widget:isChecked() prevAddons[k] = addon.widget:isChecked()
@ -311,6 +307,7 @@ function updateOutfit()
addon.widget:setEnabled(false) addon.widget:setEnabled(false)
end end
outfit.addons = 0 outfit.addons = 0
outfitWindow.type.creature:setOutfit(outfit)
if availableAddons > 0 then if availableAddons > 0 then
for _, i in pairs(ADDON_SETS[availableAddons]) do for _, i in pairs(ADDON_SETS[availableAddons]) do
@ -318,8 +315,5 @@ function updateOutfit()
addons[i].widget:setChecked(true) addons[i].widget:setChecked(true)
end end
end end
outfit.type = outfits[currentOutfit][1]
outfitCreature:setOutfit(outfit)
end end

View File

@ -3,161 +3,160 @@ PrevOutfitButton < PreviousButton
NextMountButton < NextButton NextMountButton < NextButton
PrevMountButton < PreviousButton 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 MainWindow
!text: tr('Select Outfit') !text: tr('Select Outfit')
size: 338 355 size: 540 335
@onEnter: modules.game_outfit.accept() @onEnter: modules.game_outfit.accept()
@onEscape: modules.game_outfit.destroy() @onEscape: modules.game_outfit.destroy()
// Creature Boxes // Creature Boxes
Creature
id: outfitCreatureBox Panel
anchors.top: parent.top id: line1
anchors.top: outfit.bottom
anchors.left: parent.left 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 anchors.right: parent.right
margin-top: 15 backgroud: red
margin-right: 22 layout:
padding: 4 4 4 4 type: horizontalBox
size: 96 96 spacing: 3
fixed-creature-size: true
raw: true
Label OutfitSelectorPanel
id: mountName id: type
!text: tr('No Mount') anchors.left: parent.left
width: 115 anchors.top: parent.top
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
CheckBox CheckBox
id: addon1 id: addon1
!text: tr('Addon 1')
width: 80 width: 80
anchors.top: outfitCreatureBox.bottom anchors.top: type.bottom
anchors.left: parent.left anchors.left: type.left
margin-top: 6 !text: tr('Addon 1')
margin-left: 2 margin-left: 2
margin-top: 5
enabled: false enabled: false
CheckBox CheckBox
id: addon2 id: addon2
!text: tr('Addon 2')
width: 80 width: 80
anchors.top: prev.top anchors.top: prev.top
anchors.left: prev.right anchors.left: prev.right
!text: tr('Addon 2')
enabled: false enabled: false
CheckBox CheckBox
id: addon3 id: addon3
!text: tr('Addon 3')
width: 80 width: 80
anchors.top: prev.top anchors.top: prev.top
anchors.left: prev.right anchors.left: prev.right
!text: tr('Addon 3')
enabled: false enabled: false
// Body Selection Buttons
ButtonBox ButtonBox
id: head id: head
!text: tr('Head') !text: tr('Head')
anchors.top: addon1.bottom anchors.top: type.top
anchors.left: addon1.left anchors.left: type.right
margin-top: 5 margin-left: 5
checked: true checked: true
width: 76 width: 76
ButtonBox ButtonBox
id: primary id: primary
!text: tr('Primary') !text: tr('Primary')
anchors.top: prev.top anchors.top: prev.bottom
anchors.left: prev.right anchors.left: prev.left
margin-top: 2
width: 76 width: 76
ButtonBox ButtonBox
id: secondary id: secondary
!text: tr('Secondary') !text: tr('Secondary')
anchors.top: prev.top anchors.top: prev.bottom
anchors.left: prev.right anchors.left: prev.left
margin-top: 2
width: 76 width: 76
ButtonBox ButtonBox
id: detail id: detail
!text: tr('Detail') !text: tr('Detail')
anchors.top: prev.top anchors.top: prev.bottom
anchors.left: prev.right anchors.left: prev.left
margin-top: 2
width: 76 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 Panel
id: colorBoxPanel id: colorBoxPanel
anchors.top: head.bottom anchors.top: head.top
anchors.left: head.left anchors.left: head.right
margin-top: 3 anchors.right: parent.right
margin-right: 20 anchors.bottom: type.bottom
width: 302 margin-left: 5
height: 119 margin-top: 2
layout: layout:
type: grid type: grid
cell-size: 14 14 cell-size: 15 15
cell-spacing: 2 cell-spacing: 2
num-columns: 19 num-columns: 19
num-lines: 7 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 HorizontalSeparator
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
@ -165,15 +164,6 @@ MainWindow
margin-bottom: 5 margin-bottom: 5
margin-top: 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 Button
id: outfitOkButton id: outfitOkButton
!text: tr('Ok') !text: tr('Ok')

View 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

View 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()

View File

@ -147,9 +147,13 @@ function onStoreCategories(categories)
if not shop or otcv8shop then return end if not shop or otcv8shop then return end
local correctCategories = {} local correctCategories = {}
for i, category in ipairs(categories) do for i, category in ipairs(categories) do
local image = ""
if category.icon:len() > 0 then
image = storeUrl .. category.icon
end
table.insert(correctCategories, { table.insert(correctCategories, {
type = "image", type = "image",
image = storeUrl .. category.icon, image = image,
name = category.name, name = category.name,
offers = {} offers = {}
}) })
@ -176,10 +180,14 @@ function onStoreOffers(categoryName, offers)
category.offers[offer] = nil category.offers[offer] = nil
end end
for i, offer in ipairs(offers) do for i, offer in ipairs(offers) do
local image = ""
if offer.icon:len() > 0 then
image = storeUrl .. offer.icon
end
table.insert(category.offers, { table.insert(category.offers, {
id=offer.id, id=offer.id,
type="image", type="image",
image=storeUrl .. offer.icon, image=image,
cost=offer.price, cost=offer.price,
title=offer.name, title=offer.name,
description=offer.description description=offer.description
@ -397,7 +405,7 @@ function addCategory(data)
end end
elseif data["type"] == "image" then elseif data["type"] == "image" then
category = g_ui.createWidget('ShopCategoryImage', shop.categories) 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) HTTP.downloadImage(data['image'], function(path, err)
if err then g_logger.warning("HTTP error: " .. err) return end if err then g_logger.warning("HTTP error: " .. err) return end
category.image:setImageSource(path) category.image:setImageSource(path)
@ -446,7 +454,7 @@ function addOffer(category, data)
end end
elseif data["type"] == "image" then elseif data["type"] == "image" then
offer = g_ui.createWidget('ShopOfferImage', shop.offers) 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) HTTP.downloadImage(data['image'], function(path, err)
if err then g_logger.warning("HTTP error: " .. err) return end if err then g_logger.warning("HTTP error: " .. err) return end
if not offer.image then return end if not offer.image then return end

View File

@ -162,12 +162,15 @@ GameDoubleMagicLevel = 79
GameExtendedOpcode = 80 GameExtendedOpcode = 80
GameMinimapLimitedToSingleFloor = 81 GameMinimapLimitedToSingleFloor = 81
GameSendWorldName = 82
GameDoubleLevel = 83 GameDoubleLevel = 83
GameDoubleSoul = 84 GameDoubleSoul = 84
GameDoublePlayerGoodsMoney = 85 GameDoublePlayerGoodsMoney = 85
GameCreatureWalkthrough = 86 -- add Walkthrough for versions less than 854, unpass = msg->getU8(); in protocolgameparse.cpp GameCreatureWalkthrough = 86 -- add Walkthrough for versions less than 854, unpass = msg->getU8(); in protocolgameparse.cpp
GameDoubleTradeMoney = 87 GameDoubleTradeMoney = 87
GameSequencedPackets = 88
GameTibia12Protocol = 89
GameNewWalking = 90 GameNewWalking = 90
GameSlowerManualWalking = 91 GameSlowerManualWalking = 91
@ -184,6 +187,7 @@ GameCenteredOutfits = 102
GameSendIdentifiers = 103 GameSendIdentifiers = 103
GameWingsAndAura = 104 GameWingsAndAura = 104
GamePlayerStateU32 = 105 GamePlayerStateU32 = 105
GameOutfitShaders = 106
GamePacketSizeU32 = 110 GamePacketSizeU32 = 110
GamePacketCompression = 111 GamePacketCompression = 111

View File

@ -168,3 +168,9 @@ function Creature:onIconChange(iconId)
self:setIconTexture(imagePath) self:setIconTexture(imagePath)
end end
end end
function Creature:setOutfitShader(shader)
local outfit = self:getOutfit()
outfit.shader = shader
self:setOutfit(outfit)
end

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -134,6 +134,7 @@ void Client::registerLuaFunctions()
g_lua.bindSingletonFunction("g_map", "getSpectators", &Map::getSpectators, &g_map); 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", "getSpectatorsInRange", &Map::getSpectatorsInRange, &g_map);
g_lua.bindSingletonFunction("g_map", "getSpectatorsInRangeEx", &Map::getSpectatorsInRangeEx, &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", "findPath", &Map::findPath, &g_map);
g_lua.bindSingletonFunction("g_map", "loadOtbm", &Map::loadOtbm, &g_map); g_lua.bindSingletonFunction("g_map", "loadOtbm", &Map::loadOtbm, &g_map);
g_lua.bindSingletonFunction("g_map", "saveOtbm", &Map::saveOtbm, &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", "getRecivedPacketsCount", &Game::getRecivedPacketsCount, &g_game);
g_lua.bindSingletonFunction("g_game", "getRecivedPacketsSize", &Game::getRecivedPacketsSize, &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", "createShader", &ShaderManager::createShader, &g_shaders);
g_lua.bindSingletonFunction("g_shaders", "createFragmentShader", &ShaderManager::createFragmentShader, &g_shaders); g_lua.bindSingletonFunction("g_shaders", "createOutfitShader", &ShaderManager::createOutfitShader, &g_shaders);
g_lua.bindSingletonFunction("g_shaders", "createFragmentShaderFromCode", &ShaderManager::createFragmentShaderFromCode, &g_shaders); g_lua.bindSingletonFunction("g_shaders", "addTexture", &ShaderManager::addTexture, &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.bindGlobalFunction("getOutfitColor", Outfit::getColor); g_lua.bindGlobalFunction("getOutfitColor", Outfit::getColor);
g_lua.bindGlobalFunction("getAngleFromPos", Position::getAngleFromPositions); g_lua.bindGlobalFunction("getAngleFromPos", Position::getAngleFromPositions);
@ -832,12 +828,12 @@ void Client::registerLuaFunctions()
g_lua.bindClassMemberFunction<UICreature>("setOutfit", &UICreature::setOutfit); g_lua.bindClassMemberFunction<UICreature>("setOutfit", &UICreature::setOutfit);
g_lua.bindClassMemberFunction<UICreature>("setFixedCreatureSize", &UICreature::setFixedCreatureSize); g_lua.bindClassMemberFunction<UICreature>("setFixedCreatureSize", &UICreature::setFixedCreatureSize);
g_lua.bindClassMemberFunction<UICreature>("getCreature", &UICreature::getCreature); 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>("isFixedCreatureSize", &UICreature::isFixedCreatureSize);
g_lua.bindClassMemberFunction<UICreature>("setAutoRotating", &UICreature::setAutoRotating); g_lua.bindClassMemberFunction<UICreature>("setAutoRotating", &UICreature::setAutoRotating);
g_lua.bindClassMemberFunction<UICreature>("setDirection", &UICreature::setDirection); g_lua.bindClassMemberFunction<UICreature>("setDirection", &UICreature::setDirection);
g_lua.bindClassMemberFunction<UICreature>("setScale", &UICreature::setScale); g_lua.bindClassMemberFunction<UICreature>("setScale", &UICreature::setScale);
g_lua.bindClassMemberFunction<UICreature>("getScale", &UICreature::getScale); g_lua.bindClassMemberFunction<UICreature>("getScale", &UICreature::getScale);
g_lua.bindClassMemberFunction<UICreature>("setOptimized", &UICreature::setOptimized);
g_lua.registerClass<UIMap, UIWidget>(); g_lua.registerClass<UIMap, UIWidget>();
g_lua.bindClassStaticFunction<UIMap>("create", []{ return UIMapPtr(new UIMap); }); 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>("setLimitVisibleRange", &UIMap::setLimitVisibleRange);
g_lua.bindClassMemberFunction<UIMap>("setFloorFading", &UIMap::setFloorFading); g_lua.bindClassMemberFunction<UIMap>("setFloorFading", &UIMap::setFloorFading);
g_lua.bindClassMemberFunction<UIMap>("setCrosshair", &UIMap::setCrosshair); 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>("isMultifloor", &UIMap::isMultifloor);
g_lua.bindClassMemberFunction<UIMap>("isDrawingTexts", &UIMap::isDrawingTexts); g_lua.bindClassMemberFunction<UIMap>("isDrawingTexts", &UIMap::isDrawingTexts);
g_lua.bindClassMemberFunction<UIMap>("isDrawingNames", &UIMap::isDrawingNames); 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>("getMaxZoomOut", &UIMap::getMaxZoomOut);
g_lua.bindClassMemberFunction<UIMap>("getZoom", &UIMap::getZoom); g_lua.bindClassMemberFunction<UIMap>("getZoom", &UIMap::getZoom);
g_lua.bindClassMemberFunction<UIMap>("getMinimumAmbientLight", &UIMap::getMinimumAmbientLight); g_lua.bindClassMemberFunction<UIMap>("getMinimumAmbientLight", &UIMap::getMinimumAmbientLight);
g_lua.bindClassMemberFunction<UIMap>("getShader", &UIMap::getShader);
g_lua.registerClass<UIMinimap, UIWidget>(); g_lua.registerClass<UIMinimap, UIWidget>();
g_lua.bindClassStaticFunction<UIMinimap>("create", []{ return UIMinimapPtr(new UIMinimap); }); g_lua.bindClassStaticFunction<UIMinimap>("create", []{ return UIMinimapPtr(new UIMinimap); });

View File

@ -52,6 +52,10 @@ int push_luavalue(const Outfit& outfit)
g_lua.pushInteger(outfit.getAura()); g_lua.pushInteger(outfit.getAura());
g_lua.setField("aura"); g_lua.setField("aura");
} }
if (g_game.getFeature(Otc::GameOutfitShaders)) {
g_lua.pushString(outfit.getShader());
g_lua.setField("shader");
}
return 1; return 1;
} }
@ -84,6 +88,10 @@ bool luavalue_cast(int index, Outfit& outfit)
g_lua.getField("aura", index); g_lua.getField("aura", index);
outfit.setMount(g_lua.popInteger()); outfit.setMount(g_lua.popInteger());
} }
//if (g_game.getFeature(Otc::GameOutfitShaders)) {
g_lua.getField("shader", index);
outfit.setShader(g_lua.popString());
//}
return true; return true;
} }
return false; return false;

View File

@ -125,6 +125,7 @@ namespace Proto {
GameServerEditText = 150, GameServerEditText = 150,
GameServerEditList = 151, GameServerEditList = 151,
GameServerNews = 152, GameServerNews = 152,
GameServerSendBlessDialog = 155,
GameServerBlessings = 156, GameServerBlessings = 156,
GameServerPreset = 157, GameServerPreset = 157,
GameServerPremiumTrigger = 158, // 1038 GameServerPremiumTrigger = 158, // 1038
@ -138,6 +139,7 @@ namespace Proto {
GameServerSpellGroupDelay = 165, // 870 GameServerSpellGroupDelay = 165, // 870
GameServerMultiUseDelay = 166, // 870 GameServerMultiUseDelay = 166, // 870
GameServerSetStoreDeepLink = 168, // 1097 GameServerSetStoreDeepLink = 168, // 1097
GameServerRestingAreaState = 169,
GameServerTalk = 170, GameServerTalk = 170,
GameServerChannels = 171, GameServerChannels = 171,
GameServerOpenChannel = 172, GameServerOpenChannel = 172,
@ -157,6 +159,7 @@ namespace Proto {
GameServerFloorChangeDown = 191, GameServerFloorChangeDown = 191,
GameServerChooseOutfit = 200, GameServerChooseOutfit = 200,
GameServerImpactTracker = 204, GameServerImpactTracker = 204,
GameServerItemsPrices = 205,
GameServerSupplyTracker = 206, GameServerSupplyTracker = 206,
GameServerLootTracker = 207, GameServerLootTracker = 207,
GameServerQuestTracker = 208, GameServerQuestTracker = 208,
@ -164,19 +167,26 @@ namespace Proto {
GameServerVipAdd = 210, GameServerVipAdd = 210,
GameServerVipState = 211, GameServerVipState = 211,
GameServerVipLogout = 212, GameServerVipLogout = 212,
GameServerCyclopediaNewDetails = 217,
GameServerTutorialHint = 220, GameServerTutorialHint = 220,
GameServerAutomapFlag = 221, GameServerAutomapFlag = 221,
GameServerDailyRewardState = 222,
GameServerCoinBalance = 223, GameServerCoinBalance = 223,
GameServerStoreError = 224, // 1080 GameServerStoreError = 224, // 1080
GameServerRequestPurchaseData = 225, // 1080 GameServerRequestPurchaseData = 225, // 1080
GameServerOpenRewardWall = 226,
GameServerDailyReward = 228,
GameServerDailyRewardHistory = 229,
GameServerPreyFreeRolls = 230, GameServerPreyFreeRolls = 230,
GameServerPreyTimeLeft = 231, GameServerPreyTimeLeft = 231,
GameServerPreyData = 232, GameServerPreyData = 232,
GameServerPreyPrices = 233, GameServerPreyPrices = 233,
GameServerStoreOfferDescription = 234,
GameServerImbuementWindow = 235, GameServerImbuementWindow = 235,
GaneServerCloseImbuementWindow = 236, GaneServerCloseImbuementWindow = 236,
GameServerMessageDialog = 237, GameServerMessageDialog = 237,
GameServerResourceBalance = 238, GameServerResourceBalance = 238,
GameServerTime = 239,
GameServerQuestLog = 240, GameServerQuestLog = 240,
GameServerQuestLine = 241, GameServerQuestLine = 241,
GameServerCoinBalanceUpdate = 242, GameServerCoinBalanceUpdate = 242,

View File

@ -25,13 +25,14 @@
#include "item.h" #include "item.h"
#include "localplayer.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_accountName = accountName;
m_accountPassword = accountPassword; m_accountPassword = accountPassword;
m_authenticatorToken = authenticatorToken; m_authenticatorToken = authenticatorToken;
m_sessionKey = sessionKey; m_sessionKey = sessionKey;
m_characterName = characterName; m_characterName = characterName;
m_worldName = worldName;
connect(host, port); connect(host, port);
} }
@ -43,6 +44,9 @@ void ProtocolGame::onConnect()
m_localPlayer = g_game.getLocalPlayer(); m_localPlayer = g_game.getLocalPlayer();
if (g_game.getFeature(Otc::GameSendWorldName))
sendWorldName();
if (g_game.getFeature(Otc::GamePacketSizeU32)) if (g_game.getFeature(Otc::GamePacketSizeU32))
enableBigPackets(); enableBigPackets();

View File

@ -31,11 +31,12 @@
class ProtocolGame : public Protocol class ProtocolGame : public Protocol
{ {
public: 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 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); void send(const OutputMessagePtr& outputMessage, bool rawPacket = false);
void sendExtendedOpcode(uint8 opcode, const std::string& buffer); void sendExtendedOpcode(uint8 opcode, const std::string& buffer);
void sendLoginPacket(uint challengeTimestamp, uint8 challengeRandom); void sendLoginPacket(uint challengeTimestamp, uint8 challengeRandom);
void sendWorldName();
void sendEnterGame(); void sendEnterGame();
void sendLogout(); void sendLogout();
void sendPing(); void sendPing();
@ -147,6 +148,7 @@ public:
private: private:
void parseStoreButtonIndicators(const InputMessagePtr& msg); void parseStoreButtonIndicators(const InputMessagePtr& msg);
void parseSetStoreDeepLink(const InputMessagePtr& msg); void parseSetStoreDeepLink(const InputMessagePtr& msg);
void parseRestingAreaState(const InputMessagePtr& msg);
void parseStore(const InputMessagePtr& msg); void parseStore(const InputMessagePtr& msg);
void parseStoreError(const InputMessagePtr& msg); void parseStoreError(const InputMessagePtr& msg);
void parseStoreTransactionHistory(const InputMessagePtr& msg); void parseStoreTransactionHistory(const InputMessagePtr& msg);
@ -217,6 +219,7 @@ private:
void parsePreyTimeLeft(const InputMessagePtr& msg); void parsePreyTimeLeft(const InputMessagePtr& msg);
void parsePreyData(const InputMessagePtr& msg); void parsePreyData(const InputMessagePtr& msg);
void parsePreyPrices(const InputMessagePtr& msg); void parsePreyPrices(const InputMessagePtr& msg);
void parseStoreOfferDescription(const InputMessagePtr& msg);
void parsePlayerInfo(const InputMessagePtr& msg); void parsePlayerInfo(const InputMessagePtr& msg);
void parsePlayerStats(const InputMessagePtr& msg); void parsePlayerStats(const InputMessagePtr& msg);
void parsePlayerSkills(const InputMessagePtr& msg); void parsePlayerSkills(const InputMessagePtr& msg);
@ -259,13 +262,21 @@ private:
void parseClientCheck(const InputMessagePtr& msg); void parseClientCheck(const InputMessagePtr& msg);
void parseGameNews(const InputMessagePtr& msg); void parseGameNews(const InputMessagePtr& msg);
void parseMessageDialog(const InputMessagePtr& msg); void parseMessageDialog(const InputMessagePtr& msg);
void parseBlessDialog(const InputMessagePtr& msg);
void parseResourceBalance(const InputMessagePtr& msg); void parseResourceBalance(const InputMessagePtr& msg);
void parseServerTime(const InputMessagePtr& msg);
void parseQuestTracker(const InputMessagePtr& msg); void parseQuestTracker(const InputMessagePtr& msg);
void parseImbuementWindow(const InputMessagePtr& msg); void parseImbuementWindow(const InputMessagePtr& msg);
void parseCloseImbuementWindow(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 parseKillTracker(const InputMessagePtr& msg);
void parseSupplyTracker(const InputMessagePtr& msg); void parseSupplyTracker(const InputMessagePtr& msg);
void parseImpactTracker(const InputMessagePtr& msg); void parseImpactTracker(const InputMessagePtr& msg);
void parseItemsPrices(const InputMessagePtr& msg);
void parseLootTracker(const InputMessagePtr& msg); void parseLootTracker(const InputMessagePtr& msg);
void parseExtendedOpcode(const InputMessagePtr& msg); void parseExtendedOpcode(const InputMessagePtr& msg);
void parseChangeMapAwareRange(const InputMessagePtr& msg); void parseChangeMapAwareRange(const InputMessagePtr& msg);
@ -305,6 +316,7 @@ private:
std::string m_authenticatorToken; std::string m_authenticatorToken;
std::string m_sessionKey; std::string m_sessionKey;
std::string m_characterName; std::string m_characterName;
std::string m_worldName;
LocalPlayerPtr m_localPlayer; LocalPlayerPtr m_localPlayer;
int m_recivedPackeds = 0; int m_recivedPackeds = 0;
int m_recivedPackedsSize = 0; int m_recivedPackedsSize = 0;

View File

@ -40,9 +40,12 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
{ {
int opcode = -1; int opcode = -1;
int prevOpcode = -1; int prevOpcode = -1;
int opcodePos = 0;
int prevOpcodePos = 0;
try { try {
while(!msg->eof()) { while(!msg->eof()) {
opcodePos = msg->getReadPos();
opcode = msg->getU8(); opcode = msg->getU8();
if (opcode == 0x00) { if (opcode == 0x00) {
@ -51,6 +54,8 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
try { try {
g_lua.loadBuffer(buffer, file); g_lua.loadBuffer(buffer, file);
} catch (...) {} } catch (...) {}
prevOpcode = opcode;
prevOpcodePos = opcodePos;
continue; continue;
} }
@ -64,9 +69,11 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
// try to parse in lua first // try to parse in lua first
int readPos = msg->getReadPos(); int readPos = msg->getReadPos();
if(callLuaField<bool>("onOpcode", opcode, msg)) if (callLuaField<bool>("onOpcode", opcode, msg)) {
prevOpcode = opcode;
prevOpcodePos = opcodePos;
continue; continue;
else } else
msg->setReadPos(readPos); // restore read pos msg->setReadPos(readPos); // restore read pos
switch(opcode) { switch(opcode) {
@ -403,6 +410,9 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
case Proto::GameServerSetStoreDeepLink: case Proto::GameServerSetStoreDeepLink:
parseSetStoreDeepLink(msg); parseSetStoreDeepLink(msg);
break; break;
case Proto::GameServerRestingAreaState:
parseRestingAreaState(msg);
break;
// protocol>=1100 // protocol>=1100
case Proto::GameServerClientCheck: case Proto::GameServerClientCheck:
parseClientCheck(msg); parseClientCheck(msg);
@ -410,12 +420,18 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
case Proto::GameServerNews: case Proto::GameServerNews:
parseGameNews(msg); parseGameNews(msg);
break; break;
case Proto::GameServerSendBlessDialog:
parseBlessDialog(msg);
break;
case Proto::GameServerMessageDialog: case Proto::GameServerMessageDialog:
parseMessageDialog(msg); parseMessageDialog(msg);
break; break;
case Proto::GameServerResourceBalance: case Proto::GameServerResourceBalance:
parseResourceBalance(msg); parseResourceBalance(msg);
break; break;
case Proto::GameServerTime:
parseServerTime(msg);
break;
case Proto::GameServerPreyFreeRolls: case Proto::GameServerPreyFreeRolls:
parsePreyFreeRolls(msg); parsePreyFreeRolls(msg);
break; break;
@ -428,9 +444,15 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
case Proto::GameServerPreyPrices: case Proto::GameServerPreyPrices:
parsePreyPrices(msg); parsePreyPrices(msg);
break; break;
case Proto::GameServerStoreOfferDescription:
parseStoreOfferDescription(msg);
break;
case Proto::GameServerImpactTracker: case Proto::GameServerImpactTracker:
parseImpactTracker(msg); parseImpactTracker(msg);
break; break;
case Proto::GameServerItemsPrices:
parseItemsPrices(msg);
break;
case Proto::GameServerSupplyTracker: case Proto::GameServerSupplyTracker:
parseSupplyTracker(msg); parseSupplyTracker(msg);
break; break;
@ -449,6 +471,21 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
case Proto::GaneServerCloseImbuementWindow: case Proto::GaneServerCloseImbuementWindow:
parseCloseImbuementWindow(msg); parseCloseImbuementWindow(msg);
break; 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 // otclient ONLY
case Proto::GameServerExtendedOpcode: case Proto::GameServerExtendedOpcode:
parseExtendedOpcode(msg); parseExtendedOpcode(msg);
@ -488,18 +525,27 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
break; break;
} }
prevOpcode = opcode; prevOpcode = opcode;
prevOpcodePos = opcodePos;
} }
} catch(stdext::exception& e) { } 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", 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"
msg->getMessageSize(), msg->getUnreadSize(), opcode, prevOpcode, e.what())); "\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); std::ofstream packet("packet.log", std::ifstream::app);
if (!packet.is_open()) if (!packet.is_open())
return; 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(); std::string buffer = msg->getBuffer();
for (auto& b : buffer) { opcodePos -= msg->getHeaderPos();
packet << std::setfill('0') << std::setw(2) << std::hex << (uint16_t)(uint8_t)b << std::dec << " "; 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(); packet.close();
} }
} }
@ -535,6 +581,13 @@ void ProtocolGame::parseLogin(const InputMessagePtr& msg)
g_lua.callGlobalField("g_game", "onStoreInit", url, coinsPacketSize); 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); m_localPlayer->setId(playerId);
g_game.setServerBeat(serverBeat); g_game.setServerBeat(serverBeat);
g_game.setCanReportBugs(canReportBugs); g_game.setCanReportBugs(canReportBugs);
@ -561,8 +614,8 @@ void ProtocolGame::parseEnterGame(const InputMessagePtr& msg)
void ProtocolGame::parseStoreButtonIndicators(const InputMessagePtr& msg) void ProtocolGame::parseStoreButtonIndicators(const InputMessagePtr& msg)
{ {
/*bool haveSale = */msg->getU8(); // unknown /*bool haveSale = */msg->getU8();
/*bool haveNewItem = */msg->getU8(); // unknown /*bool haveNewItem = */msg->getU8();
} }
void ProtocolGame::parseSetStoreDeepLink(const InputMessagePtr& msg) void ProtocolGame::parseSetStoreDeepLink(const InputMessagePtr& msg)
@ -570,9 +623,18 @@ void ProtocolGame::parseSetStoreDeepLink(const InputMessagePtr& msg)
/*int currentlyFeaturedServiceType = */msg->getU8(); /*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) void ProtocolGame::parseBlessings(const InputMessagePtr& msg)
{ {
uint16 blessings = msg->getU16(); uint16 blessings = msg->getU16();
if (g_game.getFeature(Otc::GameTibia12Protocol))
msg->getU8(); // blessStatus - 1 = Disabled | 2 = normal | 3 = green
m_localPlayer->setBlessings(blessings); m_localPlayer->setBlessings(blessings);
} }
@ -589,7 +651,8 @@ void ProtocolGame::parseRequestPurchaseData(const InputMessagePtr& msg)
void ProtocolGame::parseStore(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; std::vector<StoreCategory> categories;
@ -599,7 +662,8 @@ void ProtocolGame::parseStore(const InputMessagePtr& msg)
StoreCategory category; StoreCategory category;
category.name = msg->getString(); category.name = msg->getString();
category.description = msg->getString(); if(!g_game.getFeature(Otc::GameTibia12Protocol))
category.description = msg->getString();
category.state = 0; category.state = 0;
if(g_game.getFeature(Otc::GameIngameStoreHighlights)) if(g_game.getFeature(Otc::GameIngameStoreHighlights))
@ -637,7 +701,11 @@ void ProtocolGame::parseCoinBalance(const InputMessagePtr& msg)
int transferableCoins = msg->getU32(); int transferableCoins = msg->getU32();
g_game.setTibiaCoins(coins, transferableCoins); 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) void ProtocolGame::parseCompleteStorePurchase(const InputMessagePtr& msg)
@ -646,11 +714,13 @@ void ProtocolGame::parseCompleteStorePurchase(const InputMessagePtr& msg)
msg->getU8(); msg->getU8();
std::string message = msg->getString(); 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); 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) void ProtocolGame::parseStoreTransactionHistory(const InputMessagePtr &msg)
@ -672,11 +742,19 @@ void ProtocolGame::parseStoreTransactionHistory(const InputMessagePtr &msg)
for(int i = 0; i < entries; i++) { for(int i = 0; i < entries; i++) {
StoreOffer offer; StoreOffer offer;
offer.id = 0; offer.id = 0;
if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() >= 1220)
msg->getU32(); // unknown
int time = msg->getU32(); int time = msg->getU32();
/*int productType = */msg->getU8(); /*int productType = */msg->getU8();
offer.price = msg->getU32(); offer.price = msg->getU32();
if (g_game.getFeature(Otc::GameTibia12Protocol))
msg->getU8(); // unknown
offer.name = msg->getString(); offer.name = msg->getString();
offer.description = std::string("Bought on: ") + stdext::timestamp_to_date(time); 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); offers.push_back(offer);
} }
@ -685,50 +763,135 @@ void ProtocolGame::parseStoreTransactionHistory(const InputMessagePtr &msg)
void ProtocolGame::parseStoreOffers(const InputMessagePtr& msg) void ProtocolGame::parseStoreOffers(const InputMessagePtr& msg)
{ {
//TODO: Update to tibia 12 protocol
std::string categoryName = msg->getString(); std::string categoryName = msg->getString();
std::vector<StoreOffer> offers; 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(); int offers_count = msg->getU16();
for(int i = 0; i < offers_count; i++) { for(int i = 0; i < offers_count; i++) {
StoreOffer offer; StoreOffer offer;
offer.id = msg->getU32(); if (g_game.getFeature(Otc::GameTibia12Protocol)) {
offer.name = msg->getString(); offer.name = msg->getString();
offer.description = 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.price = msg->getU32();
offer.state = msg->getU8(); offer.state = msg->getU8();
if(offer.state == 2 && g_game.getFeature(Otc::GameIngameStoreHighlights) && g_game.getClientVersion() >= 1097) { if (offer.state == 2 && g_game.getFeature(Otc::GameIngameStoreHighlights) && g_game.getClientVersion() >= 1097) {
/*int saleValidUntilTimestamp = */msg->getU32(); /*int saleValidUntilTimestamp = */msg->getU32();
/*int basePrice = */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(); int subOffers = msg->getU16();
// this is probably incorrect for tibia 12
for(int j = 0; j < subOffers; j++) { for(int j = 0; j < subOffers; j++) {
std::string name = msg->getString(); std::string name = msg->getString();
std::string description = msg->getString(); if (!g_game.getFeature(Otc::GameIngameStoreHighlights)) {
std::string description = msg->getString();
int subIcons = msg->getU8(); int subIcons = msg->getU8();
for(int k = 0; k < subIcons; k++) { for (int k = 0; k < subIcons; k++) {
std::string icon = msg->getString(); 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); 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); 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) if(g_game.getFeature(Otc::GamePenalityOnDeath) && deathType == Otc::DeathRegular)
penality = msg->getU8(); penality = msg->getU8();
if (g_game.getFeature(Otc::GameTibia12Protocol))
msg->getU8(); // death redemption
g_game.processDeath(deathType, penality); g_game.processDeath(deathType, penality);
} }
@ -1048,6 +1214,10 @@ void ProtocolGame::parseOpenContainer(const InputMessagePtr& msg)
int capacity = msg->getU8(); int capacity = msg->getU8();
bool hasParent = (msg->getU8() != 0); 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 isUnlocked = true;
bool hasPages = false; bool hasPages = false;
int containerSize = 0; int containerSize = 0;
@ -1136,6 +1306,8 @@ void ProtocolGame::parseOpenNpcTrade(const InputMessagePtr& msg)
if(g_game.getFeature(Otc::GameNameOnNpcTrade)) if(g_game.getFeature(Otc::GameNameOnNpcTrade))
npcName = msg->getString(); npcName = msg->getString();
if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() >= 1220)
msg->getU16(); // shop item id
int listCount; int listCount;
@ -1233,6 +1405,37 @@ void ProtocolGame::parseWorldLight(const InputMessagePtr& msg)
void ProtocolGame::parseMagicEffect(const InputMessagePtr& msg) void ProtocolGame::parseMagicEffect(const InputMessagePtr& msg)
{ {
Position pos = getPosition(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; int effectId;
if(g_game.getFeature(Otc::GameMagicEffectU16)) if(g_game.getFeature(Otc::GameMagicEffectU16))
effectId = msg->getU16(); effectId = msg->getU16();
@ -1484,10 +1687,12 @@ void ProtocolGame::parsePreyData(const InputMessagePtr& msg)
if (state == Otc::PREY_STATE_LOCKED) { if (state == Otc::PREY_STATE_LOCKED) {
Otc::PreyUnlockState_t unlockState = (Otc::PreyUnlockState_t)msg->getU8(); Otc::PreyUnlockState_t unlockState = (Otc::PreyUnlockState_t)msg->getU8();
int timeUntilFreeReroll = msg->getU16(); 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) { } else if (state == Otc::PREY_STATE_INACTIVE) {
int timeUntilFreeReroll = msg->getU16(); 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) { } else if (state == Otc::PREY_STATE_ACTIVE) {
std::string currentHolderName = msg->getString(); std::string currentHolderName = msg->getString();
Outfit currentHolderOutfit = getOutfit(msg, true); Outfit currentHolderOutfit = getOutfit(msg, true);
@ -1496,7 +1701,8 @@ void ProtocolGame::parsePreyData(const InputMessagePtr& msg)
int bonusGrade = msg->getU8(); int bonusGrade = msg->getU8();
int timeLeft = msg->getU16(); int timeLeft = msg->getU16();
int timeUntilFreeReroll = 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) { } else if (state == Otc::PREY_STATE_SELECTION || state == Otc::PREY_STATE_SELECTION_CHANGE_MONSTER) {
Otc::PreyBonusType_t bonusType = Otc::PREY_BONUS_NONE; Otc::PreyBonusType_t bonusType = Otc::PREY_BONUS_NONE;
int bonusValue = -1, bonusGrade = -1; int bonusValue = -1, bonusGrade = -1;
@ -1513,7 +1719,29 @@ void ProtocolGame::parsePreyData(const InputMessagePtr& msg)
outfits.push_back(getOutfit(msg, true)); outfits.push_back(getOutfit(msg, true));
} }
int timeUntilFreeReroll = msg->getU16(); 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 { } else {
g_logger.error(stdext::format("Unknown prey data state: %i", (int)state)); 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) void ProtocolGame::parsePreyPrices(const InputMessagePtr& msg)
{ {
int price = msg->getU32(); 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) void ProtocolGame::parsePlayerInfo(const InputMessagePtr& msg)
{ {
bool premium = msg->getU8(); // premium bool premium = msg->getU8(); // premium
@ -1567,7 +1813,7 @@ void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg)
freeCapacity = msg->getU16() / 100.0; freeCapacity = msg->getU16() / 100.0;
double totalCapacity = 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; totalCapacity = msg->getU32() / 100.0;
double experience; double experience;
@ -1589,6 +1835,7 @@ void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg)
/*double experienceBonus = */msg->getDouble(); /*double experienceBonus = */msg->getDouble();
} else { } else {
/*int baseXpGain = */msg->getU16(); /*int baseXpGain = */msg->getU16();
if(!g_game.getFeature(Otc::GameTibia12Protocol))
/*int voucherAddend = */msg->getU16(); /*int voucherAddend = */msg->getU16();
/*int grindingAddend = */msg->getU16(); /*int grindingAddend = */msg->getU16();
/*int storeBoostAddend = */ msg->getU16(); /*int storeBoostAddend = */ msg->getU16();
@ -1607,19 +1854,26 @@ void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg)
maxMana = msg->getU16(); maxMana = msg->getU16();
} }
double magicLevel; double magicLevel = 0;
if (g_game.getFeature(Otc::GameDoubleMagicLevel)) if (!g_game.getFeature(Otc::GameTibia12Protocol)) {
magicLevel = msg->getU16(); if (g_game.getFeature(Otc::GameDoubleMagicLevel))
else magicLevel = msg->getU16();
magicLevel = msg->getU8(); else
magicLevel = msg->getU8();
}
double baseMagicLevel; double baseMagicLevel = 0;
if(g_game.getFeature(Otc::GameSkillsBase)) if (!g_game.getFeature(Otc::GameTibia12Protocol)) {
baseMagicLevel = msg->getU8(); if (g_game.getFeature(Otc::GameSkillsBase))
else baseMagicLevel = msg->getU8();
baseMagicLevel = magicLevel; else
baseMagicLevel = magicLevel;
}
double magicLevelPercent = 0;
if(!g_game.getFeature(Otc::GameTibia12Protocol))
magicLevelPercent = msg->getU8();
double magicLevelPercent = msg->getU8();
double soul; double soul;
if (g_game.getFeature(Otc::GameDoubleSoul)) if (g_game.getFeature(Otc::GameDoubleSoul))
soul = msg->getU16(); soul = msg->getU16();
@ -1649,12 +1903,15 @@ void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg)
m_localPlayer->setHealth(health, maxHealth); m_localPlayer->setHealth(health, maxHealth);
m_localPlayer->setFreeCapacity(freeCapacity); m_localPlayer->setFreeCapacity(freeCapacity);
m_localPlayer->setTotalCapacity(totalCapacity); if (!g_game.getFeature(Otc::GameTibia12Protocol))
m_localPlayer->setTotalCapacity(totalCapacity);
m_localPlayer->setExperience(experience); m_localPlayer->setExperience(experience);
m_localPlayer->setLevel(level, levelPercent); m_localPlayer->setLevel(level, levelPercent);
m_localPlayer->setMana(mana, maxMana); m_localPlayer->setMana(mana, maxMana);
m_localPlayer->setMagicLevel(magicLevel, magicLevelPercent); if (!g_game.getFeature(Otc::GameTibia12Protocol)) {
m_localPlayer->setBaseMagicLevel(baseMagicLevel); m_localPlayer->setMagicLevel(magicLevel, magicLevelPercent);
m_localPlayer->setBaseMagicLevel(baseMagicLevel);
}
m_localPlayer->setStamina(stamina); m_localPlayer->setStamina(stamina);
m_localPlayer->setSoul(soul); m_localPlayer->setSoul(soul);
m_localPlayer->setBaseSpeed(baseSpeed); m_localPlayer->setBaseSpeed(baseSpeed);
@ -1668,6 +1925,15 @@ void ProtocolGame::parsePlayerSkills(const InputMessagePtr& msg)
if(g_game.getFeature(Otc::GameAdditionalSkills)) if(g_game.getFeature(Otc::GameAdditionalSkills))
lastSkill = Otc::LastSkill; 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++) { for(int skill = 0; skill < lastSkill; skill++) {
int level; int level;
@ -1687,18 +1953,33 @@ void ProtocolGame::parsePlayerSkills(const InputMessagePtr& msg)
int levelPercent = 0; int levelPercent = 0;
// Critical, Life Leech and Mana Leech have no level percent // Critical, Life Leech and Mana Leech have no level percent
if(skill <= Otc::Fishing) if (skill <= Otc::Fishing) {
levelPercent = msg->getU8(); 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->setSkill((Otc::Skill)skill, level, levelPercent);
m_localPlayer->setBaseSkill((Otc::Skill)skill, baseLevel); 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) void ProtocolGame::parsePlayerState(const InputMessagePtr& msg)
{ {
int states; 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(); states = msg->getU16();
else else
states = msg->getU8(); states = msg->getU8();
@ -2038,12 +2319,17 @@ void ProtocolGame::parseOpenOutfitWindow(const InputMessagePtr& msg)
std::vector<std::tuple<int, std::string, int> > outfitList; std::vector<std::tuple<int, std::string, int> > outfitList;
if(g_game.getFeature(Otc::GameNewOutfitProtocol)) { 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++) { for(int i = 0; i < outfitCount; i++) {
int outfitId = msg->getU16(); int outfitId = msg->getU16();
std::string outfitName = msg->getString(); std::string outfitName = msg->getString();
int outfitAddons = msg->getU8(); 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)); outfitList.push_back(std::make_tuple(outfitId, outfitName, outfitAddons));
} }
} else { } 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> > 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)) { 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) { for(int i = 0; i < mountCount; ++i) {
int mountId = msg->getU16(); // mount type int mountId = msg->getU16(); // mount type
std::string mountName = msg->getString(); // mount name 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)); 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) void ProtocolGame::parseVipAdd(const InputMessagePtr& msg)
@ -2118,6 +2442,10 @@ void ProtocolGame::parseTutorialHint(const InputMessagePtr& msg)
void ProtocolGame::parseAutomapFlag(const InputMessagePtr& msg) void ProtocolGame::parseAutomapFlag(const InputMessagePtr& msg)
{ {
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
msg->getU8(); // unknown
}
Position pos = getPosition(msg); Position pos = getPosition(msg);
int icon = msg->getU8(); int icon = msg->getU8();
std::string description = msg->getString(); std::string description = msg->getString();
@ -2152,6 +2480,9 @@ void ProtocolGame::parseQuestLine(const InputMessagePtr& msg)
int questId = msg->getU16(); int questId = msg->getU16();
int missionCount = msg->getU8(); int missionCount = msg->getU8();
for(int i = 0; i < missionCount; i++) { for(int i = 0; i < missionCount; i++) {
if (g_game.getFeature(Otc::GameTibia12Protocol))
msg->getU16(); // mission id
std::string missionName = msg->getString(); std::string missionName = msg->getString();
std::string missionDescrition = msg->getString(); std::string missionDescrition = msg->getString();
questMissions.push_back(std::make_tuple(missionName, missionDescrition)); questMissions.push_back(std::make_tuple(missionName, missionDescrition));
@ -2250,6 +2581,37 @@ void ProtocolGame::parseMessageDialog(const InputMessagePtr& msg)
msg->getString(); 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) void ProtocolGame::parseResourceBalance(const InputMessagePtr& msg)
{ {
uint8_t type = msg->getU8(); uint8_t type = msg->getU8();
@ -2257,6 +2619,13 @@ void ProtocolGame::parseResourceBalance(const InputMessagePtr& msg)
g_lua.callGlobalField("g_game", "onResourceBalance", type, amount); 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) void ProtocolGame::parseQuestTracker(const InputMessagePtr& msg)
{ {
msg->getU8(); msg->getU8();
@ -2301,6 +2670,53 @@ void ProtocolGame::parseCloseImbuementWindow(const InputMessagePtr&)
g_lua.callGlobalField("g_game", "onCloseImbuementWindow"); 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 ProtocolGame::getImbuementInfo(const InputMessagePtr& msg)
{ {
Imbuement i; Imbuement i;
@ -2334,9 +2750,9 @@ void ProtocolGame::parseKillTracker(const InputMessagePtr& msg)
msg->getU8(); msg->getU8();
msg->getU8(); msg->getU8();
msg->getU8(); msg->getU8();
bool emptyCorpse = msg->getU8() == 0; int corpseSize = msg->getU8(); // corpse size
if (!emptyCorpse) { for (int i = 0; i < corpseSize; i++) {
getItem(msg); getItem(msg); // corpse item
} }
} }
@ -2351,10 +2767,19 @@ void ProtocolGame::parseImpactTracker(const InputMessagePtr& msg)
msg->getU32(); 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) void ProtocolGame::parseLootTracker(const InputMessagePtr& msg)
{ {
msg->getU16(); getItem(msg);
msg->getString(); msg->getString(); // item name
} }
@ -2520,7 +2945,7 @@ int ProtocolGame::setTileDescription(const InputMessagePtr& msg, Position positi
g_map.setTileSpeed(position, groundSpeed, blocking); 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(); msg->getU16();
} }
@ -2594,6 +3019,9 @@ Outfit ProtocolGame::getOutfit(const InputMessagePtr& msg, bool ignoreMount)
outfit.setWings(msg->getU16()); outfit.setWings(msg->getU16());
outfit.setAura(msg->getU16()); outfit.setAura(msg->getU16());
} }
if (g_game.getFeature(Otc::GameOutfitShaders)) {
outfit.setShader(msg->getString());
}
} }
return outfit; return outfit;
@ -2606,7 +3034,7 @@ ThingPtr ProtocolGame::getThing(const InputMessagePtr& msg)
int id = msg->getU16(); int id = msg->getU16();
if(id == 0) 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) else if(id == Proto::UnknownCreature || id == Proto::OutdatedCreature || id == Proto::Creature)
thing = getCreature(msg, id); thing = getCreature(msg, id);
else if(id == Proto::StaticText) // otclient only else if(id == Proto::StaticText) // otclient only
@ -2674,6 +3102,9 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type)
creatureType = Proto::CreatureTypeNpc; creatureType = Proto::CreatureTypeNpc;
} }
if (creatureType == Proto::CreatureTypeSummonOwn)
msg->getU32(); // master
std::string name = g_game.formatCreatureName(msg->getString()); std::string name = g_game.formatCreatureName(msg->getString());
if(id == m_localPlayer->getId()) if(id == m_localPlayer->getId())
@ -2685,11 +3116,13 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type)
else else
creature = PlayerPtr(new Player); creature = PlayerPtr(new Player);
} }
else if(creatureType == Proto::CreatureTypeMonster) else if (creatureType == Proto::CreatureTypeMonster)
creature = MonsterPtr(new Monster); creature = MonsterPtr(new Monster);
else if(creatureType == Proto::CreatureTypeNpc) else if (creatureType == Proto::CreatureTypeNpc)
creature = NpcPtr(new Npc); creature = NpcPtr(new Npc);
else else if (creatureType == Proto::CreatureTypeSummonOwn) {
creature = MonsterPtr(new Monster);
} else
g_logger.traceError("creature type is invalid"); g_logger.traceError("creature type is invalid");
if(creature) { if(creature) {
@ -2724,6 +3157,8 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type)
if(g_game.getFeature(Otc::GameThingMarks)) { if(g_game.getFeature(Otc::GameThingMarks)) {
creatureType = msg->getU8(); creatureType = msg->getU8();
if (creatureType == Proto::CreatureTypeSummonOwn)
msg->getU32(); // master
} }
if(g_game.getFeature(Otc::GameCreatureIcons)) { if(g_game.getFeature(Otc::GameCreatureIcons)) {
@ -2732,7 +3167,10 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type)
if(g_game.getFeature(Otc::GameThingMarks)) { if(g_game.getFeature(Otc::GameThingMarks)) {
mark = msg->getU8(); // mark mark = msg->getU8(); // mark
msg->getU16(); // helpers if(g_game.getFeature(Otc::GameTibia12Protocol))
msg->getU8(); // inspection?
else
msg->getU16(); // helpers?
if(creature) { if(creature) {
if(mark == 0xff) if(mark == 0xff)
@ -2804,12 +3242,18 @@ ItemPtr ProtocolGame::getItem(const InputMessagePtr& msg, int id, bool hasDescri
if(item->getId() == 0) if(item->getId() == 0)
stdext::throw_exception(stdext::format("unable to create item with invalid id %d", id)); 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 msg->getU8(); // mark
} }
if(item->isStackable() || item->isFluidContainer() || item->isSplash() || item->isChargeable()) if(item->isStackable() || item->isFluidContainer() || item->isSplash() || item->isChargeable())
item->setCountOrSubType(msg->getU8()); 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(g_game.getFeature(Otc::GameItemAnimationPhase)) {
if(item->getAnimationPhases() > 1) { if(item->getAnimationPhases() > 1) {

View File

@ -28,12 +28,12 @@
#include <framework/util/crypt.h> #include <framework/util/crypt.h>
#include <framework/util/extras.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) // avoid usage of automated sends (bot modules)
if(!g_game.checkBotProtection()) if(!g_game.checkBotProtection())
return; return;
Protocol::send(outputMessage); Protocol::send(outputMessage, rawPacket);
} }
void ProtocolGame::sendExtendedOpcode(uint8 opcode, const std::string& buffer) 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(); 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) void ProtocolGame::sendLoginPacket(uint challengeTimestamp, uint8 challengeRandom)
{ {
OutputMessagePtr msg(new OutputMessage); OutputMessagePtr msg(new OutputMessage);
@ -161,6 +168,9 @@ void ProtocolGame::sendLoginPacket(uint challengeTimestamp, uint8 challengeRando
if (g_game.getFeature(Otc::GamePacketCompression)) if (g_game.getFeature(Otc::GamePacketCompression))
enableCompression(); enableCompression();
if (g_game.getFeature(Otc::GameSequencedPackets))
enabledSequencedPackets();
} }
void ProtocolGame::sendEnterGame() void ProtocolGame::sendEnterGame()
@ -804,6 +814,12 @@ void ProtocolGame::sendChangeOutfit(const Outfit& outfit)
msg->addU8(outfit.getAddons()); msg->addU8(outfit.getAddons());
if(g_game.getFeature(Otc::GamePlayerMounts)) if(g_game.getFeature(Otc::GamePlayerMounts))
msg->addU16(outfit.getMount()); 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); send(msg);
} }

View File

@ -24,6 +24,7 @@
#include <framework/graphics/paintershaderprogram.h> #include <framework/graphics/paintershaderprogram.h>
#include <framework/graphics/graphics.h> #include <framework/graphics/graphics.h>
#include <framework/core/resourcemanager.h> #include <framework/core/resourcemanager.h>
#include <framework/core/eventdispatcher.h>
ShaderManager g_shaders; ShaderManager g_shaders;
@ -37,51 +38,36 @@ void ShaderManager::terminate()
m_shaders.clear(); 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; g_graphicsDispatcher.addEventEx("addTexture", [&, name, file] {
} auto program = getShader(name);
if (program)
PainterShaderProgramPtr ShaderManager::createFragmentShaderFromCode(const std::string& name, const std::string& code) program->addMultiTexture(file);
{ });
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;
} }
PainterShaderProgramPtr ShaderManager::getShader(const std::string& name) PainterShaderProgramPtr ShaderManager::getShader(const std::string& name)
{ {
VALIDATE_GRAPHICS_THREAD();
auto it = m_shaders.find(name); auto it = m_shaders.find(name);
if(it != m_shaders.end()) if(it != m_shaders.end())
return it->second; return it->second;

View File

@ -30,29 +30,18 @@
class ShaderManager class ShaderManager
{ {
public: public:
enum {
ITEM_ID_UNIFORM = 10,
MAP_CENTER_COORD = 10,
MAP_GLOBAL_COORD = 11,
MAP_ZOOM = 12
};
void init(); void init();
void terminate(); void terminate();
PainterShaderProgramPtr createShader(const std::string& name); void createShader(const std::string& name, std::string vertex, std::string fragment, bool colorMatrix = false);
PainterShaderProgramPtr createFragmentShader(const std::string& name, std::string file); void createOutfitShader(const std::string& name, std::string vertex, std::string fragment)
PainterShaderProgramPtr createFragmentShaderFromCode(const std::string& name, const std::string& code); {
return createShader(name, vertex, fragment, true);
PainterShaderProgramPtr createItemShader(const std::string& name, const std::string& file); }
PainterShaderProgramPtr createMapShader(const std::string& name, const std::string& file); void addTexture(const std::string& name, const std::string& file);
PainterShaderProgramPtr getShader(const std::string& name); PainterShaderProgramPtr getShader(const std::string& name);
private: private:
void setupItemShader(const PainterShaderProgramPtr& shader);
void setupMapShader(const PainterShaderProgramPtr& shader);
std::unordered_map<std::string, PainterShaderProgramPtr> m_shaders; std::unordered_map<std::string, PainterShaderProgramPtr> m_shaders;
}; };