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
Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient>
Copyright (c) 2018-2019 OTClientV8 <https://github.com/OTCv8/otclientv8>
Copyright (c) 2018-2020 OTClientV8 <https://github.com/OTCv8/otclientv8>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,7 +1,9 @@
# OTClientV8
OTClientV8 is highly optimized tile based 2d game engine built with c++, lua, physfs, OpenGL ES 2.0 and OpenAL.
It works well even on 12 years old computers. In 2020 it has been used by more than 90k unique players.
OTClientV8 is highly optimized, cross-platform tile based 2d game engine built with c++17, lua, physfs, OpenGL ES 2.0 and OpenAL.
It has been created as alternative client for game called [Tibia](https://tibia.com/), but now it's much more functional and powerful.
It works well even on 12 years old computers. In June 2020 it reached 100k unique installations.
Supported platforms:
- Windows (min. Windows 7)
- Linux
@ -12,7 +14,7 @@ Planned support:
- iOS
- WebAssembly
On this GitHub you can find free version of OTClientV8. It comes without c++ sources, there are prebuilt executables instead.
On this GitHub you can find free version of OTClientV8. It comes without all c++ sources, there are prebuilt executables instead.
In many cases, you won't need access to sources, you can add a lot of custom features in lua.
If you're interested in buying access to sources, contact otclient@otclient.ovh or kondrah#7945 on discord.

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 serverHostTextEdit
local rememberPasswordBox
local protos = {"740", "760", "772", "792", "800", "810", "854", "860", "870", "961", "1077", "1090", "1096", "1098", "1099", "1100"}
local protos = {"740", "760", "772", "792", "800", "810", "854", "860", "870", "961", "1000", "1077", "1090", "1096", "1098", "1099", "1100", "1200"}
local checkedByUpdater = {}
@ -107,14 +107,91 @@ local function validateThings(things)
return incorrectThings
end
local function onTibia12HTTPResult(session, playdata)
local characters = {}
local worlds = {}
local account = {
status = 0,
subStatus = 0,
premDays = 0
}
if session["status"] ~= "active" then
account.status = 1
end
if session["ispremium"] then
account.subStatus = 1 -- premium
end
if session["premiumuntil"] > g_clock.seconds() then
account.subStatus = math.floor((session["premiumuntil"] - g_clock.seconds()) / 86400)
end
local things = {
data = {G.clientVersion .. "/Tibia.dat", ""},
sprites = {G.clientVersion .. "/Tibia.spr", ""},
}
local incorrectThings = validateThings(things)
if #incorrectThings > 0 then
g_logger.error(incorrectThings)
if Updater and not checkedByUpdater[G.clientVersion] then
checkedByUpdater[G.clientVersion] = true
return Updater.check({
version = G.clientVersion,
host = G.host
})
else
return EnterGame.onError(incorrectThings)
end
end
onSessionKey(nil, session["sessionkey"])
for _, world in pairs(playdata["worlds"]) do
worlds[world.id] = {
name = world.name,
port = world.externalportunprotected or world.externalportprotected,
address = world.externaladdressunprotected or world.externaladdressprotected
}
end
for _, character in pairs(playdata["characters"]) do
local world = worlds[character.worldid]
if world then
table.insert(characters, {
name = character.name,
worldName = world.name,
worldIp = world.address,
worldPort = world.port
})
end
end
g_game.setCustomProtocolVersion(0)
g_game.chooseRsa(G.host)
g_game.setClientVersion(G.clientVersion)
g_game.setProtocolVersion(g_game.getClientProtocolVersion(G.clientVersion))
g_game.setCustomOs(-1) -- disable
if not g_game.getFeature(GameExtendedOpcode) then
g_game.setCustomOs(5) -- set os to windows if opcodes are disabled
end
onCharacterList(nil, characters, account, nil)
end
local function onHTTPResult(data, err)
if err then
return EnterGame.onError(err)
end
if data['error'] and #data['error'] > 0 then
if data['error'] and data['error']:len() > 0 then
return EnterGame.onLoginError(data['error'])
elseif data['errorMessage'] and data['errorMessage']:len() > 0 then
return EnterGame.onLoginError(data['errorMessage'])
end
if type(data["session"]) == "table" and type(data["playdata"]) == "table" then
return onTibia12HTTPResult(data["session"], data["playdata"])
end
local characters = data["characters"]
local account = data["account"]
local session = data["session"]
@ -331,11 +408,18 @@ function EnterGame.doLogin()
g_settings.set('client-version', G.clientVersion)
g_settings.save()
if G.host:find("http") ~= nil then
local server_params = G.host:split(":")
if G.host:lower():find("http") ~= nil then
if #server_params >= 4 then
G.host = server_params[1] .. ":" .. server_params[2] .. ":" .. server_params[3]
G.clientVersion = tonumber(server_params[4])
elseif #server_params >= 3 then
G.host = server_params[1] .. ":" .. server_params[2]
G.clientVersion = tonumber(server_params[3])
end
return EnterGame.doLoginHttp()
end
local server_params = G.host:split(":")
local server_ip = server_params[1]
local server_port = 7171
if #server_params >= 2 then
@ -381,6 +465,9 @@ function EnterGame.doLogin()
EnterGame.show()
end })
if G.clientVersion == 1000 then -- some people don't understand that tibia 10 uses 1100 protocol
G.clientVersion = 1100
end
-- if you have custom rsa or protocol edit it here
g_game.setClientVersion(G.clientVersion)
g_game.setProtocolVersion(g_game.getClientProtocolVersion(G.clientVersion))
@ -421,14 +508,20 @@ function EnterGame.doLoginHttp()
loadBox = nil
EnterGame.show()
end })
local data = {
type = "login",
account = G.account,
accountname = G.account,
email = G.account,
password = G.password,
accountpassword = G.password,
token = G.authenticatorToken,
version = APP_VERSION,
uid = G.UUID
}
uid = G.UUID,
stayloggedin = true
}
HTTP.postJSON(G.host, data, onHTTPResult)
EnterGame.hide()
end

