diff --git a/modules/client_entergamev2/entergamev2.lua b/modules/client_entergamev2/entergamev2.lua index a180b04..608829f 100644 --- a/modules/client_entergamev2/entergamev2.lua +++ b/modules/client_entergamev2/entergamev2.lua @@ -1,26 +1,27 @@ local entergameWindow local characterGroup +local outfitGroup +local protocol +local infoBox +local loadingBox function init() if not USE_NEW_ENERGAME then return end entergameWindow = g_ui.displayUI('entergamev2') - --entergameWindow.news:hide() - --entergameWindow.quick:hide() + entergameWindow.news:hide() + entergameWindow.quick:hide() entergameWindow.registration:hide() entergameWindow.characters:hide() entergameWindow.createcharacter:hide() + entergameWindow.settings:hide() -- entergame entergameWindow.entergame.register.onClick = function() entergameWindow.registration:show() entergameWindow.entergame:hide() end - entergameWindow.entergame.mainPanel.button.onClick = function() - entergameWindow.entergame:hide() - entergameWindow.characters:show() - g_game.setClientVersion(1099) -- for tests - end + entergameWindow.entergame.mainPanel.button.onClick = login -- registration entergameWindow.registration.back.onClick = function() @@ -29,44 +30,455 @@ function init() end -- characters + --- outfits + entergameWindow.characters.mainPanel.showOutfits.onClick = function() + local status = not (entergameWindow.characters.mainPanel.showOutfits:isOn()) + g_settings.set('showOutfits', status) + entergameWindow.characters.mainPanel.showOutfits:setOn(status) + if status then + entergameWindow.characters.mainPanel.outfitsPanel:show() + entergameWindow.characters.mainPanel.outfitsScroll:show() + entergameWindow.characters.mainPanel.charactersPanel:hide() + entergameWindow.characters.mainPanel.charactersScroll:hide() + else + entergameWindow.characters.mainPanel.outfitsPanel:hide() + entergameWindow.characters.mainPanel.outfitsScroll:hide() + entergameWindow.characters.mainPanel.charactersPanel:show() + entergameWindow.characters.mainPanel.charactersScroll:show() + end + end + + local showOutfits = g_settings.getBoolean("showOutfits", false) + entergameWindow.characters.mainPanel.showOutfits:setOn(showOutfits) + if showOutfits then + entergameWindow.characters.mainPanel.charactersPanel:hide() + entergameWindow.characters.mainPanel.charactersScroll:hide() + else + entergameWindow.characters.mainPanel.outfitsPanel:hide() + entergameWindow.characters.mainPanel.outfitsScroll:hide() + end + + --- auto reconnect + entergameWindow.characters.mainPanel.autoReconnect.onClick = function() + local status = (not entergameWindow.characters.mainPanel.autoReconnect:isOn()) + g_settings.set('autoReconnect', status) + entergameWindow.characters.mainPanel.autoReconnect:setOn(status) + end + local autoReconnect = g_settings.getBoolean("autoReconnect", true) + entergameWindow.characters.mainPanel.autoReconnect:setOn(autoReconnect) + + --- buttons entergameWindow.characters.logout.onClick = function() + protocol:logout() entergameWindow.characters:hide() entergameWindow.entergame:show() + entergameWindow.entergame.mainPanel.account:setText("") + entergameWindow.entergame.mainPanel.password:setText("") end entergameWindow.characters.createcharacter.onClick = function() entergameWindow.characters:hide() entergameWindow.createcharacter:show() + entergameWindow.createcharacter.mainPanel.name:setText("") end - entergameWindow.characters.mainPanel.autoReconnect.onClick = function() - entergameWindow.characters.mainPanel.autoReconnect:setOn(not entergameWindow.characters.mainPanel.autoReconnect:isOn()) - end + entergameWindow.characters.settings.onClick = function() + entergameWindow.characters:hide() + entergameWindow.settings:show() + end -- create character entergameWindow.createcharacter.back.onClick = function() entergameWindow.createcharacter:hide() entergameWindow.characters:show() end - - -- tests - characterGroup = UIRadioGroup.create() - for i=1,20 do - local character = g_ui.createWidget('EntergameCharacter', entergameWindow.characters.mainPanel.charactersPanel) - characterGroup:addWidget(character) - character.outfit:setOutfit({feet=10,legs=10,body=176,type=129,auxType=0,addons=3,head=48}) + entergameWindow.createcharacter.mainPanel.createButton.onClick = createcharacter + + entergameWindow.settings.back.onClick = function() + entergameWindow.settings:hide() + entergameWindow.characters:show() end - characterGroup:selectWidget(entergameWindow.characters.mainPanel.charactersPanel:getFirstChild()) - characterGroup:getSelectedWidget() + entergameWindow.settings.mainPanel.updateButton.onClick = updateSettings - for i=1,100 do - local l = g_ui.createWidget("NewsLabel", entergameWindow.news.content) - l:setText("test xxx ssss eeee uu u llel " .. i) + -- pick server + local server = nil + if type(Servers) == "table" then + for name, url in pairs(Servers) do + server = url + end + elseif type(Servers) == "string" then + server = Servers + elseif type(Server) == "string" then + server = Server + end + + if not server then + message("Configuration error", "You must set server url in init.lua!\nExample:\nServer = \"ws://otclient.ovh:8000\"") + return + end + + -- init protocol + -- token is random string + local session = g_crypt.sha1Encode("" .. math.random() .. g_clock.realMicros() .. tostring(G.UUID) .. g_platform.getCPUName() .. g_platform.getProcessId()) + protocol = EnterGameV2Protocol.new(session) + if not protocol:setUrl(server) then + return message("Configuration error", "Invalid url for entergamev2:\n" .. server) + end + + protocol.onLogin = onLogin + protocol.onLogout = logout + protocol.onMessage = serverMessage + protocol.onLoading = showLoading + protocol.onQAuth = updateQAuth + protocol.onCharacters = updateCharacters + protocol.onNews = updateNews + protocol.onMotd = updateMotd + protocol.onCharacterCreate = onCharacterCreate + + -- game stuff + connect(g_game, { onLoginError = onLoginError, + onLoginToken = onLoginToken , + onUpdateNeeded = onUpdateNeeded, + onConnectionError = onConnectionError, + onGameStart = onGameStart, + onGameEnd = onGameEnd, + onLoginWait = onLoginWait, + onLogout = onLogout + }) + + if g_game.isOnline() then + onGameStart() end end function terminate() if not USE_NEW_ENERGAME then return end + if protocol then + protocol:destroy() + protocol = nil + end + if infoBox then + infoBox:destroy() + infoBox = nil + end + if loadingBox then + loadingBox:destroy() + loadingBox = nil + end + if characterGroup then + characterGroup:destroy() + characterGroup = nil + end + if outfitGroup then + outfitGroup:destroy() + outfitGroup = nil + end entergameWindow:destroy() + entergameWindow = nil + + disconnect(g_game, { onLoginError = onLoginError, + onLoginToken = onLoginToken , + onUpdateNeeded = onUpdateNeeded, + onConnectionError = onConnectionError, + onGameStart = onGameStart, + onGameEnd = onGameEnd, + onLoginWait = onLoginWait, + onLogout = onLogout + }) +end + +function show() + +end + +function hide() + +end + +function message(title, text) + if infoBox then + infoBox:destroy() + end + + infoBox = displayInfoBox(title, text) + infoBox.onDestroy = function(widget) + if widget == infoBox then + infoBox = nil + end + end + infoBox:show() + infoBox:raise() + infoBox:focus() +end + +function showLoading(titie, text) + if loadingBox then + loadingBox:destroy() + end + + local callback = function() end -- do nothing + loadingBox = displayGeneralBox(titie, text, {}, callback, callback) + loadingBox.onDestroy = function(widget) + if widget == loadingBox then + loadingBox = nil + end + end + loadingBox:show() + loadingBox:raise() + loadingBox:focus() +end + +function serverMessage(title, text) + return message(title, text) +end + +function updateCharacters(characters) + if outfitGroup then + outfitGroup:destroy() + end if characterGroup then characterGroup:destroy() end + entergameWindow.characters.mainPanel.charactersPanel:destroyChildren() + entergameWindow.characters.mainPanel.outfitsPanel:destroyChildren() + + outfitGroup = UIRadioGroup.create() + characterGroup = UIRadioGroup.create() + for i, character in ipairs(characters) do + local characterWidget = g_ui.createWidget('EntergameCharacter', entergameWindow.characters.mainPanel.charactersPanel) + characterGroup:addWidget(characterWidget) + local outfitWidget = g_ui.createWidget('EntergameBigCharacter', entergameWindow.characters.mainPanel.outfitsPanel) + outfitGroup:addWidget(outfitWidget) + for i, widget in ipairs({characterWidget, outfitWidget}) do + widget.character = character + widget.outfit:setOutfit(character["outfit"]) + widget.line1:setText(character["line1"]) + widget.line2:setText(character["line2"]) + widget.line3:setText(character["line3"]) + end + end + if #characters > 1 then + characterGroup:selectWidget(entergameWindow.characters.mainPanel.charactersPanel:getFirstChild()) + outfitGroup:selectWidget(entergameWindow.characters.mainPanel.outfitsPanel:getFirstChild()) + end +end + +function updateQAuth(token) + if not token or token:len() == 0 then + return entergameWindow.quick:hide() + end + entergameWindow.quick:show() + entergameWindow.quick.qrcode:setQRCode(token, 1) + entergameWindow.quick.qrcode.onClick = function() + g_platform.openUrl(token) + end + entergameWindow.quick.quathlogo.onClick = entergameWindow.quick.qrcode.onClick +end + +function updateNews(news) + if not news or #news == 0 then + return entergameWindow.news:hide() + end + entergameWindow.news:show() + entergameWindow.news.content:destroyChildren() + for i, entry in ipairs(news) do + local title = entry["title"] + local text = entry["text"] + local image = entry["image"] + if title then + local newsLabel = g_ui.createWidget('NewsLabel', entergameWindow.news.content) + newsLabel:setText(title) + end + if text ~= nil then + local newsText = g_ui.createWidget('NewsText', entergameWindow.news.content) + newsText:setText(text) + end + end +end + +function updateMotd(text) + if not text or text:len() == 0 then + return entergameWindow.characters.mainPanel.motd:hide() + end + entergameWindow.characters.mainPanel.motd:show() + entergameWindow.characters.mainPanel.motd:setText(text) +end + +function login() + local account = entergameWindow.entergame.mainPanel.account:getText() + local password = entergameWindow.entergame.mainPanel.password:getText() + entergameWindow.entergame:hide() + showLoading("Login", "Connecting to server...") + protocol:login(account, password, "") +end + +function onLogin(data) + if loadingBox then + loadingBox:destroy() + loadingBox = nil + end + + if data["error"] and data["error"]:len() > 0 then + entergameWindow.entergame:show() + return message("Login error", data["error"]) + end + + local incorrectThings = validateThings(data["things"]) + if incorrectThings:len() > 0 then + entergameWindow.entergame:show() + return message("Login error - missing things", incorrectThings) + end + + if infoBox then + infoBox:destroy() + end + + local version = data["version"] + G.clientVersion = version + g_game.setClientVersion(version) + g_game.setProtocolVersion(g_game.getClientProtocolVersion(version)) + g_game.setCustomOs(-1) -- disable custom os + + local customProtocol = data["customProtocol"] + g_game.setCustomProtocolVersion(0) + if type(customProtocol) == 'number' then + g_game.setCustomProtocolVersion(customProtocol) + end + + local email = data["email"] + local security = data["security"] + entergameWindow.settings.mainPanel.email:setText(email) + entergameWindow.settings.mainPanel.security:setCurrentIndex(math.max(1, security)) + + entergameWindow.characters:show() + entergameWindow.entergame:hide() +end + +function logout() + if not entergameWindow.characters:isVisible() and not entergameWindow.createcharacter:isVisible() then + return + end + entergameWindow.characters:hide() + entergameWindow.createcharacter:hide() + entergameWindow.entergame:show() + message("Information", "Session expired, you has been logged out.") +end + +function validateThings(things) + local incorrectThings = "" + local missingFiles = false + local versionForMissingFiles = 0 + if things ~= nil then + local thingsNode = {} + for thingtype, thingdata in pairs(things) do + thingsNode[thingtype] = thingdata[1] + if not g_resources.fileExists("/data/things/" .. thingdata[1]) then + incorrectThings = incorrectThings .. "Missing file: " .. thingdata[1] .. "\n" + missingFiles = true + versionForMissingFiles = thingdata[1]:split("/")[1] + else + local localChecksum = g_resources.fileChecksum("/data/things/" .. thingdata[1]):lower() + if localChecksum ~= thingdata[2]:lower() and #thingdata[2] > 1 then + if g_resources.isLoadedFromArchive() then -- ignore checksum if it's test/debug version + incorrectThings = incorrectThings .. "Invalid checksum of file: " .. thingdata[1] .. " (is " .. localChecksum .. ", should be " .. thingdata[2]:lower() .. ")\n" + end + end + end + end + g_settings.setNode("things", thingsNode) + else + g_settings.setNode("things", {}) + end + if missingFiles then + 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) " .. + "from correct Tibia version." + end + return incorrectThings +end + +function doGameLogin() + local selected = nil + if entergameWindow.characters.mainPanel.charactersPanel:isVisible() then + selected = characterGroup:getSelectedWidget() + else + selected = outfitGroup:getSelectedWidget() + end + if not selected then + return message("Entergame error", "Please select character") + end + local character = selected.character + if not g_game.getFeature(GameSessionKey) then + g_game.enableFeature(GameSessionKey) + end + g_game.loginWorld("", "", character.worldName, character.worldHost, character.worldPort, character.name, "", protocol.session) +end + +function onLoginError(err) + message("Login error", err) +end + +function onLoginToken() + +end + +function onUpdateNeeded(signature) + +end + +function onConnectionError(message, code) + +end + +function onGameStart() + entergameWindow:hide() +end + +function onGameEnd() + entergameWindow:show() +end + +function onLoginWait(message, time) + +end + +function onLogout() + +end + +function createcharacter() + local name = entergameWindow.createcharacter.mainPanel.name:getText() + local gender = entergameWindow.createcharacter.mainPanel.gender:getCurrentOption().text + local vocation = entergameWindow.createcharacter.mainPanel.vocation:getCurrentOption().text + local town = entergameWindow.createcharacter.mainPanel.town:getCurrentOption().text + if name:len() < 3 or name:len() > 20 then + return message("Error", "Invalid character name") + end + protocol:createCharacter(name, gender, vocation, town) + showLoading("Creating character", "Creating new character...") +end + +function onCharacterCreate(err, msg) + if loadingBox then + loadingBox:destroy() + loadingBox = nil + end + + if err then + return message("Error", err) + end + message("Success", msg) + entergameWindow.createcharacter:hide() + entergameWindow.characters:show() +end + +function updateSettings() + local email = entergameWindow.settings.mainPanel.email:getText() + local security = entergameWindow.settings.mainPanel.security.currentIndex + + protocol:updateSettings({ + email=email, + security=security + }) + + entergameWindow.settings:hide() + entergameWindow.characters:show() end \ No newline at end of file diff --git a/modules/client_entergamev2/entergamev2.otmod b/modules/client_entergamev2/entergamev2.otmod index 1b54369..2fd817e 100644 --- a/modules/client_entergamev2/entergamev2.otmod +++ b/modules/client_entergamev2/entergamev2.otmod @@ -4,7 +4,7 @@ Module author: otclient.ovh website: http://otclient.ovh sandboxed: true - scripts: [ entergamev2 ] + scripts: [ protocol, entergamev2 ] @onLoad: init() @onUnload: terminate() \ No newline at end of file diff --git a/modules/client_entergamev2/entergamev2.otui b/modules/client_entergamev2/entergamev2.otui index b8942b9..b4b381a 100644 --- a/modules/client_entergamev2/entergamev2.otui +++ b/modules/client_entergamev2/entergamev2.otui @@ -60,7 +60,7 @@ EnterGame < Panel id: mainPanel anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter - size: 250 210 + size: 230 210 !text: tr("Enter Game") padding-top: 36 padding-left: 16 @@ -130,7 +130,7 @@ EnterGame < Panel anchors.horizontalCenter: prev.horizontalCenter anchors.top: prev.bottom margin-top: 20 - size: 200 50 + size: 290 50 image-source: /images/ui/window_headless image-border: 6 padding-top: 8 @@ -138,9 +138,18 @@ EnterGame < Panel Button id: register anchors.verticalCenter: buttons.verticalCenter - anchors.horizontalCenter: buttons.horizontalCenter + anchors.left: buttons.left + margin-left: 10 !text: tr("Create account") - size: 160 30 + size: 130 30 + + Button + id: register + anchors.verticalCenter: buttons.verticalCenter + anchors.right: buttons.right + margin-right: 10 + !text: tr("Casts") + size: 130 30 Registration < Panel anchors.fill: parent @@ -226,7 +235,6 @@ Registration < Panel margin-top: 10 margin-left: 50 margin-right: 50 - @onClick: EnterGame.doLogin() EnterGamePanel id: buttons @@ -261,7 +269,6 @@ QuickLogin < EnterGamePanel image-fixed-ratio: true image-smooth: false margin-top: 5 - qr: 123 UIButton id: quathlogo @@ -319,7 +326,6 @@ Characters < Panel anchors.right: parent.right text-auto-resize: true text-wrap: true - text: This is motd ;) text-align: center HorizontalSeparator @@ -328,22 +334,45 @@ Characters < Panel anchors.right: parent.right anchors.top: prev.bottom margin-top: 5 - height: 10 ScrollablePanel - id: charactersPanel + id: outfitsPanel layout: type: grid - cell-size: 100 100 + cell-size: 125 125 cell-spacing: 1 flow: true - vertical-scrollbar: charactersScroll + vertical-scrollbar: outfitsScroll + background-color: #444444 anchors.top: prev.bottom anchors.bottom: bottomSeparator.top anchors.left: parent.left anchors.right: parent.right - margin-bottom: 10 margin-right: 12 + margin-bottom: 5 + margin-top: 5 + + VerticalScrollBar + id: outfitsScroll + anchors.top: outfitsPanel.top + anchors.bottom: outfitsPanel.bottom + anchors.left: outfitsPanel.right + step: 14 + pixels-scroll: true + + ScrollablePanel + id: charactersPanel + layout: + type: verticalBox + vertical-scrollbar: charactersScroll + background-color: #444444 + anchors.top: motdSeparator.bottom + anchors.bottom: bottomSeparator.top + anchors.left: parent.left + anchors.right: parent.right + margin-right: 12 + margin-bottom: 5 + margin-top: 5 VerticalScrollBar id: charactersScroll @@ -351,14 +380,14 @@ Characters < Panel anchors.bottom: charactersPanel.bottom anchors.left: charactersPanel.right step: 14 - pixels-scroll: true + pixels-scroll: true HorizontalSeparator id: bottomSeparator anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom - height: 35 + margin-bottom: 30 Button id: autoReconnect @@ -370,7 +399,7 @@ Characters < Panel $!on: image-color: red !text: tr('Auto reconnect: Off') - + Button id: showOutfits anchors.horizontalCenter: parent.horizontalCenter @@ -378,10 +407,10 @@ Characters < Panel width: 140 $on: - !text: tr("Show outfits") + !text: tr("Hide big outfits") $!on: - !text: tr("Hide outfits") + !text: tr("Show big outfits") Button id: connect @@ -389,6 +418,7 @@ Characters < Panel anchors.bottom: parent.bottom !text: tr("Connect") width: 140 + @onClick: modules.client_entergamev2.doGameLogin() EnterGamePanel id: buttons @@ -409,10 +439,10 @@ Characters < Panel size: 140 30 Button - id: casts + id: settings anchors.verticalCenter: buttons.verticalCenter anchors.horizontalCenter: buttons.horizontalCenter - !text: tr("Casts") + !text: tr("Account settings") size: 140 30 Button @@ -423,10 +453,11 @@ Characters < Panel !text: tr("Logout") size: 140 30 -EntergameCharacter < UIButton +EntergameBigCharacter < UIButton border-width: 1 padding: 1 1 1 1 @onClick: self:setChecked(true) + @onDoubleClick: modules.client_entergamev2.doGameLogin() $checked: border-color: #ffffff @@ -438,7 +469,7 @@ EntergameCharacter < UIButton id: outfit anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter - size: 48 48 + size: 70 70 margin-bottom: 3 phantom: true @@ -447,39 +478,99 @@ EntergameCharacter < UIButton anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top - text: Dagusia Druid text-align: center text-wrap: false height: 16 font: terminus-10px border-width-bottom: 1 border-color: #00000077 + phantom: true Label id: line2 anchors.left: parent.left anchors.right: parent.right anchors.top: prev.bottom - text: Level: 666 text-align: center text-wrap: false height: 16 font: terminus-10px border-width-bottom: 1 border-color: #00000077 + phantom: true Label id: line3 anchors.left: parent.left anchors.right: parent.right anchors.top: prev.bottom - text: World: nemezis text-align: center text-wrap: false height: 16 font: terminus-10px border-width-bottom: 1 border-color: #00000077 + phantom: true + +EntergameCharacter < UIButton + padding: 3 3 3 3 + @onClick: self:setChecked(true) + @onDoubleClick: modules.client_entergamev2.doGameLogin() + height: 34 + + $checked: + background-color: #333333 + + $!checked: + background-color: #555555 + + UICreature + id: outfit + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + size: 32 32 + margin-top: -2 + margin-bottom: -2 + phantom: true + + UILabel + id: line1 + anchors.left: prev.right + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-left: 6 + width: 150 + text-align: left + phantom: true + + VerticalSeparator + anchors.left: prev.right + anchors.top: parent.top + anchors.bottom: parent.bottom + + UILabel + id: line2 + anchors.left: prev.right + anchors.top: parent.top + anchors.bottom: parent.bottom + width: 150 + text-align: center + phantom: true + + VerticalSeparator + anchors.left: prev.right + anchors.top: parent.top + anchors.bottom: parent.bottom + + UILabel + id: line3 + anchors.left: prev.right + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + text-align: center + phantom: true CreateCharacter < Panel anchors.fill: parent @@ -503,12 +594,32 @@ CreateCharacter < Panel text-auto-resize: true TextEdit - id: accountNameTextEdit + id: name anchors.left: parent.left anchors.right: parent.right anchors.top: prev.bottom margin-top: 2 + MenuLabel + !text: tr('Gender') + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 8 + text-auto-resize: true + + ComboBox + id: gender + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + menu-scroll: true + menu-height: 125 + menu-scroll-step: 25 + margin-right: 3 + @onSetup: | + self:addOption("Male") + self:addOption("Female") + MenuLabel !text: tr('Vocation') anchors.left: prev.left @@ -516,40 +627,42 @@ CreateCharacter < Panel margin-top: 8 text-auto-resize: true - TextEdit - id: emailTextEdit + ComboBox + id: vocation anchors.left: parent.left anchors.right: parent.right anchors.top: prev.bottom - margin-top: 2 - - MenuLabel - !text: tr('Password') - anchors.left: prev.left - anchors.top: prev.bottom - margin-top: 8 - text-auto-resize: true - - PasswordTextEdit - id: accountPasswordTextEdit - anchors.left: parent.left - anchors.right: parent.right - anchors.top: prev.bottom - margin-top: 2 + menu-scroll: true + menu-height: 125 + menu-scroll-step: 25 + margin-right: 3 + @onSetup: | + self:addOption("Sorcerer") + self:addOption("Druid") + self:addOption("Paladin") + self:addOption("Knight") MenuLabel - !text: tr('Password confirmation') + !text: tr('Town') anchors.left: prev.left anchors.top: prev.bottom margin-top: 8 text-auto-resize: true - PasswordTextEdit - id: accountPasswordTextEdit + ComboBox + id: town anchors.left: parent.left anchors.right: parent.right anchors.top: prev.bottom - margin-top: 2 + menu-scroll: true + menu-height: 125 + menu-scroll-step: 25 + margin-right: 3 + @onSetup: | + self:addOption("Carlin") + self:addOption("Edron") + self:addOption("Thais") + self:addOption("Venore") HorizontalSeparator anchors.left: parent.left @@ -558,6 +671,7 @@ CreateCharacter < Panel margin-top: 9 Button + id: createButton !text: tr('Create character') anchors.left: parent.left anchors.right: parent.right @@ -565,7 +679,89 @@ CreateCharacter < Panel margin-top: 10 margin-left: 50 margin-right: 50 - @onClick: EnterGame.doLogin() + + EnterGamePanel + id: buttons + anchors.horizontalCenter: prev.horizontalCenter + anchors.top: prev.bottom + margin-top: 20 + size: 200 50 + image-source: /images/ui/window_headless + image-border: 6 + padding-top: 8 + + Button + id: back + anchors.verticalCenter: buttons.verticalCenter + anchors.horizontalCenter: buttons.horizontalCenter + !text: tr("Back") + size: 160 30 + + +AccountSettings < Panel + anchors.fill: parent + id: settings + + EnterGamePanel + id: mainPanel + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + size: 250 173 + !text: tr("Account settings") + padding-top: 36 + padding-left: 16 + padding-right: 16 + padding-bottom: 16 + + MenuLabel + !text: tr('Email') + anchors.left: parent.left + anchors.top: parent.top + text-auto-resize: true + + TextEdit + id: email + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 2 + + MenuLabel + !text: tr('Security level') + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 8 + text-auto-resize: true + + ComboBox + id: security + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + menu-scroll: true + menu-height: 125 + menu-scroll-step: 25 + margin-right: 3 + @onSetup: | + self:addOption("Low") + self:addOption("Medium") + self:addOption("High") + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 9 + + Button + id: updateButton + !text: tr('Update settings') + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 10 + margin-left: 50 + margin-right: 50 EnterGamePanel id: buttons @@ -597,3 +793,4 @@ Panel Registration Characters CreateCharacter + AccountSettings diff --git a/modules/client_entergamev2/protocol.lua b/modules/client_entergamev2/protocol.lua new file mode 100644 index 0000000..bd18607 --- /dev/null +++ b/modules/client_entergamev2/protocol.lua @@ -0,0 +1,228 @@ +EnterGameV2Protocol = {} +EnterGameV2Protocol.__index = EnterGameV2Protocol + +EnterGameV2Protocol.new = function(session) + if type(session) ~= 'string' then + return error("You need to specify string session for EnterGameV2Protocol") + end + local data = {} + data.socket = nil + data.terminated = false + data.reconnectEvent = nil + data.connected = false + data.session = session + data.sendQueue = {} + data.sendQueueMsgId = 1 + data.loginTimeoutEvent = nil + setmetatable(data, EnterGameV2Protocol) + return data +end + +function EnterGameV2Protocol:destroy() + self.terminated = true + self.sendQueue = {} + + if self.loginTimeoutEvent then + removeEvent(self.loginTimeoutEvent) + self.loginTimeoutEvent = nil + end + + self:reset() +end + +function EnterGameV2Protocol:reset() + self.connected = false + if self.reconnectEvent then + removeEvent(self.reconnectEvent) + self.reconnectEvent = nil + end + if self.socket then + self.socket.close() + self.socket = nil + end +end + +function EnterGameV2Protocol:setUrl(url) + if self.terminated then + return false + end + self:reset() + if self.url ~= url then + self.sendQueue = {} + self.sendQueueMsgId = 1 + if self.loginTimeoutEvent then + removeEvent(self.loginTimeoutEvent) + self.loginTimeoutEvent = nil + end + end + if type(url) ~= 'string' or not url:lower():find("ws") then + g_logger.error("Invalid url for EnterGameV2Protocol:\n" .. url) + return false + end + self.url = url + self.socket = HTTP.WebSocketJSON(url, { + onOpen = function(message, websocketId) + if self.terminated or not self.socket or self.socket.id ~= websocketId then return end + self.connected = true + self:sendFirstMessage() + end, + onMessage = function(message, websocketId) + if self.terminated or not self.socket or self.socket.id ~= websocketId then return end + self:onSocketMessage(message) + end, + onClose = function(message, websocketId) + if self.terminated or not self.socket or self.socket.id ~= websocketId then return end + self.connected = false + self:scheduleReconnect() + end, + onError = function(message, websocketId) + if self.terminated or not self.socket or self.socket.id ~= websocketId then return end + self.connected = false + self:scheduleReconnect() + end + }) + return true +end + +function EnterGameV2Protocol:isConnected() + return self.socket and self.connected +end + +function EnterGameV2Protocol:scheduleReconnect() + if self.socket then + self.connected = false + self.socket.close() + self.socket = nil + end + if self.terminated then return end + if self.reconnectEvent then return end + self.reconnectEvent = scheduleEvent(function() self:reconnect() end, 500) +end + +function EnterGameV2Protocol:login(account, password, token, callback) + self:send({ + type="login", + account=account, + password=password, + token=token, + }) + if self.loginTimeoutEvent then + removeEvent(self.loginTimeoutEvent) + end + self.loginTimeoutEvent = scheduleEvent(function() + self.loginTimeoutEvent = nil + self.onLogin({error="Connection timeout"}) + end, 10000) +end + +function EnterGameV2Protocol:logout() + self:send({ + type="logout" + }) +end + +function EnterGameV2Protocol:register(name, email, password, callback) + +end + +function EnterGameV2Protocol:createCharacter(name, gender, vocation, town) + self:send({ + type="createcharacter", + name=name, + gender=gender, + vocation=vocation, + town=town + }) +end + +function EnterGameV2Protocol:updateSettings(settings) + self:send({ + type="settings", + settings=settings + }) +end + +-- private functions +function EnterGameV2Protocol:reconnect() + if #self.sendQueue > 1 then + self.sendQueue = {} -- TEMPORARY + end + self.reconnectEvent = nil + if self.terminated then return end + self:setUrl(self.url) +end + +function EnterGameV2Protocol:send(data) + if type(data) ~= "table" then + return error("data should be table") + end + data["id"] = self.sendQueueMsgId + table.insert(self.sendQueue, {id=self.sendQueueMsgId, msg=json.encode(data)}) + self.sendQueueMsgId = self.sendQueueMsgId + 1 + if self.socket then + self.socket.send(self.sendQueue[#self.sendQueue].msg) + end +end + +function EnterGameV2Protocol:sendFirstMessage() + self.socket.send({type="init", session=self.session}) + for i, msg in ipairs(self.sendQueue) do + self.socket.send(msg.msg) + end +end + +function EnterGameV2Protocol:onSocketMessage(message) + local lastId = message["lastId"] + if type(lastId) == 'number' then -- clear send queue + while #self.sendQueue > 0 do + local id = self.sendQueue[1].id + if id < lastId then + break + end + table.remove(self.sendQueue, 1) + end + end + if message["type"] == "ping" then + self.socket.send({type="ping"}) + elseif message["type"] == "login" then + if self.loginTimeoutEvent then + removeEvent(self.loginTimeoutEvent) + self.loginTimeoutEvent = nil + end + if self.onLogin then + self.onLogin(message) + end + elseif message["type"] == "logout" then + if self.onLogout then + self.onLogout() + end + elseif message["type"] == "qauth" then + if self.onQAuth then + self.onQAuth(message["token"]) + end + elseif message["type"] == "characters" then + if self.onCharacters then + self.onCharacters(message["characters"]) + end + elseif message["type"] == "message" then + if self.onMessage then + self.onMessage(message["title"], message["text"]) + end + elseif message["type"] == "loading" then + if self.onMessage then + self.onLoading(message["title"], message["text"]) + end + elseif message["type"] == "news" then + if self.onNews then + self.onNews(message["news"]) + end + elseif message["type"] == "motd" then + if self.onMotd then + self.onMotd(message["text"]) + end + elseif message["type"] == "createcharacter" then + if self.onMessage then + self.onCharacterCreate(message["error"], message["message"]) + end + end +end diff --git a/modules/client_stats/stats.lua b/modules/client_stats/stats.lua index 9af95fb..bfb32b3 100644 --- a/modules/client_stats/stats.lua +++ b/modules/client_stats/stats.lua @@ -170,7 +170,7 @@ function update() return end - statsWindow.debugPanel.sleepTime:setText("Sleep: " .. math.round(g_stats.getSleepTime() / math.max(1, g_clock.micros() - lastSleepTimeReset), 2) .. "%") + statsWindow.debugPanel.sleepTime:setText("Sleep: " .. math.round(g_stats.getSleepTime() / math.max(1, g_clock.micros() - lastSleepTimeReset), 2) .. "%, Packets: " .. g_game.getRecivedPacketsCount() .. " , " .. (g_game.getRecivedPacketsSize() / 1024) .. " KB") statsWindow.debugPanel.luaRamUsage:setText("Ram usage by lua: " .. gcinfo() .. " kb") local adaptive = "Adaptive: " .. g_adaptiveRenderer.getLevel() .. " | " .. g_adaptiveRenderer.getDebugInfo() adaptiveRender:setText(adaptive) diff --git a/modules/client_terminal/terminal.lua b/modules/client_terminal/terminal.lua index 1f1246c..46994c6 100644 --- a/modules/client_terminal/terminal.lua +++ b/modules/client_terminal/terminal.lua @@ -141,6 +141,7 @@ function init() terminalWindow.onDoubleClick = popWindow terminalButton = modules.client_topmenu.addLeftButton('terminalButton', tr('Terminal') .. ' (Ctrl + T)', '/images/topbuttons/terminal', toggle) + terminalButton:setOn(false) g_keyboard.bindKeyDown('Ctrl+T', toggle) commandHistory = g_settings.getList('terminal-history') @@ -197,7 +198,7 @@ function terminate() g_keyboard.unbindKeyDown('Ctrl+T') g_logger.setOnLog(nil) terminalWindow:destroy() - --terminalButton:destroy() + terminalButton:destroy() commandEnv = nil _G.terminalLines = allLines end diff --git a/modules/corelib/table.lua b/modules/corelib/table.lua index 8f9838f..4dd0ad2 100644 --- a/modules/corelib/table.lua +++ b/modules/corelib/table.lua @@ -209,4 +209,79 @@ function table.equal(t1,t2,ignore_mt) if v1 == nil or not table.equal(v1,v2) then return false end end return true +end + +function table.isList(t) + local size = #t + return table.size(t) == size and size > 0 +end + +function table.isStringList(t) + if not table.isList(t) then return false end + for k,v in ipairs(t) do + if type(v) ~= 'string' then + return false + end + end + return true +end + +function table.isStringPairList(t) + if not table.isList(t) then return false end + for k,v in ipairs(t) do + if type(v) ~= 'table' or #v ~= 2 or type(v[1]) ~= 'string' or type(v[2]) ~= 'string' then + return false + end + end + return true +end + +function table.encodeStringPairList(t) + local ret = "" + for k,v in ipairs(t) do + if v[2]:find("\n") then + ret = ret .. v[1] .. ":[[\n" .. v[2] .. "\n]]\n" + else + ret = ret .. v[1] .. ":" .. v[2] .. "\n" + end + end + return ret end + +function table.decodeStringPairList(l) + local ret = {} + local r = regexMatch(l, "^([^:^\n]{1,20}):?(.*)$") + local multiline = "" + local multilineKey = "" + local multilineActive = false + for k,v in ipairs(r) do + if multilineActive then + local endPos = v[1]:find("%]%]") + if endPos then + if endPos > 1 then + table.insert(ret, {multilineKey, multiline .. "\n" .. v[1]:sub(1, endPos - 1)}) + else + table.insert(ret, {multilineKey, multiline}) + end + multilineActive = false + multiline = "" + multilineKey = "" + else + if multiline:len() == 0 then + multiline = v[1] + else + multiline = multiline .. "\n" .. v[1] + end + end + else + local bracketPos = v[3]:find("%[%[") + if bracketPos == 1 then -- multiline begin + multiline = v[3]:sub(bracketPos + 2) + multilineActive = true + multilineKey = v[2] + elseif v[2]:len() > 0 and v[3]:len() > 0 then + table.insert(ret, {v[2], v[3]}) + end + end + end + return ret end \ No newline at end of file diff --git a/modules/game_actionbar/actionbar.lua b/modules/game_actionbar/actionbar.lua index 5a2d9aa..6e2136e 100644 --- a/modules/game_actionbar/actionbar.lua +++ b/modules/game_actionbar/actionbar.lua @@ -144,7 +144,7 @@ function setupAction(index, action, config) if config then action.hotkey = config.hotkey - if action.hotkey then + if action.hotkey and action.hotkey:len() > 0 then local gameRootPanel = modules.game_interface.getRootPanel() g_keyboard.bindKeyPress(action.hotkey, action.callback, gameRootPanel) end @@ -300,11 +300,11 @@ function actionOnMouseRelease(action, mousePosition, mouseButton) end assignWindow.addButton.onClick = function() local gameRootPanel = modules.game_interface.getRootPanel() - if action.hotkey then + if action.hotkey and action.hotkey:len() > 0 then g_keyboard.unbindKeyPress(action.hotkey, action.callback, gameRootPanel) end action.hotkey = assignWindow.comboPreview.keyCombo - if action.hotkey then + if action.hotkey and action.hotkey:len() > 0 then g_keyboard.bindKeyPress(action.hotkey, action.callback, gameRootPanel) end action.hotkeyLabel:setText(action.hotkey or "") @@ -318,7 +318,7 @@ function actionOnMouseRelease(action, mousePosition, mouseButton) action.text:setText("") action.hotkeyLabel:setText("") local gameRootPanel = modules.game_interface.getRootPanel() - if action.hotkey then + if action.hotkey and action.hotkey:len() > 0 then g_keyboard.unbindKeyPress(action.hotkey, action.callback, gameRootPanel) end action.hotkey = nil diff --git a/modules/game_bot/bot.lua b/modules/game_bot/bot.lua index bcc6373..129e8fb 100644 --- a/modules/game_bot/bot.lua +++ b/modules/game_bot/bot.lua @@ -24,6 +24,7 @@ function init() g_ui.importStyle("ui/panels.otui") g_ui.importStyle("ui/config.otui") g_ui.importStyle("ui/icons.otui") + g_ui.importStyle("ui/container.otui") connect(g_game, { onGameStart = online, @@ -195,7 +196,7 @@ function refresh() -- run script local status, result = pcall(function() - return executeBot(configName, botStorage, botTabs, message, save, botWebSockets) end + return executeBot(configName, botStorage, botTabs, message, save, refresh, botWebSockets) end ) if not status then return onError(result) diff --git a/modules/game_bot/executor.lua b/modules/game_bot/executor.lua index ed3c4c2..733e8c4 100644 --- a/modules/game_bot/executor.lua +++ b/modules/game_bot/executor.lua @@ -1,4 +1,4 @@ -function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, websockets) +function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, reloadCallback, websockets) -- load lua and otui files local configFiles = g_resources.listDirectoryFiles("/bot/" .. config, true, false) local luaFiles = {} @@ -21,16 +21,18 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, webs local context = {} context.configDir = "/bot/".. config context.tabs = tabs - context.panel = context.tabs:addTab("Main", g_ui.createWidget('BotPanel')).tabPanel.content + context.mainTab = context.tabs:addTab("Main", g_ui.createWidget('BotPanel')).tabPanel.content + context.panel = context.mainTab context.saveConfig = saveConfigCallback - context._websockets = websockets + context.reload = reloadCallback context.storage = storage if context.storage._macros == nil then context.storage._macros = {} -- active macros end - -- macros, hotkeys, scheduler, icons, callbacks + -- websockets, macros, hotkeys, scheduler, icons, callbacks + context._websockets = websockets context._macros = {} context._hotkeys = {} context._scheduler = {} @@ -71,9 +73,10 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, webs context.tonumber = tonumber context.type = type context.pcall = pcall - context.load = function(str) return load(str, nil, nil, context) end + context.load = function(str) return assert(load(str, nil, nil, context)) end context.loadstring = context.load context.assert = assert + context.dofile = function(file) assert(load(g_resources.readFileContents("/bot/" .. config .. "/" .. file), file, nil, context))() end context.gcinfo = gcinfo context.tr = tr context.json = json @@ -129,7 +132,8 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, webs -- run lua script for i, file in ipairs(luaFiles) do - assert(load(g_resources.readFileContents(file), file, nil, context))() + assert(load(g_resources.readFileContents(file), file, nil, context))() + context.panel = context.mainTab -- reset default tab end return { diff --git a/modules/game_bot/functions/config.lua b/modules/game_bot/functions/config.lua index f02befb..f277ef6 100644 --- a/modules/game_bot/functions/config.lua +++ b/modules/game_bot/functions/config.lua @@ -1,5 +1,5 @@ --[[ -Config. create. load and save config file (.json) +Config - create, load and save config file (.json / .cfg) Used by cavebot and other things ]]-- @@ -22,7 +22,32 @@ Config.list = function(dir) return contex.error("Can't create config dir: " .. context.configDir .. "/" .. dir) end end - return g_resources.listDirectoryFiles(context.configDir .. "/" .. dir) + local list = g_resources.listDirectoryFiles(context.configDir .. "/" .. dir) + local correctList = {} + for k,v in ipairs(list) do -- filter files + local nv = v:gsub(".json", ""):gsub(".cfg", "") + if nv ~= v then + table.insert(correctList, nv) + end + end + return correctList +end + +-- load config from string insteaf of dile +Config.parse = function(data) + local status, result = pcall(function() + return json.decode(data) + end) + if status and type(result) == 'table' then + return result + end + local status, result = pcall(function() + return table.decodeStringPairList(data) + end) + if status and type(result) == 'table' then + return result + end + return context.error("Invalid config format") end Config.load = function(dir, name) @@ -31,63 +56,154 @@ Config.load = function(dir, name) return json.decode(g_resources.readFileContents(file)) end file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg" + if g_resources.fileExists(file) then -- load cfg + return table.decodeStringPairList(g_resources.readFileContents(file)) + end + return context.error("Config " .. file .. " doesn't exist") +end + +Config.loadRaw = function(dir, name) + local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json" + if g_resources.fileExists(file) then -- load json + return g_resources.readFileContents(file) + end + file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg" if g_resources.fileExists(file) then -- load cfg return g_resources.readFileContents(file) end return context.error("Config " .. file .. " doesn't exist") end -Config.save = function(dir, name, value) +Config.save = function(dir, name, value, forcedExtension) if not Config.exist(dir) then if not Config.create(dir) then return contex.error("Can't create config dir: " .. context.configDir .. "/" .. dir) end end + if type(value) ~= 'table' then + return context.error("Invalid config value type: " .. type(value) .. ", should be table") + end local file = context.configDir .. "/" .. dir .. "/" .. name - if type(value) == 'string' then -- cfg - g_resources.writeFileContents(file .. ".cfg", value) - elseif type(value) == 'table' then -- json + if (table.isStringPairList(value) and forcedExtension ~= "json") or forcedExtension == "cfg" then -- cfg + g_resources.writeFileContents(file .. ".cfg", table.encodeStringPairList(value)) + else g_resources.writeFileContents(file .. ".json", json.encode(value)) end - return context.error("Invalid config value type: " .. type(value)) + return true end Config.remove = function(dir, name) local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json" + local ret = false if g_resources.fileExists(file) then - return g_resources.deleteFile(file) + g_resources.deleteFile(file) + ret = true end file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg" if g_resources.fileExists(file) then - return g_resources.deleteFile(file) + g_resources.deleteFile(file) + ret = true end + return ret end -- setup is used for BotConfig widget -- not done yet -Config.setup = function(dir, widget, callback) - local refresh = function() - -- +Config.setup = function(dir, widget, configExtension, callback) + if type(dir) ~= 'string' or dir:len() == 0 then + return context.error("Invalid config dir") end - + if not Config.exist(dir) and not Config.create(dir) then + return context.error("Can't create config dir: " .. dir) + end + if type(context.storage._configs) ~= "table" then + context.storage._configs = {} + end + if type(context.storage._configs[dir]) ~= "table" then + context.storage._configs[dir] = { + enabled = false, + selected = "" + } + else + widget.switch:setOn(context.storage._configs[dir].enabled) + end + + local isRefreshing = false + local refresh = function() + isRefreshing = true + local configs = Config.list(dir) + local configIndex = 1 + widget.list:clear() + for v,k in ipairs(configs) do + widget.list:addOption(k) + if k == context.storage._configs[dir].selected then + configIndex = v + end + end + local data = nil + if #configs > 0 then + widget.list:setCurrentIndex(configIndex) + context.storage._configs[dir].selected = widget.list:getCurrentOption().text + data = Config.load(dir, configs[configIndex]) + else + context.storage._configs[dir].selected = nil + end + context.storage._configs[dir].enabled = widget.switch:isOn() + isRefreshing = false + callback(context.storage._configs[dir].selected, widget.switch:isOn(), data) + end + + widget.list.onOptionChange = function(widget) + if not isRefreshing then + context.storage._configs[dir].selected = widget:getCurrentOption().text + refresh() + end + end + widget.switch.onClick = function() widget.switch:setOn(not widget.switch:isOn()) + refresh() end - widget.add = function() - + widget.add.onClick = function() + context.UI.SinglelineEditorWindow("config_name", function(name) + name = name:gsub("%s+", "_") + if name:len() == 0 or name:len() >= 30 or name:find("/") or name:find("\\") then + return context.error("Invalid config name") + end + local file = context.configDir .. "/" .. dir .. "/" .. name .. "." .. configExtension + if g_resources.fileExists(file) then + return context.error("Config " .. name .. " already exist") + end + g_resources.writeFileContents(file, "") + context.storage._configs[dir].selected = name + widget.switch:setOn(false) + refresh() + end) end - widget.edit = function() - + widget.edit.onClick = function() + local name = context.storage._configs[dir].selected + if not name then return end + context.UI.MultilineEditorWindow("Config editor - " .. name .. " in " .. dir, + Config.loadRaw(dir, name), function(newValue) + local data = Config.parse(newValue) + Config.save(dir, name, data, configExtension) + refresh() + end) end - widget.remove = function() - + widget.remove.onClick = function() + local name = context.storage._configs[dir].selected + if not name then return end + context.UI.ConfirmationWindow("Config removal", "Do you want to remove config " .. name .. " from " .. dir .. "?", function() + Config.remove(dir, name) + widget.switch:setOn(false) + refresh() + end) end - --local configs = Config.list(dir) - --widget.list. + refresh() return { isOn = function() @@ -96,18 +212,35 @@ Config.setup = function(dir, widget, callback) isOff = function() return not widget.switch:isOn() end, - enable = function() + setOn = function(val) + if val == false then + if widget.switch:isOn() then + widget.switch:onClick() + end + return + end if not widget.switch:isOn() then widget.switch:onClick() end end, - disable = function() + setOff = function(val) + if val == false then + if not widget.switch:isOn() then + widget.switch:onClick() + end + return + end if widget.switch:isOn() then widget.switch:onClick() end end, - save = function() - - end + save = function(data) + Config.save(dir, context.storage._configs[dir].selected, data, configExtension) + end, + refresh = refresh, + reload = refresh, + getActiveConfigName = function() + return context.storage._configs[dir].selected + end } end \ No newline at end of file diff --git a/modules/game_bot/functions/icon.lua b/modules/game_bot/functions/icon.lua index 6c44796..81fcc16 100644 --- a/modules/game_bot/functions/icon.lua +++ b/modules/game_bot/functions/icon.lua @@ -31,6 +31,7 @@ context.addIcon = function(id, options, callback) local config = context.storage._icons[id] local widget = g_ui.createWidget("BotIcon", panel) widget.botWidget = true + widget.botIcon = true if type(config.x) ~= 'number' and type(config.y) ~= 'number' then if type(options.x) == 'number' and type(options.y) == 'number' then @@ -138,22 +139,22 @@ context.addIcon = function(id, options, callback) config.x = math.min(1, math.max(0, x / width)) config.y = math.min(1, math.max(0, y / height)) + widget:addAnchor(AnchorHorizontalCenter, 'parent', AnchorHorizontalCenter) widget:addAnchor(AnchorVerticalCenter, 'parent', AnchorVerticalCenter) - - widget:setMarginTop(height * (-0.5 + config.y)) + widget:setMarginTop(math.max(height * (-0.5) - parent:getMarginTop(), height * (-0.5 + config.y))) widget:setMarginLeft(width * (-0.5 + config.x)) return true end end - widget.onGeometryChange = function(widget, oldRect, newRect) + widget.onGeometryChange = function(widget) if widget:isDragging() then return end local parent = widget:getParent() local parentRect = parent:getRect() local width = parentRect.width - widget:getWidth() local height = parentRect.height - widget:getHeight() - widget:setMarginTop(-parent:getMarginTop() + height * (-0.5 + config.y)) + widget:setMarginTop(math.max(height * (-0.5) - parent:getMarginTop(), height * (-0.5 + config.y))) widget:setMarginLeft(width * (-0.5 + config.x)) end diff --git a/modules/game_bot/functions/map.lua b/modules/game_bot/functions/map.lua index e1bd6a0..4a6ee8d 100644 --- a/modules/game_bot/functions/map.lua +++ b/modules/game_bot/functions/map.lua @@ -183,7 +183,13 @@ context.findPath = function(startPos, destPos, maxDist, params) end context.getPath = context.findPath +-- also works as autoWalk(dirs) where dirs is a list eg.: {1,2,3,0,1,1,2,} context.autoWalk = function(destination, maxDist, params) + if type(destination) == "table" and table.isList(destination) and not maxDist and not params then + g_game.autoWalk(destination, {x=0,y=0,z=0}) + return true + end + -- Available params same as for findPath local path = context.findPath(context.player:getPosition(), destination, maxDist, params) if not path then diff --git a/modules/game_bot/functions/ui.lua b/modules/game_bot/functions/ui.lua index 56f90d5..759305f 100644 --- a/modules/game_bot/functions/ui.lua +++ b/modules/game_bot/functions/ui.lua @@ -1,114 +1,12 @@ local context = G.botContext +if type(context.UI) ~= "table" then + context.UI = {} +end +local UI = context.UI -context.setupUI = function(otml, parent) +UI.createWidget = function(name, parent) if parent == nil then parent = context.panel end - local widget = g_ui.loadUIFromString(otml, parent) - widget.botWidget = true - return widget + return g_ui.createWidget(name, parent) end - -context.importStyle = function(otml) - return g_ui.importStyleFromString(otml) -end - -context.addTab = function(name) - local tab = context.tabs:getTab(name) - if tab then -- return existing tab - return tab.tabPanel.content - end - - context.tabs:setOn(true) - local newTab = context.tabs:addTab(name, g_ui.createWidget('BotPanel')).tabPanel.content - if #(context.tabs.tabs) > 5 then - for k,tab in pairs(context.tabs.tabs) do - tab:setPadding(3) - tab:setFont('small-9px') - end - end - - return newTab -end -context.getTab = context.addTab - -context.addSwitch = function(id, text, onClickCallback, parent) - if not parent then - parent = context.panel - end - local switch = g_ui.createWidget('BotSwitch', parent) - switch:setId(id) - switch:setText(text) - switch.onClick = onClickCallback - return switch -end - -context.addButton = function(id, text, onClickCallback, parent) - if not parent then - parent = context.panel - end - local button = g_ui.createWidget('BotButton', parent) - button:setId(id) - button:setText(text) - button.onClick = onClickCallback - return button -end - -context.addLabel = function(id, text, parent) - if not parent then - parent = context.panel - end - local label = g_ui.createWidget('BotLabel', parent) - label:setId(id) - label:setText(text) - return label -end - -context.addTextEdit = function(id, text, onTextChangeCallback, parent) - if not parent then - parent = context.panel - end - local widget = g_ui.createWidget('BotTextEdit', parent) - widget:setId(id) - widget.onTextChange = onTextChangeCallback - widget:setText(text) - return widget -end - -context.addSeparator = function(id, parent) - if not parent then - parent = context.panel - end - local separator = g_ui.createWidget('BotSeparator', parent) - separator:setId(id) - return separator -end - -context._addMacroSwitch = function(name, keys, parent) - if not parent then - parent = context.panel - end - local text = name - if keys:len() > 0 then - text = name .. " [" .. keys .. "]" - end - local switch = context.addSwitch("macro_" .. #context._macros, text, function(widget) - context.storage._macros[name] = not context.storage._macros[name] - widget:setOn(context.storage._macros[name]) - end, parent) - switch:setOn(context.storage._macros[name]) - return switch -end - -context._addHotkeySwitch = function(name, keys, parent) - if not parent then - parent = context.panel - end - local text = name - if keys:len() > 0 then - text = name .. " [" .. keys .. "]" - end - local switch = context.addSwitch("hotkey_" .. #context._hotkeys, text, nil, parent) - switch:setOn(false) - return switch -end \ No newline at end of file diff --git a/modules/game_bot/functions/ui_elements.lua b/modules/game_bot/functions/ui_elements.lua new file mode 100644 index 0000000..a5f76a0 --- /dev/null +++ b/modules/game_bot/functions/ui_elements.lua @@ -0,0 +1,81 @@ +local context = G.botContext +if type(context.UI) ~= "table" then + context.UI = {} +end +local UI = context.UI + +UI.Config = function(parent) + return UI.createWidget("BotConfig", parent) +end + +-- call :setItems(table) to set items, call :getItems() to get them +-- unique if true, won't allow duplicates +-- callback (can be nil) gets table with new item list, eg: {{id=2160, count=1}, {id=268, count=100}, {id=269, count=20}} +UI.Container = function(callback, unique, parent) + local widget = UI.createWidget("BotContainer", parent) + local oldItems = {} + + local updateItems = function() + local items = widget:getItems() + widget:setItems(items) + + -- callback part + if not callback then return end + local somethingNew = false + for i, item in ipairs(items) do + if type(oldItems[i]) ~= "table" then + somethingNew = true + break + end + if oldItems[i].id ~= item.id or oldItems[i].count ~= item.count then + somethingNew = true + break + end + end + if somethingNew then + oldItems = items + callback(items) + end + end + + widget.setItems = function(self, items) + if type(self) == 'table' then + items = self + end + local itemsToShow = math.max(10, #items + 2) + if itemsToShow % 5 ~= 0 then + itemsToShow = itemsToShow + 5 - itemsToShow % 5 + end + widget.items:destroyChildren() + for i = 1, itemsToShow do + local widget = g_ui.createWidget("BotItem", widget.items) + if type(items[i]) == 'number' then + items[i] = {id=items[i], count=1} + end + if type(items[i]) == 'table' then + widget:setItem(Item.create(items[i].id, items[i].count)) + end + end + oldItems = items + for i, child in ipairs(widget.items:getChildren()) do + child.onItemChange = updateItems + end + end + + widget.getItems = function() + local items = {} + local duplicates = {} + for i, child in ipairs(widget.items:getChildren()) do + if child:getItemId() >= 100 then + if not duplicates[child:getItemId()] or not unique then + table.insert(items, {id=child:getItemId(), count=child:getItemCount()}) + duplicates[child:getItemId()] = true + end + end + end + return items + end + + return widget +end + diff --git a/modules/game_bot/functions/ui_legacy.lua b/modules/game_bot/functions/ui_legacy.lua new file mode 100644 index 0000000..5d77098 --- /dev/null +++ b/modules/game_bot/functions/ui_legacy.lua @@ -0,0 +1,132 @@ +local context = G.botContext + +context.createWidget = function(name, parent) + if parent == nil then + parent = context.panel + end + g_ui.createWidget(name, parent) +end + +context.setupUI = function(otml, parent) + if parent == nil then + parent = context.panel + end + local widget = g_ui.loadUIFromString(otml, parent) + widget.botWidget = true + return widget +end + +context.importStyle = function(otml) + if type(otml) ~= "string" then + return error("Invalid parameter for importStyle, should be string") + end + if otml:find(".otui") and not otml:find("\n") then + return g_ui.importStyle(context.configDir .. "/" .. otml) + end + return g_ui.importStyleFromString(otml) +end + +context.addTab = function(name) + local tab = context.tabs:getTab(name) + if tab then -- return existing tab + return tab.tabPanel.content + end + + context.tabs:setOn(true) + local newTab = context.tabs:addTab(name, g_ui.createWidget('BotPanel')).tabPanel.content + if #(context.tabs.tabs) > 5 then + for k,tab in pairs(context.tabs.tabs) do + tab:setPadding(3) + tab:setFont('small-9px') + end + end + + return newTab +end +context.getTab = context.addTab + +context.setDefaultTab = function(name) + local tab = context.addTab(name) + context.panel = tab +end + +context.addSwitch = function(id, text, onClickCallback, parent) + if not parent then + parent = context.panel + end + local switch = g_ui.createWidget('BotSwitch', parent) + switch:setId(id) + switch:setText(text) + switch.onClick = onClickCallback + return switch +end + +context.addButton = function(id, text, onClickCallback, parent) + if not parent then + parent = context.panel + end + local button = g_ui.createWidget('BotButton', parent) + button:setId(id) + button:setText(text) + button.onClick = onClickCallback + return button +end + +context.addLabel = function(id, text, parent) + if not parent then + parent = context.panel + end + local label = g_ui.createWidget('BotLabel', parent) + label:setId(id) + label:setText(text) + return label +end + +context.addTextEdit = function(id, text, onTextChangeCallback, parent) + if not parent then + parent = context.panel + end + local widget = g_ui.createWidget('BotTextEdit', parent) + widget:setId(id) + widget.onTextChange = onTextChangeCallback + widget:setText(text) + return widget +end + +context.addSeparator = function(id, parent) + if not parent then + parent = context.panel + end + local separator = g_ui.createWidget('BotSeparator', parent) + separator:setId(id) + return separator +end + +context._addMacroSwitch = function(name, keys, parent) + if not parent then + parent = context.panel + end + local text = name + if keys:len() > 0 then + text = name .. " [" .. keys .. "]" + end + local switch = context.addSwitch("macro_" .. #context._macros, text, function(widget) + context.storage._macros[name] = not context.storage._macros[name] + widget:setOn(context.storage._macros[name]) + end, parent) + switch:setOn(context.storage._macros[name]) + return switch +end + +context._addHotkeySwitch = function(name, keys, parent) + if not parent then + parent = context.panel + end + local text = name + if keys:len() > 0 then + text = name .. " [" .. keys .. "]" + end + local switch = context.addSwitch("hotkey_" .. #context._hotkeys, text, nil, parent) + switch:setOn(false) + return switch +end \ No newline at end of file diff --git a/modules/game_bot/functions/ui_windows.lua b/modules/game_bot/functions/ui_windows.lua new file mode 100644 index 0000000..04c9f8e --- /dev/null +++ b/modules/game_bot/functions/ui_windows.lua @@ -0,0 +1,30 @@ +local context = G.botContext +if type(context.UI) ~= "table" then + context.UI = {} +end +local UI = context.UI + +UI.SinglelineEditorWindow = function(text, callback) + return modules.game_textedit.singlelineEditor(text, callback) +end + +UI.MultilineEditorWindow = function(description, test, callback) + return modules.game_textedit.multilineEditor(description, test, callback) +end + +UI.ConfirmationWindow = function(title, question, callback) + local window = nil + local onConfirm = function() + window:destroy() + callback() + end + local closeWindow = function() + window:destroy() + end + window = context.displayGeneralBox(title, question, { + { text=tr('Yes'), callback=onConfirm }, + { text=tr('No'), callback=closeWindow }, + anchor=AnchorHorizontalCenter}, onConfirm, closeWindow) + window.botWidget = true + return window +end \ No newline at end of file diff --git a/modules/game_bot/ui/config.otui b/modules/game_bot/ui/config.otui index efc787a..fc332a9 100644 --- a/modules/game_bot/ui/config.otui +++ b/modules/game_bot/ui/config.otui @@ -1,8 +1,6 @@ BotConfig < Panel - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - height: 40 + id: botConfig + height: 45 ComboBox id: list @@ -31,21 +29,21 @@ BotConfig < Panel anchors.top: prev.bottom anchors.left: parent.left text: Add - width: 58 - height: 17 + width: 59 + height: 20 Button id: edit anchors.top: prev.top anchors.horizontalCenter: parent.horizontalCenter text: Edit - width: 58 - height: 17 + width: 59 + height: 20 Button id: remove anchors.top: prev.top anchors.right: parent.right text: Remove - width: 58 - height: 17 \ No newline at end of file + width: 59 + height: 20 \ No newline at end of file diff --git a/modules/game_bot/ui/container.otui b/modules/game_bot/ui/container.otui new file mode 100644 index 0000000..b7ec078 --- /dev/null +++ b/modules/game_bot/ui/container.otui @@ -0,0 +1,19 @@ +BotContainer < Panel + height: 68 + + ScrollablePanel + id: items + anchors.fill: parent + vertical-scrollbar: scroll + layout: + type: grid + cell-size: 34 34 + flow: true + + BotSmallScrollBar + id: scroll + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.right: parent.right + step: 10 + pixels-scroll: true diff --git a/modules/game_features/features.lua b/modules/game_features/features.lua index 088fdb7..eedcc35 100644 --- a/modules/game_features/features.lua +++ b/modules/game_features/features.lua @@ -8,6 +8,9 @@ end function updateFeatures(version) g_game.resetFeatures() + if version <= 0 then + return + end -- you can add custom features here, list of them is in the modules\gamelib\const.lua g_game.enableFeature(GameBot) diff --git a/modules/game_imbuement/imbuement.lua b/modules/game_imbuement/imbuement.lua new file mode 100644 index 0000000..3c8c414 --- /dev/null +++ b/modules/game_imbuement/imbuement.lua @@ -0,0 +1,39 @@ + function init() + connect(g_game, { + onImbuementWindow = onImbuementWindow, + onCloseImbuementWindow = onCloseImbuementWindow + }) +end + +function terminate() + disconnect(g_game, { + onImbuementWindow = onImbuementWindow, + onCloseImbuementWindow = onCloseImbuementWindow + }) + +end + +function onImbuementWindow(itemId, slots, activeSlots, imbuements, needItems) + print("window " .. slots) + for i, slot in pairs(activeSlots) do + local duration = slot.duration + local removalCost = slot.removalCost + local imbuement = slot.imbuement + for i, source in pairs(imbuement.sources) do + print(source.description, source.item:getId(), source.item:getCount()) + end + + end + for i, imbuement in ipairs(imbuements) do + for i, source in pairs(imbuement.sources) do + print(source.description, source.item:getId(), source.item:getCount()) + end + end + for i, item in ipairs(needItems) do + print(item:getId(), item:getCount()) + end +end + +function onCloseImbuementWindow() + print("close") +end \ No newline at end of file diff --git a/modules/game_imbuement/imbuement.otmod b/modules/game_imbuement/imbuement.otmod new file mode 100644 index 0000000..d690907 --- /dev/null +++ b/modules/game_imbuement/imbuement.otmod @@ -0,0 +1,9 @@ +Module + name: game_imbuement + description: Imbuement + author: otclient.ovh + website: http://otclient.ovh + sandboxed: true + scripts: [ imbuement ] + @onLoad: init() + @onUnload: terminate() diff --git a/modules/game_imbuement/imbuement.otui b/modules/game_imbuement/imbuement.otui new file mode 100644 index 0000000..ddd262d --- /dev/null +++ b/modules/game_imbuement/imbuement.otui @@ -0,0 +1,199 @@ +PreySelectionLabel < Label + font: verdana-11px-monochrome + background-color: alpha + text-offset: 34 0 + margin-right: 5 + text-align: center + text-wrap: true + focusable: true + height: 34 + + $focus: + background-color: #00000055 + color: #ffffff + + UICreature + id: creature + size: 32 32 + anchors.top: parent.top + anchors.left: parent.left + margin-top: 1 + +PreySlot < Panel + width: 190 + height: 280 + border: 1 black + padding: 5 + + Label + id: title + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-align: center + !text: tr("Prey Inactive") + + TextList + id: list + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + margin-top: 15 + margin-right: 10 + vertical-scrollbar: listScrollbar + height: 150 + visible: false + focusable: false + + VerticalScrollBar + id: listScrollbar + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.right: parent.right + pixels-scroll: true + visible: false + step: 10 + + UICreature + id: creature + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 40 + height: 64 + width: 64 + visible: false + + Label + id: description + margin-top: 30 + margin-left: 5 + margin-right: 5 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + text-auto-resize: true + text-align: center + text-wrap: true + visible: false + + Label + id: bonuses + margin-top: 5 + margin-left: 5 + margin-right: 5 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + text-auto-resize: true + text-align: center + text-wrap: true + visible: false + + Button + id: button + margin-top: 5 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 10 + margin-right: 10 + visible: false + + Label + id: bottomLabel + margin-left: 5 + margin-right: 5 + margin-bottom: 5 + anchors.bottom: next.top + anchors.left: parent.left + anchors.right: parent.right + text-auto-resize: true + text-align: center + text-wrap: true + visible: false + + Button + id: bottomButton + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 10 + margin-right: 10 + text: 11 + visible: false + + $hidden: + height: 0 + + $!hidden: + height: 22 + +MainWindow + id: preyWindow + !text: tr('Preys') + size: 600 405 + background-color: #AAAAAA + @onEscape: modules.game_prey.hide() + + PreySlot + id: slot1 + anchors.left: parent.left + anchors.top: parent.top + + PreySlot + id: slot2 + anchors.left: prev.right + anchors.top: prev.top + + PreySlot + id: slot3 + anchors.left: prev.right + anchors.top: prev.top + + HorizontalSeparator + id: mainSeparator + anchors.top: slot3.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 5 + + Label + id: rerollPrice + anchors.top: mainSeparator.bottom + anchors.left: parent.left + margin-top: 5 + width: 190 + height: 30 + text-align: center + + Label + id: balance + anchors.top: mainSeparator.bottom + anchors.left: prev.right + margin-top: 5 + width: 190 + height: 30 + text-align: center + + Label + id: bonusRerolls + anchors.top: mainSeparator.bottom + anchors.left: prev.right + margin-top: 5 + width: 190 + height: 30 + text-align: center + + HorizontalSeparator + anchors.bottom: buttonCancel.top + anchors.left: parent.left + anchors.right: parent.right + margin-bottom: 5 + + Button + id: buttonCancel + !text: tr('Close') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: modules.game_prey.hide() \ No newline at end of file diff --git a/modules/game_interface/gameinterface.lua b/modules/game_interface/gameinterface.lua index 87d22f3..618f7a3 100644 --- a/modules/game_interface/gameinterface.lua +++ b/modules/game_interface/gameinterface.lua @@ -414,6 +414,12 @@ function createThingMenu(menuPosition, lookThing, useThing, creatureThing) if useThing:isRotateable() then menu:addOption(tr('Rotate'), function() g_game.rotate(useThing) end) end + if useThing:isWrapable() then + menu:addOption(tr('Wrap'), function() g_game.wrap(useThing) end) + end + if useThing:isUnwrapable() then + menu:addOption(tr('Unwrap'), function() g_game.wrap(useThing) end) + end if g_game.getFeature(GameBrowseField) and useThing:getPosition().x ~= 0xffff then menu:addOption(tr('Browse Field'), function() g_game.browseField(useThing:getPosition()) end) @@ -832,10 +838,7 @@ function refreshViewMode() return end - local minimumWidth = (g_settings.getNumber("rightPanels") + g_settings.getNumber("leftPanels") - 1) * 200 - if classic then - minimumWidth = minimumWidth + 400 - end + local minimumWidth = (g_settings.getNumber("rightPanels") + g_settings.getNumber("leftPanels") - 1) * 200 + 300 minimumWidth = math.max(minimumWidth, 800) g_window.setMinimumSize({ width = minimumWidth, height = 600 }) if g_window.getWidth() < minimumWidth then @@ -900,7 +903,7 @@ function refreshViewMode() gameRootPanel:fill('parent') gameMapPanel:setKeepAspectRatio(false) gameMapPanel:setLimitVisibleRange(false) - gameMapPanel:setZoom(14) + gameMapPanel:setZoom(15) modules.client_topmenu.getTopMenu():setImageColor('#ffffff66') @@ -939,10 +942,10 @@ function updateSize() local dwidth = dimenstion.width local tileSize = rheight / dheight local maxWidth = tileSize * (awareRange.width + 1) - if g_game.getFeature(GameChangeMapAwareRange) then - local maxWidth = tileSize * (awareRange.width - 1) + if g_game.getFeature(GameChangeMapAwareRange) and g_game.getFeature(GameNewWalking) then + maxWidth = tileSize * (awareRange.width - 1) end - gameMapPanel:setMarginTop(-tileSize * 2) + gameMapPanel:setMarginTop(-tileSize) if g_settings.getBoolean("cacheMap") then gameMapPanel:setMarginLeft(0) gameMapPanel:setMarginRight(0) @@ -956,6 +959,13 @@ function updateSize() modules.game_textmessage.messagesPanel:setMarginTop(-gameMapPanel:getMarginTop()) end + if modules.game_bot then + for i, child in ipairs(gameMapPanel:getChildren()) do + if child.botIcon and child.onGeometryChange then + child.onGeometryChange(child) + end + end + end end --[[ diff --git a/modules/game_interface/interface.otmod b/modules/game_interface/interface.otmod index 52b78b9..a102c65 100644 --- a/modules/game_interface/interface.otmod +++ b/modules/game_interface/interface.otmod @@ -36,6 +36,7 @@ Module - game_textedit - game_actionbar - game_prey + - game_imbuement - game_bot @onLoad: init() @onUnload: terminate() diff --git a/modules/game_itemselector/itemselector.lua b/modules/game_itemselector/itemselector.lua index a3e6a5d..80892d1 100644 --- a/modules/game_itemselector/itemselector.lua +++ b/modules/game_itemselector/itemselector.lua @@ -35,8 +35,7 @@ function show(itemWidget) end end local doneFunc = function() - itemWidget:setItemId(window.item:getItemId()) - itemWidget:setItemCount(window.item:getItemCount()) + itemWidget:setItem(Item.create(window.item:getItemId(), window.item:getItemCount())) destroy() end @@ -45,8 +44,7 @@ function show(itemWidget) window.onEnter = doneFunc window.onEscape = destroy - window.item:setItemId(itemWidget:getItemId()) - window.item:setItemCount(itemWidget:getItemCount()) + window.item:setItem(Item.create(itemWidget:getItemId(), itemWidget:getItemCount())) window.itemId:setValue(itemWidget:getItemId()) if itemWidget:getItemCount() > 1 then diff --git a/modules/game_outfit/outfitwindow.otui b/modules/game_outfit/outfitwindow.otui index abe66ea..ae5afc4 100644 --- a/modules/game_outfit/outfitwindow.otui +++ b/modules/game_outfit/outfitwindow.otui @@ -18,6 +18,7 @@ MainWindow margin-top: 15 margin-left: 22 padding: 4 4 4 4 + size: 96 96 fixed-creature-size: true Label @@ -44,13 +45,14 @@ MainWindow enabled: true @onClick: modules.game_outfit.previousOutfitType() - Creature + Creature id: mountCreatureBox anchors.top: parent.top anchors.right: parent.right margin-top: 15 margin-right: 22 padding: 4 4 4 4 + size: 96 96 fixed-creature-size: true Label diff --git a/modules/game_shop/shop.lua b/modules/game_shop/shop.lua index a63e134..5ace658 100644 --- a/modules/game_shop/shop.lua +++ b/modules/game_shop/shop.lua @@ -317,6 +317,11 @@ function processMessage(data) local title = tr(data["title"]) local msg = data["msg"] msgWindow = displayInfoBox(title, msg) + msgWindow.onDestroy = function(widget) + if widget == msgWindow then + msgWindow = nil + end + end msgWindow:show() msgWindow:raise() msgWindow:focus() diff --git a/modules/gamelib/const.lua b/modules/gamelib/const.lua index 40a50d7..0435e74 100644 --- a/modules/gamelib/const.lua +++ b/modules/gamelib/const.lua @@ -165,6 +165,8 @@ GameMinimapLimitedToSingleFloor = 81 GameDoubleLevel = 83 GameDoubleSoul = 84 GameDoublePlayerGoodsMoney = 85 +GameCreatureWalkthrough = 86 -- add Walkthrough for versions less than 854, unpass = msg->getU8(); in protocolgameparse.cpp +GameDoubleTradeMoney = 87 GameNewWalking = 90 GameSlowerManualWalking = 91 @@ -174,9 +176,14 @@ GameBiggerMapCache = 96 GameForceLight = 97 GameNoDebug = 98 GameBotProtection = 99 -GameFasterAnimations = 101 -LastGameFeature = 110 +GameFasterAnimations = 101 +GameCenteredOutfits = 102 + +GamePacketSizeU32 = 110 +GamePacketCompression = 111 + +LastGameFeature = 120 TextColors = { red = '#f55e5e', --'#c83200' diff --git a/modules/gamelib/protocollogin.lua b/modules/gamelib/protocollogin.lua index 44e28dc..bfaad7a 100644 --- a/modules/gamelib/protocollogin.lua +++ b/modules/gamelib/protocollogin.lua @@ -131,6 +131,10 @@ function ProtocolLogin:sendLoginPacket() msg:encryptRsa() end + if g_game.getFeature(GamePacketSizeU32) then + self:enableBigPackets() + end + if g_game.getFeature(GameProtocolChecksum) then self:enableChecksum() end diff --git a/modules/gamelib/ui/uiitem.lua b/modules/gamelib/ui/uiitem.lua index 3133da8..6cbcf57 100644 --- a/modules/gamelib/ui/uiitem.lua +++ b/modules/gamelib/ui/uiitem.lua @@ -26,11 +26,7 @@ function UIItem:onDrop(widget, mousePos, forced) if not item or not item:isItem() then return false end if self.selectable then - self:setItemId(item:getId()) - self:setItemCount(item:getCount()) - if item:getSubType() > 1 then - self:setItemSubType(item:getSubType()) - end + self:setItem(Item.create(item:getId(), item:getCount())) return end diff --git a/otclient_dx.exe b/otclient_dx.exe index 99e9a56..4625e8c 100644 Binary files a/otclient_dx.exe and b/otclient_dx.exe differ diff --git a/otclient_gl.exe b/otclient_gl.exe index 9ffaf89..3262eb2 100644 Binary files a/otclient_gl.exe and b/otclient_gl.exe differ diff --git a/otclient_linux b/otclient_linux index 6deb95f..9c0f4ff 100644 Binary files a/otclient_linux and b/otclient_linux differ diff --git a/pdb/otclient_dx.zip b/pdb/otclient_dx.zip index bc401f8..83e2fc9 100644 Binary files a/pdb/otclient_dx.zip and b/pdb/otclient_dx.zip differ diff --git a/pdb/otclient_gl.zip b/pdb/otclient_gl.zip index eed2615..a6041b6 100644 Binary files a/pdb/otclient_gl.zip and b/pdb/otclient_gl.zip differ