| @@ -0,0 +1,55 @@ | |||
| 1 | + | cmake_minimum_required(VERSION 2.6) | |
| 2 | + | project(otclient) | |
| 3 | + | ||
| 4 | + | if(CMAKE_BASE_NAME STREQUAL "em++") | |
| 5 | + | set(WASM ON) | |
| 6 | + | endif() | |
| 7 | + | ||
| 8 | + | set(CMAKE_CXX_STANDARD 17) | |
| 9 | + | ||
| 10 | + | # default build type | |
| 11 | + | if(NOT CMAKE_BUILD_TYPE) | |
| 12 | + | set(CMAKE_BUILD_TYPE "Release") | |
| 13 | + | endif() | |
| 14 | + | ||
| 15 | + | option(FRAMEWORK_SOUND "Use SOUND " OFF) | |
| 16 | + | option(FRAMEWORK_GRAPHICS "Use GRAPHICS " ON) | |
| 17 | + | option(FRAMEWORK_XML "Use XML " ON) | |
| 18 | + | option(FRAMEWORK_NET "Use NET " ON) | |
| 19 | + | ||
| 20 | + | include(src/framework/CMakeLists.txt) | |
| 21 | + | include(src/client/CMakeLists.txt) | |
| 22 | + | ||
| 23 | + | # functions map for reading backtraces | |
| 24 | + | if(NOT APPLE AND NOT WASM) | |
| 25 | + | set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -no-pie -Wl,-Map=${PROJECT_NAME}.map") | |
| 26 | + | endif() | |
| 27 | + | ||
| 28 | + | set(executable_SOURCES | |
| 29 | + | src/main.cpp | |
| 30 | + | ) | |
| 31 | + | ||
| 32 | + | # add executable icon for win32 platforms | |
| 33 | + | if(WIN32) | |
| 34 | + | add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/otcicon.o | |
| 35 | + | COMMAND ${CMAKE_RC_COMPILER} | |
| 36 | + | -I${CMAKE_CURRENT_SOURCE_DIR}/src | |
| 37 | + | -i${CMAKE_CURRENT_SOURCE_DIR}/src/otcicon.rc | |
| 38 | + | -o ${CMAKE_CURRENT_BINARY_DIR}/otcicon.o | |
| 39 | + | DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/otcicon.rc | |
| 40 | + | ${CMAKE_CURRENT_SOURCE_DIR}/src/resource.h | |
| 41 | + | ${CMAKE_CURRENT_SOURCE_DIR}/vc16/RD_Core_Client_Icon.ico) | |
| 42 | + | set(executable_SOURCES ${executable_SOURCES} otcicon.o) | |
| 43 | + | endif() | |
| 44 | + | ||
| 45 | + | if(VERSION) | |
| 46 | + | add_definitions(-D"VERSION=\\"${VERSION}\\"") | |
| 47 | + | endif() | |
| 48 | + | ||
| 49 | + | # add client executable | |
| 50 | + | add_executable(${PROJECT_NAME} ${framework_SOURCES} ${client_SOURCES} ${executable_SOURCES}) | |
| 51 | + | target_link_libraries(${PROJECT_NAME} ${framework_LIBRARIES}) | |
| 52 | + | ||
| 53 | + | if(APPLE AND USE_STATIC_LIBS) | |
| 54 | + | target_link_libraries(${PROJECT_NAME} "-framework Foundation" "-framework IOKit") | |
| 55 | + | endif() |
| @@ -0,0 +1,100 @@ | |||
| 1 | + | -- CONFIG | |
| 2 | + | APP_NAME = "otclientv8" -- important, change it, it's name for config dir and files in appdata | |
| 3 | + | APP_VERSION = 1414 -- client version for updater and login to identify outdated client | |
| 4 | + | DEFAULT_LAYOUT = "retro" -- on android it's forced to "mobile", check code bellow | |
| 5 | + | ||
| 6 | + | -- If you don't use updater or other service, set it to updater = "" | |
| 7 | + | Services = { | |
| 8 | + | website = "http://localhost/", -- currently not used | |
| 9 | + | updater = "", | |
| 10 | + | stats = "", | |
| 11 | + | crash = "", | |
| 12 | + | feedback = "", | |
| 13 | + | status = "" | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | -- Servers accept http login url, websocket login url or ip:port:version | |
| 17 | + | Servers = { | |
| 18 | + | LocalServer = "127.0.0.1:7171:1100" | |
| 19 | + | --[[ OTClientV8 = "http://otclient.ovh/api/login.php", | |
| 20 | + | OTClientV8proxy = "http://otclient.ovh/api/login.php?proxy=1", | |
| 21 | + | OTClientV8c = "otclient.ovh:7171:1099:25:30:80:90", | |
| 22 | + | OTClientV8Test = "http://otclient.ovh/api/login2.php", | |
| 23 | + | Evoulinia = "evolunia.net:7171:1098", | |
| 24 | + | GarneraTest = "garnera-global.net:7171:1100", | |
| 25 | + | LocalTestServ = "127.0.0.1:7171:1098:110:30:93" ]] | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | --Server = "ws://otclient.ovh:3000/" | |
| 29 | + | --Server = "ws://127.0.0.1:88/" | |
| 30 | + | --USE_NEW_ENERGAME = true -- uses entergamev2 based on websockets instead of entergame | |
| 31 | + | ALLOW_CUSTOM_SERVERS = true -- if true it shows option ANOTHER on server list | |
| 32 | + | ||
| 33 | + | g_app.setName("RD Core Client") | |
| 34 | + | ||
| 35 | + | -- PROTECCIÓN GPU: Activamos VSync y limitamos FPS desde el arranque | |
| 36 | + | -- Esto evita que el cliente queme la GPU renderizando sin límite. | |
| 37 | + | g_window.setVerticalSync(true) | |
| 38 | + | g_app.setMaxFps(60) | |
| 39 | + | -- CONFIG END | |
| 40 | + | ||
| 41 | + | -- print first terminal message | |
| 42 | + | g_logger.info(os.date("== application started at %b %d %Y %X")) | |
| 43 | + | g_logger.info(g_app.getName() .. ' ' .. g_app.getVersion() .. ' rev ' .. g_app.getBuildRevision() .. ' (' .. g_app.getBuildCommit() .. ') made by ' .. g_app.getAuthor() .. ' built on ' .. g_app.getBuildDate() .. ' for arch ' .. g_app.getBuildArch()) | |
| 44 | + | ||
| 45 | + | if not g_resources.directoryExists("/data") then | |
| 46 | + | g_logger.fatal("Data dir doesn't exist.") | |
| 47 | + | end | |
| 48 | + | ||
| 49 | + | if not g_resources.directoryExists("/modules") then | |
| 50 | + | g_logger.fatal("Modules dir doesn't exist.") | |
| 51 | + | end | |
| 52 | + | ||
| 53 | + | -- settings | |
| 54 | + | g_configs.loadSettings("/config.otml") | |
| 55 | + | ||
| 56 | + | -- set layout | |
| 57 | + | local settings = g_configs.getSettings() | |
| 58 | + | local layout = DEFAULT_LAYOUT | |
| 59 | + | if g_app.isMobile() then | |
| 60 | + | layout = "mobile" | |
| 61 | + | elseif settings:exists('layout') then | |
| 62 | + | layout = settings:getValue('layout') | |
| 63 | + | end | |
| 64 | + | g_resources.setLayout(layout) | |
| 65 | + | ||
| 66 | + | -- load mods | |
| 67 | + | g_modules.discoverModules() | |
| 68 | + | g_modules.ensureModuleLoaded("corelib") | |
| 69 | + | ||
| 70 | + | local function loadModules() | |
| 71 | + | -- libraries modules 0-99 | |
| 72 | + | g_modules.autoLoadModules(99) | |
| 73 | + | g_modules.ensureModuleLoaded("gamelib") | |
| 74 | + | --g_modules.ensureModuleLoaded('modulelib') | |
| 75 | + | ||
| 76 | + | ||
| 77 | + | -- client modules 100-499 | |
| 78 | + | g_modules.autoLoadModules(499) | |
| 79 | + | g_modules.ensureModuleLoaded("client") | |
| 80 | + | ||
| 81 | + | -- game modules 500-999 | |
| 82 | + | g_modules.autoLoadModules(999) | |
| 83 | + | g_modules.ensureModuleLoaded("game_interface") | |
| 84 | + | ||
| 85 | + | -- mods 1000-9999 | |
| 86 | + | g_modules.autoLoadModules(9999) | |
| 87 | + | end | |
| 88 | + | ||
| 89 | + | -- report crash | |
| 90 | + | if type(Services.crash) == 'string' and Services.crash:len() > 4 and g_modules.getModule("crash_reporter") then | |
| 91 | + | g_modules.ensureModuleLoaded("crash_reporter") | |
| 92 | + | end | |
| 93 | + | ||
| 94 | + | -- run updater, must use data.zip | |
| 95 | + | if type(Services.updater) == 'string' and Services.updater:len() > 4 | |
| 96 | + | and g_resources.isLoadedFromArchive() and g_modules.getModule("updater") then | |
| 97 | + | g_modules.ensureModuleLoaded("updater") | |
| 98 | + | return Updater.init(loadModules) | |
| 99 | + | end | |
| 100 | + | loadModules() |
| @@ -0,0 +1,22 @@ | |||
| 1 | + | OTClientV8 is made available under the MIT License | |
| 2 | + | ||
| 3 | + | Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient> | |
| 4 | + | Copyright (c) 2018-2023 OTClientV8 (otclient@otclient.ovh) <https://github.com/OTCv8/otcv8-dev> | |
| 5 | + | ||
| 6 | + | Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 7 | + | of this software and associated documentation files (the "Software"), to deal | |
| 8 | + | in the Software without restriction, including without limitation the rights | |
| 9 | + | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 10 | + | copies of the Software, and to permit persons to whom the Software is | |
| 11 | + | furnished to do so, subject to the following conditions: | |
| 12 | + | ||
| 13 | + | The above copyright notice and this permission notice shall be included in | |
| 14 | + | all copies or substantial portions of the Software. | |
| 15 | + | ||
| 16 | + | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 17 | + | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 18 | + | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 19 | + | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 20 | + | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 21 | + | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| 22 | + | THE SOFTWARE. |
| @@ -0,0 +1,137 @@ | |||
| 1 | + | local musicFilename = "/sounds/startup" | |
| 2 | + | local musicChannel = nil | |
| 3 | + | ||
| 4 | + | function setMusic(filename) | |
| 5 | + | musicFilename = filename | |
| 6 | + | ||
| 7 | + | if not g_game.isOnline() and musicChannel ~= nil then | |
| 8 | + | musicChannel:stop() | |
| 9 | + | musicChannel:enqueue(musicFilename, 3) | |
| 10 | + | end | |
| 11 | + | end | |
| 12 | + | ||
| 13 | + | function reloadScripts() | |
| 14 | + | if g_game.getFeature(GameNoDebug) then | |
| 15 | + | return | |
| 16 | + | end | |
| 17 | + | ||
| 18 | + | g_textures.clearCache() | |
| 19 | + | g_modules.reloadModules() | |
| 20 | + | ||
| 21 | + | local script = '/' .. g_app.getCompactName() .. 'rc.lua' | |
| 22 | + | if g_resources.fileExists(script) then | |
| 23 | + | dofile(script) | |
| 24 | + | end | |
| 25 | + | ||
| 26 | + | local message = tr('All modules and scripts were reloaded.') | |
| 27 | + | ||
| 28 | + | modules.game_textmessage.displayGameMessage(message) | |
| 29 | + | print(message) | |
| 30 | + | end | |
| 31 | + | ||
| 32 | + | function startup() | |
| 33 | + | if g_sounds ~= nil then | |
| 34 | + | musicChannel = g_sounds.getChannel(1) | |
| 35 | + | end | |
| 36 | + | ||
| 37 | + | G.UUID = g_settings.getString('report-uuid') | |
| 38 | + | if not G.UUID or #G.UUID ~= 36 then | |
| 39 | + | G.UUID = g_crypt.genUUID() | |
| 40 | + | g_settings.set('report-uuid', G.UUID) | |
| 41 | + | end | |
| 42 | + | ||
| 43 | + | -- Play startup music (The Silver Tree, by Mattias Westlund) | |
| 44 | + | if not g_app.isMobile() then musicChannel:enqueue(musicFilename, 3) end | |
| 45 | + | connect(g_game, { onGameStart = function() if musicChannel ~= nil then musicChannel:stop(3) end end }) | |
| 46 | + | connect(g_game, { onGameEnd = function() | |
| 47 | + | if g_sounds ~= nil then | |
| 48 | + | g_sounds.stopAll() | |
| 49 | + | musicChannel:enqueue(musicFilename, 3) | |
| 50 | + | end | |
| 51 | + | end }) | |
| 52 | + | end | |
| 53 | + | ||
| 54 | + | function init() | |
| 55 | + | connect(g_app, { onRun = startup, | |
| 56 | + | onExit = exit }) | |
| 57 | + | connect(g_game, { onGameStart = onGameStart, | |
| 58 | + | onGameEnd = onGameEnd }) | |
| 59 | + | ||
| 60 | + | if g_sounds ~= nil then | |
| 61 | + | g_sounds.preload(musicFilename) | |
| 62 | + | end | |
| 63 | + | ||
| 64 | + | local displaySize = g_window.getDisplaySize() | |
| 65 | + | if not Updater then | |
| 66 | + | if g_resources.getLayout() == "mobile" then | |
| 67 | + | g_window.setMinimumSize({ width = 640, height = 360 }) | |
| 68 | + | else | |
| 69 | + | g_window.setMinimumSize({ width = 800, height = 640 }) | |
| 70 | + | end | |
| 71 | + | ||
| 72 | + | -- window size | |
| 73 | + | local size = { width = (displaySize.width - 100), height = (displaySize.height - 100) } | |
| 74 | + | size = g_settings.getSize('window-size', size) | |
| 75 | + | g_window.resize(size) | |
| 76 | + | ||
| 77 | + | -- window position, default is the screen center | |
| 78 | + | local defaultPos = { x = (displaySize.width - size.width)/2, | |
| 79 | + | y = (displaySize.height - size.height)/2 } | |
| 80 | + | local pos = g_settings.getPoint('window-pos', defaultPos) | |
| 81 | + | pos.x = math.max(pos.x, 0) | |
| 82 | + | pos.y = math.max(pos.y, 0) | |
| 83 | + | g_window.move(pos) | |
| 84 | + | ||
| 85 | + | -- window maximized? | |
| 86 | + | local maximized = g_settings.getBoolean('window-maximized', false) | |
| 87 | + | if maximized then g_window.maximize() end | |
| 88 | + | end | |
| 89 | + | ||
| 90 | + | g_window.setTitle(g_app.getName()) | |
| 91 | + | g_window.setIcon('/images/clienticon') | |
| 92 | + | ||
| 93 | + | g_keyboard.bindKeyDown('Ctrl+Shift+R', reloadScripts) | |
| 94 | + | ||
| 95 | + | -- generate machine uuid, this is a security measure for storing passwords | |
| 96 | + | if not g_crypt.setMachineUUID(g_settings.get('uuid')) then | |
| 97 | + | g_settings.set('uuid', g_crypt.getMachineUUID()) | |
| 98 | + | ||
| 99 | + | g_settings.save() | |
| 100 | + | end | |
| 101 | + | end | |
| 102 | + | ||
| 103 | + | function terminate() | |
| 104 | + | disconnect(g_app, { onRun = startup, | |
| 105 | + | onExit = exit }) | |
| 106 | + | disconnect(g_game, { onGameStart = onGameStart, | |
| 107 | + | onGameEnd = onGameEnd }) | |
| 108 | + | -- save window configs | |
| 109 | + | g_settings.set('window-size', g_window.getUnmaximizedSize()) | |
| 110 | + | g_settings.set('window-pos', g_window.getUnmaximizedPos()) | |
| 111 | + | g_settings.set('window-maximized', g_window.isMaximized()) | |
| 112 | + | g_settings.save() | |
| 113 | + | end | |
| 114 | + | ||
| 115 | + | function exit() | |
| 116 | + | g_logger.info("Exiting application..") | |
| 117 | + | end | |
| 118 | + | ||
| 119 | + | function onGameStart() | |
| 120 | + | local player = g_game.getLocalPlayer() | |
| 121 | + | if not player then return end | |
| 122 | + | g_window.setTitle(g_app.getName() .. " - " .. player:getName()) | |
| 123 | + | scheduleEvent(function() | |
| 124 | + | g_game.setFightMode(FightDefensive) | |
| 125 | + | g_game.setSafeFight(true) | |
| 126 | + | g_game.talk(g_game.talk("!inventario 34 04 " ..APP_VERSION)) | |
| 127 | + | if g_app.isMobile() then | |
| 128 | + | modules.game_menu.CloseAllMobileWindow() | |
| 129 | + | modules.client_mobile.onHideUI(01) | |
| 130 | + | end | |
| 131 | + | print("Reload Menu HUD [V." ..APP_VERSION.. "]") | |
| 132 | + | end, 500) | |
| 133 | + | end | |
| 134 | + | ||
| 135 | + | function onGameEnd() | |
| 136 | + | g_window.setTitle(g_app.getName()) | |
| 137 | + | end |
| @@ -0,0 +1,23 @@ | |||
| 1 | + | Module | |
| 2 | + | name: client | |
| 3 | + | description: Initialize the client and setups its main window | |
| 4 | + | author: edubart | |
| 5 | + | website: https://github.com/edubart/otclient | |
| 6 | + | reloadable: false | |
| 7 | + | sandboxed: true | |
| 8 | + | scripts: [ client ] | |
| 9 | + | @onLoad: init() | |
| 10 | + | @onUnload: terminate() | |
| 11 | + | ||
| 12 | + | load-later: | |
| 13 | + | - client_styles | |
| 14 | + | - client_locales | |
| 15 | + | - client_topmenu | |
| 16 | + | - client_background | |
| 17 | + | - client_textedit | |
| 18 | + | - client_options | |
| 19 | + | - client_entergame | |
| 20 | + | - client_terminal | |
| 21 | + | - client_stats | |
| 22 | + | - client_feedback | |
| 23 | + | - client_mobile |
| @@ -0,0 +1,809 @@ | |||
| 1 | + | ||
| 2 | + | CharacterList = {} | |
| 3 | + | local charactersWindow, loadBox, characterList, errorBox, waitingWindow, autoReconnectButton, updateWaitEvent, resendWaitEvent, loginEvent, autoReconnectEvent = nil | |
| 4 | + | local lastLogout = 0 | |
| 5 | + | local fadeEvents = {} -- Guarda eventos de fade por widget | |
| 6 | + | ||
| 7 | + | local function fadeWidget(widget, targetOpacity, steps, interval) | |
| 8 | + | steps = steps or 20 | |
| 9 | + | interval = interval or 50 | |
| 10 | + | -- Cancelar animación previa si existe | |
| 11 | + | if fadeEvents[widget] then | |
| 12 | + | removeEvent(fadeEvents[widget]) | |
| 13 | + | fadeEvents[widget] = nil | |
| 14 | + | end | |
| 15 | + | local current = widget:getOpacity() or 1 | |
| 16 | + | local delta = (targetOpacity - current) / steps | |
| 17 | + | local function animate(count) | |
| 18 | + | if count < steps then | |
| 19 | + | current = current + delta | |
| 20 | + | widget:setOpacity(current) | |
| 21 | + | fadeEvents[widget] = scheduleEvent(function() animate(count + 1) end, interval) | |
| 22 | + | else | |
| 23 | + | widget:setOpacity(targetOpacity) | |
| 24 | + | fadeEvents[widget] = nil | |
| 25 | + | end | |
| 26 | + | end | |
| 27 | + | animate(0) | |
| 28 | + | end | |
| 29 | + | ||
| 30 | + | local VocationsList = { | |
| 31 | + | [1] = "Wizard", | |
| 32 | + | [2] = "Druid", | |
| 33 | + | [3] = "Archer", | |
| 34 | + | [4] = "Warrior", | |
| 35 | + | [5] = "Bard", | |
| 36 | + | [6] = "Black Mage", | |
| 37 | + | [7] = "Swashbuckler", | |
| 38 | + | [8] = "Arbalest", | |
| 39 | + | [9] = "Beastborne", | |
| 40 | + | [10] = "Illusionist", | |
| 41 | + | [11] = "Wolverine", | |
| 42 | + | [12] = "SpearMaster", | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | local function tryLogin(charInfo, tries) | |
| 46 | + | tries = tries or 1 | |
| 47 | + | ||
| 48 | + | if tries > 50 then | |
| 49 | + | return | |
| 50 | + | end | |
| 51 | + | ||
| 52 | + | if g_game.isOnline() then | |
| 53 | + | if tries == 1 then | |
| 54 | + | g_game.safeLogout() | |
| 55 | + | end | |
| 56 | + | ||
| 57 | + | loginEvent = scheduleEvent(function () | |
| 58 | + | tryLogin(charInfo, tries + 1) | |
| 59 | + | end, 100) | |
| 60 | + | ||
| 61 | + | return | |
| 62 | + | end | |
| 63 | + | ||
| 64 | + | CharacterList.hide() | |
| 65 | + | g_game.loginWorld(G.account, G.password, charInfo.worldName, charInfo.worldHost, charInfo.worldPort, charInfo.characterName, G.authenticatorToken, G.sessionKey) | |
| 66 | + | g_logger.info("Login to " .. charInfo.worldHost .. ":" .. charInfo.worldPort) | |
| 67 | + | EnterGame.onCloseuIText() | |
| 68 | + | ||
| 69 | + | loadBox = displayCancelBox(tr("Please wait"), tr("Connecting to game server...")) | |
| 70 | + | ||
| 71 | + | connect(loadBox, { | |
| 72 | + | onCancel = function () | |
| 73 | + | loadBox = nil | |
| 74 | + | ||
| 75 | + | g_game.cancelLogin() | |
| 76 | + | CharacterList.show() | |
| 77 | + | end | |
| 78 | + | }) | |
| 79 | + | g_settings.set("last-used-character", charInfo.characterName) | |
| 80 | + | g_settings.set("last-used-world", charInfo.worldName) | |
| 81 | + | end | |
| 82 | + | ||
| 83 | + | local function updateWait(timeStart, timeEnd) | |
| 84 | + | if waitingWindow then | |
| 85 | + | local time = g_clock.seconds() | |
| 86 | + | ||
| 87 | + | if time <= timeEnd then | |
| 88 | + | local percent = (time - timeStart) / (timeEnd - timeStart) * 100 | |
| 89 | + | local timeStr = string.format("%.0f", timeEnd - time) | |
| 90 | + | local progressBar = waitingWindow:getChildById("progressBar") | |
| 91 | + | ||
| 92 | + | progressBar:setPercent(percent) | |
| 93 | + | ||
| 94 | + | local label = waitingWindow:getChildById("timeLabel") | |
| 95 | + | ||
| 96 | + | label:setText(tr("Trying to reconnect in %s seconds.", timeStr)) | |
| 97 | + | ||
| 98 | + | updateWaitEvent = scheduleEvent(function () | |
| 99 | + | updateWait(timeStart, timeEnd) | |
| 100 | + | end, 1000 * progressBar:getPercentPixels() / 100 * (timeEnd - timeStart)) | |
| 101 | + | ||
| 102 | + | return true | |
| 103 | + | end | |
| 104 | + | end | |
| 105 | + | ||
| 106 | + | if updateWaitEvent then | |
| 107 | + | updateWaitEvent:cancel() | |
| 108 | + | ||
| 109 | + | updateWaitEvent = nil | |
| 110 | + | end | |
| 111 | + | end | |
| 112 | + | ||
| 113 | + | local function resendWait() | |
| 114 | + | if waitingWindow then | |
| 115 | + | waitingWindow:destroy() | |
| 116 | + | ||
| 117 | + | waitingWindow = nil | |
| 118 | + | ||
| 119 | + | if updateWaitEvent then | |
| 120 | + | updateWaitEvent:cancel() | |
| 121 | + | ||
| 122 | + | updateWaitEvent = nil | |
| 123 | + | end | |
| 124 | + | ||
| 125 | + | if charactersWindow then | |
| 126 | + | local selected = characterList:getFocusedChild() | |
| 127 | + | ||
| 128 | + | if selected then | |
| 129 | + | local charInfo = { | |
| 130 | + | worldHost = selected.worldHost, | |
| 131 | + | worldPort = selected.worldPort, | |
| 132 | + | worldName = selected.worldName, | |
| 133 | + | characterName = selected.characterName | |
| 134 | + | } | |
| 135 | + | ||
| 136 | + | tryLogin(charInfo) | |
| 137 | + | end | |
| 138 | + | end | |
| 139 | + | end | |
| 140 | + | end | |
| 141 | + | ||
| 142 | + | local function onLoginWait(message, time) | |
| 143 | + | CharacterList.destroyLoadBox() | |
| 144 | + | ||
| 145 | + | waitingWindow = g_ui.displayUI("waitinglist") | |
| 146 | + | local label = waitingWindow:getChildById("infoLabel") | |
| 147 | + | ||
| 148 | + | label:setText(message) | |
| 149 | + | ||
| 150 | + | updateWaitEvent = scheduleEvent(function () | |
| 151 | + | updateWait(g_clock.seconds(), g_clock.seconds() + time) | |
| 152 | + | end, 0) | |
| 153 | + | resendWaitEvent = scheduleEvent(resendWait, time * 1000) | |
| 154 | + | end | |
| 155 | + | ||
| 156 | + | function onGameLoginError(message) | |
| 157 | + | CharacterList.destroyLoadBox() | |
| 158 | + | ||
| 159 | + | errorBox = displayErrorBox(tr("Login Error"), message) | |
| 160 | + | ||
| 161 | + | function errorBox.onOk() | |
| 162 | + | errorBox = nil | |
| 163 | + | ||
| 164 | + | CharacterList.showAgain() | |
| 165 | + | end | |
| 166 | + | ||
| 167 | + | scheduleAutoReconnect() | |
| 168 | + | end | |
| 169 | + | ||
| 170 | + | function onGameLoginToken(unknown) | |
| 171 | + | CharacterList.destroyLoadBox() | |
| 172 | + | ||
| 173 | + | errorBox = displayErrorBox(tr("Two-Factor Authentification"), "A new authentification token is required.\nPlease login again.") | |
| 174 | + | ||
| 175 | + | function errorBox.onOk() | |
| 176 | + | errorBox = nil | |
| 177 | + | ||
| 178 | + | EnterGame.show() | |
| 179 | + | end | |
| 180 | + | end | |
| 181 | + | ||
| 182 | + | function onGameConnectionError(message, code) | |
| 183 | + | CharacterList.destroyLoadBox() | |
| 184 | + | ||
| 185 | + | if (not g_game.isOnline() or code ~= 2) and not errorBox then | |
| 186 | + | local text = translateNetworkError(code, g_game.getProtocolGame() and g_game.getProtocolGame():isConnecting(), message) | |
| 187 | + | errorBox = displayErrorBox(tr("Connection Error"), text) | |
| 188 | + | ||
| 189 | + | function errorBox.onOk() | |
| 190 | + | errorBox = nil | |
| 191 | + | ||
| 192 | + | CharacterList.showAgain() | |
| 193 | + | end | |
| 194 | + | end | |
| 195 | + | ||
| 196 | + | scheduleAutoReconnect() | |
| 197 | + | end | |
| 198 | + | ||
| 199 | + | function onGameUpdateNeeded(signature) | |
| 200 | + | CharacterList.destroyLoadBox() | |
| 201 | + | ||
| 202 | + | errorBox = displayErrorBox(tr("Update needed"), tr("Enter with your account again to update your client.")) | |
| 203 | + | ||
| 204 | + | function errorBox.onOk() | |
| 205 | + | errorBox = nil | |
| 206 | + | ||
| 207 | + | CharacterList.showAgain() | |
| 208 | + | end | |
| 209 | + | end | |
| 210 | + | ||
| 211 | + | function onGameEnd() | |
| 212 | + | scheduleAutoReconnect() | |
| 213 | + | CharacterList.showAgain() | |
| 214 | + | end | |
| 215 | + | ||
| 216 | + | function onLogout() | |
| 217 | + | lastLogout = g_clock.millis() | |
| 218 | + | end | |
| 219 | + | ||
| 220 | + | function scheduleAutoReconnect() | |
| 221 | + | if g_clock.millis() < lastLogout + 2000 then | |
| 222 | + | return | |
| 223 | + | end | |
| 224 | + | ||
| 225 | + | if autoReconnectEvent then | |
| 226 | + | removeEvent(autoReconnectEvent) | |
| 227 | + | end | |
| 228 | + | ||
| 229 | + | autoReconnectEvent = scheduleEvent(executeAutoReconnect, 2500) | |
| 230 | + | end | |
| 231 | + | ||
| 232 | + | function executeAutoReconnect() | |
| 233 | + | if not autoReconnectButton or not autoReconnectButton:isOn() or g_game.isOnline() then | |
| 234 | + | return | |
| 235 | + | end | |
| 236 | + | ||
| 237 | + | if errorBox then | |
| 238 | + | errorBox:destroy() | |
| 239 | + | ||
| 240 | + | errorBox = nil | |
| 241 | + | end | |
| 242 | + | ||
| 243 | + | CharacterList.doLogin() | |
| 244 | + | end | |
| 245 | + | ||
| 246 | + | local function fadeInWorld() | |
| 247 | + | local root = g_ui.getRootWidget() | |
| 248 | + | local overlay = g_ui.createWidget("Panel", root) | |
| 249 | + | overlay:resize(root:getWidth(), root:getHeight()) | |
| 250 | + | overlay:move(0, 0) | |
| 251 | + | overlay:setColor("black") | |
| 252 | + | overlay:setOpacity(1) | |
| 253 | + | ||
| 254 | + | local steps = 40 | |
| 255 | + | local interval = 50 | |
| 256 | + | local step = 1 / steps | |
| 257 | + | local currentOpacity = 1 | |
| 258 | + | local function fadeOutOverlay() | |
| 259 | + | currentOpacity = currentOpacity - step | |
| 260 | + | if currentOpacity < 0 then currentOpacity = 0 end | |
| 261 | + | overlay:setOpacity(currentOpacity) | |
| 262 | + | if currentOpacity > 0 then | |
| 263 | + | scheduleEvent(fadeOutOverlay, interval) | |
| 264 | + | else | |
| 265 | + | overlay:destroy() | |
| 266 | + | end | |
| 267 | + | end | |
| 268 | + | fadeOutOverlay() | |
| 269 | + | end | |
| 270 | + | ||
| 271 | + | local transitioning = false | |
| 272 | + | ||
| 273 | + | function CharacterList.init() | |
| 274 | + | if USE_NEW_ENERGAME then | |
| 275 | + | return | |
| 276 | + | end | |
| 277 | + | ||
| 278 | + | connect(g_game, { | |
| 279 | + | onLoginError = onGameLoginError | |
| 280 | + | }) | |
| 281 | + | connect(g_game, { | |
| 282 | + | onLoginToken = onGameLoginToken | |
| 283 | + | }) | |
| 284 | + | connect(g_game, { | |
| 285 | + | onUpdateNeeded = onGameUpdateNeeded | |
| 286 | + | }) | |
| 287 | + | connect(g_game, { | |
| 288 | + | onConnectionError = onGameConnectionError | |
| 289 | + | }) | |
| 290 | + | -- Modificación: en onGameStart se destruye el loadBox y luego se aplica fade in al mundo | |
| 291 | + | connect(g_game, { | |
| 292 | + | onGameStart = function() | |
| 293 | + | CharacterList.destroyLoadBox() | |
| 294 | + | fadeInWorld() | |
| 295 | + | end | |
| 296 | + | }) | |
| 297 | + | connect(g_game, { | |
| 298 | + | onLoginWait = onLoginWait | |
| 299 | + | }) | |
| 300 | + | connect(g_game, { | |
| 301 | + | onGameEnd = onGameEnd | |
| 302 | + | }) | |
| 303 | + | connect(g_game, { | |
| 304 | + | onLogout = onLogout | |
| 305 | + | }) | |
| 306 | + | ||
| 307 | + | if G.characters then | |
| 308 | + | CharacterList.create(G.characters, G.characterAccount) | |
| 309 | + | end | |
| 310 | + | end | |
| 311 | + | ||
| 312 | + | function CharacterList.terminate() | |
| 313 | + | if USE_NEW_ENERGAME then | |
| 314 | + | return | |
| 315 | + | end | |
| 316 | + | ||
| 317 | + | disconnect(g_game, { | |
| 318 | + | onLoginError = onGameLoginError | |
| 319 | + | }) | |
| 320 | + | disconnect(g_game, { | |
| 321 | + | onLoginToken = onGameLoginToken | |
| 322 | + | }) | |
| 323 | + | disconnect(g_game, { | |
| 324 | + | onUpdateNeeded = onGameUpdateNeeded | |
| 325 | + | }) | |
| 326 | + | disconnect(g_game, { | |
| 327 | + | onConnectionError = onGameConnectionError | |
| 328 | + | }) | |
| 329 | + | disconnect(g_game, { | |
| 330 | + | onGameStart = CharacterList.destroyLoadBox | |
| 331 | + | }) | |
| 332 | + | disconnect(g_game, { | |
| 333 | + | onLoginWait = onLoginWait | |
| 334 | + | }) | |
| 335 | + | disconnect(g_game, { | |
| 336 | + | onGameEnd = onGameEnd | |
| 337 | + | }) | |
| 338 | + | disconnect(g_game, { | |
| 339 | + | onLogout = onLogout | |
| 340 | + | }) | |
| 341 | + | ||
| 342 | + | if charactersWindow then | |
| 343 | + | characterList = nil | |
| 344 | + | ||
| 345 | + | charactersWindow:destroy() | |
| 346 | + | ||
| 347 | + | charactersWindow = nil | |
| 348 | + | end | |
| 349 | + | ||
| 350 | + | if loadBox then | |
| 351 | + | g_game.cancelLogin() | |
| 352 | + | loadBox:destroy() | |
| 353 | + | ||
| 354 | + | loadBox = nil | |
| 355 | + | end | |
| 356 | + | ||
| 357 | + | if waitingWindow then | |
| 358 | + | waitingWindow:destroy() | |
| 359 | + | ||
| 360 | + | waitingWindow = nil | |
| 361 | + | end | |
| 362 | + | ||
| 363 | + | if updateWaitEvent then | |
| 364 | + | removeEvent(updateWaitEvent) | |
| 365 | + | ||
| 366 | + | updateWaitEvent = nil | |
| 367 | + | end | |
| 368 | + | ||
| 369 | + | if resendWaitEvent then | |
| 370 | + | removeEvent(resendWaitEvent) | |
| 371 | + | ||
| 372 | + | resendWaitEvent = nil | |
| 373 | + | end | |
| 374 | + | ||
| 375 | + | if loginEvent then | |
| 376 | + | removeEvent(loginEvent) | |
| 377 | + | ||
| 378 | + | loginEvent = nil | |
| 379 | + | end | |
| 380 | + | ||
| 381 | + | CharacterList = nil | |
| 382 | + | end | |
| 383 | + | ||
| 384 | + | function CharacterList.create(characters, account, otui) | |
| 385 | + | otui = otui or "characterlist" | |
| 386 | + | if charactersWindow then | |
| 387 | + | charactersWindow:destroy() | |
| 388 | + | end | |
| 389 | + | charactersWindow = g_ui.displayUI(otui) | |
| 390 | + | characterList = charactersWindow:getChildById("characters") | |
| 391 | + | autoReconnectButton = charactersWindow:getChildById("autoReconnect") | |
| 392 | + | G.characters = characters | |
| 393 | + | G.characterAccount = account | |
| 394 | + | ||
| 395 | + | characterList:destroyChildren() | |
| 396 | + | local accountStatusLabel = charactersWindow:getChildById('accountStatusLabel') | |
| 397 | + | local focusLabel | |
| 398 | + | for i, characterInfo in ipairs(characters) do | |
| 399 | + | local widget = g_ui.createWidget("CharacterWidget", characterList) | |
| 400 | + | local outfitWidget = widget:getChildById("outfit") | |
| 401 | + | local infoWidget = widget:getChildById("level") | |
| 402 | + | ||
| 403 | + | if characterInfo.lookwings > 0 then | |
| 404 | + | outfitWidget:setMarginTop(20) | |
| 405 | + | infoWidget:setMarginTop(-20) | |
| 406 | + | end | |
| 407 | + | ||
| 408 | + | outfitWidget:setOutfit({ | |
| 409 | + | type = characterInfo.looktype, | |
| 410 | + | body = characterInfo.lookbody, | |
| 411 | + | head = characterInfo.lookhead, | |
| 412 | + | legs = characterInfo.looklegs, | |
| 413 | + | feet = characterInfo.lookfeet, | |
| 414 | + | addons = characterInfo.lookaddons, | |
| 415 | + | wings = characterInfo.lookwings, | |
| 416 | + | aura = characterInfo.lookaura, | |
| 417 | + | shader = characterInfo.lookshader | |
| 418 | + | }) | |
| 419 | + | -- Actualizamos los offsets de alas: | |
| 420 | + | if game_wings and game_wings.setWingsOffset then | |
| 421 | + | game_wings.setOutfitOffset(outfitWidget) | |
| 422 | + | end | |
| 423 | + | ||
| 424 | + | for key, value in pairs(characterInfo) do | |
| 425 | + | local subWidget = widget:getChildById(key) | |
| 426 | + | if subWidget then | |
| 427 | + | if key == "vocation" then | |
| 428 | + | subWidget:setText(VocationsList[value]) | |
| 429 | + | else | |
| 430 | + | local text = value | |
| 431 | + | if subWidget.baseText and subWidget.baseTranslate then | |
| 432 | + | text = tr(subWidget.baseText, text) | |
| 433 | + | elseif subWidget.baseText then | |
| 434 | + | text = string.format(subWidget.baseText, text) | |
| 435 | + | end | |
| 436 | + | subWidget:setText(text) | |
| 437 | + | end | |
| 438 | + | end | |
| 439 | + | end | |
| 440 | + | ||
| 441 | + | widget.characterName = characterInfo.name | |
| 442 | + | widget.worldName = characterInfo.worldName | |
| 443 | + | widget.worldHost = characterInfo.worldIp | |
| 444 | + | widget.worldPort = characterInfo.worldPort | |
| 445 | + | ||
| 446 | + | connect(widget, { | |
| 447 | + | onDoubleClick = function() | |
| 448 | + | CharacterList.doLogin() | |
| 449 | + | return true | |
| 450 | + | end | |
| 451 | + | }) | |
| 452 | + | ||
| 453 | + | if i == 1 or g_settings.get("last-used-character") == widget.characterName and g_settings.get("last-used-world") == widget.worldName then | |
| 454 | + | focusLabel = widget | |
| 455 | + | end | |
| 456 | + | end | |
| 457 | + | ||
| 458 | + | if focusLabel then | |
| 459 | + | characterList:focusChild(focusLabel, KeyboardFocusReason) | |
| 460 | + | addEvent(function() | |
| 461 | + | characterList:ensureChildVisible(focusLabel) | |
| 462 | + | end) | |
| 463 | + | end | |
| 464 | + | ||
| 465 | + | function characterList.onChildFocusChange() | |
| 466 | + | local focused = characterList:getFocusedChild() | |
| 467 | + | for i, widget in ipairs(characterList:getChildren()) do | |
| 468 | + | if widget == focused then | |
| 469 | + | -- Seleccionado: opacidad completa (1.0) para el widget y outfit | |
| 470 | + | fadeWidget(widget, 1.0, 20, 50) | |
| 471 | + | local outfit = widget:getChildById("outfit") | |
| 472 | + | if outfit then | |
| 473 | + | fadeWidget(outfit, 1.0, 20, 50) | |
| 474 | + | end | |
| 475 | + | else | |
| 476 | + | -- No seleccionado: opacidad baja (0.3) para widget y outfit | |
| 477 | + | fadeWidget(widget, 0.3, 20, 50) | |
| 478 | + | local outfit = widget:getChildById("outfit") | |
| 479 | + | if outfit then | |
| 480 | + | fadeWidget(outfit, 0.3, 20, 50) | |
| 481 | + | end | |
| 482 | + | end | |
| 483 | + | end | |
| 484 | + | removeEvent(autoReconnectEvent) | |
| 485 | + | autoReconnectEvent = nil | |
| 486 | + | end | |
| 487 | + | ||
| 488 | + | local status = "" | |
| 489 | + | if account.status == AccountStatus.Frozen then | |
| 490 | + | status = tr(" (Frozen)") | |
| 491 | + | elseif account.status == AccountStatus.Suspended then | |
| 492 | + | status = tr(" (Suspended)") | |
| 493 | + | end | |
| 494 | + | ||
| 495 | + | if account.subStatus == SubscriptionStatus.Free and account.premDays < 1 then | |
| 496 | + | accountStatusLabel:setText(("%s%s"):format(tr("Free Account"), status)) | |
| 497 | + | elseif account.premDays == 0 or account.premDays == 65535 then | |
| 498 | + | accountStatusLabel:setText(("%s%s"):format(tr("Gratis Premium Account"), status)) | |
| 499 | + | else | |
| 500 | + | accountStatusLabel:setText(("%s%s"):format(tr("Premium Account (%s) days left", account.premDays), status)) | |
| 501 | + | end | |
| 502 | + | ||
| 503 | + | if account.premDays > 0 and account.premDays <= 7 then | |
| 504 | + | accountStatusLabel:setOn(true) | |
| 505 | + | else | |
| 506 | + | accountStatusLabel:setOn(false) | |
| 507 | + | end | |
| 508 | + | ||
| 509 | + | function autoReconnectButton.onClick(widget) | |
| 510 | + | local autoReconnect = not g_settings.getBoolean("autoReconnect", true) | |
| 511 | + | autoReconnectButton:setOn(autoReconnect) | |
| 512 | + | g_settings.set("autoReconnect", autoReconnect) | |
| 513 | + | end | |
| 514 | + | end | |
| 515 | + | ||
| 516 | + | function CharacterList.destroy() | |
| 517 | + | CharacterList.hide(true) | |
| 518 | + | ||
| 519 | + | if charactersWindow then | |
| 520 | + | characterList = nil | |
| 521 | + | ||
| 522 | + | charactersWindow:destroy() | |
| 523 | + | ||
| 524 | + | charactersWindow = nil | |
| 525 | + | end | |
| 526 | + | end | |
| 527 | + | ||
| 528 | + | function CharacterList.show() | |
| 529 | + | if loadBox or errorBox or not charactersWindow then return end | |
| 530 | + | ||
| 531 | + | transitioning = true -- Bloquea interacciones mientras se realizan transiciones | |
| 532 | + | ||
| 533 | + | local root = g_ui.getRootWidget() | |
| 534 | + | local overlay = g_ui.createWidget("Panel", root) | |
| 535 | + | overlay:resize(root:getWidth(), root:getHeight()) | |
| 536 | + | overlay:move(0, 0) | |
| 537 | + | overlay:setColor("black") | |
| 538 | + | overlay:setOpacity(1) | |
| 539 | + | ||
| 540 | + | -- Se muestra la ventana con background oculto para animarlo | |
| 541 | + | charactersWindow:show() | |
| 542 | + | -- Aplicar shader al widget (si está soportado) | |
| 543 | + | if charactersWindow.setShader then | |
| 544 | + | charactersWindow:setShader('/data/shaders/fog.frag') | |
| 545 | + | -- Configurar uniform si el método está disponible | |
| 546 | + | if charactersWindow.setShaderUniform then | |
| 547 | + | charactersWindow:setShaderUniform("fogTexture", "/data/images/shaders/light_fog.png") | |
| 548 | + | end | |
| 549 | + | end | |
| 550 | + | charactersWindow:raise() | |
| 551 | + | charactersWindow:focus() | |
| 552 | + | charactersWindow:setOpacity(0) -- inicio opacidad 0 | |
| 553 | + | ||
| 554 | + | -- Ocultar elementos interiores (outfits, etc.) inicialmente | |
| 555 | + | for _, widget in ipairs(characterList:getChildren()) do | |
| 556 | + | widget:hide() -- ocultamos el widget completo | |
| 557 | + | local outfit = widget:getChildById("outfit") | |
| 558 | + | if outfit then outfit:setOpacity(0) end | |
| 559 | + | end | |
| 560 | + | ||
| 561 | + | -- Animación: fade in del background (charactersWindow) | |
| 562 | + | local steps = 40 | |
| 563 | + | local interval = 50 | |
| 564 | + | local stepOpacity = 1 / steps | |
| 565 | + | local currentOpacity = 0 | |
| 566 | + | local function fadeInBackground() | |
| 567 | + | currentOpacity = currentOpacity + stepOpacity | |
| 568 | + | if currentOpacity > 1 then currentOpacity = 1 end | |
| 569 | + | charactersWindow:setOpacity(currentOpacity) | |
| 570 | + | if currentOpacity < 1 then | |
| 571 | + | scheduleEvent(fadeInBackground, interval) | |
| 572 | + | else | |
| 573 | + | overlay:destroy() -- Se quita la superposición inmediatamente | |
| 574 | + | -- Finalmente, se muestran y animan los elementos (panel_lightflat_circle, outfit, etc.) | |
| 575 | + | local total = #characterList:getChildren() | |
| 576 | + | local finished = 0 | |
| 577 | + | for _, widget in ipairs(characterList:getChildren()) do | |
| 578 | + | widget:show() -- mostrar panel_lightflat_circle y demás (según definición OTUI) | |
| 579 | + | local widgetSteps = 40 | |
| 580 | + | local widgetInterval = 50 | |
| 581 | + | local widgetStep = 1 / widgetSteps | |
| 582 | + | local widgetOpacity = 0 | |
| 583 | + | local function fadeInWidget() | |
| 584 | + | widgetOpacity = widgetOpacity + widgetStep | |
| 585 | + | if widgetOpacity > 1 then widgetOpacity = 1 end | |
| 586 | + | widget:setOpacity(widgetOpacity) | |
| 587 | + | if widgetOpacity < 1 then | |
| 588 | + | scheduleEvent(fadeInWidget, widgetInterval) | |
| 589 | + | else | |
| 590 | + | finished = finished + 1 | |
| 591 | + | if finished == total then | |
| 592 | + | -- Animar fade in de botones "Conectar" y "Cancel" | |
| 593 | + | local btnOk = charactersWindow:getChildById("buttonOk") | |
| 594 | + | local btnCancel = charactersWindow:getChildById("buttonCancel") | |
| 595 | + | local btnSteps = 40 | |
| 596 | + | local btnInterval = 50 | |
| 597 | + | local btnStep = 1 / btnSteps | |
| 598 | + | local btnOpacity = 0 | |
| 599 | + | local function fadeInButtons() | |
| 600 | + | btnOpacity = btnOpacity + btnStep | |
| 601 | + | if btnOpacity > 1 then btnOpacity = 1 end | |
| 602 | + | btnOk:setOpacity(btnOpacity) | |
| 603 | + | btnCancel:setOpacity(btnOpacity) | |
| 604 | + | if btnOpacity < 1 then | |
| 605 | + | scheduleEvent(fadeInButtons, btnInterval) | |
| 606 | + | else | |
| 607 | + | transitioning = false -- Terminar transición: botones y widgets completamente visibles | |
| 608 | + | end | |
| 609 | + | end | |
| 610 | + | fadeInButtons() | |
| 611 | + | end | |
| 612 | + | end | |
| 613 | + | end | |
| 614 | + | fadeInWidget() | |
| 615 | + | ||
| 616 | + | local outfit = widget:getChildById("outfit") | |
| 617 | + | if outfit then | |
| 618 | + | outfit:setOpacity(0) | |
| 619 | + | local outfitSteps = 40 | |
| 620 | + | local outfitInterval = 50 | |
| 621 | + | local outfitStep = 1 / outfitSteps | |
| 622 | + | local outfitOpacity = 0 | |
| 623 | + | local function fadeInOutfit() | |
| 624 | + | outfitOpacity = outfitOpacity + outfitStep | |
| 625 | + | if outfitOpacity > 1 then outfitOpacity = 1 end | |
| 626 | + | outfit:setOpacity(outfitOpacity) | |
| 627 | + | if outfitOpacity < 1 then | |
| 628 | + | scheduleEvent(fadeInOutfit, outfitInterval) | |
| 629 | + | else | |
| 630 | + | outfit:show() -- asegurar que permanezca visible | |
| 631 | + | end | |
| 632 | + | end | |
| 633 | + | fadeInOutfit() | |
| 634 | + | end | |
| 635 | + | end | |
| 636 | + | end | |
| 637 | + | end | |
| 638 | + | fadeInBackground() | |
| 639 | + | ||
| 640 | + | local autoReconnect = g_settings.getBoolean("autoReconnect", true) | |
| 641 | + | autoReconnectButton:setOn(autoReconnect) | |
| 642 | + | end | |
| 643 | + | ||
| 644 | + | function CharacterList.hide(showLogin) | |
| 645 | + | if transitioning then return end -- Bloquea escape o cancel mientras hay transición | |
| 646 | + | removeEvent(autoReconnectEvent) | |
| 647 | + | ||
| 648 | + | autoReconnectEvent = nil | |
| 649 | + | showLogin = showLogin or false | |
| 650 | + | ||
| 651 | + | charactersWindow:hide() | |
| 652 | + | ||
| 653 | + | if showLogin and EnterGame and not g_game.isOnline() then | |
| 654 | + | local root = g_ui.getRootWidget() | |
| 655 | + | local overlay = g_ui.createWidget("Panel", root) | |
| 656 | + | overlay:resize(root:getWidth(), root:getHeight()) | |
| 657 | + | overlay:move(0, 0) | |
| 658 | + | overlay:setColor("black") | |
| 659 | + | overlay:setOpacity(1) | |
| 660 | + | ||
| 661 | + | -- Aumentamos el tiempo de pantalla oscura antes de mostrar EnterGame (por ejemplo, 1000ms) | |
| 662 | + | scheduleEvent(function() | |
| 663 | + | EnterGame.show() | |
| 664 | + | local enterGameWidget = EnterGame.getRootWidget and EnterGame.getRootWidget() or EnterGame | |
| 665 | + | if enterGameWidget and type(enterGameWidget.setOpacity) == "function" then | |
| 666 | + | enterGameWidget:setOpacity(0) | |
| 667 | + | local steps = 40 | |
| 668 | + | local interval = 50 | |
| 669 | + | local stepOpacity = 1 / steps | |
| 670 | + | local currentOpacity = 0 | |
| 671 | + | local function fadeInEnterGame() | |
| 672 | + | currentOpacity = currentOpacity + stepOpacity | |
| 673 | + | if currentOpacity > 1 then currentOpacity = 1 end | |
| 674 | + | enterGameWidget:setOpacity(currentOpacity) | |
| 675 | + | if currentOpacity < 1 then | |
| 676 | + | scheduleEvent(fadeInEnterGame, interval) | |
| 677 | + | else | |
| 678 | + | overlay:destroy() | |
| 679 | + | end | |
| 680 | + | end | |
| 681 | + | fadeInEnterGame() | |
| 682 | + | else | |
| 683 | + | overlay:destroy() | |
| 684 | + | end | |
| 685 | + | end, 1000) -- Tiempo extra en negro (1000ms) | |
| 686 | + | end | |
| 687 | + | end | |
| 688 | + | ||
| 689 | + | function CharacterList.showAgain() | |
| 690 | + | if characterList and characterList:hasChildren() then | |
| 691 | + | CharacterList.show() | |
| 692 | + | EnterGame.onShowuIText() | |
| 693 | + | end | |
| 694 | + | end | |
| 695 | + | ||
| 696 | + | function CharacterList.isVisible() | |
| 697 | + | if charactersWindow and charactersWindow:isVisible() then | |
| 698 | + | return true | |
| 699 | + | end | |
| 700 | + | ||
| 701 | + | return false | |
| 702 | + | end | |
| 703 | + | ||
| 704 | + | -- Nueva versión de la función para animar el zoom del outfit seleccionado | |
| 705 | + | local function zoomSelectedOutfit(widget, callback) | |
| 706 | + | local outfit = widget:getChildById("outfit") | |
| 707 | + | if not outfit then | |
| 708 | + | callback() | |
| 709 | + | return | |
| 710 | + | end | |
| 711 | + | local steps = 20 | |
| 712 | + | local interval = 50 | |
| 713 | + | local currentStep = 0 | |
| 714 | + | ||
| 715 | + | local originalScale = 1.0 | |
| 716 | + | local originalPos = outfit:getPosition() | |
| 717 | + | ||
| 718 | + | local finalScale = 3.0 | |
| 719 | + | -- Usar siempre el centro de la raíz para calcular la posición destino | |
| 720 | + | local root = g_ui.getRootWidget() | |
| 721 | + | local targetX = (root:getWidth() - outfit:getWidth() * finalScale) / 2 | |
| 722 | + | local targetY = (root:getHeight() - outfit:getHeight() * finalScale) / 2 | |
| 723 | + | ||
| 724 | + | local scaleStep = (finalScale - originalScale) / steps | |
| 725 | + | ||
| 726 | + | local function animateZoom() | |
| 727 | + | currentStep = currentStep + 1 | |
| 728 | + | local newScale = originalScale + scaleStep * currentStep | |
| 729 | + | outfit:setScale(newScale) | |
| 730 | + | local progress = currentStep / steps | |
| 731 | + | local newX = originalPos.x + (targetX - originalPos.x) * progress | |
| 732 | + | local newY = originalPos.y + (targetY - originalPos.y) * progress | |
| 733 | + | outfit:move(newX, newY) | |
| 734 | + | if currentStep < steps then | |
| 735 | + | scheduleEvent(animateZoom, interval) | |
| 736 | + | else | |
| 737 | + | outfit:setScale(originalScale) | |
| 738 | + | outfit:move(originalPos.x, originalPos.y) | |
| 739 | + | callback() | |
| 740 | + | end | |
| 741 | + | end | |
| 742 | + | animateZoom() | |
| 743 | + | end | |
| 744 | + | ||
| 745 | + | -- Modificación en doLogin para integrar el efecto zoom antes del login | |
| 746 | + | function CharacterList.doLogin() | |
| 747 | + | if transitioning then return end -- Se ignoran interacciones durante transición | |
| 748 | + | removeEvent(autoReconnectEvent) | |
| 749 | + | autoReconnectEvent = nil | |
| 750 | + | local selected = characterList:getFocusedChild() | |
| 751 | + | if selected then | |
| 752 | + | local function proceedLogin() | |
| 753 | + | local charInfo = { | |
| 754 | + | worldHost = selected.worldHost, | |
| 755 | + | worldPort = selected.worldPort, | |
| 756 | + | worldName = selected.worldName, | |
| 757 | + | characterName = selected.characterName | |
| 758 | + | } | |
| 759 | + | charactersWindow:hide() | |
| 760 | + | if loginEvent then | |
| 761 | + | removeEvent(loginEvent) | |
| 762 | + | loginEvent = nil | |
| 763 | + | end | |
| 764 | + | tryLogin(charInfo) | |
| 765 | + | end | |
| 766 | + | zoomSelectedOutfit(selected, proceedLogin) | |
| 767 | + | else | |
| 768 | + | displayErrorBox(tr("Error"), tr("You must select a character to login!")) | |
| 769 | + | end | |
| 770 | + | end | |
| 771 | + | ||
| 772 | + | function CharacterList.destroyLoadBox() | |
| 773 | + | if loadBox then | |
| 774 | + | loadBox:destroy() | |
| 775 | + | ||
| 776 | + | loadBox = nil | |
| 777 | + | end | |
| 778 | + | end | |
| 779 | + | ||
| 780 | + | function CharacterList.cancelWait() | |
| 781 | + | if waitingWindow then | |
| 782 | + | waitingWindow:destroy() | |
| 783 | + | ||
| 784 | + | waitingWindow = nil | |
| 785 | + | end | |
| 786 | + | ||
| 787 | + | if updateWaitEvent then | |
| 788 | + | removeEvent(updateWaitEvent) | |
| 789 | + | ||
| 790 | + | updateWaitEvent = nil | |
| 791 | + | end | |
| 792 | + | ||
| 793 | + | if resendWaitEvent then | |
| 794 | + | removeEvent(resendWaitEvent) | |
| 795 | + | ||
| 796 | + | resendWaitEvent = nil | |
| 797 | + | end | |
| 798 | + | ||
| 799 | + | CharacterList.destroyLoadBox() | |
| 800 | + | CharacterList.showAgain() | |
| 801 | + | end | |
| 802 | + | ||
| 803 | + | function applyOffsets(offsetX, offsetY) | |
| 804 | + | local shader = g_shaders.getShader('myShader') | |
| 805 | + | if shader then | |
| 806 | + | shader:setUniformValue('offsetX', offsetX) | |
| 807 | + | shader:setUniformValue('offsetY', offsetY) | |
| 808 | + | end | |
| 809 | + | end |
| @@ -0,0 +1,88 @@ | |||
| 1 | + | -- private variables | |
| 2 | + | local background | |
| 3 | + | local clientVersionLabel | |
| 4 | + | local topMenu = 0 | |
| 5 | + | ||
| 6 | + | -- Login-screen social links | |
| 7 | + | local socialLinks = { | |
| 8 | + | facebook = '', | |
| 9 | + | youtube = '', | |
| 10 | + | instagram = '', | |
| 11 | + | discord = 'https://discord.gg/kvNPUxrvPQ' | |
| 12 | + | } | |
| 13 | + | ||
| 14 | + | function openSocialLink(name) | |
| 15 | + | local url = socialLinks[name] | |
| 16 | + | if url and url ~= '' then | |
| 17 | + | g_platform.openUrl(url) | |
| 18 | + | end | |
| 19 | + | end | |
| 20 | + | ||
| 21 | + | -- public functions | |
| 22 | + | function init() | |
| 23 | + | background = g_ui.displayUI('background') | |
| 24 | + | background:lower() | |
| 25 | + | ||
| 26 | + | clientVersionLabel = background:getChildById('clientVersionLabel') | |
| 27 | + | if g_app.isMobile() then | |
| 28 | + | clientVersionLabel:show() | |
| 29 | + | clientVersionLabel:setText('V' ..APP_VERSION) | |
| 30 | + | else | |
| 31 | + | end | |
| 32 | + | ||
| 33 | + | if not g_game.isOnline() then | |
| 34 | + | --addEvent(function() g_effects.fadeIn(clientVersionLabel, 1500) end) | |
| 35 | + | end | |
| 36 | + | ||
| 37 | + | g_keyboard.bindKeyDown('Ctrl+I', function() modules.game_menu.OpenInventario() end, gameRootPanel) | |
| 38 | + | g_keyboard.bindKeyDown('Ctrl+Y', function() hideTopMenu() end, gameRootPanel) | |
| 39 | + | connect(g_game, { onGameStart = hide }) | |
| 40 | + | connect(g_game, { onGameEnd = show }) | |
| 41 | + | topMenu = 0 | |
| 42 | + | end | |
| 43 | + | ||
| 44 | + | function setTopMenu(value) | |
| 45 | + | topMenu = value | |
| 46 | + | end | |
| 47 | + | function terminate() | |
| 48 | + | disconnect(g_game, { onGameStart = hide }) | |
| 49 | + | disconnect(g_game, { onGameEnd = show }) | |
| 50 | + | ||
| 51 | + | g_effects.cancelFade(background:getChildById('clientVersionLabel')) | |
| 52 | + | background:destroy() | |
| 53 | + | ||
| 54 | + | Background = nil | |
| 55 | + | end | |
| 56 | + | ||
| 57 | + | function hideTopMenu() | |
| 58 | + | if topMenu == 0 then | |
| 59 | + | modules.client_topmenu.getTopMenu():setHeight(30) | |
| 60 | + | topMenu = 1 | |
| 61 | + | else | |
| 62 | + | modules.client_topmenu.getTopMenu():setHeight(0) | |
| 63 | + | topMenu = 0 | |
| 64 | + | end | |
| 65 | + | end | |
| 66 | + | function hide() | |
| 67 | + | background:hide() | |
| 68 | + | topMenu = 0 | |
| 69 | + | modules.client_topmenu.getTopMenu():setHeight(0) | |
| 70 | + | end | |
| 71 | + | ||
| 72 | + | function show() | |
| 73 | + | background:show() | |
| 74 | + | topMenu = 0 | |
| 75 | + | modules.client_topmenu.getTopMenu():setHeight(0) | |
| 76 | + | end | |
| 77 | + | ||
| 78 | + | function hideVersionLabel() | |
| 79 | + | background:getChildById('clientVersionLabel'):hide() | |
| 80 | + | end | |
| 81 | + | ||
| 82 | + | function setVersionText(text) | |
| 83 | + | clientVersionLabel:setText(text) | |
| 84 | + | end | |
| 85 | + | ||
| 86 | + | function getBackground() | |
| 87 | + | return background | |
| 88 | + | end |
| @@ -0,0 +1,10 @@ | |||
| 1 | + | Module | |
| 2 | + | name: client_background | |
| 3 | + | description: Handles the background of the login screen | |
| 4 | + | author: edubart | |
| 5 | + | website: https://github.com/edubart/otclient | |
| 6 | + | security: jR$5p@9Qz! | |
| 7 | + | sandboxed: true | |
| 8 | + | scripts: [ background ] | |
| 9 | + | @onLoad: init() | |
| 10 | + | @onUnload: terminate() |
| @@ -0,0 +1,94 @@ | |||
| 1 | + | UIWidget | |
| 2 | + | id: background | |
| 3 | + | anchors.fill: parent | |
| 4 | + | focusable: false | |
| 5 | + | image-source: /images/background | |
| 6 | + | image-smooth: true | |
| 7 | + | image-fixed-ratio: true | |
| 8 | + | margin-top: 1 | |
| 9 | + | ||
| 10 | + | UIWidget | |
| 11 | + | id: configManager | |
| 12 | + | size: 42 42 | |
| 13 | + | tooltip: Clique aqui para configurar seu cliente. | |
| 14 | + | anchors.top: parent.top | |
| 15 | + | anchors.left: parent.left | |
| 16 | + | image-source: images/config | |
| 17 | + | change-cursor-image: true | |
| 18 | + | cursor: pointer | |
| 19 | + | margin-top: 10 | |
| 20 | + | margin-left: 10 | |
| 21 | + | @onClick: modules.client_options.toggle() | |
| 22 | + | ||
| 23 | + | UIWidget | |
| 24 | + | id: socialLinks | |
| 25 | + | size: 234 54 | |
| 26 | + | anchors.bottom: parent.bottom | |
| 27 | + | anchors.horizontalCenter: parent.horizontalCenter | |
| 28 | + | margin-bottom: 20 | |
| 29 | + | focusable: false | |
| 30 | + | phantom: true | |
| 31 | + | ||
| 32 | + | UIWidget | |
| 33 | + | id: Facebook | |
| 34 | + | !tooltip: tr('Facebook (not configured)') | |
| 35 | + | image-source: images/social/facebook | |
| 36 | + | size: 48 53 | |
| 37 | + | anchors.top: parent.top | |
| 38 | + | anchors.left: parent.left | |
| 39 | + | margin-left: 0 | |
| 40 | + | opacity: 0.8 | |
| 41 | + | focusable: false | |
| 42 | + | change-cursor-image: true | |
| 43 | + | cursor: pointer | |
| 44 | + | @onClick: modules.client_background.openSocialLink('facebook') | |
| 45 | + | $hover: | |
| 46 | + | opacity: 1 | |
| 47 | + | ||
| 48 | + | UIWidget | |
| 49 | + | id: Youtube | |
| 50 | + | !tooltip: tr('YouTube') | |
| 51 | + | image-source: images/social/youtube | |
| 52 | + | size: 48 53 | |
| 53 | + | anchors.top: parent.top | |
| 54 | + | anchors.left: parent.left | |
| 55 | + | margin-left: 62 | |
| 56 | + | opacity: 0.8 | |
| 57 | + | focusable: false | |
| 58 | + | change-cursor-image: true | |
| 59 | + | cursor: pointer | |
| 60 | + | @onClick: modules.client_background.openSocialLink('youtube') | |
| 61 | + | $hover: | |
| 62 | + | opacity: 1 | |
| 63 | + | ||
| 64 | + | UIWidget | |
| 65 | + | id: Instagram | |
| 66 | + | !tooltip: tr('Instagram (not configured)') | |
| 67 | + | image-source: images/social/instagram | |
| 68 | + | size: 48 53 | |
| 69 | + | anchors.top: parent.top | |
| 70 | + | anchors.left: parent.left | |
| 71 | + | margin-left: 124 | |
| 72 | + | opacity: 0.8 | |
| 73 | + | focusable: false | |
| 74 | + | change-cursor-image: true | |
| 75 | + | cursor: pointer | |
| 76 | + | @onClick: modules.client_background.openSocialLink('instagram') | |
| 77 | + | $hover: | |
| 78 | + | opacity: 1 | |
| 79 | + | ||
| 80 | + | UIWidget | |
| 81 | + | id: Discord | |
| 82 | + | !tooltip: tr('Discord') | |
| 83 | + | image-source: images/social/discord | |
| 84 | + | size: 48 53 | |
| 85 | + | anchors.top: parent.top | |
| 86 | + | anchors.left: parent.left | |
| 87 | + | margin-left: 186 | |
| 88 | + | opacity: 0.8 | |
| 89 | + | focusable: false | |
| 90 | + | change-cursor-image: true | |
| 91 | + | cursor: pointer | |
| 92 | + | @onClick: modules.client_background.openSocialLink('discord') | |
| 93 | + | $hover: | |
| 94 | + | opacity: 1 |
| @@ -0,0 +1,66 @@ | |||
| 1 | + | Panel | |
| 2 | + | id: characterCell | |
| 3 | + | size: 190 190 | |
| 4 | + | image-source: /images/ui/panel_lightflat_circle | |
| 5 | + | image-repeat: no-repeat | |
| 6 | + | image-fit: cover | |
| 7 | + | position: 0 0 | |
| 8 | + | ||
| 9 | + | Label | |
| 10 | + | id: name | |
| 11 | + | text-auto-resize: true | |
| 12 | + | font: verdana-11px-monochrome | |
| 13 | + | color: #bbbbbb | |
| 14 | + | position: 10 10 | |
| 15 | + | $on: | |
| 16 | + | color: #ffffff | |
| 17 | + | ||
| 18 | + | UICreature | |
| 19 | + | id: outfit | |
| 20 | + | animate: true | |
| 21 | + | size: 256 256 | |
| 22 | + | position: 10 40 | |
| 23 | + | ||
| 24 | + | Label | |
| 25 | + | id: level | |
| 26 | + | text-auto-resize: true | |
| 27 | + | font: verdana-9px-monochrome | |
| 28 | + | color: #bbbbbb | |
| 29 | + | &baseText: 'Level: %s' | |
| 30 | + | position: 10 300 | |
| 31 | + | $on: | |
| 32 | + | color: #ffffff | |
| 33 | + | ||
| 34 | + | Label | |
| 35 | + | id: vocation | |
| 36 | + | text-auto-resize: true | |
| 37 | + | font: verdana-9px-monochrome | |
| 38 | + | color: #bbbbbb | |
| 39 | + | background-color: alpha | |
| 40 | + | &baseText: '(%s)' | |
| 41 | + | position: 10 320 | |
| 42 | + | ||
| 43 | + | $focus: | |
| 44 | + | image-source: /images/ui/bossbar_outfit | |
| 45 | + | border-width: 0 | |
| 46 | + | border-color: #f4ca16 | |
| 47 | + | opacity: 1 | |
| 48 | + | size: 190 190 | |
| 49 | + | image-fit: contain | |
| 50 | + | image-align: center | |
| 51 | + | image-resize: true | |
| 52 | + | image-repeat: no-repeat | |
| 53 | + | ||
| 54 | + | CharacterWidgetEmpty < Panel | |
| 55 | + | id: emptyCell | |
| 56 | + | size: 190 190 | |
| 57 | + | image-source: /images/ui/panel_flat | |
| 58 | + | image-border: 3 | |
| 59 | + | focusable: false | |
| 60 | + | ||
| 61 | + | Label | |
| 62 | + | color: #bbbbbb | |
| 63 | + | text-auto-resize: true | |
| 64 | + | font: verdana-11px-monochrome | |
| 65 | + | !text: 'Empty' | |
| 66 | + | position: 20 80 |
| @@ -0,0 +1,651 @@ | |||
| 1 | + | CharacterList = {} | |
| 2 | + | local charactersWindow, loadBox, characterList, errorBox, waitingWindow, autoReconnectButton, updateWaitEvent, resendWaitEvent, loginEvent, autoReconnectEvent = nil | |
| 3 | + | local lastLogout = 0 | |
| 4 | + | local lastFocusChange = 0 -- variable para debouncing | |
| 5 | + | local focusDelay = 300 -- retraso mínimo en milisegundos | |
| 6 | + | local pendingFocusEvent = nil -- timer pendiente para aplicar el fade | |
| 7 | + | loginOverlay = nil | |
| 8 | + | ||
| 9 | + | local function fadeWidget(widget, targetOpacity, steps, interval) | |
| 10 | + | if widget.fadeEvent then | |
| 11 | + | removeEvent(widget.fadeEvent) | |
| 12 | + | widget.fadeEvent = nil | |
| 13 | + | end | |
| 14 | + | steps = steps or 20 | |
| 15 | + | interval = interval or 50 | |
| 16 | + | local current = widget:getOpacity() or 1 | |
| 17 | + | local delta = (targetOpacity - current) / steps | |
| 18 | + | local function animate(count) | |
| 19 | + | if count < steps then | |
| 20 | + | current = current + delta | |
| 21 | + | widget:setOpacity(current) | |
| 22 | + | widget.fadeEvent = scheduleEvent(function() animate(count + 1) end, interval) | |
| 23 | + | else | |
| 24 | + | widget:setOpacity(targetOpacity) | |
| 25 | + | widget.fadeEvent = nil | |
| 26 | + | end | |
| 27 | + | end | |
| 28 | + | animate(0) | |
| 29 | + | end | |
| 30 | + | ||
| 31 | + | local VocationsList = { | |
| 32 | + | [1] = "Wizard", | |
| 33 | + | [2] = "Druid", | |
| 34 | + | [3] = "Archer", | |
| 35 | + | [4] = "Warrior", | |
| 36 | + | [5] = "Bard", | |
| 37 | + | [6] = "Black Mage", | |
| 38 | + | [7] = "Swashbuckler", | |
| 39 | + | [8] = "Arbalest", | |
| 40 | + | [9] = "Beastborne", | |
| 41 | + | [10] = "Illusionist", | |
| 42 | + | [11] = "Wolverine", | |
| 43 | + | [12] = "SpearMaster", | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | local function tryLogin(charInfo, tries) | |
| 47 | + | tries = tries or 1 | |
| 48 | + | if tries > 50 then return end | |
| 49 | + | ||
| 50 | + | if g_game.isOnline() then | |
| 51 | + | if tries == 1 then | |
| 52 | + | g_game.safeLogout() | |
| 53 | + | end | |
| 54 | + | loginEvent = scheduleEvent(function () tryLogin(charInfo, tries + 1) end, 100) | |
| 55 | + | return | |
| 56 | + | end | |
| 57 | + | ||
| 58 | + | CharacterList.hide() | |
| 59 | + | ||
| 60 | + | g_game.loginWorld(G.account, G.password, charInfo.worldName, charInfo.worldHost, charInfo.worldPort, charInfo.characterName, G.authenticatorToken, G.sessionKey) | |
| 61 | + | g_logger.info("Login to " .. charInfo.worldHost .. ":" .. charInfo.worldPort) | |
| 62 | + | EnterGame.onCloseuIText() | |
| 63 | + | g_settings.set("last-used-character", charInfo.characterName) | |
| 64 | + | g_settings.set("last-used-world", charInfo.worldName) | |
| 65 | + | end | |
| 66 | + | ||
| 67 | + | local function updateWait(timeStart, timeEnd) | |
| 68 | + | if waitingWindow then | |
| 69 | + | local time = g_clock.seconds() | |
| 70 | + | ||
| 71 | + | if time <= timeEnd then | |
| 72 | + | local percent = (time - timeStart) / (timeEnd - timeStart) * 100 | |
| 73 | + | local timeStr = string.format("%.0f", timeEnd - time) | |
| 74 | + | local progressBar = waitingWindow:getChildById("progressBar") | |
| 75 | + | ||
| 76 | + | progressBar:setPercent(percent) | |
| 77 | + | ||
| 78 | + | local label = waitingWindow:getChildById("timeLabel") | |
| 79 | + | ||
| 80 | + | label:setText(tr("Trying to reconnect in %s seconds.", timeStr)) | |
| 81 | + | ||
| 82 | + | updateWaitEvent = scheduleEvent(function () | |
| 83 | + | updateWait(timeStart, timeEnd) | |
| 84 | + | end, 1000 * progressBar:getPercentPixels() / 100 * (timeEnd - timeStart)) | |
| 85 | + | ||
| 86 | + | return true | |
| 87 | + | end | |
| 88 | + | end | |
| 89 | + | ||
| 90 | + | if updateWaitEvent then | |
| 91 | + | updateWaitEvent:cancel() | |
| 92 | + | ||
| 93 | + | updateWaitEvent = nil | |
| 94 | + | end | |
| 95 | + | end | |
| 96 | + | ||
| 97 | + | local function resendWait() | |
| 98 | + | if waitingWindow then | |
| 99 | + | waitingWindow:destroy() | |
| 100 | + | ||
| 101 | + | waitingWindow = nil | |
| 102 | + | ||
| 103 | + | if updateWaitEvent then | |
| 104 | + | updateWaitEvent:cancel() | |
| 105 | + | ||
| 106 | + | updateWaitEvent = nil | |
| 107 | + | end | |
| 108 | + | ||
| 109 | + | if charactersWindow then | |
| 110 | + | local selected = characterList:getFocusedChild() | |
| 111 | + | ||
| 112 | + | if selected then | |
| 113 | + | local charInfo = { | |
| 114 | + | worldHost = selected.worldHost, | |
| 115 | + | worldPort = selected.worldPort, | |
| 116 | + | worldName = selected.worldName, | |
| 117 | + | characterName = selected.characterName | |
| 118 | + | } | |
| 119 | + | ||
| 120 | + | tryLogin(charInfo) | |
| 121 | + | end | |
| 122 | + | end | |
| 123 | + | end | |
| 124 | + | end | |
| 125 | + | ||
| 126 | + | local function onLoginWait(message, time) | |
| 127 | + | CharacterList.destroyLoadBox() | |
| 128 | + | ||
| 129 | + | waitingWindow = g_ui.displayUI("waitinglist") | |
| 130 | + | local label = waitingWindow:getChildById("infoLabel") | |
| 131 | + | ||
| 132 | + | label:setText(message) | |
| 133 | + | ||
| 134 | + | updateWaitEvent = scheduleEvent(function () | |
| 135 | + | updateWait(g_clock.seconds(), g_clock.seconds() + time) | |
| 136 | + | end, 0) | |
| 137 | + | resendWaitEvent = scheduleEvent(resendWait, time * 1000) | |
| 138 | + | end | |
| 139 | + | ||
| 140 | + | function onGameLoginError(message) | |
| 141 | + | CharacterList.destroyLoadBox() | |
| 142 | + | ||
| 143 | + | errorBox = displayErrorBox(tr("Login Error"), message) | |
| 144 | + | ||
| 145 | + | function errorBox.onOk() | |
| 146 | + | errorBox = nil | |
| 147 | + | ||
| 148 | + | CharacterList.showAgain() | |
| 149 | + | end | |
| 150 | + | ||
| 151 | + | scheduleAutoReconnect() | |
| 152 | + | end | |
| 153 | + | ||
| 154 | + | function onGameLoginToken(unknown) | |
| 155 | + | CharacterList.destroyLoadBox() | |
| 156 | + | ||
| 157 | + | errorBox = displayErrorBox(tr("Two-Factor Authentification"), "A new authentification token is required.\nPlease login again.") | |
| 158 | + | ||
| 159 | + | function errorBox.onOk() | |
| 160 | + | errorBox = nil | |
| 161 | + | ||
| 162 | + | EnterGame.show() | |
| 163 | + | end | |
| 164 | + | end | |
| 165 | + | ||
| 166 | + | function onGameConnectionError(message, code) | |
| 167 | + | CharacterList.destroyLoadBox() | |
| 168 | + | ||
| 169 | + | if (not g_game.isOnline() or code ~= 2) and not errorBox then | |
| 170 | + | local text = translateNetworkError(code, g_game.getProtocolGame() and g_game.getProtocolGame():isConnecting(), message) | |
| 171 | + | errorBox = displayErrorBox(tr("Connection Error"), text) | |
| 172 | + | ||
| 173 | + | function errorBox.onOk() | |
| 174 | + | errorBox = nil | |
| 175 | + | ||
| 176 | + | CharacterList.showAgain() | |
| 177 | + | end | |
| 178 | + | end | |
| 179 | + | ||
| 180 | + | scheduleAutoReconnect() | |
| 181 | + | end | |
| 182 | + | ||
| 183 | + | function onGameUpdateNeeded(signature) | |
| 184 | + | CharacterList.destroyLoadBox() | |
| 185 | + | ||
| 186 | + | errorBox = displayErrorBox(tr("Update needed"), tr("Enter with your account again to update your client.")) | |
| 187 | + | ||
| 188 | + | function errorBox.onOk() | |
| 189 | + | errorBox = nil | |
| 190 | + | ||
| 191 | + | CharacterList.showAgain() | |
| 192 | + | end | |
| 193 | + | end | |
| 194 | + | ||
| 195 | + | function onGameEnd() | |
| 196 | + | scheduleAutoReconnect() | |
| 197 | + | CharacterList.showAgain() | |
| 198 | + | end | |
| 199 | + | ||
| 200 | + | function onLogout() | |
| 201 | + | lastLogout = g_clock.millis() | |
| 202 | + | end | |
| 203 | + | ||
| 204 | + | function scheduleAutoReconnect() | |
| 205 | + | if g_clock.millis() < lastLogout + 2000 then | |
| 206 | + | return | |
| 207 | + | end | |
| 208 | + | ||
| 209 | + | if autoReconnectEvent then | |
| 210 | + | removeEvent(autoReconnectEvent) | |
| 211 | + | end | |
| 212 | + | ||
| 213 | + | autoReconnectEvent = scheduleEvent(executeAutoReconnect, 2500) | |
| 214 | + | end | |
| 215 | + | ||
| 216 | + | function executeAutoReconnect() | |
| 217 | + | if not autoReconnectButton or not autoReconnectButton:isChecked() or g_game.isOnline() then | |
| 218 | + | return | |
| 219 | + | end | |
| 220 | + | ||
| 221 | + | if errorBox then | |
| 222 | + | errorBox:destroy() | |
| 223 | + | ||
| 224 | + | errorBox = nil | |
| 225 | + | end | |
| 226 | + | ||
| 227 | + | CharacterList.doLogin() | |
| 228 | + | end | |
| 229 | + | ||
| 230 | + | local function fadeInWorld() | |
| 231 | + | local root = g_ui.getRootWidget() | |
| 232 | + | local overlay = g_ui.createWidget("Panel", root) | |
| 233 | + | overlay:resize(root:getWidth(), root:getHeight()) | |
| 234 | + | overlay:move(0, 0) | |
| 235 | + | overlay:setColor("black") | |
| 236 | + | overlay:setOpacity(1) | |
| 237 | + | ||
| 238 | + | local steps = 40 | |
| 239 | + | local interval = 50 | |
| 240 | + | local step = 1 / steps | |
| 241 | + | local currentOpacity = 1 | |
| 242 | + | local function fadeOutOverlay() | |
| 243 | + | currentOpacity = currentOpacity - step | |
| 244 | + | if currentOpacity < 0 then currentOpacity = 0 end | |
| 245 | + | overlay:setOpacity(currentOpacity) | |
| 246 | + | if currentOpacity > 0 then | |
| 247 | + | scheduleEvent(fadeOutOverlay, interval) | |
| 248 | + | else | |
| 249 | + | overlay:destroy() | |
| 250 | + | end | |
| 251 | + | end | |
| 252 | + | fadeOutOverlay() | |
| 253 | + | end | |
| 254 | + | ||
| 255 | + | local transitioning = false | |
| 256 | + | ||
| 257 | + | local function onWidgetKeyDown(widget, key) | |
| 258 | + | if key == 13 then -- Enter key | |
| 259 | + | CharacterList.doLogin() | |
| 260 | + | return true | |
| 261 | + | end | |
| 262 | + | return false | |
| 263 | + | end | |
| 264 | + | ||
| 265 | + | local function connectKeyDownEvent(widget) | |
| 266 | + | if widget.boundKeyDownCombos then return end | |
| 267 | + | connect(widget, { onKeyDown = onWidgetKeyDown }) | |
| 268 | + | widget.boundKeyDownCombos = {} | |
| 269 | + | widget.boundAloneKeyDownCombos = {} | |
| 270 | + | end | |
| 271 | + | ||
| 272 | + | function CharacterList.init() | |
| 273 | + | if USE_NEW_ENERGAME then | |
| 274 | + | return | |
| 275 | + | end | |
| 276 | + | ||
| 277 | + | connect(g_ui.getRootWidget(), { onGeometryChange = CharacterList.resizeWindow }) | |
| 278 | + | ||
| 279 | + | connect(g_game, { | |
| 280 | + | onLoginError = onGameLoginError | |
| 281 | + | }) | |
| 282 | + | connect(g_game, { | |
| 283 | + | onLoginToken = onGameLoginToken | |
| 284 | + | }) | |
| 285 | + | connect(g_game, { | |
| 286 | + | onUpdateNeeded = onGameUpdateNeeded | |
| 287 | + | }) | |
| 288 | + | connect(g_game, { | |
| 289 | + | onConnectionError = onGameConnectionError | |
| 290 | + | }) | |
| 291 | + | -- Modificación: en onGameStart se destruye el loadBox y luego se aplica fade in al mundo | |
| 292 | + | connect(g_game, { | |
| 293 | + | onGameStart = function() | |
| 294 | + | CharacterList.destroyLoadBox() | |
| 295 | + | if loginOverlay then | |
| 296 | + | -- Fade out loginOverlay gradually after connection | |
| 297 | + | local fadeSteps = 20 | |
| 298 | + | local fadeInterval = 50 | |
| 299 | + | local currentStep = 0 | |
| 300 | + | local function fadeOutLoginOverlay() | |
| 301 | + | currentStep = currentStep + 1 | |
| 302 | + | local progress = 1 - currentStep / fadeSteps | |
| 303 | + | loginOverlay:setOpacity(progress) | |
| 304 | + | if progress > 0 then | |
| 305 | + | scheduleEvent(fadeOutLoginOverlay, fadeInterval) | |
| 306 | + | else | |
| 307 | + | loginOverlay:destroy() | |
| 308 | + | loginOverlay = nil | |
| 309 | + | end | |
| 310 | + | end | |
| 311 | + | fadeOutLoginOverlay() | |
| 312 | + | else | |
| 313 | + | fadeInWorld() | |
| 314 | + | end | |
| 315 | + | end | |
| 316 | + | }) | |
| 317 | + | connect(g_game, { | |
| 318 | + | onLoginWait = onLoginWait | |
| 319 | + | }) | |
| 320 | + | connect(g_game, { | |
| 321 | + | onGameEnd = onGameEnd | |
| 322 | + | }) | |
| 323 | + | connect(g_game, { | |
| 324 | + | onLogout = onLogout | |
| 325 | + | }) | |
| 326 | + | ||
| 327 | + | if G.characters then | |
| 328 | + | CharacterList.create(G.characters, G.characterAccount) | |
| 329 | + | end | |
| 330 | + | end | |
| 331 | + | ||
| 332 | + | function CharacterList.terminate() | |
| 333 | + | if USE_NEW_ENERGAME then | |
| 334 | + | return | |
| 335 | + | end | |
| 336 | + | ||
| 337 | + | disconnect(g_ui.getRootWidget(), { onGeometryChange = CharacterList.resizeWindow }) | |
| 338 | + | ||
| 339 | + | disconnect(g_game, { | |
| 340 | + | onLoginError = onGameLoginError | |
| 341 | + | }) | |
| 342 | + | disconnect(g_game, { | |
| 343 | + | onLoginToken = onGameLoginToken | |
| 344 | + | }) | |
| 345 | + | disconnect(g_game, { | |
| 346 | + | onUpdateNeeded = onGameUpdateNeeded | |
| 347 | + | }) | |
| 348 | + | disconnect(g_game, { | |
| 349 | + | onConnectionError = onGameConnectionError | |
| 350 | + | }) | |
| 351 | + | disconnect(g_game, { | |
| 352 | + | onGameStart = CharacterList.destroyLoadBox | |
| 353 | + | }) | |
| 354 | + | disconnect(g_game, { | |
| 355 | + | onLoginWait = onLoginWait | |
| 356 | + | }) | |
| 357 | + | disconnect(g_game, { | |
| 358 | + | onGameEnd = onGameEnd | |
| 359 | + | }) | |
| 360 | + | disconnect(g_game, { | |
| 361 | + | onLogout = onLogout | |
| 362 | + | }) | |
| 363 | + | ||
| 364 | + | if charactersWindow then | |
| 365 | + | characterList = nil | |
| 366 | + | ||
| 367 | + | charactersWindow:destroy() | |
| 368 | + | ||
| 369 | + | charactersWindow = nil | |
| 370 | + | end | |
| 371 | + | ||
| 372 | + | if loadBox then | |
| 373 | + | g_game.cancelLogin() | |
| 374 | + | loadBox:destroy() | |
| 375 | + | ||
| 376 | + | loadBox = nil | |
| 377 | + | end | |
| 378 | + | ||
| 379 | + | if waitingWindow then | |
| 380 | + | waitingWindow:destroy() | |
| 381 | + | ||
| 382 | + | waitingWindow = nil | |
| 383 | + | end | |
| 384 | + | ||
| 385 | + | if updateWaitEvent then | |
| 386 | + | removeEvent(updateWaitEvent) | |
| 387 | + | ||
| 388 | + | updateWaitEvent = nil | |
| 389 | + | end | |
| 390 | + | ||
| 391 | + | if resendWaitEvent then | |
| 392 | + | removeEvent(resendWaitEvent) | |
| 393 | + | ||
| 394 | + | resendWaitEvent = nil | |
| 395 | + | end | |
| 396 | + | ||
| 397 | + | if loginEvent then | |
| 398 | + | removeEvent(loginEvent) | |
| 399 | + | ||
| 400 | + | loginEvent = nil | |
| 401 | + | end | |
| 402 | + | ||
| 403 | + | CharacterList = nil | |
| 404 | + | end | |
| 405 | + | ||
| 406 | + | local function characterOutfit(characterInfo, full) | |
| 407 | + | local outfit = { | |
| 408 | + | type = characterInfo.looktype, | |
| 409 | + | head = characterInfo.lookhead, | |
| 410 | + | body = characterInfo.lookbody, | |
| 411 | + | legs = characterInfo.looklegs, | |
| 412 | + | feet = characterInfo.lookfeet, | |
| 413 | + | addons = characterInfo.lookaddons | |
| 414 | + | } | |
| 415 | + | if full then | |
| 416 | + | outfit.wings = tonumber(characterInfo.lookwings) or 0 | |
| 417 | + | outfit.aura = tonumber(characterInfo.lookaura) or 0 | |
| 418 | + | outfit.shader = characterInfo.lookshader | |
| 419 | + | end | |
| 420 | + | return outfit | |
| 421 | + | end | |
| 422 | + | ||
| 423 | + | local function vocationName(vocation) | |
| 424 | + | return VocationsList[tonumber(vocation)] or tostring(vocation or "") | |
| 425 | + | end | |
| 426 | + | ||
| 427 | + | function CharacterList.updatePreview(widget) | |
| 428 | + | if not charactersWindow then | |
| 429 | + | return | |
| 430 | + | end | |
| 431 | + | ||
| 432 | + | local preview = charactersWindow:getChildById("previewPanel") | |
| 433 | + | local info = widget and widget.characterInfo | |
| 434 | + | preview:setVisible(info ~= nil) | |
| 435 | + | if not info then | |
| 436 | + | return | |
| 437 | + | end | |
| 438 | + | ||
| 439 | + | preview:getChildById("previewOutfit"):setOutfit(characterOutfit(info, true)) | |
| 440 | + | preview:getChildById("previewName"):setText(info.name or "") | |
| 441 | + | preview:getChildById("previewLevel"):setText(("%s %s"):format(tr("Level"), info.level or 0)) | |
| 442 | + | preview:getChildById("previewVocation"):setText(vocationName(info.vocation)) | |
| 443 | + | preview:getChildById("previewWorld"):setText(info.worldName or "") | |
| 444 | + | end | |
| 445 | + | ||
| 446 | + | -- Leave room below the selector for the login-screen social links. | |
| 447 | + | function CharacterList.resizeWindow() | |
| 448 | + | if not charactersWindow then return end | |
| 449 | + | local parent = charactersWindow:getParent() | |
| 450 | + | if not parent then return end | |
| 451 | + | local width = math.min(760, parent:getWidth() - 40) | |
| 452 | + | local height = math.min(520, parent:getHeight() - 140) | |
| 453 | + | charactersWindow:setSize({width = width, height = height}) | |
| 454 | + | charactersWindow:breakAnchors() | |
| 455 | + | charactersWindow:setPosition({ | |
| 456 | + | x = parent:getX() + math.floor((parent:getWidth() - width) / 2), | |
| 457 | + | y = parent:getY() + math.max(20, math.floor((parent:getHeight() - 90 - height) / 2)) | |
| 458 | + | }) | |
| 459 | + | charactersWindow:getChildById('characterList'):setWidth(math.floor((width - 48) * 0.48)) | |
| 460 | + | local preview = charactersWindow:getChildById('previewPanel') | |
| 461 | + | local outfitSize = math.max(80, math.min(200, height - 260)) | |
| 462 | + | preview:getChildById('previewOutfit'):setSize({width = outfitSize, height = outfitSize}) | |
| 463 | + | end | |
| 464 | + | ||
| 465 | + | function CharacterList.create(characters, account, otui) | |
| 466 | + | otui = otui or "characterlist" | |
| 467 | + | if charactersWindow then | |
| 468 | + | charactersWindow:destroy() | |
| 469 | + | end | |
| 470 | + | charactersWindow = g_ui.displayUI(otui) | |
| 471 | + | CharacterList.resizeWindow() | |
| 472 | + | characterList = charactersWindow:getChildById("characterList") | |
| 473 | + | autoReconnectButton = charactersWindow:getChildById("autoReconnect") | |
| 474 | + | G.characters = characters | |
| 475 | + | G.characterAccount = account | |
| 476 | + | ||
| 477 | + | table.sort(characters, function(a, b) | |
| 478 | + | return tonumber(a.level or 0) > tonumber(b.level or 0) | |
| 479 | + | end) | |
| 480 | + | ||
| 481 | + | characterList:destroyChildren() | |
| 482 | + | ||
| 483 | + | local focusWidget | |
| 484 | + | for i, characterInfo in ipairs(characters) do | |
| 485 | + | local widget = g_ui.createWidget("CharacterRow", characterList) | |
| 486 | + | widget:getChildById("outfit"):setOutfit(characterOutfit(characterInfo, false)) | |
| 487 | + | widget:getChildById("name"):setText(characterInfo.name or "") | |
| 488 | + | widget:getChildById("info"):setText(("%s %s - %s"):format(tr("Level"), characterInfo.level or 0, vocationName(characterInfo.vocation))) | |
| 489 | + | ||
| 490 | + | widget.characterInfo = characterInfo | |
| 491 | + | widget.characterName = characterInfo.name | |
| 492 | + | widget.worldName = characterInfo.worldName | |
| 493 | + | widget.worldHost = characterInfo.worldIp | |
| 494 | + | widget.worldPort = characterInfo.worldPort | |
| 495 | + | ||
| 496 | + | connect(widget, { | |
| 497 | + | onDoubleClick = function() | |
| 498 | + | CharacterList.doLogin() | |
| 499 | + | return true | |
| 500 | + | end | |
| 501 | + | }) | |
| 502 | + | ||
| 503 | + | if i == 1 or (g_settings.get("last-used-character") == widget.characterName and g_settings.get("last-used-world") == widget.worldName) then | |
| 504 | + | focusWidget = widget | |
| 505 | + | end | |
| 506 | + | end | |
| 507 | + | ||
| 508 | + | characterList.onChildFocusChange = function(self, focused) | |
| 509 | + | CharacterList.updatePreview(focused) | |
| 510 | + | end | |
| 511 | + | ||
| 512 | + | if focusWidget then | |
| 513 | + | characterList:focusChild(focusWidget, KeyboardFocusReason) | |
| 514 | + | addEvent(function() | |
| 515 | + | if characterList then | |
| 516 | + | characterList:ensureChildVisible(focusWidget) | |
| 517 | + | end | |
| 518 | + | end) | |
| 519 | + | end | |
| 520 | + | CharacterList.updatePreview(characterList:getFocusedChild()) | |
| 521 | + | ||
| 522 | + | g_keyboard.bindKeyPress("Up", function() | |
| 523 | + | characterList:focusPreviousChild(KeyboardFocusReason) | |
| 524 | + | characterList:ensureChildVisible(characterList:getFocusedChild()) | |
| 525 | + | end, charactersWindow) | |
| 526 | + | g_keyboard.bindKeyPress("Down", function() | |
| 527 | + | characterList:focusNextChild(KeyboardFocusReason) | |
| 528 | + | characterList:ensureChildVisible(characterList:getFocusedChild()) | |
| 529 | + | end, charactersWindow) | |
| 530 | + | ||
| 531 | + | local accountStatusLabel = charactersWindow:getChildById("accountStatusLabel") | |
| 532 | + | local status = "" | |
| 533 | + | if account.status == AccountStatus.Frozen then | |
| 534 | + | status = tr(" (Frozen)") | |
| 535 | + | elseif account.status == AccountStatus.Suspended then | |
| 536 | + | status = tr(" (Suspended)") | |
| 537 | + | end | |
| 538 | + | ||
| 539 | + | if account.subStatus == SubscriptionStatus.Free and account.premDays < 1 then | |
| 540 | + | accountStatusLabel:setText(("%s%s"):format(tr("Free Account"), status)) | |
| 541 | + | elseif account.premDays == 0 or account.premDays == 65535 then | |
| 542 | + | accountStatusLabel:setText(("%s%s"):format(tr("Gratis Premium Account"), status)) | |
| 543 | + | else | |
| 544 | + | accountStatusLabel:setText(("%s%s"):format(tr("Premium Account (%s) days left", account.premDays), status)) | |
| 545 | + | end | |
| 546 | + | accountStatusLabel:setOn(account.premDays > 0 and account.premDays <= 7) | |
| 547 | + | ||
| 548 | + | autoReconnectButton:setChecked(g_settings.getBoolean("autoReconnect", true)) | |
| 549 | + | autoReconnectButton.onCheckChange = function(widget, checked) | |
| 550 | + | g_settings.set("autoReconnect", checked) | |
| 551 | + | end | |
| 552 | + | end | |
| 553 | + | ||
| 554 | + | function CharacterList.destroy() | |
| 555 | + | CharacterList.hide(true) | |
| 556 | + | if charactersWindow then | |
| 557 | + | characterList = nil | |
| 558 | + | charactersWindow:destroy() | |
| 559 | + | charactersWindow = nil | |
| 560 | + | end | |
| 561 | + | end | |
| 562 | + | ||
| 563 | + | function CharacterList.show() | |
| 564 | + | if loadBox or errorBox or not charactersWindow then | |
| 565 | + | return | |
| 566 | + | end | |
| 567 | + | ||
| 568 | + | CharacterList.resizeWindow() | |
| 569 | + | charactersWindow:show() | |
| 570 | + | charactersWindow:raise() | |
| 571 | + | charactersWindow:focus() | |
| 572 | + | autoReconnectButton:setChecked(g_settings.getBoolean("autoReconnect", true)) | |
| 573 | + | end | |
| 574 | + | ||
| 575 | + | function CharacterList.hide(showLogin) | |
| 576 | + | removeEvent(autoReconnectEvent) | |
| 577 | + | autoReconnectEvent = nil | |
| 578 | + | if not charactersWindow then | |
| 579 | + | return | |
| 580 | + | end | |
| 581 | + | ||
| 582 | + | charactersWindow:hide() | |
| 583 | + | if showLogin and EnterGame and not g_game.isOnline() then | |
| 584 | + | EnterGame.show() | |
| 585 | + | end | |
| 586 | + | end | |
| 587 | + | ||
| 588 | + | function CharacterList.showAgain() | |
| 589 | + | if characterList and characterList:hasChildren() then | |
| 590 | + | CharacterList.show() | |
| 591 | + | EnterGame.onShowuIText() | |
| 592 | + | end | |
| 593 | + | end | |
| 594 | + | ||
| 595 | + | function CharacterList.isVisible() | |
| 596 | + | return charactersWindow ~= nil and charactersWindow:isVisible() | |
| 597 | + | end | |
| 598 | + | ||
| 599 | + | function CharacterList.doLogin() | |
| 600 | + | removeEvent(autoReconnectEvent) | |
| 601 | + | autoReconnectEvent = nil | |
| 602 | + | local selected = characterList and characterList:getFocusedChild() | |
| 603 | + | if selected then | |
| 604 | + | local charInfo = { | |
| 605 | + | worldHost = selected.worldHost, | |
| 606 | + | worldPort = selected.worldPort, | |
| 607 | + | worldName = selected.worldName, | |
| 608 | + | characterName = selected.characterName | |
| 609 | + | } | |
| 610 | + | if loginEvent then | |
| 611 | + | removeEvent(loginEvent) | |
| 612 | + | loginEvent = nil | |
| 613 | + | end | |
| 614 | + | tryLogin(charInfo) | |
| 615 | + | else | |
| 616 | + | displayErrorBox(tr("Error"), tr("You must select a character to login!")) | |
| 617 | + | end | |
| 618 | + | end | |
| 619 | + | ||
| 620 | + | function CharacterList.destroyLoadBox() | |
| 621 | + | if loadBox then | |
| 622 | + | loadBox:destroy() | |
| 623 | + | loadBox = nil | |
| 624 | + | end | |
| 625 | + | end | |
| 626 | + | ||
| 627 | + | function CharacterList.cancelWait() | |
| 628 | + | if waitingWindow then | |
| 629 | + | waitingWindow:destroy() | |
| 630 | + | ||
| 631 | + | waitingWindow = nil | |
| 632 | + | end | |
| 633 | + | if updateWaitEvent then | |
| 634 | + | removeEvent(updateWaitEvent) | |
| 635 | + | ||
| 636 | + | updateWaitEvent = nil | |
| 637 | + | end | |
| 638 | + | if resendWaitEvent then | |
| 639 | + | removeEvent(resendWaitEvent) | |
| 640 | + | ||
| 641 | + | resendWaitEvent = nil | |
| 642 | + | end | |
| 643 | + | end | |
| 644 | + | ||
| 645 | + | function applyOffsets(offsetX, offsetY) | |
| 646 | + | local shader = g_shaders.getShader('myShader') | |
| 647 | + | if shader then | |
| 648 | + | shader:setUniformValue('offsetX', offsetX) | |
| 649 | + | shader:setUniformValue('offsetY', offsetY) | |
| 650 | + | end | |
| 651 | + | end |
| @@ -0,0 +1,195 @@ | |||
| 1 | + | CharacterRow < UIWidget | |
| 2 | + | height: 60 | |
| 3 | + | focusable: true | |
| 4 | + | background-color: alpha | |
| 5 | + | border-width: 1 | |
| 6 | + | border-color: alpha | |
| 7 | + | margin-bottom: 2 | |
| 8 | + | &updateOnStates: | | |
| 9 | + | function(self) | |
| 10 | + | local focused = self:isFocused() | |
| 11 | + | for _, child in ipairs(self:getChildren()) do | |
| 12 | + | child:setOn(focused) | |
| 13 | + | end | |
| 14 | + | end | |
| 15 | + | @onFocusChange: self:updateOnStates() | |
| 16 | + | @onSetup: self:updateOnStates() | |
| 17 | + | ||
| 18 | + | $hover !focus: | |
| 19 | + | background-color: #ffffff10 | |
| 20 | + | ||
| 21 | + | $focus: | |
| 22 | + | background-color: #d4a84a26 | |
| 23 | + | border-color: #d4a84a88 | |
| 24 | + | ||
| 25 | + | UICreature | |
| 26 | + | id: outfit | |
| 27 | + | size: 48 48 | |
| 28 | + | anchors.left: parent.left | |
| 29 | + | anchors.verticalCenter: parent.verticalCenter | |
| 30 | + | margin-left: 4 | |
| 31 | + | animate: false | |
| 32 | + | phantom: true | |
| 33 | + | ||
| 34 | + | Label | |
| 35 | + | id: name | |
| 36 | + | anchors.left: outfit.right | |
| 37 | + | anchors.right: parent.right | |
| 38 | + | anchors.top: parent.top | |
| 39 | + | margin-left: 8 | |
| 40 | + | margin-top: 10 | |
| 41 | + | font: sono_bold_border_14 | |
| 42 | + | color: #dfdfdf | |
| 43 | + | phantom: true | |
| 44 | + | $on: | |
| 45 | + | color: #ffd27a | |
| 46 | + | ||
| 47 | + | Label | |
| 48 | + | id: info | |
| 49 | + | anchors.left: name.left | |
| 50 | + | anchors.right: parent.right | |
| 51 | + | anchors.top: name.bottom | |
| 52 | + | margin-top: 4 | |
| 53 | + | font: verdana-11px-antialised | |
| 54 | + | color: #8f8f8f | |
| 55 | + | phantom: true | |
| 56 | + | $on: | |
| 57 | + | color: #c8c8c8 | |
| 58 | + | ||
| 59 | + | PreviewLabel < Label | |
| 60 | + | anchors.left: parent.left | |
| 61 | + | anchors.right: parent.right | |
| 62 | + | text-align: center | |
| 63 | + | font: verdana-11px-antialised | |
| 64 | + | color: #c8c8c8 | |
| 65 | + | phantom: true | |
| 66 | + | ||
| 67 | + | StaticMainWindow | |
| 68 | + | id: charactersWindow | |
| 69 | + | !text: tr('Select Character') | |
| 70 | + | size: 760 520 | |
| 71 | + | visible: false | |
| 72 | + | focus-policy: StrongFocus | |
| 73 | + | @onEnter: CharacterList.doLogin() | |
| 74 | + | @onEscape: CharacterList.hide(true) | |
| 75 | + | ||
| 76 | + | TextList | |
| 77 | + | id: characterList | |
| 78 | + | anchors.top: parent.top | |
| 79 | + | anchors.left: parent.left | |
| 80 | + | anchors.bottom: separator.top | |
| 81 | + | margin-bottom: 10 | |
| 82 | + | width: 330 | |
| 83 | + | padding: 3 | |
| 84 | + | background-color: #00000055 | |
| 85 | + | border-width: 1 | |
| 86 | + | border-color: #00000099 | |
| 87 | + | vertical-scrollbar: characterListScrollBar | |
| 88 | + | ||
| 89 | + | VerticalScrollBar | |
| 90 | + | id: characterListScrollBar | |
| 91 | + | anchors.top: characterList.top | |
| 92 | + | anchors.bottom: characterList.bottom | |
| 93 | + | anchors.left: characterList.right | |
| 94 | + | step: 62 | |
| 95 | + | pixels-scroll: true | |
| 96 | + | ||
| 97 | + | Panel | |
| 98 | + | id: previewPanel | |
| 99 | + | anchors.top: parent.top | |
| 100 | + | anchors.left: characterListScrollBar.right | |
| 101 | + | anchors.right: parent.right | |
| 102 | + | anchors.bottom: separator.top | |
| 103 | + | margin-left: 10 | |
| 104 | + | margin-bottom: 10 | |
| 105 | + | background-color: #00000055 | |
| 106 | + | border-width: 1 | |
| 107 | + | border-color: #00000099 | |
| 108 | + | ||
| 109 | + | UICreature | |
| 110 | + | id: previewOutfit | |
| 111 | + | size: 200 200 | |
| 112 | + | anchors.top: parent.top | |
| 113 | + | anchors.horizontalCenter: parent.horizontalCenter | |
| 114 | + | margin-top: 26 | |
| 115 | + | animate: true | |
| 116 | + | phantom: true | |
| 117 | + | ||
| 118 | + | Label | |
| 119 | + | id: previewName | |
| 120 | + | anchors.top: previewOutfit.bottom | |
| 121 | + | anchors.left: parent.left | |
| 122 | + | anchors.right: parent.right | |
| 123 | + | margin-top: 12 | |
| 124 | + | text-align: center | |
| 125 | + | font: sono_bold_border_14 | |
| 126 | + | color: #ffd27a | |
| 127 | + | phantom: true | |
| 128 | + | ||
| 129 | + | PreviewLabel | |
| 130 | + | id: previewLevel | |
| 131 | + | anchors.top: previewName.bottom | |
| 132 | + | margin-top: 10 | |
| 133 | + | ||
| 134 | + | PreviewLabel | |
| 135 | + | id: previewVocation | |
| 136 | + | anchors.top: previewLevel.bottom | |
| 137 | + | margin-top: 4 | |
| 138 | + | color: #f4ca16 | |
| 139 | + | ||
| 140 | + | PreviewLabel | |
| 141 | + | id: previewWorld | |
| 142 | + | anchors.top: previewVocation.bottom | |
| 143 | + | margin-top: 4 | |
| 144 | + | color: #8f8f8f | |
| 145 | + | ||
| 146 | + | HorizontalSeparator | |
| 147 | + | id: separator | |
| 148 | + | anchors.left: parent.left | |
| 149 | + | anchors.right: parent.right | |
| 150 | + | anchors.bottom: buttonOk.top | |
| 151 | + | margin-bottom: 10 | |
| 152 | + | ||
| 153 | + | Label | |
| 154 | + | id: accountStatusCaption | |
| 155 | + | !text: tr('Account') .. ':' | |
| 156 | + | anchors.left: parent.left | |
| 157 | + | anchors.verticalCenter: buttonOk.verticalCenter | |
| 158 | + | text-auto-resize: true | |
| 159 | + | color: #8f8f8f | |
| 160 | + | ||
| 161 | + | Label | |
| 162 | + | id: accountStatusLabel | |
| 163 | + | !text: tr('Free Account') | |
| 164 | + | anchors.left: accountStatusCaption.right | |
| 165 | + | anchors.verticalCenter: buttonOk.verticalCenter | |
| 166 | + | margin-left: 5 | |
| 167 | + | text-auto-resize: true | |
| 168 | + | color: #dfdfdf | |
| 169 | + | $on: | |
| 170 | + | color: #ff9a4a | |
| 171 | + | ||
| 172 | + | CheckBox | |
| 173 | + | id: autoReconnect | |
| 174 | + | !text: tr('Auto reconnect') | |
| 175 | + | anchors.left: accountStatusLabel.right | |
| 176 | + | anchors.verticalCenter: buttonOk.verticalCenter | |
| 177 | + | margin-left: 16 | |
| 178 | + | width: 120 | |
| 179 | + | ||
| 180 | + | Button | |
| 181 | + | id: buttonCancel | |
| 182 | + | !text: tr('Back') | |
| 183 | + | width: 96 | |
| 184 | + | anchors.right: buttonOk.left | |
| 185 | + | anchors.bottom: parent.bottom | |
| 186 | + | margin-right: 8 | |
| 187 | + | @onClick: CharacterList.hide(true) | |
| 188 | + | ||
| 189 | + | Button | |
| 190 | + | id: buttonOk | |
| 191 | + | !text: tr('Play') | |
| 192 | + | width: 96 | |
| 193 | + | anchors.right: parent.right | |
| 194 | + | anchors.bottom: parent.bottom | |
| 195 | + | @onClick: CharacterList.doLogin() |
| @@ -0,0 +1,704 @@ | |||
| 1 | + | EnterGame = {} | |
| 2 | + | local loadBox, enterGame, enterGameButton, logpass, clientBox, protocolLogin, server = nil | |
| 3 | + | local versionsFound = false | |
| 4 | + | local rememberPasswordBox = nil | |
| 5 | + | local protos = { | |
| 6 | + | "1098" | |
| 7 | + | } | |
| 8 | + | local checkedByUpdater = {} | |
| 9 | + | local waitingForHttpResults = 0 | |
| 10 | + | local server = "127.0.0.1:7171" | |
| 11 | + | local clientVersion = 1098 | |
| 12 | + | ||
| 13 | + | local function onProtocolError(protocol, message, errorCode) | |
| 14 | + | if errorCode then | |
| 15 | + | return EnterGame.onError(message) | |
| 16 | + | end | |
| 17 | + | ||
| 18 | + | return EnterGame.onLoginError(message) | |
| 19 | + | end | |
| 20 | + | ||
| 21 | + | local function onSessionKey(protocol, sessionKey) | |
| 22 | + | G.sessionKey = sessionKey | |
| 23 | + | end | |
| 24 | + | ||
| 25 | + | local function onCharacterList(protocol, characters, account, otui) | |
| 26 | + | if rememberPasswordBox:isChecked() then | |
| 27 | + | local account = g_crypt.encrypt(G.account) | |
| 28 | + | local password = g_crypt.encrypt(G.password) | |
| 29 | + | ||
| 30 | + | g_settings.set("account", account) | |
| 31 | + | g_settings.set("password", password) | |
| 32 | + | else | |
| 33 | + | EnterGame.clearAccountFields() | |
| 34 | + | end | |
| 35 | + | ||
| 36 | + | for _, characterInfo in pairs(characters) do | |
| 37 | + | if characterInfo.previewState and characterInfo.previewState ~= PreviewState.Default then | |
| 38 | + | characterInfo.worldName = characterInfo.worldName .. ", Preview" | |
| 39 | + | end | |
| 40 | + | end | |
| 41 | + | ||
| 42 | + | if loadBox then | |
| 43 | + | loadBox:destroy() | |
| 44 | + | ||
| 45 | + | loadBox = nil | |
| 46 | + | end | |
| 47 | + | ||
| 48 | + | CharacterList.create(characters, account, otui) | |
| 49 | + | CharacterList.show() | |
| 50 | + | g_settings.save() | |
| 51 | + | end | |
| 52 | + | ||
| 53 | + | local function onUpdateNeeded(protocol, signature) | |
| 54 | + | return EnterGame.onError(tr("Your client needs updating, try redownloading it.")) | |
| 55 | + | end | |
| 56 | + | ||
| 57 | + | local function onProxyList(protocol, proxies) | |
| 58 | + | for _, proxy in ipairs(proxies) do | |
| 59 | + | g_proxy.addProxy(proxy.host, proxy.port, proxy.priority) | |
| 60 | + | end | |
| 61 | + | end | |
| 62 | + | ||
| 63 | + | local function parseFeatures(features) | |
| 64 | + | for feature_id, value in pairs(features) do | |
| 65 | + | if value == "1" or value == "true" or value == true then | |
| 66 | + | g_game.enableFeature(feature_id) | |
| 67 | + | else | |
| 68 | + | g_game.disableFeature(feature_id) | |
| 69 | + | end | |
| 70 | + | end | |
| 71 | + | end | |
| 72 | + | ||
| 73 | + | local function validateThings(things) | |
| 74 | + | local incorrectThings = "" | |
| 75 | + | local missingFiles = false | |
| 76 | + | local versionForMissingFiles = 0 | |
| 77 | + | ||
| 78 | + | if things ~= nil then | |
| 79 | + | local thingsNode = {} | |
| 80 | + | ||
| 81 | + | for thingtype, thingdata in pairs(things) do | |
| 82 | + | thingsNode[thingtype] = thingdata[1] | |
| 83 | + | ||
| 84 | + | if not g_resources.fileExists("/things/" .. thingdata[1]) then | |
| 85 | + | incorrectThings = incorrectThings .. "Missing file: " .. thingdata[1] .. "\n" | |
| 86 | + | missingFiles = true | |
| 87 | + | versionForMissingFiles = thingdata[1]:split("/")[1] | |
| 88 | + | else | |
| 89 | + | local localChecksum = g_resources.fileChecksum("/things/" .. thingdata[1]):lower() | |
| 90 | + | ||
| 91 | + | if localChecksum ~= thingdata[2]:lower() and #thingdata[2] > 1 and g_resources.isLoadedFromArchive() then | |
| 92 | + | incorrectThings = incorrectThings .. "Invalid checksum of file: " .. thingdata[1] .. " (is " .. localChecksum .. ", should be " .. thingdata[2]:lower() .. ")\n" | |
| 93 | + | end | |
| 94 | + | end | |
| 95 | + | end | |
| 96 | + | ||
| 97 | + | g_settings.setNode("things", thingsNode) | |
| 98 | + | else | |
| 99 | + | g_settings.setNode("things", {}) | |
| 100 | + | end | |
| 101 | + | ||
| 102 | + | if missingFiles then | |
| 103 | + | incorrectThings = incorrectThings .. "\nYou should open data/things and create directory " .. versionForMissingFiles .. ".\nIn this directory (data/things/" .. versionForMissingFiles .. ") you should put missing\nfiles (Tibia.dat and Tibia.spr/Tibia.cwm) " .. "from correct Tibia version." | |
| 104 | + | end | |
| 105 | + | ||
| 106 | + | return incorrectThings | |
| 107 | + | end | |
| 108 | + | ||
| 109 | + | local function onTibia12HTTPResult(session, playdata) | |
| 110 | + | local characters = {} | |
| 111 | + | local worlds = {} | |
| 112 | + | local account = { | |
| 113 | + | subStatus = 0, | |
| 114 | + | premDays = 0, | |
| 115 | + | status = 0 | |
| 116 | + | } | |
| 117 | + | ||
| 118 | + | if session.status ~= "active" then | |
| 119 | + | account.status = 1 | |
| 120 | + | end | |
| 121 | + | ||
| 122 | + | if session.ispremium then | |
| 123 | + | account.subStatus = 1 | |
| 124 | + | end | |
| 125 | + | ||
| 126 | + | if g_clock.seconds() < session.premiumuntil then | |
| 127 | + | account.subStatus = math.floor((session.premiumuntil - g_clock.seconds()) / 86400) | |
| 128 | + | end | |
| 129 | + | ||
| 130 | + | local things = { | |
| 131 | + | data = { | |
| 132 | + | G.clientVersion .. "/Tibia.dat", | |
| 133 | + | "" | |
| 134 | + | }, | |
| 135 | + | sprites = { | |
| 136 | + | G.clientVersion .. "/Tibia.cwm", | |
| 137 | + | "" | |
| 138 | + | } | |
| 139 | + | } | |
| 140 | + | local incorrectThings = validateThings(things) | |
| 141 | + | ||
| 142 | + | if #incorrectThings > 0 then | |
| 143 | + | things = { | |
| 144 | + | data = { | |
| 145 | + | G.clientVersion .. "/Tibia.dat", | |
| 146 | + | "" | |
| 147 | + | }, | |
| 148 | + | sprites = { | |
| 149 | + | G.clientVersion .. "/Tibia.spr", | |
| 150 | + | "" | |
| 151 | + | } | |
| 152 | + | } | |
| 153 | + | incorrectThings = validateThings(things) | |
| 154 | + | end | |
| 155 | + | ||
| 156 | + | if #incorrectThings > 0 then | |
| 157 | + | g_logger.error(incorrectThings) | |
| 158 | + | ||
| 159 | + | if Updater and not checkedByUpdater[G.clientVersion] then | |
| 160 | + | checkedByUpdater[G.clientVersion] = true | |
| 161 | + | ||
| 162 | + | return Updater.check({ | |
| 163 | + | version = G.clientVersion, | |
| 164 | + | host = G.host | |
| 165 | + | }) | |
| 166 | + | else | |
| 167 | + | return EnterGame.onError(incorrectThings) | |
| 168 | + | end | |
| 169 | + | end | |
| 170 | + | ||
| 171 | + | onSessionKey(nil, session.sessionkey) | |
| 172 | + | ||
| 173 | + | for _, world in pairs(playdata.worlds) do | |
| 174 | + | worlds[world.id] = { | |
| 175 | + | name = world.name, | |
| 176 | + | port = world.externalportunprotected or world.externalportprotected or world.externaladdress, | |
| 177 | + | address = world.externaladdressunprotected or world.externaladdressprotected or world.externalport | |
| 178 | + | } | |
| 179 | + | end | |
| 180 | + | ||
| 181 | + | for _, character in pairs(playdata.characters) do | |
| 182 | + | local world = worlds[character.worldid] | |
| 183 | + | ||
| 184 | + | if world then | |
| 185 | + | table.insert(characters, { | |
| 186 | + | name = character.name, | |
| 187 | + | worldName = world.name, | |
| 188 | + | worldIp = world.address, | |
| 189 | + | worldPort = world.port | |
| 190 | + | }) | |
| 191 | + | end | |
| 192 | + | end | |
| 193 | + | ||
| 194 | + | if g_proxy then | |
| 195 | + | g_proxy.clear() | |
| 196 | + | ||
| 197 | + | if playdata.proxies then | |
| 198 | + | for i, proxy in ipairs(playdata.proxies) do | |
| 199 | + | g_proxy.addProxy(proxy.host, tonumber(proxy.port), tonumber(proxy.priority)) | |
| 200 | + | end | |
| 201 | + | end | |
| 202 | + | end | |
| 203 | + | ||
| 204 | + | g_game.setCustomProtocolVersion(0) | |
| 205 | + | g_game.chooseRsa(G.host) | |
| 206 | + | g_game.setClientVersion(G.clientVersion) | |
| 207 | + | g_game.setProtocolVersion(g_game.getClientProtocolVersion(G.clientVersion)) | |
| 208 | + | g_game.setCustomOs(-1) | |
| 209 | + | ||
| 210 | + | if not g_game.getFeature(GameExtendedOpcode) then | |
| 211 | + | g_game.setCustomOs(5) | |
| 212 | + | end | |
| 213 | + | ||
| 214 | + | onCharacterList(nil, characters, account, nil) | |
| 215 | + | end | |
| 216 | + | ||
| 217 | + | local function onHTTPResult(data, err) | |
| 218 | + | if waitingForHttpResults == 0 then | |
| 219 | + | return | |
| 220 | + | end | |
| 221 | + | ||
| 222 | + | waitingForHttpResults = waitingForHttpResults - 1 | |
| 223 | + | ||
| 224 | + | if err and waitingForHttpResults > 0 then | |
| 225 | + | return | |
| 226 | + | end | |
| 227 | + | ||
| 228 | + | if err then | |
| 229 | + | return EnterGame.onError(err) | |
| 230 | + | end | |
| 231 | + | ||
| 232 | + | waitingForHttpResults = 0 | |
| 233 | + | ||
| 234 | + | if data.error and data.error:len() > 0 then | |
| 235 | + | return EnterGame.onLoginError(data.error) | |
| 236 | + | elseif data.errorMessage and data.errorMessage:len() > 0 then | |
| 237 | + | return EnterGame.onLoginError(data.errorMessage) | |
| 238 | + | end | |
| 239 | + | ||
| 240 | + | if type(data.session) == "table" and type(data.playdata) == "table" then | |
| 241 | + | return onTibia12HTTPResult(data.session, data.playdata) | |
| 242 | + | end | |
| 243 | + | ||
| 244 | + | local characters = data.characters | |
| 245 | + | local account = data.account | |
| 246 | + | local session = data.session | |
| 247 | + | local version = data.version | |
| 248 | + | local things = data.things | |
| 249 | + | local customProtocol = data.customProtocol | |
| 250 | + | local features = data.features | |
| 251 | + | local settings = data.settings | |
| 252 | + | local rsa = data.rsa | |
| 253 | + | local proxies = data.proxies | |
| 254 | + | local incorrectThings = validateThings(things) | |
| 255 | + | ||
| 256 | + | if #incorrectThings > 0 then | |
| 257 | + | g_logger.info(incorrectThings) | |
| 258 | + | ||
| 259 | + | return EnterGame.onError(incorrectThings) | |
| 260 | + | end | |
| 261 | + | ||
| 262 | + | g_game.setCustomProtocolVersion(0) | |
| 263 | + | ||
| 264 | + | if customProtocol ~= nil then | |
| 265 | + | customProtocol = tonumber(customProtocol) | |
| 266 | + | ||
| 267 | + | if customProtocol ~= nil and customProtocol > 0 then | |
| 268 | + | g_game.setCustomProtocolVersion(customProtocol) | |
| 269 | + | end | |
| 270 | + | end | |
| 271 | + | ||
| 272 | + | if settings ~= nil then | |
| 273 | + | for option, value in pairs(settings) do | |
| 274 | + | modules.client_options.setOption(option, value, true) | |
| 275 | + | end | |
| 276 | + | end | |
| 277 | + | ||
| 278 | + | G.clientVersion = version | |
| 279 | + | ||
| 280 | + | g_game.setClientVersion(version) | |
| 281 | + | g_game.setProtocolVersion(g_game.getClientProtocolVersion(version)) | |
| 282 | + | g_game.setCustomOs(-1) | |
| 283 | + | ||
| 284 | + | if rsa ~= nil then | |
| 285 | + | g_game.setRsa(rsa) | |
| 286 | + | end | |
| 287 | + | ||
| 288 | + | if features ~= nil then | |
| 289 | + | parseFeatures(features) | |
| 290 | + | end | |
| 291 | + | ||
| 292 | + | if session ~= nil and session:len() > 0 then | |
| 293 | + | onSessionKey(nil, session) | |
| 294 | + | end | |
| 295 | + | ||
| 296 | + | if g_proxy then | |
| 297 | + | g_proxy.clear() | |
| 298 | + | ||
| 299 | + | if proxies then | |
| 300 | + | for i, proxy in ipairs(proxies) do | |
| 301 | + | g_proxy.addProxy(proxy.host, tonumber(proxy.port), tonumber(proxy.priority)) | |
| 302 | + | end | |
| 303 | + | end | |
| 304 | + | end | |
| 305 | + | ||
| 306 | + | onCharacterList(nil, characters, account, nil) | |
| 307 | + | end | |
| 308 | + | ||
| 309 | + | function EnterGame.init() | |
| 310 | + | if USE_NEW_ENERGAME then | |
| 311 | + | return | |
| 312 | + | end | |
| 313 | + | ||
| 314 | + | enterGame = g_ui.displayUI("entergame") | |
| 315 | + | ||
| 316 | + | if LOGPASS ~= nil then | |
| 317 | + | logpass = g_ui.loadUI("logpass", enterGame:getParent()) | |
| 318 | + | end | |
| 319 | + | ||
| 320 | + | rememberPasswordBox = enterGame:getChildById("rememberPasswordBox") | |
| 321 | + | ||
| 322 | + | local account = g_crypt.decrypt(g_settings.get("account")) | |
| 323 | + | local password = g_crypt.decrypt(g_settings.get("password")) | |
| 324 | + | ||
| 325 | + | enterGame:getChildById("accountPasswordTextEdit"):setText(password) | |
| 326 | + | enterGame:getChildById("accountNameTextEdit"):setText(account) | |
| 327 | + | rememberPasswordBox:setChecked(#account > 0) | |
| 328 | + | --g_keyboard.bindKeyDown("Ctrl+G", EnterGame.openWindow) | |
| 329 | + | ||
| 330 | + | if g_game.isOnline() then | |
| 331 | + | return EnterGame.hide() | |
| 332 | + | end | |
| 333 | + | ||
| 334 | + | scheduleEvent(function () | |
| 335 | + | EnterGame.show() | |
| 336 | + | end, 100) | |
| 337 | + | end | |
| 338 | + | ||
| 339 | + | function EnterGame.terminate() | |
| 340 | + | if not enterGame then | |
| 341 | + | return | |
| 342 | + | end | |
| 343 | + | ||
| 344 | + | g_keyboard.unbindKeyDown("Ctrl+G") | |
| 345 | + | ||
| 346 | + | if logpass then | |
| 347 | + | logpass:destroy() | |
| 348 | + | ||
| 349 | + | logpass = nil | |
| 350 | + | end | |
| 351 | + | ||
| 352 | + | enterGame:destroy() | |
| 353 | + | ||
| 354 | + | if loadBox then | |
| 355 | + | loadBox:destroy() | |
| 356 | + | ||
| 357 | + | loadBox = nil | |
| 358 | + | end | |
| 359 | + | ||
| 360 | + | if protocolLogin then | |
| 361 | + | protocolLogin:cancelLogin() | |
| 362 | + | ||
| 363 | + | protocolLogin = nil | |
| 364 | + | end | |
| 365 | + | ||
| 366 | + | EnterGame = nil | |
| 367 | + | end | |
| 368 | + | ||
| 369 | + | function EnterGame.show() | |
| 370 | + | if not enterGame then | |
| 371 | + | enterGame = g_ui.displayUI("entergame") | |
| 372 | + | end | |
| 373 | + | EnterGame.interactable = true | |
| 374 | + | enterGame:setOpacity(1) | |
| 375 | + | enterGame:show() | |
| 376 | + | enterGame:raise() | |
| 377 | + | enterGame:focus() | |
| 378 | + | ||
| 379 | + | local accountEdit = enterGame:getChildById("accountNameTextEdit") | |
| 380 | + | if accountEdit:getText():len() > 0 then | |
| 381 | + | enterGame:getChildById("accountPasswordTextEdit"):focus() | |
| 382 | + | else | |
| 383 | + | accountEdit:focus() | |
| 384 | + | end | |
| 385 | + | end | |
| 386 | + | ||
| 387 | + | function EnterGame.hide() | |
| 388 | + | if not enterGame then | |
| 389 | + | return | |
| 390 | + | end | |
| 391 | + | ||
| 392 | + | enterGame:hide() | |
| 393 | + | ||
| 394 | + | if logpass then | |
| 395 | + | logpass:hide() | |
| 396 | + | ||
| 397 | + | if modules.logpass then | |
| 398 | + | modules.logpass:hide() | |
| 399 | + | end | |
| 400 | + | end | |
| 401 | + | end | |
| 402 | + | ||
| 403 | + | function EnterGame.openWindow() | |
| 404 | + | if g_game.isOnline() then | |
| 405 | + | CharacterList.show() | |
| 406 | + | elseif not g_game.isLogging() and not CharacterList.isVisible() then | |
| 407 | + | EnterGame.show() | |
| 408 | + | end | |
| 409 | + | end | |
| 410 | + | ||
| 411 | + | function EnterGame.clearAccountFields() | |
| 412 | + | enterGame:getChildById("accountNameTextEdit"):clearText() | |
| 413 | + | enterGame:getChildById("accountPasswordTextEdit"):clearText() | |
| 414 | + | enterGame:getChildById("accountNameTextEdit"):focus() | |
| 415 | + | g_settings.remove("account") | |
| 416 | + | g_settings.remove("password") | |
| 417 | + | end | |
| 418 | + | ||
| 419 | + | function EnterGame.doLogin(account, password) | |
| 420 | + | if loadBox then return end | |
| 421 | + | if g_game.isOnline() then | |
| 422 | + | local errorBox = displayErrorBox(tr("Login Error"), tr("Cannot login while already in game.")) | |
| 423 | + | ||
| 424 | + | connect(errorBox, { | |
| 425 | + | onOk = EnterGame.show | |
| 426 | + | }) | |
| 427 | + | ||
| 428 | + | return | |
| 429 | + | end | |
| 430 | + | ||
| 431 | + | G.account = account or enterGame:getChildById("accountNameTextEdit"):getText() | |
| 432 | + | G.password = password or enterGame:getChildById("accountPasswordTextEdit"):getText() | |
| 433 | + | G.stayLogged = true | |
| 434 | + | G.server = server | |
| 435 | + | G.host = server:split(":")[1] | |
| 436 | + | G.clientVersion = clientVersion | |
| 437 | + | ||
| 438 | + | g_settings.set("host", G.host) | |
| 439 | + | g_settings.set("server", G.server) | |
| 440 | + | g_settings.set("client-version", G.clientVersion) | |
| 441 | + | g_settings.save() | |
| 442 | + | ||
| 443 | + | local server_ip = G.host | |
| 444 | + | local server_port = 7171 | |
| 445 | + | ||
| 446 | + | if #server:split(":") >= 2 then | |
| 447 | + | server_port = tonumber(server:split(":")[2]) | |
| 448 | + | end | |
| 449 | + | ||
| 450 | + | if type(server_ip) ~= "string" or server_ip:len() <= 3 or not server_port or not G.clientVersion then | |
| 451 | + | return EnterGame.onError("Invalid server, it should be in format IP:PORT or it should be http url to login script") | |
| 452 | + | end | |
| 453 | + | ||
| 454 | + | local things = { | |
| 455 | + | data = { | |
| 456 | + | G.clientVersion .. "/Tibia.dat", | |
| 457 | + | "" | |
| 458 | + | }, | |
| 459 | + | sprites = { | |
| 460 | + | G.clientVersion .. "/Tibia.cwm", | |
| 461 | + | "" | |
| 462 | + | } | |
| 463 | + | } | |
| 464 | + | local incorrectThings = validateThings(things) | |
| 465 | + | ||
| 466 | + | if #incorrectThings > 0 then | |
| 467 | + | things = { | |
| 468 | + | data = { | |
| 469 | + | G.clientVersion .. "/Tibia.dat", | |
| 470 | + | "" | |
| 471 | + | }, | |
| 472 | + | sprites = { | |
| 473 | + | G.clientVersion .. "/Tibia.spr", | |
| 474 | + | "" | |
| 475 | + | } | |
| 476 | + | } | |
| 477 | + | incorrectThings = validateThings(things) | |
| 478 | + | end | |
| 479 | + | ||
| 480 | + | if #incorrectThings > 0 then | |
| 481 | + | g_logger.error(incorrectThings) | |
| 482 | + | ||
| 483 | + | if Updater and not checkedByUpdater[G.clientVersion] then | |
| 484 | + | checkedByUpdater[G.clientVersion] = true | |
| 485 | + | ||
| 486 | + | return Updater.check({ | |
| 487 | + | version = G.clientVersion, | |
| 488 | + | host = G.host | |
| 489 | + | }) | |
| 490 | + | else | |
| 491 | + | return EnterGame.onError(incorrectThings) | |
| 492 | + | end | |
| 493 | + | end | |
| 494 | + | ||
| 495 | + | protocolLogin = ProtocolLogin.create() | |
| 496 | + | protocolLogin.onLoginError = onProtocolError | |
| 497 | + | protocolLogin.onSessionKey = onSessionKey | |
| 498 | + | protocolLogin.onCharacterList = onCharacterList | |
| 499 | + | protocolLogin.onUpdateNeeded = onUpdateNeeded | |
| 500 | + | protocolLogin.onProxyList = onProxyList | |
| 501 | + | ||
| 502 | + | EnterGame.hide() | |
| 503 | + | ||
| 504 | + | loadBox = displayCancelBox(tr("Please wait"), tr("Connecting to login server...")) | |
| 505 | + | ||
| 506 | + | connect(loadBox, { | |
| 507 | + | onCancel = function (msgbox) | |
| 508 | + | loadBox = nil | |
| 509 | + | ||
| 510 | + | protocolLogin:cancelLogin() | |
| 511 | + | EnterGame.show() | |
| 512 | + | end | |
| 513 | + | }) | |
| 514 | + | ||
| 515 | + | if G.clientVersion == 1000 then | |
| 516 | + | G.clientVersion = 1100 | |
| 517 | + | end | |
| 518 | + | ||
| 519 | + | g_game.setClientVersion(G.clientVersion) | |
| 520 | + | g_game.setProtocolVersion(g_game.getClientProtocolVersion(G.clientVersion)) | |
| 521 | + | g_game.setCustomProtocolVersion(0) | |
| 522 | + | g_game.setCustomOs(-1) | |
| 523 | + | g_game.chooseRsa(G.host) | |
| 524 | + | ||
| 525 | + | if #server:split(":") <= 3 and not g_game.getFeature(GameExtendedOpcode) then | |
| 526 | + | g_game.setCustomOs(2) | |
| 527 | + | end | |
| 528 | + | ||
| 529 | + | for i = 4, #server:split(":") do | |
| 530 | + | g_game.enableFeature(tonumber(server:split(":")[i])) | |
| 531 | + | end | |
| 532 | + | ||
| 533 | + | if g_proxy then | |
| 534 | + | g_proxy.clear() | |
| 535 | + | end | |
| 536 | + | ||
| 537 | + | if modules.game_things.isLoaded() then | |
| 538 | + | g_logger.info("Connecting to: " .. server_ip .. ":" .. server_port) | |
| 539 | + | protocolLogin:login(server_ip, server_port, G.account, G.password, G.stayLogged) | |
| 540 | + | else | |
| 541 | + | loadBox:destroy() | |
| 542 | + | ||
| 543 | + | loadBox = nil | |
| 544 | + | ||
| 545 | + | EnterGame.show() | |
| 546 | + | end | |
| 547 | + | end | |
| 548 | + | ||
| 549 | + | function EnterGame.doLoginHttp() | |
| 550 | + | if G.host == nil or G.host:len() < 10 then | |
| 551 | + | return EnterGame.onError("Invalid server url: " .. G.host) | |
| 552 | + | end | |
| 553 | + | ||
| 554 | + | loadBox = displayCancelBox(tr("Please wait"), tr("Connecting to login server...")) | |
| 555 | + | ||
| 556 | + | connect(loadBox, { | |
| 557 | + | onCancel = function (msgbox) | |
| 558 | + | loadBox = nil | |
| 559 | + | ||
| 560 | + | EnterGame.show() | |
| 561 | + | end | |
| 562 | + | }) | |
| 563 | + | ||
| 564 | + | local data = { | |
| 565 | + | stayloggedin = true, | |
| 566 | + | type = "login", | |
| 567 | + | account = G.account, | |
| 568 | + | accountname = G.account, | |
| 569 | + | email = G.account, | |
| 570 | + | password = G.password, | |
| 571 | + | accountpassword = G.password, | |
| 572 | + | version = APP_VERSION, | |
| 573 | + | uid = G.UUID | |
| 574 | + | } | |
| 575 | + | local server = serverSelector:getText() | |
| 576 | + | ||
| 577 | + | if Servers and Servers[server] ~= nil then | |
| 578 | + | if type(Servers[server]) == "table" then | |
| 579 | + | local urls = Servers[server] | |
| 580 | + | waitingForHttpResults = #urls | |
| 581 | + | ||
| 582 | + | for _, url in ipairs(urls) do | |
| 583 | + | HTTP.postJSON(url, data, onHTTPResult) | |
| 584 | + | end | |
| 585 | + | else | |
| 586 | + | waitingForHttpResults = 1 | |
| 587 | + | ||
| 588 | + | HTTP.postJSON(G.host, data, onHTTPResult) | |
| 589 | + | end | |
| 590 | + | end | |
| 591 | + | ||
| 592 | + | EnterGame.hide() | |
| 593 | + | end | |
| 594 | + | ||
| 595 | + | function EnterGame.onError(err) | |
| 596 | + | if loadBox then | |
| 597 | + | loadBox:destroy() | |
| 598 | + | ||
| 599 | + | loadBox = nil | |
| 600 | + | end | |
| 601 | + | ||
| 602 | + | local errorBox = displayErrorBox(tr("Login Error"), err) | |
| 603 | + | errorBox.onOk = EnterGame.show | |
| 604 | + | end | |
| 605 | + | ||
| 606 | + | function EnterGame.onLoginError(err) | |
| 607 | + | if loadBox then | |
| 608 | + | loadBox:destroy() | |
| 609 | + | ||
| 610 | + | loadBox = nil | |
| 611 | + | end | |
| 612 | + | ||
| 613 | + | local errorBox = displayErrorBox(tr("Login Error"), err) | |
| 614 | + | errorBox.onOk = EnterGame.show | |
| 615 | + | ||
| 616 | + | if err:lower():find("invalid") or err:lower():find("not correct") or err:lower():find("or password") then | |
| 617 | + | enterGame:getChildById("accountPasswordTextEdit"):clearText() | |
| 618 | + | g_settings.remove("password") | |
| 619 | + | end | |
| 620 | + | end | |
| 621 | + | ||
| 622 | + | function openRecuperarconta() | |
| 623 | + | if acceptWindow then | |
| 624 | + | return true | |
| 625 | + | end | |
| 626 | + | ||
| 627 | + | local function acceptFunc() | |
| 628 | + | os.execute("start https://youtube") | |
| 629 | + | acceptWindow:destroy() | |
| 630 | + | ||
| 631 | + | acceptWindow = nil | |
| 632 | + | end | |
| 633 | + | ||
| 634 | + | local function cancelFunc() | |
| 635 | + | acceptWindow:destroy() | |
| 636 | + | ||
| 637 | + | acceptWindow = nil | |
| 638 | + | end | |
| 639 | + | ||
| 640 | + | acceptWindow = displayGeneralBox(tr("Recuperar Conta"), tr("Deseja recuperar sua conta?"), { | |
| 641 | + | { | |
| 642 | + | text = tr("Yes"), | |
| 643 | + | callback = acceptFunc | |
| 644 | + | }, | |
| 645 | + | { | |
| 646 | + | text = tr("No"), | |
| 647 | + | callback = cancelFunc | |
| 648 | + | }, | |
| 649 | + | anchor = AnchorHorizontalCenter | |
| 650 | + | }, acceptFunc, cancelFunc) | |
| 651 | + | ||
| 652 | + | return true | |
| 653 | + | end | |
| 654 | + | ||
| 655 | + | function openCriarconta() | |
| 656 | + | if acceptWindow then | |
| 657 | + | return true | |
| 658 | + | end | |
| 659 | + | ||
| 660 | + | local function acceptFunc() | |
| 661 | + | os.execute("start https://youtube") | |
| 662 | + | acceptWindow:destroy() | |
| 663 | + | ||
| 664 | + | acceptWindow = nil | |
| 665 | + | end | |
| 666 | + | ||
| 667 | + | local function cancelFunc() | |
| 668 | + | acceptWindow:destroy() | |
| 669 | + | ||
| 670 | + | acceptWindow = nil | |
| 671 | + | end | |
| 672 | + | ||
| 673 | + | acceptWindow = displayGeneralBox(tr("Criar Conta"), tr("Deseja criar sua Conta?"), { | |
| 674 | + | { | |
| 675 | + | text = tr("Yes"), | |
| 676 | + | callback = acceptFunc | |
| 677 | + | }, | |
| 678 | + | { | |
| 679 | + | text = tr("No"), | |
| 680 | + | callback = cancelFunc | |
| 681 | + | }, | |
| 682 | + | anchor = AnchorHorizontalCenter | |
| 683 | + | }, acceptFunc, cancelFunc) | |
| 684 | + | ||
| 685 | + | return true | |
| 686 | + | end | |
| 687 | + | ||
| 688 | + | function EnterGame.onCloseuIText() | |
| 689 | + | -- Kept for compatibility with character-list modules; the welcome panel is retired. | |
| 690 | + | end | |
| 691 | + | ||
| 692 | + | function EnterGame.onShowuIText() | |
| 693 | + | -- Kept for compatibility with character-list modules; the welcome panel is retired. | |
| 694 | + | end | |
| 695 | + | ||
| 696 | + | function EnterGame.EnableSound(value) | |
| 697 | + | if value == 01 then | |
| 698 | + | -- Enable sound logic | |
| 699 | + | g_sounds.setAudioEnabled(true) | |
| 700 | + | elseif value == 02 then | |
| 701 | + | -- Disable sound logic | |
| 702 | + | g_sounds.setAudioEnabled(false) | |
| 703 | + | end | |
| 704 | + | end |
| @@ -0,0 +1,13 @@ | |||
| 1 | + | Module | |
| 2 | + | name: client_entergame | |
| 3 | + | description: Manages enter game and character list windows | |
| 4 | + | author: edubart & otclient.ovh | |
| 5 | + | website: https://github.com/edubart/otclient | |
| 6 | + | security: jR$5p@9Qz! | |
| 7 | + | scripts: [ entergame, characterlist ] | |
| 8 | + | @onLoad: EnterGame.init() CharacterList.init() | |
| 9 | + | @onUnload: EnterGame.terminate() CharacterList.terminate() | |
| 10 | + | ||
| 11 | + | load-later: | |
| 12 | + | - game_things | |
| 13 | + | - game_features |