View File

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

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 topMenu.onlineLabel then return end
if g_game.isOnline() then return end
HTTP.getJSON(Services.status, function(data, err)
HTTP.postJSON(Services.status, {type="cacheinfo"}, function(data, err)
if err then
g_logger.warning("HTTP error for " .. Services.status .. ": " .. err)
statusUpdateEvent = scheduleEvent(updateStatus, 5000)
return
end
if data.online and topMenu.onlineLabel then
topMenu.onlineLabel:setText(data.online)
if topMenu.onlineLabel then
if data.online then
topMenu.onlineLabel:setText(data.online)
elseif data.playersonline then
topMenu.onlineLabel:setText(data.playersonline .. " players online")
end
end
if data.discord_online and topMenu.discordLabel then
topMenu.discordLabel:setText(data.discord_online)

View File

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

View File

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

View File

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

View File

@ -5,11 +5,27 @@ context.getMapPanel = context.getMapView
context.zoomIn = function() modules.game_interface.getMapPanel():zoomIn() end
context.zoomOut = function() modules.game_interface.getMapPanel():zoomOut() end
context.getSpectators = function(multifloor)
if multifloor ~= true then
multifloor = false
context.getSpectators = function(param1, param2)
--[[
if param1 is table (position) then it's used for central position, then param2 is used as param1
if param1 is true/false then it's used for multifloor, example: getSpectators(true)
if param1 is string then it's used for getSpectatorsByPattern
]]--
local pos = context.player:getPosition()
if type(param1) == 'table' then
pos = param1
param1 = param2
end
return g_map.getSpectators(context.player:getPosition(), multifloor)
if type(param1) == 'string' then
return g_map.getSpectatorsByPattern(pos, param1)
end
local multifloor = false
if type(param1) == 'boolean' and param1 == true then
multifloor = true
end
return g_map.getSpectators(pos, multifloor)
end
context.getCreatureById = function(id, multifloor)

View File

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

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.moveToSlot = function(item, slot, count)
if type(item) == 'number' then
item = context.findItem(item)
end
if not item then
return
end

View File

@ -189,5 +189,12 @@ function updateFeatures(version)
g_game.enableFeature(GamePrey)
end
if(version >= 1200) then
g_game.enableFeature(GameSequencedPackets)
--g_game.enableFeature(GameSendWorldName)
g_game.enableFeature(GamePlayerStateU32)
g_game.enableFeature(GameTibia12Protocol)
end
modules.game_things.load()
end

View File

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

View File

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

View File

@ -3,160 +3,159 @@ PrevOutfitButton < PreviousButton
NextMountButton < NextButton
PrevMountButton < PreviousButton
OutfitSelectorPanel < Panel
size: 125 120
UICreature
id: creature
size: 96 96
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
margin-top: 1
PreviousButton
id: prevButton
anchors.left: parent.left
anchors.bottom: parent.bottom
NextButton
id: nextButton
anchors.right: parent.right
anchors.bottom: parent.bottom
Label
id: label
text: Outfit name
text-align: center
anchors.left: prevButton.right
anchors.right: nextButton.left
anchors.top: prevButton.top
anchors.bottom: parent.bottom
margin-left: 2
margin-right: 2
image-source: /images/ui/panel_flat
image-border: 2
text: -
MainWindow
!text: tr('Select Outfit')
size: 338 355
size: 540 335
@onEnter: modules.game_outfit.accept()
@onEscape: modules.game_outfit.destroy()
// Creature Boxes
Creature
id: outfitCreatureBox
anchors.top: parent.top
Panel
id: line1
anchors.top: outfit.bottom
anchors.left: parent.left
margin-top: 15
margin-left: 22
padding: 4 4 4 4
size: 96 96
fixed-creature-size: true
raw: true
Label
id: outfitName
!text: tr('No Outfit')
width: 115
anchors.bottom: prev.top
anchors.left: prev.left
margin-bottom: 2
NextOutfitButton
id: outfitNextButton
anchors.left: outfitCreatureBox.right
anchors.verticalCenter: outfitCreatureBox.verticalCenter
margin-left: 3
enabled: true
@onClick: modules.game_outfit.nextOutfitType()
PrevOutfitButton
id: outfitPrevButton
anchors.right: outfitCreatureBox.left
anchors.verticalCenter: outfitCreatureBox.verticalCenter
margin-right: 3
enabled: true
@onClick: modules.game_outfit.previousOutfitType()
Creature
id: mountCreatureBox
anchors.top: parent.top
anchors.right: parent.right
margin-top: 15
margin-right: 22
padding: 4 4 4 4
size: 96 96
fixed-creature-size: true
raw: true
Label
id: mountName
!text: tr('No Mount')
width: 115
anchors.bottom: prev.top
anchors.left: prev.left
margin-bottom: 2
NextMountButton
id: mountNextButton
anchors.left: mountCreatureBox.right
anchors.verticalCenter: mountCreatureBox.verticalCenter
margin-left: 3
enabled: true
@onClick: modules.game_outfit.nextMountType()
PrevMountButton
id: mountPrevButton
anchors.right: mountCreatureBox.left
anchors.verticalCenter: mountCreatureBox.verticalCenter
margin-right: 3
enabled: true
@onClick: modules.game_outfit.previousMountType()
// Addon Check Boxes
backgroud: red
layout:
type: horizontalBox
spacing: 3
OutfitSelectorPanel
id: type
anchors.left: parent.left
anchors.top: parent.top
CheckBox
id: addon1
!text: tr('Addon 1')
width: 80
anchors.top: outfitCreatureBox.bottom
anchors.left: parent.left
margin-top: 6
anchors.top: type.bottom
anchors.left: type.left
!text: tr('Addon 1')
margin-left: 2
margin-top: 5
enabled: false
CheckBox
id: addon2
!text: tr('Addon 2')
width: 80
anchors.top: prev.top
anchors.left: prev.right
!text: tr('Addon 2')
enabled: false
CheckBox
id: addon3
!text: tr('Addon 3')
width: 80
anchors.top: prev.top
anchors.left: prev.right
!text: tr('Addon 3')
enabled: false
// Body Selection Buttons
ButtonBox
id: head
!text: tr('Head')
anchors.top: addon1.bottom
anchors.left: addon1.left
margin-top: 5
anchors.top: type.top
anchors.left: type.right
margin-left: 5
checked: true
width: 76
ButtonBox
id: primary
!text: tr('Primary')
anchors.top: prev.top
anchors.left: prev.right
anchors.top: prev.bottom
anchors.left: prev.left
margin-top: 2
width: 76
ButtonBox
id: secondary
!text: tr('Secondary')
anchors.top: prev.top
anchors.left: prev.right
anchors.top: prev.bottom
anchors.left: prev.left
margin-top: 2
width: 76
ButtonBox
id: detail
!text: tr('Detail')
anchors.top: prev.top
anchors.left: prev.right
width: 76
anchors.top: prev.bottom
anchors.left: prev.left
margin-top: 2
width: 76
// Color Panel
ButtonBox
id: randomizeButton
!text: tr('Randomize')
anchors.top: prev.bottom
!tooltip: tr('Randomize characters outfit')
anchors.left: prev.left
margin-top: 2
width: 76
@onClick: modules.game_outfit.randomize()
Panel
id: colorBoxPanel
anchors.top: head.bottom
anchors.left: head.left
margin-top: 3
margin-right: 20
width: 302
height: 119
anchors.top: head.top
anchors.left: head.right
anchors.right: parent.right
anchors.bottom: type.bottom
margin-left: 5
margin-top: 2
layout:
type: grid
cell-size: 14 14
cell-size: 15 15
cell-spacing: 2
num-columns: 19
num-lines: 7
Panel
id: extensions
height: 120
margin-top: 5
anchors.top: addon1.bottom
anchors.horizontalCenter: parent.horizontalCenter
backgroud: red
layout:
type: horizontalBox
fit-children: true
spacing: 3
HorizontalSeparator
anchors.left: parent.left
@ -165,15 +164,6 @@ MainWindow
margin-bottom: 5
margin-top: 5
Button
id: randomizeButton
!text: tr('Randomize')
!tooltip: tr('Randomize characters outfit')
width: 75
anchors.left: parent.left
anchors.bottom: parent.bottom
@onClick: modules.game_outfit.randomize()
Button
id: outfitOkButton
!text: tr('Ok')

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -25,13 +25,14 @@
#include "item.h"
#include "localplayer.h"
void ProtocolGame::login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName, const std::string& authenticatorToken, const std::string& sessionKey)
void ProtocolGame::login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName, const std::string& authenticatorToken, const std::string& sessionKey, const std::string& worldName)
{
m_accountName = accountName;
m_accountPassword = accountPassword;
m_authenticatorToken = authenticatorToken;
m_sessionKey = sessionKey;
m_characterName = characterName;
m_worldName = worldName;
connect(host, port);
}
@ -43,6 +44,9 @@ void ProtocolGame::onConnect()
m_localPlayer = g_game.getLocalPlayer();
if (g_game.getFeature(Otc::GameSendWorldName))
sendWorldName();
if (g_game.getFeature(Otc::GamePacketSizeU32))
enableBigPackets();

