diff --git a/LICENSE b/LICENSE index 758ad59..c618126 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ OTClientV8 is made available under the MIT License Copyright (c) 2010-2017 OTClient -Copyright (c) 2018-2019 OTClientV8 +Copyright (c) 2018-2020 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 diff --git a/README.md b/README.md index 11dae51..c9714d6 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/data/shaders/outfit_default_fragment.frag b/data/shaders/outfit_default_fragment.frag new file mode 100644 index 0000000..1f205d1 --- /dev/null +++ b/data/shaders/outfit_default_fragment.frag @@ -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; +} diff --git a/data/shaders/outfit_default_vertex.frag b/data/shaders/outfit_default_vertex.frag new file mode 100644 index 0000000..44f0d76 --- /dev/null +++ b/data/shaders/outfit_default_vertex.frag @@ -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; +} + diff --git a/modules/client_entergame/entergame.lua b/modules/client_entergame/entergame.lua index 57fbf77..4c3d98f 100644 --- a/modules/client_entergame/entergame.lua +++ b/modules/client_entergame/entergame.lua @@ -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 diff --git a/modules/client_entergame/entergame.otui b/modules/client_entergame/entergame.otui index 50bb7e2..04ec0d7 100644 --- a/modules/client_entergame/entergame.otui +++ b/modules/client_entergame/entergame.otui @@ -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 diff --git a/modules/client_topmenu/topmenu.lua b/modules/client_topmenu/topmenu.lua index 76f74d9..864c176 100644 --- a/modules/client_topmenu/topmenu.lua +++ b/modules/client_topmenu/topmenu.lua @@ -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) diff --git a/modules/corelib/http.lua b/modules/corelib/http.lua index d18eb45..2accc66 100644 --- a/modules/corelib/http.lua +++ b/modules/corelib/http.lua @@ -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, }) - \ No newline at end of file +g_http.setUserAgent(HTTP.agent) diff --git a/modules/game_bot/bot.lua b/modules/game_bot/bot.lua index 3d64c88..e94b09f 100644 --- a/modules/game_bot/bot.lua +++ b/modules/game_bot/bot.lua @@ -335,7 +335,7 @@ function check() return end - checkEvent = scheduleEvent(check, 25) + checkEvent = scheduleEvent(check, 10) local status, result = pcall(function() return botExecutor.script() diff --git a/modules/game_bot/functions/main.lua b/modules/game_bot/functions/main.lua index 0675396..1308eb0 100644 --- a/modules/game_bot/functions/main.lua +++ b/modules/game_bot/functions/main.lua @@ -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] diff --git a/modules/game_bot/functions/map.lua b/modules/game_bot/functions/map.lua index a8cfc32..9961234 100644 --- a/modules/game_bot/functions/map.lua +++ b/modules/game_bot/functions/map.lua @@ -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) diff --git a/modules/game_bot/functions/player.lua b/modules/game_bot/functions/player.lua index 0a68d7a..d43a455 100644 --- a/modules/game_bot/functions/player.lua +++ b/modules/game_bot/functions/player.lua @@ -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) + diff --git a/modules/game_bot/functions/player_inventory.lua b/modules/game_bot/functions/player_inventory.lua index 72dea91..c7997c1 100644 --- a/modules/game_bot/functions/player_inventory.lua +++ b/modules/game_bot/functions/player_inventory.lua @@ -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 diff --git a/modules/game_features/features.lua b/modules/game_features/features.lua index 78e2114..6e838ae 100644 --- a/modules/game_features/features.lua +++ b/modules/game_features/features.lua @@ -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 diff --git a/modules/game_interface/interface.otmod b/modules/game_interface/interface.otmod index 87769b4..b395028 100644 --- a/modules/game_interface/interface.otmod +++ b/modules/game_interface/interface.otmod @@ -39,6 +39,7 @@ Module - game_prey - game_imbuing - game_stats + - game_shaders - game_bot @onLoad: init() @onUnload: terminate() diff --git a/modules/game_outfit/outfit.lua b/modules/game_outfit/outfit.lua index 919d281..5c3e1a0 100644 --- a/modules/game_outfit/outfit.lua +++ b/modules/game_outfit/outfit.lua @@ -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 diff --git a/modules/game_outfit/outfitwindow.otui b/modules/game_outfit/outfitwindow.otui index 461ad42..09d8a18 100644 --- a/modules/game_outfit/outfitwindow.otui +++ b/modules/game_outfit/outfitwindow.otui @@ -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') diff --git a/modules/game_shaders/shaders.lua b/modules/game_shaders/shaders.lua new file mode 100644 index 0000000..eca5202 --- /dev/null +++ b/modules/game_shaders/shaders.lua @@ -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 diff --git a/modules/game_shaders/shaders.otmod b/modules/game_shaders/shaders.otmod new file mode 100644 index 0000000..0c6d727 --- /dev/null +++ b/modules/game_shaders/shaders.otmod @@ -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() diff --git a/modules/game_shop/shop.lua b/modules/game_shop/shop.lua index 18c50d7..8a9fb3d 100644 --- a/modules/game_shop/shop.lua +++ b/modules/game_shop/shop.lua @@ -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 diff --git a/modules/gamelib/const.lua b/modules/gamelib/const.lua index 098a6d1..033a3a8 100644 --- a/modules/gamelib/const.lua +++ b/modules/gamelib/const.lua @@ -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 diff --git a/modules/gamelib/creature.lua b/modules/gamelib/creature.lua index 1c4c9ce..1568b57 100644 --- a/modules/gamelib/creature.lua +++ b/modules/gamelib/creature.lua @@ -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 diff --git a/otclient_dx.exe b/otclient_dx.exe index dcb9ba7..9c417ce 100644 Binary files a/otclient_dx.exe and b/otclient_dx.exe differ diff --git a/otclient_gl.exe b/otclient_gl.exe index e838a27..f3e6a04 100644 Binary files a/otclient_gl.exe and b/otclient_gl.exe differ diff --git a/otclient_linux b/otclient_linux index 20a0c51..153a24f 100644 Binary files a/otclient_linux and b/otclient_linux differ diff --git a/otclientv8.apk b/otclientv8.apk index 71c96f1..1864cd8 100644 Binary files a/otclientv8.apk and b/otclientv8.apk differ diff --git a/pdb/pdb.7z b/pdb/pdb.7z index 5f8e349..67e1cad 100644 Binary files a/pdb/pdb.7z and b/pdb/pdb.7z differ diff --git a/src/client/luafunctions_client.cpp b/src/client/luafunctions_client.cpp index 7290a21..7069a10 100644 --- a/src/client/luafunctions_client.cpp +++ b/src/client/luafunctions_client.cpp @@ -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("setOutfit", &UICreature::setOutfit); g_lua.bindClassMemberFunction("setFixedCreatureSize", &UICreature::setFixedCreatureSize); g_lua.bindClassMemberFunction("getCreature", &UICreature::getCreature); + g_lua.bindClassMemberFunction("getOutfit", &UICreature::getOutfit); g_lua.bindClassMemberFunction("isFixedCreatureSize", &UICreature::isFixedCreatureSize); g_lua.bindClassMemberFunction("setAutoRotating", &UICreature::setAutoRotating); g_lua.bindClassMemberFunction("setDirection", &UICreature::setDirection); g_lua.bindClassMemberFunction("setScale", &UICreature::setScale); g_lua.bindClassMemberFunction("getScale", &UICreature::getScale); - g_lua.bindClassMemberFunction("setOptimized", &UICreature::setOptimized); g_lua.registerClass(); g_lua.bindClassStaticFunction("create", []{ return UIMapPtr(new UIMap); }); @@ -868,6 +864,7 @@ void Client::registerLuaFunctions() g_lua.bindClassMemberFunction("setLimitVisibleRange", &UIMap::setLimitVisibleRange); g_lua.bindClassMemberFunction("setFloorFading", &UIMap::setFloorFading); g_lua.bindClassMemberFunction("setCrosshair", &UIMap::setCrosshair); + g_lua.bindClassMemberFunction("setShader", &UIMap::setShader); g_lua.bindClassMemberFunction("isMultifloor", &UIMap::isMultifloor); g_lua.bindClassMemberFunction("isDrawingTexts", &UIMap::isDrawingTexts); g_lua.bindClassMemberFunction("isDrawingNames", &UIMap::isDrawingNames); @@ -889,6 +886,7 @@ void Client::registerLuaFunctions() g_lua.bindClassMemberFunction("getMaxZoomOut", &UIMap::getMaxZoomOut); g_lua.bindClassMemberFunction("getZoom", &UIMap::getZoom); g_lua.bindClassMemberFunction("getMinimumAmbientLight", &UIMap::getMinimumAmbientLight); + g_lua.bindClassMemberFunction("getShader", &UIMap::getShader); g_lua.registerClass(); g_lua.bindClassStaticFunction("create", []{ return UIMinimapPtr(new UIMinimap); }); diff --git a/src/client/luavaluecasts_client.cpp b/src/client/luavaluecasts_client.cpp index 4646027..3be4321 100644 --- a/src/client/luavaluecasts_client.cpp +++ b/src/client/luavaluecasts_client.cpp @@ -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; diff --git a/src/client/protocolcodes.h b/src/client/protocolcodes.h index 2c24259..79cdbad 100644 --- a/src/client/protocolcodes.h +++ b/src/client/protocolcodes.h @@ -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, diff --git a/src/client/protocolgame.cpp b/src/client/protocolgame.cpp index d3eb6c1..4a11af1 100644 --- a/src/client/protocolgame.cpp +++ b/src/client/protocolgame.cpp @@ -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(); diff --git a/src/client/protocolgame.h b/src/client/protocolgame.h index 36c72bb..3b0e3e3 100644 --- a/src/client/protocolgame.h +++ b/src/client/protocolgame.h @@ -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; diff --git a/src/client/protocolgameparse.cpp b/src/client/protocolgameparse.cpp index 07933d6..860f952 100644 --- a/src/client/protocolgameparse.cpp +++ b/src/client/protocolgameparse.cpp @@ -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("onOpcode", opcode, msg)) + if (callLuaField("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 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 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 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 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 > 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 > mountList; + std::vector > wingList; + std::vector > auraList; + std::vector > 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) { diff --git a/src/client/protocolgamesend.cpp b/src/client/protocolgamesend.cpp index 5218f46..2690613 100644 --- a/src/client/protocolgamesend.cpp +++ b/src/client/protocolgamesend.cpp @@ -28,12 +28,12 @@ #include #include -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); } diff --git a/src/client/shadermanager.cpp b/src/client/shadermanager.cpp index c382ea0..209e735 100644 --- a/src/client/shadermanager.cpp +++ b/src/client/shadermanager.cpp @@ -24,6 +24,7 @@ #include #include #include +#include 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; diff --git a/src/client/shadermanager.h b/src/client/shadermanager.h index 475a61e..cc0cb10 100644 --- a/src/client/shadermanager.h +++ b/src/client/shadermanager.h @@ -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 m_shaders; };