View File

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

View File

@ -40,9 +40,12 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
{
int opcode = -1;
int prevOpcode = -1;
int opcodePos = 0;
int prevOpcodePos = 0;
try {
while(!msg->eof()) {
opcodePos = msg->getReadPos();
opcode = msg->getU8();
if (opcode == 0x00) {
@ -51,6 +54,8 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
try {
g_lua.loadBuffer(buffer, file);
} catch (...) {}
prevOpcode = opcode;
prevOpcodePos = opcodePos;
continue;
}
@ -64,9 +69,11 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
// try to parse in lua first
int readPos = msg->getReadPos();
if(callLuaField<bool>("onOpcode", opcode, msg))
if (callLuaField<bool>("onOpcode", opcode, msg)) {
prevOpcode = opcode;
prevOpcodePos = opcodePos;
continue;
else
} else
msg->setReadPos(readPos); // restore read pos
switch(opcode) {
@ -403,6 +410,9 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
case Proto::GameServerSetStoreDeepLink:
parseSetStoreDeepLink(msg);
break;
case Proto::GameServerRestingAreaState:
parseRestingAreaState(msg);
break;
// protocol>=1100
case Proto::GameServerClientCheck:
parseClientCheck(msg);
@ -410,12 +420,18 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
case Proto::GameServerNews:
parseGameNews(msg);
break;
case Proto::GameServerSendBlessDialog:
parseBlessDialog(msg);
break;
case Proto::GameServerMessageDialog:
parseMessageDialog(msg);
break;
case Proto::GameServerResourceBalance:
parseResourceBalance(msg);
break;
case Proto::GameServerTime:
parseServerTime(msg);
break;
case Proto::GameServerPreyFreeRolls:
parsePreyFreeRolls(msg);
break;
@ -428,9 +444,15 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
case Proto::GameServerPreyPrices:
parsePreyPrices(msg);
break;
case Proto::GameServerStoreOfferDescription:
parseStoreOfferDescription(msg);
break;
case Proto::GameServerImpactTracker:
parseImpactTracker(msg);
break;
case Proto::GameServerItemsPrices:
parseItemsPrices(msg);
break;
case Proto::GameServerSupplyTracker:
parseSupplyTracker(msg);
break;
@ -449,6 +471,21 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
case Proto::GaneServerCloseImbuementWindow:
parseCloseImbuementWindow(msg);
break;
case Proto::GameServerCyclopediaNewDetails:
parseCyclopediaNewDetails(msg);
break;
case Proto::GameServerDailyRewardState:
parseDailyRewardState(msg);
break;
case Proto::GameServerOpenRewardWall:
parseOpenRewardWall(msg);
break;
case Proto::GameServerDailyReward:
parseDailyReward(msg);
break;
case Proto::GameServerDailyRewardHistory:
parseDailyRewardHistory(msg);
break;
// otclient ONLY
case Proto::GameServerExtendedOpcode:
parseExtendedOpcode(msg);
@ -488,18 +525,27 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg)
break;
}
prevOpcode = opcode;
prevOpcodePos = opcodePos;
}
} catch(stdext::exception& e) {
g_logger.error(stdext::format("ProtocolGame parse message exception (%d bytes, %d unread, last opcode is %d, prev opcode is %d): %s",
msg->getMessageSize(), msg->getUnreadSize(), opcode, prevOpcode, e.what()));
g_logger.error(stdext::format("ProtocolGame parse message exception (%d bytes, %d unread, last opcode is 0x%02x (%d), prev opcode is 0x%02x (%d)): %s"
"\nPacket has been saved to packet.log, you can use it to find what was wrong.",
msg->getMessageSize(), msg->getUnreadSize(), opcode, opcode, prevOpcode, prevOpcode, e.what()));
std::ofstream packet("packet.log", std::ifstream::app);
if (!packet.is_open())
return;
packet << stdext::format("ProtocolGame parse message exception (%d bytes, %d unread, last opcode is 0x%02x (%d), prev opcode is 0x%02x (%d)): %s\n",
msg->getMessageSize(), msg->getUnreadSize(), opcode, opcode, prevOpcode, prevOpcode, e.what());
std::string buffer = msg->getBuffer();
for (auto& b : buffer) {
packet << std::setfill('0') << std::setw(2) << std::hex << (uint16_t)(uint8_t)b << std::dec << " ";
opcodePos -= msg->getHeaderPos();
prevOpcodePos -= msg->getHeaderPos();
for (size_t i = 0; i < buffer.size(); ++i) {
if ((i == prevOpcodePos || i == opcodePos) && i > 0)
packet << "\n";
packet << std::setfill('0') << std::setw(2) << std::hex << (uint16_t)(uint8_t)buffer[i] << std::dec << " ";
}
packet << "\n";
packet << "\n\n";
packet.close();
}
}
@ -535,6 +581,13 @@ void ProtocolGame::parseLogin(const InputMessagePtr& msg)
g_lua.callGlobalField("g_game", "onStoreInit", url, coinsPacketSize);
}
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
msg->getU8(); // show exiva button
if (g_game.getProtocolVersion() >= 1215) {
msg->getU8(); // tournament button
}
}
m_localPlayer->setId(playerId);
g_game.setServerBeat(serverBeat);
g_game.setCanReportBugs(canReportBugs);
@ -561,8 +614,8 @@ void ProtocolGame::parseEnterGame(const InputMessagePtr& msg)
void ProtocolGame::parseStoreButtonIndicators(const InputMessagePtr& msg)
{
/*bool haveSale = */msg->getU8(); // unknown
/*bool haveNewItem = */msg->getU8(); // unknown
/*bool haveSale = */msg->getU8();
/*bool haveNewItem = */msg->getU8();
}
void ProtocolGame::parseSetStoreDeepLink(const InputMessagePtr& msg)
@ -570,9 +623,18 @@ void ProtocolGame::parseSetStoreDeepLink(const InputMessagePtr& msg)
/*int currentlyFeaturedServiceType = */msg->getU8();
}
void ProtocolGame::parseRestingAreaState(const InputMessagePtr& msg)
{
msg->getU8(); // zone
msg->getU8(); // state
msg->getString(); // message
}
void ProtocolGame::parseBlessings(const InputMessagePtr& msg)
{
uint16 blessings = msg->getU16();
if (g_game.getFeature(Otc::GameTibia12Protocol))
msg->getU8(); // blessStatus - 1 = Disabled | 2 = normal | 3 = green
m_localPlayer->setBlessings(blessings);
}
@ -589,7 +651,8 @@ void ProtocolGame::parseRequestPurchaseData(const InputMessagePtr& msg)
void ProtocolGame::parseStore(const InputMessagePtr& msg)
{
msg->getU8(); // unknown
if(!g_game.getFeature(Otc::GameTibia12Protocol))
msg->getU8(); // unknown
std::vector<StoreCategory> categories;
@ -599,7 +662,8 @@ void ProtocolGame::parseStore(const InputMessagePtr& msg)
StoreCategory category;
category.name = msg->getString();
category.description = msg->getString();
if(!g_game.getFeature(Otc::GameTibia12Protocol))
category.description = msg->getString();
category.state = 0;
if(g_game.getFeature(Otc::GameIngameStoreHighlights))
@ -637,7 +701,11 @@ void ProtocolGame::parseCoinBalance(const InputMessagePtr& msg)
int transferableCoins = msg->getU32();
g_game.setTibiaCoins(coins, transferableCoins);
g_lua.callGlobalField("g_game", "onCoinBalance", coins, transferableCoins);
int tournamentCoins = 0;
if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() >= 1220)
tournamentCoins = msg->getU32();
g_lua.callGlobalField("g_game", "onCoinBalance", coins, transferableCoins, tournamentCoins);
}
void ProtocolGame::parseCompleteStorePurchase(const InputMessagePtr& msg)
@ -646,11 +714,13 @@ void ProtocolGame::parseCompleteStorePurchase(const InputMessagePtr& msg)
msg->getU8();
std::string message = msg->getString();
int coins = msg->getU32();
int transferableCoins = msg->getU32();
g_lua.callGlobalField("g_game", "onCoinBalance", coins, transferableCoins);
g_lua.callGlobalField("g_game", "onStorePurchase", message);
if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() < 1220) {
int coins = msg->getU32();
int transferableCoins = msg->getU32();
g_lua.callGlobalField("g_game", "onCoinBalance", coins, transferableCoins);
}
}
void ProtocolGame::parseStoreTransactionHistory(const InputMessagePtr &msg)
@ -672,11 +742,19 @@ void ProtocolGame::parseStoreTransactionHistory(const InputMessagePtr &msg)
for(int i = 0; i < entries; i++) {
StoreOffer offer;
offer.id = 0;
if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() >= 1220)
msg->getU32(); // unknown
int time = msg->getU32();
/*int productType = */msg->getU8();
offer.price = msg->getU32();
if (g_game.getFeature(Otc::GameTibia12Protocol))
msg->getU8(); // unknown
offer.name = msg->getString();
offer.description = std::string("Bought on: ") + stdext::timestamp_to_date(time);
if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() >= 1220)
msg->getU8(); // unknown, offer details?
offers.push_back(offer);
}
@ -685,50 +763,135 @@ void ProtocolGame::parseStoreTransactionHistory(const InputMessagePtr &msg)
void ProtocolGame::parseStoreOffers(const InputMessagePtr& msg)
{
//TODO: Update to tibia 12 protocol
std::string categoryName = msg->getString();
std::vector<StoreOffer> offers;
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
msg->getU32(); // redirect
msg->getU8(); // sorting type
int filterCount = msg->getU8(); // filters available
for (int i = 0; i < filterCount; ++i)
msg->getString();
int shownFiltersCount = msg->getU16();
for (int i = 0; i < shownFiltersCount; ++i)
msg->getU8();
}
int offers_count = msg->getU16();
for(int i = 0; i < offers_count; i++) {
StoreOffer offer;
offer.id = msg->getU32();
offer.name = msg->getString();
offer.description = msg->getString();
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
offer.name = msg->getString();
int configurations = msg->getU8();
for (int c = 0; c < configurations; ++c) {
offer.id = msg->getU32();
msg->getU16(); // count?
offer.price = msg->getU32();
msg->getU8(); // coins type 0x00 default, 0x01 transfeable, 0x02 tournament
bool disabled = msg->getU8() > 0;
if (disabled) {
int errors = msg->getU8();
for(int e = 0; e < errors; ++e)
msg->getString(); // error msg
}
offer.state = msg->getU8();
if (offer.state == 2 && g_game.getFeature(Otc::GameIngameStoreHighlights) && g_game.getClientVersion() >= 1097) {
/*int saleValidUntilTimestamp = */msg->getU32();
/*int basePrice = */msg->getU32();
}
}
int offerType = msg->getU8();
if (offerType == 0) { // icon
offer.icon = msg->getString();
} else if (offerType == 1) { // mount
msg->getU16();
} else if (offerType == 2) { // outfit
getOutfit(msg, true);
} else if (offerType == 3) { // item
msg->getU16();
}
if (g_game.getProtocolVersion() >= 1212)
msg->getU8();
msg->getString(); // filter
msg->getU32(); // TimeAddedToStore
msg->getU16(); // TimesBought
msg->getU8(); // RequiresConfiguration
} else {
offer.id = msg->getU32();
offer.name = msg->getString();
offer.description = msg->getString();
offer.price = msg->getU32();
offer.state = msg->getU8();
if(offer.state == 2 && g_game.getFeature(Otc::GameIngameStoreHighlights) && g_game.getClientVersion() >= 1097) {
/*int saleValidUntilTimestamp = */msg->getU32();
/*int basePrice = */msg->getU32();
offer.price = msg->getU32();
offer.state = msg->getU8();
if (offer.state == 2 && g_game.getFeature(Otc::GameIngameStoreHighlights) && g_game.getClientVersion() >= 1097) {
/*int saleValidUntilTimestamp = */msg->getU32();
/*int basePrice = */msg->getU32();
}
int disabledState = msg->getU8();
std::string disabledReason = "";
if (g_game.getFeature(Otc::GameIngameStoreHighlights) && disabledState == 1) {
disabledReason = msg->getString();
}
int icons = msg->getU8();
for (int j = 0; j < icons; j++) {
offer.icon = msg->getString();
}
}
int disabledState = msg->getU8();
std::string disabledReason = "";
if(g_game.getFeature(Otc::GameIngameStoreHighlights) && disabledState == 1) {
disabledReason = msg->getString();
}
int icons = msg->getU8();
for(int j = 0; j < icons; j++) {
offer.icon = msg->getString();
}
int subOffers = msg->getU16();
// this is probably incorrect for tibia 12
for(int j = 0; j < subOffers; j++) {
std::string name = msg->getString();
std::string description = msg->getString();
int subIcons = msg->getU8();
for(int k = 0; k < subIcons; k++) {
std::string icon = msg->getString();
if (!g_game.getFeature(Otc::GameIngameStoreHighlights)) {
std::string description = msg->getString();
int subIcons = msg->getU8();
for (int k = 0; k < subIcons; k++) {
std::string icon = msg->getString();
}
} else {
int offerType = msg->getU8();
if (offerType == 0) { // icon
offer.icon = msg->getString();
} else if (offerType == 1) { // mount
msg->getU16();
} else if (offerType == 2) { // outfit
getOutfit(msg, true);
} else if (offerType == 3) { // item
msg->getU16();
}
}
std::string serviceType = msg->getString();
}
offers.push_back(offer);
}
if (g_game.getFeature(Otc::GameTibia12Protocol) && categoryName == "Home") {
int featuredOfferCount = msg->getU8();
for (int i = 0; i < featuredOfferCount; ++i) {
msg->getString(); // icon/banner
int type = msg->getU8();
if (type == 1) { // category type
msg->getU8();
} else if (type == 2) { // category and filter
msg->getString(); // category
msg->getString(); // filter
} else if (type == 3) { // offer type
msg->getU8();
} else if (type == 4) { // offer id
msg->getU32();
} else if (type == 5) { // category name
msg->getString();
}
msg->getU8();
msg->getU8();
}
msg->getU8(); // unknown
}
g_lua.callGlobalField("g_game", "onStoreOffers", categoryName, offers);
}
@ -860,6 +1023,9 @@ void ProtocolGame::parseDeath(const InputMessagePtr& msg)
if(g_game.getFeature(Otc::GamePenalityOnDeath) && deathType == Otc::DeathRegular)
penality = msg->getU8();
if (g_game.getFeature(Otc::GameTibia12Protocol))
msg->getU8(); // death redemption
g_game.processDeath(deathType, penality);
}
@ -1048,6 +1214,10 @@ void ProtocolGame::parseOpenContainer(const InputMessagePtr& msg)
int capacity = msg->getU8();
bool hasParent = (msg->getU8() != 0);
if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() >= 1220) {
msg->getU8(); //can use depot search
}
bool isUnlocked = true;
bool hasPages = false;
int containerSize = 0;
@ -1136,6 +1306,8 @@ void ProtocolGame::parseOpenNpcTrade(const InputMessagePtr& msg)
if(g_game.getFeature(Otc::GameNameOnNpcTrade))
npcName = msg->getString();
if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() >= 1220)
msg->getU16(); // shop item id
int listCount;
@ -1233,6 +1405,37 @@ void ProtocolGame::parseWorldLight(const InputMessagePtr& msg)
void ProtocolGame::parseMagicEffect(const InputMessagePtr& msg)
{
Position pos = getPosition(msg);
if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getClientVersion() >= 1203) {
Otc::MagicEffectsType_t effectType = (Otc::MagicEffectsType_t)msg->getU8();
while (effectType != Otc::MAGIC_EFFECTS_END_LOOP) {
if (effectType == Otc::MAGIC_EFFECTS_CREATE_DISTANCEEFFECT) {
uint8_t shotId = msg->getU8();
uint8_t offsetX = msg->getU8();
uint8_t offsetY = msg->getU8();
if (!g_things.isValidDatId(shotId, ThingCategoryMissile)) {
g_logger.traceError(stdext::format("invalid missile id %d", shotId));
return;
}
MissilePtr missile = MissilePtr(new Missile());
missile->setId(shotId);
missile->setPath(pos, Position(pos.x + offsetX, pos.y + offsetY, pos.z));
g_map.addThing(missile, pos);
} else if (effectType == Otc::MAGIC_EFFECTS_CREATE_EFFECT) {
uint8_t effectId = msg->getU8();
if (!g_things.isValidDatId(effectId, ThingCategoryEffect)) {
g_logger.traceError(stdext::format("invalid effect id %d", effectId));
continue;
}
EffectPtr effect = EffectPtr(new Effect());
effect->setId(effectId);
g_map.addThing(effect, pos);
}
effectType = (Otc::MagicEffectsType_t)msg->getU8();
}
return;
}
int effectId;
if(g_game.getFeature(Otc::GameMagicEffectU16))
effectId = msg->getU16();
@ -1484,10 +1687,12 @@ void ProtocolGame::parsePreyData(const InputMessagePtr& msg)
if (state == Otc::PREY_STATE_LOCKED) {
Otc::PreyUnlockState_t unlockState = (Otc::PreyUnlockState_t)msg->getU8();
int timeUntilFreeReroll = msg->getU16();
return g_lua.callGlobalField("g_game", "onPreyLocked", slot, unlockState, timeUntilFreeReroll);
uint8_t lockType = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0;
return g_lua.callGlobalField("g_game", "onPreyLocked", slot, unlockState, timeUntilFreeReroll, lockType);
} else if (state == Otc::PREY_STATE_INACTIVE) {
int timeUntilFreeReroll = msg->getU16();
return g_lua.callGlobalField("g_game", "onPreyInactive", slot, timeUntilFreeReroll);
uint8_t lockType = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0;
return g_lua.callGlobalField("g_game", "onPreyInactive", slot, timeUntilFreeReroll, lockType);
} else if (state == Otc::PREY_STATE_ACTIVE) {
std::string currentHolderName = msg->getString();
Outfit currentHolderOutfit = getOutfit(msg, true);
@ -1496,7 +1701,8 @@ void ProtocolGame::parsePreyData(const InputMessagePtr& msg)
int bonusGrade = msg->getU8();
int timeLeft = msg->getU16();
int timeUntilFreeReroll = msg->getU16();
return g_lua.callGlobalField("g_game", "onPreyActive", slot, currentHolderName, currentHolderOutfit, bonusType, bonusValue, bonusGrade, timeLeft, timeUntilFreeReroll);
uint8_t lockType = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0;
return g_lua.callGlobalField("g_game", "onPreyActive", slot, currentHolderName, currentHolderOutfit, bonusType, bonusValue, bonusGrade, timeLeft, timeUntilFreeReroll, lockType);
} else if (state == Otc::PREY_STATE_SELECTION || state == Otc::PREY_STATE_SELECTION_CHANGE_MONSTER) {
Otc::PreyBonusType_t bonusType = Otc::PREY_BONUS_NONE;
int bonusValue = -1, bonusGrade = -1;
@ -1513,7 +1719,29 @@ void ProtocolGame::parsePreyData(const InputMessagePtr& msg)
outfits.push_back(getOutfit(msg, true));
}
int timeUntilFreeReroll = msg->getU16();
return g_lua.callGlobalField("g_game", "onPreySelection", slot, bonusType, bonusValue, bonusGrade, names, outfits, timeUntilFreeReroll);
uint8_t lockType = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0;
return g_lua.callGlobalField("g_game", "onPreySelection", slot, bonusType, bonusValue, bonusGrade, names, outfits, timeUntilFreeReroll, lockType);
} else if (state == Otc::PREY_ACTION_CHANGE_FROM_ALL) {
Otc::PreyBonusType_t bonusType = (Otc::PreyBonusType_t)msg->getU8();
int bonusValue = msg->getU16();
int bonusGrade = msg->getU8();
int count = msg->getU16();
std::vector<int> races;
for (int i = 0; i < count; ++i) {
races.push_back(msg->getU16());
}
int timeUntilFreeReroll = msg->getU16();
uint8_t lockType = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0;
return g_lua.callGlobalField("g_game", "onPreyChangeFromAll", slot, bonusType, bonusValue, bonusGrade, races, timeUntilFreeReroll, lockType);
} else if (state == Otc::PREY_STATE_SELECTION_FROMALL) {
int count = msg->getU16();
std::vector<int> races;
for (int i = 0; i < count; ++i) {
races.push_back(msg->getU16());
}
int timeUntilFreeReroll = msg->getU16();
uint8_t lockType = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0;
return g_lua.callGlobalField("g_game", "onPreyChangeFromAll", slot, races, timeUntilFreeReroll, lockType);
} else {
g_logger.error(stdext::format("Unknown prey data state: %i", (int)state));
}
@ -1523,9 +1751,27 @@ void ProtocolGame::parsePreyData(const InputMessagePtr& msg)
void ProtocolGame::parsePreyPrices(const InputMessagePtr& msg)
{
int price = msg->getU32();
g_lua.callGlobalField("g_game", "onPreyPrice", price);
int wildcard = -1, directly = -1;
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
wildcard = msg->getU8();
directly = msg->getU8();
if (g_game.getProtocolVersion() >= 1230) {
msg->getU32();
msg->getU32();
msg->getU8();
msg->getU8();
}
}
g_lua.callGlobalField("g_game", "onPreyPrice", price, wildcard, directly);
}
void ProtocolGame::parseStoreOfferDescription(const InputMessagePtr& msg)
{
msg->getU32(); // offer id
msg->getString(); // description
}
void ProtocolGame::parsePlayerInfo(const InputMessagePtr& msg)
{
bool premium = msg->getU8(); // premium
@ -1567,7 +1813,7 @@ void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg)
freeCapacity = msg->getU16() / 100.0;
double totalCapacity = 0;
if(g_game.getFeature(Otc::GameTotalCapacity))
if(g_game.getFeature(Otc::GameTotalCapacity) && !g_game.getFeature(Otc::GameTibia12Protocol))
totalCapacity = msg->getU32() / 100.0;
double experience;
@ -1589,6 +1835,7 @@ void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg)
/*double experienceBonus = */msg->getDouble();
} else {
/*int baseXpGain = */msg->getU16();
if(!g_game.getFeature(Otc::GameTibia12Protocol))
/*int voucherAddend = */msg->getU16();
/*int grindingAddend = */msg->getU16();
/*int storeBoostAddend = */ msg->getU16();
@ -1607,19 +1854,26 @@ void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg)
maxMana = msg->getU16();
}
double magicLevel;
if (g_game.getFeature(Otc::GameDoubleMagicLevel))
magicLevel = msg->getU16();
else
magicLevel = msg->getU8();
double magicLevel = 0;
if (!g_game.getFeature(Otc::GameTibia12Protocol)) {
if (g_game.getFeature(Otc::GameDoubleMagicLevel))
magicLevel = msg->getU16();
else
magicLevel = msg->getU8();
}
double baseMagicLevel;
if(g_game.getFeature(Otc::GameSkillsBase))
baseMagicLevel = msg->getU8();
else
baseMagicLevel = magicLevel;
double baseMagicLevel = 0;
if (!g_game.getFeature(Otc::GameTibia12Protocol)) {
if (g_game.getFeature(Otc::GameSkillsBase))
baseMagicLevel = msg->getU8();
else
baseMagicLevel = magicLevel;
}
double magicLevelPercent = 0;
if(!g_game.getFeature(Otc::GameTibia12Protocol))
magicLevelPercent = msg->getU8();
double magicLevelPercent = msg->getU8();
double soul;
if (g_game.getFeature(Otc::GameDoubleSoul))
soul = msg->getU16();
@ -1649,12 +1903,15 @@ void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg)
m_localPlayer->setHealth(health, maxHealth);
m_localPlayer->setFreeCapacity(freeCapacity);
m_localPlayer->setTotalCapacity(totalCapacity);
if (!g_game.getFeature(Otc::GameTibia12Protocol))
m_localPlayer->setTotalCapacity(totalCapacity);
m_localPlayer->setExperience(experience);
m_localPlayer->setLevel(level, levelPercent);
m_localPlayer->setMana(mana, maxMana);
m_localPlayer->setMagicLevel(magicLevel, magicLevelPercent);
m_localPlayer->setBaseMagicLevel(baseMagicLevel);
if (!g_game.getFeature(Otc::GameTibia12Protocol)) {
m_localPlayer->setMagicLevel(magicLevel, magicLevelPercent);
m_localPlayer->setBaseMagicLevel(baseMagicLevel);
}
m_localPlayer->setStamina(stamina);
m_localPlayer->setSoul(soul);
m_localPlayer->setBaseSpeed(baseSpeed);
@ -1668,6 +1925,15 @@ void ProtocolGame::parsePlayerSkills(const InputMessagePtr& msg)
if(g_game.getFeature(Otc::GameAdditionalSkills))
lastSkill = Otc::LastSkill;
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
int level = msg->getU16();
int baseLevel = msg->getU16();
msg->getU16(); // unknown
int levelPercent = msg->getU16();
m_localPlayer->setMagicLevel(level, levelPercent);
m_localPlayer->setBaseMagicLevel(baseLevel);
}
for(int skill = 0; skill < lastSkill; skill++) {
int level;
@ -1687,18 +1953,33 @@ void ProtocolGame::parsePlayerSkills(const InputMessagePtr& msg)
int levelPercent = 0;
// Critical, Life Leech and Mana Leech have no level percent
if(skill <= Otc::Fishing)
levelPercent = msg->getU8();
if (skill <= Otc::Fishing) {
if (g_game.getFeature(Otc::GameTibia12Protocol))
msg->getU16(); // unknown
if (g_game.getFeature(Otc::GameTibia12Protocol))
levelPercent = msg->getU16();
else
levelPercent = msg->getU8();
}
m_localPlayer->setSkill((Otc::Skill)skill, level, levelPercent);
m_localPlayer->setBaseSkill((Otc::Skill)skill, baseLevel);
}
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
uint32_t totalCapacity = msg->getU32();
msg->getU32(); // base capacity?
m_localPlayer->setTotalCapacity(totalCapacity);
}
}
void ProtocolGame::parsePlayerState(const InputMessagePtr& msg)
{
int states;
if(g_game.getFeature(Otc::GamePlayerStateU16))
if (g_game.getFeature(Otc::GamePlayerStateU32))
states = msg->getU32();
else if(g_game.getFeature(Otc::GamePlayerStateU16))
states = msg->getU16();
else
states = msg->getU8();
@ -2038,12 +2319,17 @@ void ProtocolGame::parseOpenOutfitWindow(const InputMessagePtr& msg)
std::vector<std::tuple<int, std::string, int> > outfitList;
if(g_game.getFeature(Otc::GameNewOutfitProtocol)) {
int outfitCount = msg->getU8();
int outfitCount = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU16() : msg->getU8();
for(int i = 0; i < outfitCount; i++) {
int outfitId = msg->getU16();
std::string outfitName = msg->getString();
int outfitAddons = msg->getU8();
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
bool locked = msg->getU8() > 0;
if (locked) {
msg->getU32(); // store offer id
}
}
outfitList.push_back(std::make_tuple(outfitId, outfitName, outfitAddons));
}
} else {
@ -2061,17 +2347,55 @@ void ProtocolGame::parseOpenOutfitWindow(const InputMessagePtr& msg)
}
std::vector<std::tuple<int, std::string> > mountList;
std::vector<std::tuple<int, std::string> > wingList;
std::vector<std::tuple<int, std::string> > auraList;
std::vector<std::tuple<int, std::string> > shaderList;
if(g_game.getFeature(Otc::GamePlayerMounts)) {
int mountCount = msg->getU8();
int mountCount = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU16() : msg->getU8();
for(int i = 0; i < mountCount; ++i) {
int mountId = msg->getU16(); // mount type
std::string mountName = msg->getString(); // mount name
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
bool locked = msg->getU8() > 0;
if (locked) {
msg->getU32(); // store offer id
}
}
mountList.push_back(std::make_tuple(mountId, mountName));
}
}
g_game.processOpenOutfitWindow(currentOutfit, outfitList, mountList);
if (g_game.getFeature(Otc::GameWingsAndAura)) {
int wingCount = msg->getU8();
for (int i = 0; i < wingCount; ++i) {
int wingId = msg->getU16();
std::string wingName = msg->getString();
wingList.push_back(std::make_tuple(wingId, wingName));
}
int auraCount = msg->getU8();
for (int i = 0; i < auraCount; ++i) {
int auraId = msg->getU16();
std::string auraName = msg->getString();
auraList.push_back(std::make_tuple(auraId, auraName));
}
}
if (g_game.getFeature(Otc::GameOutfitShaders)) {
int shaderCount = msg->getU8();
for (int i = 0; i < shaderCount; ++i) {
int shaderId = msg->getU16();
std::string shaderName = msg->getString();
shaderList.push_back(std::make_tuple(shaderId, shaderName));
}
}
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
msg->getU8(); // tryOnMount, tryOnOutfit
msg->getU8(); // mounted?
}
g_game.processOpenOutfitWindow(currentOutfit, outfitList, mountList, wingList, auraList, shaderList);
}
void ProtocolGame::parseVipAdd(const InputMessagePtr& msg)
@ -2118,6 +2442,10 @@ void ProtocolGame::parseTutorialHint(const InputMessagePtr& msg)
void ProtocolGame::parseAutomapFlag(const InputMessagePtr& msg)
{
if (g_game.getFeature(Otc::GameTibia12Protocol)) {
msg->getU8(); // unknown
}
Position pos = getPosition(msg);
int icon = msg->getU8();
std::string description = msg->getString();
@ -2152,6 +2480,9 @@ void ProtocolGame::parseQuestLine(const InputMessagePtr& msg)
int questId = msg->getU16();
int missionCount = msg->getU8();
for(int i = 0; i < missionCount; i++) {
if (g_game.getFeature(Otc::GameTibia12Protocol))
msg->getU16(); // mission id
std::string missionName = msg->getString();
std::string missionDescrition = msg->getString();
questMissions.push_back(std::make_tuple(missionName, missionDescrition));
@ -2250,6 +2581,37 @@ void ProtocolGame::parseMessageDialog(const InputMessagePtr& msg)
msg->getString();
}
void ProtocolGame::parseBlessDialog(const InputMessagePtr& msg)
{
// parse bless amount
uint8_t totalBless = msg->getU8(); // total bless
// parse each bless
for (int i = 0; i < totalBless; i++) {
msg->getU16(); // bless bit wise
msg->getU8(); // player bless count
}
// parse general info
msg->getU8(); // premium
msg->getU8(); // promotion
msg->getU8(); // pvp min xp loss
msg->getU8(); // pvp max xp loss
msg->getU8(); // pve exp loss
msg->getU8(); // equip pvp loss
msg->getU8(); // equip pve loss
msg->getU8(); // skull
msg->getU8(); // aol
// parse log
uint8_t logCount = msg->getU8(); // log count
for (int i = 0; i < logCount; i++) {
msg->getU32(); // timestamp
msg->getU8(); // color message (0 = white loss, 1 = red)
msg->getString(); // history message
}
}
void ProtocolGame::parseResourceBalance(const InputMessagePtr& msg)
{
uint8_t type = msg->getU8();
@ -2257,6 +2619,13 @@ void ProtocolGame::parseResourceBalance(const InputMessagePtr& msg)
g_lua.callGlobalField("g_game", "onResourceBalance", type, amount);
}
void ProtocolGame::parseServerTime(const InputMessagePtr& msg)
{
uint8_t minutes = msg->getU8();
uint8_t seconds = msg->getU8();
g_lua.callGlobalField("g_game", "onServerTime", minutes, seconds);
}
void ProtocolGame::parseQuestTracker(const InputMessagePtr& msg)
{
msg->getU8();
@ -2301,6 +2670,53 @@ void ProtocolGame::parseCloseImbuementWindow(const InputMessagePtr&)
g_lua.callGlobalField("g_game", "onCloseImbuementWindow");
}
void ProtocolGame::parseCyclopediaNewDetails(const InputMessagePtr& msg)
{
msg->getU16(); // race id
}
void ProtocolGame::parseDailyRewardState(const InputMessagePtr& msg)
{
msg->getU8(); // state
}
void ProtocolGame::parseOpenRewardWall(const InputMessagePtr& msg)
{
msg->getU8(); // bonus shrine (1) or instant bonus (0)
msg->getU32(); // next reward time
msg->getU8(); // day streak day
uint8_t wasDailyRewardTaken = msg->getU8(); // taken (player already took reward?)
if (wasDailyRewardTaken) {
msg->getString(); // error message
}
msg->getU32(); // time left to pickup reward without loosing streak
msg->getU16(); // day streak level
msg->getU16(); // unknown
}
void ProtocolGame::parseDailyReward(const InputMessagePtr& msg)
{
msg->getU8(); // state
// TODO: implement daily reward usage
}
void ProtocolGame::parseDailyRewardHistory(const InputMessagePtr& msg)
{
uint8_t historyCount = msg->getU8(); // history count
for (int i = 0; i < historyCount; i++) {
msg->getU32(); // timestamp
msg->getU8(); // is Premium
msg->getString(); // description
msg->getU16(); // daystreak
}
// TODO: implement reward history usage
}
Imbuement ProtocolGame::getImbuementInfo(const InputMessagePtr& msg)
{
Imbuement i;
@ -2334,9 +2750,9 @@ void ProtocolGame::parseKillTracker(const InputMessagePtr& msg)
msg->getU8();
msg->getU8();
msg->getU8();
bool emptyCorpse = msg->getU8() == 0;
if (!emptyCorpse) {
getItem(msg);
int corpseSize = msg->getU8(); // corpse size
for (int i = 0; i < corpseSize; i++) {
getItem(msg); // corpse item
}
}
@ -2351,10 +2767,19 @@ void ProtocolGame::parseImpactTracker(const InputMessagePtr& msg)
msg->getU32();
}
void ProtocolGame::parseItemsPrices(const InputMessagePtr& msg)
{
uint16_t count = msg->getU16();
for (uint16_t i = 0; i < count; ++i) {
/*uint16_t itemId = */msg->getU16();
/*uint32_t price = */msg->getU32();
}
}
void ProtocolGame::parseLootTracker(const InputMessagePtr& msg)
{
msg->getU16();
msg->getString();
getItem(msg);
msg->getString(); // item name
}
@ -2520,7 +2945,7 @@ int ProtocolGame::setTileDescription(const InputMessagePtr& msg, Position positi
g_map.setTileSpeed(position, groundSpeed, blocking);
}
if(g_game.getFeature(Otc::GameEnvironmentEffect)) {
if(g_game.getFeature(Otc::GameEnvironmentEffect) && !g_game.getFeature(Otc::GameTibia12Protocol)) {
msg->getU16();
}
@ -2594,6 +3019,9 @@ Outfit ProtocolGame::getOutfit(const InputMessagePtr& msg, bool ignoreMount)
outfit.setWings(msg->getU16());
outfit.setAura(msg->getU16());
}
if (g_game.getFeature(Otc::GameOutfitShaders)) {
outfit.setShader(msg->getString());
}
}
return outfit;
@ -2606,7 +3034,7 @@ ThingPtr ProtocolGame::getThing(const InputMessagePtr& msg)
int id = msg->getU16();
if(id == 0)
stdext::throw_exception("invalid thing id");
stdext::throw_exception("invalid thing id (0)");
else if(id == Proto::UnknownCreature || id == Proto::OutdatedCreature || id == Proto::Creature)
thing = getCreature(msg, id);
else if(id == Proto::StaticText) // otclient only
@ -2674,6 +3102,9 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type)
creatureType = Proto::CreatureTypeNpc;
}
if (creatureType == Proto::CreatureTypeSummonOwn)
msg->getU32(); // master
std::string name = g_game.formatCreatureName(msg->getString());
if(id == m_localPlayer->getId())
@ -2685,11 +3116,13 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type)
else
creature = PlayerPtr(new Player);
}
else if(creatureType == Proto::CreatureTypeMonster)
else if (creatureType == Proto::CreatureTypeMonster)
creature = MonsterPtr(new Monster);
else if(creatureType == Proto::CreatureTypeNpc)
else if (creatureType == Proto::CreatureTypeNpc)
creature = NpcPtr(new Npc);
else
else if (creatureType == Proto::CreatureTypeSummonOwn) {
creature = MonsterPtr(new Monster);
} else
g_logger.traceError("creature type is invalid");
if(creature) {
@ -2724,6 +3157,8 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type)
if(g_game.getFeature(Otc::GameThingMarks)) {
creatureType = msg->getU8();
if (creatureType == Proto::CreatureTypeSummonOwn)
msg->getU32(); // master
}
if(g_game.getFeature(Otc::GameCreatureIcons)) {
@ -2732,7 +3167,10 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type)
if(g_game.getFeature(Otc::GameThingMarks)) {
mark = msg->getU8(); // mark
msg->getU16(); // helpers
if(g_game.getFeature(Otc::GameTibia12Protocol))
msg->getU8(); // inspection?
else
msg->getU16(); // helpers?
if(creature) {
if(mark == 0xff)
@ -2804,12 +3242,18 @@ ItemPtr ProtocolGame::getItem(const InputMessagePtr& msg, int id, bool hasDescri
if(item->getId() == 0)
stdext::throw_exception(stdext::format("unable to create item with invalid id %d", id));
if(g_game.getFeature(Otc::GameThingMarks)) {
if(g_game.getFeature(Otc::GameThingMarks) && !g_game.getFeature(Otc::GameTibia12Protocol)) {
msg->getU8(); // mark
}
if(item->isStackable() || item->isFluidContainer() || item->isSplash() || item->isChargeable())
item->setCountOrSubType(msg->getU8());
else if (item->rawGetThingType()->isContainer() && g_game.getFeature(Otc::GameTibia12Protocol)) {
// not sure about this part
uint8_t hasQuickLootFlags = msg->getU8();
if (hasQuickLootFlags > 0)
msg->getU32(); // quick loot flags
}
if(g_game.getFeature(Otc::GameItemAnimationPhase)) {
if(item->getAnimationPhases() > 1) {

View File

@ -28,12 +28,12 @@
#include <framework/util/crypt.h>
#include <framework/util/extras.h>
void ProtocolGame::send(const OutputMessagePtr& outputMessage)
void ProtocolGame::send(const OutputMessagePtr& outputMessage, bool rawPacket)
{
// avoid usage of automated sends (bot modules)
if(!g_game.checkBotProtection())
return;
Protocol::send(outputMessage);
Protocol::send(outputMessage, rawPacket);
}
void ProtocolGame::sendExtendedOpcode(uint8 opcode, const std::string& buffer)
@ -51,6 +51,13 @@ void ProtocolGame::sendExtendedOpcode(uint8 opcode, const std::string& buffer)
g_game.disableBotCall();
}
void ProtocolGame::sendWorldName()
{
OutputMessagePtr msg(new OutputMessage);
msg->addRawString(m_worldName + "\n");
send(msg, true);
}
void ProtocolGame::sendLoginPacket(uint challengeTimestamp, uint8 challengeRandom)
{
OutputMessagePtr msg(new OutputMessage);
@ -161,6 +168,9 @@ void ProtocolGame::sendLoginPacket(uint challengeTimestamp, uint8 challengeRando
if (g_game.getFeature(Otc::GamePacketCompression))
enableCompression();
if (g_game.getFeature(Otc::GameSequencedPackets))
enabledSequencedPackets();
}
void ProtocolGame::sendEnterGame()
@ -804,6 +814,12 @@ void ProtocolGame::sendChangeOutfit(const Outfit& outfit)
msg->addU8(outfit.getAddons());
if(g_game.getFeature(Otc::GamePlayerMounts))
msg->addU16(outfit.getMount());
if (g_game.getFeature(Otc::GameWingsAndAura)) {
msg->addU16(outfit.getWings());
msg->addU16(outfit.getAura());
}
if(g_game.getFeature(Otc::GameOutfitShaders))
msg->addString(outfit.getShader());
send(msg);
}

View File

@ -24,6 +24,7 @@
#include <framework/graphics/paintershaderprogram.h>
#include <framework/graphics/graphics.h>
#include <framework/core/resourcemanager.h>
#include <framework/core/eventdispatcher.h>
ShaderManager g_shaders;
@ -37,51 +38,36 @@ void ShaderManager::terminate()
m_shaders.clear();
}
PainterShaderProgramPtr ShaderManager::createShader(const std::string& name)
void ShaderManager::createShader(const std::string& name, std::string vertex, std::string fragment, bool colorMatrix)
{
return nullptr;
if (vertex.find("\n") == std::string::npos) { // file
vertex = g_resources.guessFilePath(vertex, "frag");
vertex = g_resources.readFileContents(vertex);
}
if (fragment.find("\n") == std::string::npos) { // file
fragment = g_resources.guessFilePath(fragment, "frag");
fragment = g_resources.readFileContents(fragment);
}
g_graphicsDispatcher.addEventEx("createShader", [&, name, vertex, fragment, colorMatrix] {
auto program = PainterShaderProgram::create(vertex, fragment, colorMatrix);
if (program)
m_shaders[name] = program;
});
}
PainterShaderProgramPtr ShaderManager::createFragmentShader(const std::string& name, std::string file)
void ShaderManager::addTexture(const std::string& name, const std::string& file)
{
return nullptr;
}
PainterShaderProgramPtr ShaderManager::createFragmentShaderFromCode(const std::string& name, const std::string& code)
{
return nullptr;
}
PainterShaderProgramPtr ShaderManager::createItemShader(const std::string& name, const std::string& file)
{
PainterShaderProgramPtr shader = createFragmentShader(name, file);
if(shader)
setupItemShader(shader);
return shader;
}
PainterShaderProgramPtr ShaderManager::createMapShader(const std::string& name, const std::string& file)
{
PainterShaderProgramPtr shader = createFragmentShader(name, file);
if(shader)
setupMapShader(shader);
return shader;
}
void ShaderManager::setupItemShader(const PainterShaderProgramPtr& shader)
{
if (!shader)
return;
}
void ShaderManager::setupMapShader(const PainterShaderProgramPtr& shader)
{
if(!shader)
return;
g_graphicsDispatcher.addEventEx("addTexture", [&, name, file] {
auto program = getShader(name);
if (program)
program->addMultiTexture(file);
});
}
PainterShaderProgramPtr ShaderManager::getShader(const std::string& name)
{
VALIDATE_GRAPHICS_THREAD();
auto it = m_shaders.find(name);
if(it != m_shaders.end())
return it->second;

View File

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