mirror of
https://github.com/OTCv8/otclientv8.git
synced 2025-10-19 14:13:27 +02:00
Version 2.4 - http://otclient.net/showthread.php?tid=160
This commit is contained in:
@@ -57,14 +57,17 @@ function init()
|
||||
connect(g_game, { onGameStart = onGameStart,
|
||||
onGameEnd = onGameEnd })
|
||||
|
||||
g_window.setMinimumSize({ width = 800, height = 600 })
|
||||
if g_sounds ~= nil then
|
||||
--g_sounds.preload(musicFilename)
|
||||
end
|
||||
-- initialize in fullscreen mode on mobile devices
|
||||
if g_window.getPlatformType() == "X11-EGL" then
|
||||
g_window.setFullscreen(true)
|
||||
else
|
||||
|
||||
if not Updater then
|
||||
if g_resources.getLayout() == "mobile" then
|
||||
g_window.setMinimumSize({ width = 640, height = 360 })
|
||||
else
|
||||
g_window.setMinimumSize({ width = 800, height = 640 })
|
||||
end
|
||||
|
||||
-- window size
|
||||
local size = { width = 1024, height = 600 }
|
||||
size = g_settings.getSize('window-size', size)
|
||||
@@ -87,12 +90,7 @@ function init()
|
||||
g_window.setTitle(g_app.getName())
|
||||
g_window.setIcon('/images/clienticon')
|
||||
|
||||
-- poll resize events
|
||||
g_window.poll()
|
||||
|
||||
g_keyboard.bindKeyDown('Ctrl+Shift+R', reloadScripts)
|
||||
g_keyboard.bindKeyDown('Ctrl+Shift+[', function() g_extras.setTestMode((g_extras.getTestMode() - 1) % 10) end)
|
||||
g_keyboard.bindKeyDown('Ctrl+Shift+]', function() g_extras.setTestMode((g_extras.getTestMode() + 1) % 10) end)
|
||||
|
||||
-- generate machine uuid, this is a security measure for storing passwords
|
||||
if not g_crypt.setMachineUUID(g_settings.get('uuid')) then
|
||||
|
@@ -14,10 +14,11 @@ Module
|
||||
- client_locales
|
||||
- client_topmenu
|
||||
- client_background
|
||||
- client_textedit
|
||||
- client_options
|
||||
- client_entergame
|
||||
- client_entergamev2
|
||||
- client_terminal
|
||||
- client_stats
|
||||
- client_feedback
|
||||
- client_updater
|
||||
- client_mobile
|
||||
|
@@ -8,9 +8,8 @@ function init()
|
||||
background:lower()
|
||||
|
||||
clientVersionLabel = background:getChildById('clientVersionLabel')
|
||||
clientVersionLabel:setText(g_app.getName() .. ' ' .. g_app.getVersion() .. '\nMade by:\n' .. g_app.getAuthor() .. "\notclient@otclient.ovh")
|
||||
clientVersionLabel:setText('OTClientV8 ' .. g_app.getVersion() .. '\nMade by:\n' .. g_app.getAuthor() .. "\notclient@otclient.ovh")
|
||||
|
||||
|
||||
if not g_game.isOnline() then
|
||||
addEvent(function() g_effects.fadeIn(clientVersionLabel, 1500) end)
|
||||
end
|
||||
|
@@ -5,6 +5,5 @@ Module
|
||||
website: https://github.com/edubart/otclient
|
||||
sandboxed: true
|
||||
scripts: [ background ]
|
||||
dependencies: [ client_topmenu ]
|
||||
@onLoad: init()
|
||||
@onUnload: terminate()
|
||||
|
@@ -1,7 +1,11 @@
|
||||
Background
|
||||
UIWidget
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
focusable: false
|
||||
image-source: /images/background
|
||||
image-smooth: true
|
||||
image-fixed-ratio: true
|
||||
margin-top: 1
|
||||
|
||||
UILabel
|
||||
id: clientVersionLabel
|
||||
|
@@ -45,12 +45,14 @@ StaticMainWindow
|
||||
id: charactersWindow
|
||||
!text: tr('Character List')
|
||||
visible: false
|
||||
size: 350 400
|
||||
$mobile:
|
||||
size: 350 280
|
||||
@onEnter: CharacterList.doLogin()
|
||||
@onEscape: CharacterList.hide(true)
|
||||
@onSetup: |
|
||||
g_keyboard.bindKeyPress('Up', function() self:getChildById('characters'):focusPreviousChild(KeyboardFocusReason) end, self)
|
||||
g_keyboard.bindKeyPress('Down', function() self:getChildById('characters'):focusNextChild(KeyboardFocusReason) end, self)
|
||||
self:setSize({width = 350, height = 400})
|
||||
g_keyboard.bindKeyPress('Down', function() self:getChildById('characters'):focusNextChild(KeyboardFocusReason) end, self)
|
||||
|
||||
TextList
|
||||
id: characters
|
||||
|
@@ -17,6 +17,8 @@ local serverHostTextEdit
|
||||
local rememberPasswordBox
|
||||
local protos = {"740", "760", "772", "792", "800", "810", "854", "860", "870", "961", "1077", "1090", "1096", "1098", "1099", "1100"}
|
||||
|
||||
local checkedByUpdater = {}
|
||||
|
||||
-- private functions
|
||||
local function onProtocolError(protocol, message, errorCode)
|
||||
if errorCode then
|
||||
@@ -129,11 +131,7 @@ local function onHTTPResult(data, err)
|
||||
local incorrectThings = validateThings(things)
|
||||
if #incorrectThings > 0 then
|
||||
g_logger.info(incorrectThings)
|
||||
if Updater then
|
||||
return Updater.updateThings(things, incorrectThings)
|
||||
else
|
||||
return EnterGame.onError(incorrectThings)
|
||||
end
|
||||
return EnterGame.onError(incorrectThings)
|
||||
end
|
||||
|
||||
-- custom protocol
|
||||
@@ -264,9 +262,6 @@ end
|
||||
|
||||
function EnterGame.show()
|
||||
if not enterGame then return end
|
||||
if Updater and Updater.isVisible() or g_game.isOnline() then
|
||||
return EnterGame.hide()
|
||||
end
|
||||
enterGame:show()
|
||||
enterGame:raise()
|
||||
enterGame:focus()
|
||||
@@ -313,9 +308,6 @@ function EnterGame.onServerChange()
|
||||
end
|
||||
|
||||
function EnterGame.doLogin()
|
||||
if Updater and Updater.isVisible() then
|
||||
return
|
||||
end
|
||||
if g_game.isOnline() then
|
||||
local errorBox = displayErrorBox(tr('Login Error'), tr('Cannot login while already in game.'))
|
||||
connect(errorBox, { onOk = EnterGame.show })
|
||||
@@ -339,23 +331,20 @@ function EnterGame.doLogin()
|
||||
g_settings.set('client-version', G.clientVersion)
|
||||
g_settings.save()
|
||||
|
||||
if G.host:find("ws://") ~= nil or G.host:find("wss://") ~= nil then
|
||||
return EnterGame.doLoginWs()
|
||||
end
|
||||
if G.host:find("http") ~= nil then
|
||||
return EnterGame.doLoginHttp()
|
||||
end
|
||||
|
||||
local server_params = G.host:split(":")
|
||||
if #server_params < 2 then
|
||||
return EnterGame.onError("Invalid server, it should be in format IP:PORT or it should be http url to login script")
|
||||
end
|
||||
local server_ip = server_params[1]
|
||||
local server_port = tonumber(server_params[2])
|
||||
local server_port = 7171
|
||||
if #server_params >= 2 then
|
||||
server_port = tonumber(server_params[2])
|
||||
end
|
||||
if #server_params >= 3 then
|
||||
G.clientVersion = tonumber(server_params[3])
|
||||
end
|
||||
if not server_port or not G.clientVersion then
|
||||
if type(server_ip) ~= 'string' or server_ip:len() <= 3 or not server_port or not G.clientVersion then
|
||||
return EnterGame.onError("Invalid server, it should be in format IP:PORT or it should be http url to login script")
|
||||
end
|
||||
|
||||
@@ -367,8 +356,12 @@ function EnterGame.doLogin()
|
||||
local incorrectThings = validateThings(things)
|
||||
if #incorrectThings > 0 then
|
||||
g_logger.error(incorrectThings)
|
||||
if Updater then
|
||||
return Updater.updateThings(things, incorrectThings)
|
||||
if Updater and not checkedByUpdater[G.clientVersion] then
|
||||
checkedByUpdater[G.clientVersion] = true
|
||||
return Updater.check({
|
||||
version = G.clientVersion,
|
||||
host = G.host
|
||||
})
|
||||
else
|
||||
return EnterGame.onError(incorrectThings)
|
||||
end
|
||||
|
@@ -6,4 +6,7 @@ Module
|
||||
scripts: [ entergame, characterlist ]
|
||||
@onLoad: EnterGame.init() CharacterList.init()
|
||||
@onUnload: EnterGame.terminate() CharacterList.terminate()
|
||||
|
||||
|
||||
load-later:
|
||||
- game_things
|
||||
- game_features
|
||||
|
@@ -4,19 +4,7 @@ dofile 'neededtranslations'
|
||||
local defaultLocaleName = 'en'
|
||||
local installedLocales
|
||||
local currentLocale
|
||||
|
||||
function sendLocale(localeName)
|
||||
if not g_game.getFeature(GameExtendedOpcode) then
|
||||
return
|
||||
end
|
||||
|
||||
local protocolGame = g_game.getProtocolGame()
|
||||
if protocolGame then
|
||||
protocolGame:sendExtendedOpcode(ExtendedIds.Locale, localeName)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
local missingTranslations = {}
|
||||
|
||||
function createWindow()
|
||||
localesWindow = g_ui.displayUI('locales')
|
||||
@@ -51,18 +39,6 @@ function selectFirstLocale(name)
|
||||
g_settings.save()
|
||||
end
|
||||
|
||||
-- hooked functions
|
||||
function onGameStart()
|
||||
sendLocale(currentLocale.name)
|
||||
end
|
||||
|
||||
function onExtendedLocales(protocol, opcode, buffer)
|
||||
local locale = installedLocales[buffer]
|
||||
if locale and setLocale(locale.name) then
|
||||
g_modules.reloadModules()
|
||||
end
|
||||
end
|
||||
|
||||
-- public functions
|
||||
function init()
|
||||
installedLocales = {}
|
||||
@@ -76,18 +52,13 @@ function init()
|
||||
setLocale(defaultLocaleName)
|
||||
--connect(g_app, { onRun = createWindow })
|
||||
end
|
||||
|
||||
ProtocolGame.registerExtendedOpcode(ExtendedIds.Locale, onExtendedLocales)
|
||||
connect(g_game, { onGameStart = onGameStart })
|
||||
end
|
||||
|
||||
function terminate()
|
||||
installedLocales = nil
|
||||
currentLocale = nil
|
||||
|
||||
ProtocolGame.unregisterExtendedOpcode(ExtendedIds.Locale)
|
||||
disconnect(g_app, { onRun = createWindow })
|
||||
disconnect(g_game, { onGameStart = onGameStart })
|
||||
--disconnect(g_app, { onRun = createWindow })
|
||||
end
|
||||
|
||||
function generateNewTranslationTable(localename)
|
||||
@@ -154,9 +125,6 @@ function setLocale(name)
|
||||
pwarning("Locale " .. name .. ' does not exist.')
|
||||
return false
|
||||
end
|
||||
if currentLocale then
|
||||
sendLocale(locale.name)
|
||||
end
|
||||
currentLocale = locale
|
||||
g_settings.set('locale', name)
|
||||
if onLocaleChanged then onLocaleChanged(name) end
|
||||
@@ -194,7 +162,10 @@ function _G.tr(text, ...)
|
||||
if not translation then
|
||||
if translation == nil then
|
||||
if currentLocale.name ~= defaultLocaleName then
|
||||
pdebug('Unable to translate: \"' .. text .. '\"')
|
||||
if not missingTranslations[text] then
|
||||
pdebug('Unable to translate: \"' .. text .. '\"')
|
||||
missingTranslations[text] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
translation = text
|
||||
|
84
modules/client_mobile/mobile.lua
Normal file
84
modules/client_mobile/mobile.lua
Normal file
@@ -0,0 +1,84 @@
|
||||
local overlay
|
||||
local touchStart = 0
|
||||
local updateCursorEvent = nil
|
||||
local zoomInButton
|
||||
local zoomOutButton
|
||||
|
||||
-- public functions
|
||||
function init()
|
||||
if not g_app.isMobile() then return end
|
||||
overlay = g_ui.displayUI('mobile')
|
||||
overlay:raise()
|
||||
|
||||
zoomInButton = modules.client_topmenu.addLeftButton('zoomInButton', 'Zoom In', '/images/topbuttons/zoomin', function() g_app.scaleUp() end)
|
||||
zoomOutButton = modules.client_topmenu.addLeftButton('zoomOutButton', 'Zoom Out', '/images/topbuttons/zoomout', function() g_app.scaleDown() end)
|
||||
scheduleEvent(function()
|
||||
g_app.scale(5.0)
|
||||
end, 10)
|
||||
|
||||
connect(overlay, {
|
||||
onMousePress = onMousePress,
|
||||
onMouseRelease = onMouseRelease,
|
||||
onTouchPress = onMousePress,
|
||||
onTouchRelease = onMouseRelease,
|
||||
onMouseMove = onMouseMove
|
||||
})
|
||||
end
|
||||
|
||||
function terminate()
|
||||
if not g_app.isMobile() then return end
|
||||
disconnect(overlay, {
|
||||
onMousePress = onMousePress,
|
||||
onMouseRelease = onMouseRelease,
|
||||
onTouchPress = onMousePress,
|
||||
onTouchRelease = onMouseRelease,
|
||||
onMouseMove = onMouseMove
|
||||
})
|
||||
zoomInButton:destroy()
|
||||
zoomOutButton:destroy()
|
||||
overlay:destroy()
|
||||
overlay = nil
|
||||
end
|
||||
|
||||
function hide()
|
||||
overlay:hide()
|
||||
end
|
||||
|
||||
function show()
|
||||
overlay:show()
|
||||
end
|
||||
|
||||
function onMouseMove(widget, pos, offset)
|
||||
|
||||
end
|
||||
|
||||
function onMousePress(widget, pos, button)
|
||||
overlay:raise()
|
||||
if button == 4 then -- touch
|
||||
overlay:raise()
|
||||
overlay.cursor:show()
|
||||
overlay.cursor:setPosition({x=pos.x - 32, y = pos.y - 32})
|
||||
touchStart = g_clock.millis()
|
||||
updateCursor()
|
||||
else
|
||||
overlay.cursor:hide()
|
||||
removeEvent(updateCursorEvent)
|
||||
end
|
||||
end
|
||||
|
||||
function onMouseRelease(widget, pos, button)
|
||||
overlay.cursor:hide()
|
||||
removeEvent(updateCursorEvent)
|
||||
end
|
||||
|
||||
function updateCursor()
|
||||
removeEvent(updateCursorEvent)
|
||||
local percent = 100 - math.max(0, math.min(100, (g_clock.millis() - touchStart) / 5)) -- 500 ms
|
||||
overlay.cursor:setPercent(percent)
|
||||
if percent > 0 then
|
||||
overlay.cursor:setOpacity(0.5)
|
||||
updateCursorEvent = scheduleEvent(updateCursor, 10)
|
||||
else
|
||||
overlay.cursor:setOpacity(0.8)
|
||||
end
|
||||
end
|
9
modules/client_mobile/mobile.otmod
Normal file
9
modules/client_mobile/mobile.otmod
Normal file
@@ -0,0 +1,9 @@
|
||||
Module
|
||||
name: client_mobile
|
||||
description: Handles the mobile interface for smartphones
|
||||
author: otclient@otclient.ovh
|
||||
website: http://otclient.net
|
||||
sandboxed: true
|
||||
scripts: [ mobile ]
|
||||
@onLoad: init()
|
||||
@onUnload: terminate()
|
15
modules/client_mobile/mobile.otui
Normal file
15
modules/client_mobile/mobile.otui
Normal file
@@ -0,0 +1,15 @@
|
||||
UIWidget
|
||||
anchors.fill: parent
|
||||
focusable: false
|
||||
phantom: true
|
||||
|
||||
UIProgressRect
|
||||
id: cursor
|
||||
size: 64 64
|
||||
background: #FF5858
|
||||
percent: 100
|
||||
visible: false
|
||||
x: 0
|
||||
y: 0
|
||||
focusable: false
|
||||
phantom: true
|
@@ -1,4 +1,4 @@
|
||||
Panel
|
||||
OptionPanel
|
||||
OptionCheckBox
|
||||
id: enableAudio
|
||||
!text: tr('Enable audio')
|
||||
@@ -10,9 +10,6 @@ Panel
|
||||
Label
|
||||
id: musicSoundVolumeLabel
|
||||
!text: tr('Music volume: %d', 100)
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 6
|
||||
@onSetup: |
|
||||
local value = modules.client_options.getOption('musicSoundVolume')
|
||||
@@ -20,9 +17,6 @@ Panel
|
||||
|
||||
OptionScrollbar
|
||||
id: musicSoundVolume
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 3
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
@@ -30,9 +24,6 @@ Panel
|
||||
Label
|
||||
id: botSoundVolumeLabel
|
||||
!text: tr('Bot sound volume: %d', 100)
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 6
|
||||
@onSetup: |
|
||||
local value = modules.client_options.getOption('botSoundVolume')
|
||||
@@ -40,9 +31,6 @@ Panel
|
||||
|
||||
OptionScrollbar
|
||||
id: botSoundVolume
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 3
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
|
@@ -1,4 +1,4 @@
|
||||
Panel
|
||||
OptionPanel
|
||||
OptionCheckBox
|
||||
id: showInfoMessagesInConsole
|
||||
!text: tr('Show info messages in console')
|
||||
|
@@ -1,8 +1,11 @@
|
||||
Panel
|
||||
OptionPanel
|
||||
OptionCheckBox
|
||||
id: classicControl
|
||||
!text: tr('Classic control')
|
||||
|
||||
$mobile:
|
||||
visible: false
|
||||
|
||||
OptionCheckBox
|
||||
id: autoChaseOverride
|
||||
!text: tr('Allow auto chase override')
|
||||
@@ -15,6 +18,8 @@ Panel
|
||||
id: wsadWalking
|
||||
!text: tr('Enable WSAD walking')
|
||||
!tooltip: tr('Disable chat and allow walk using WSAD keys')
|
||||
$mobile:
|
||||
visible: false
|
||||
|
||||
OptionCheckBox
|
||||
id: dash
|
||||
@@ -27,9 +32,6 @@ Panel
|
||||
!tooltip: tr('Will detect when to use diagonal step based on the\nkeys you are pressing')
|
||||
|
||||
Label
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
id: hotkeyDelayLabel
|
||||
margin-top: 10
|
||||
!tooltip: tr('Give you some time to make a turn while walking if you press many keys simultaneously')
|
||||
@@ -39,114 +41,107 @@ Panel
|
||||
|
||||
OptionScrollbar
|
||||
id: hotkeyDelay
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-top: 3
|
||||
minimum: 5
|
||||
maximum: 50
|
||||
|
||||
Label
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
id: walkFirstStepDelayLabel
|
||||
margin-top: 10
|
||||
@onSetup: |
|
||||
local value = modules.client_options.getOption('walkFirstStepDelay')
|
||||
self:setText(tr('Walk delay after first step: %s ms', value))
|
||||
|
||||
$mobile:
|
||||
visible: false
|
||||
|
||||
OptionScrollbar
|
||||
id: walkFirstStepDelay
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-top: 3
|
||||
minimum: 50
|
||||
maximum: 300
|
||||
|
||||
$mobile:
|
||||
visible: false
|
||||
|
||||
Label
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
id: walkTurnDelayLabel
|
||||
margin-top: 10
|
||||
@onSetup: |
|
||||
local value = modules.client_options.getOption('walkTurnDelay')
|
||||
self:setText(tr('Walk delay after turn: %s ms', value))
|
||||
|
||||
$mobile:
|
||||
visible: false
|
||||
|
||||
OptionScrollbar
|
||||
id: walkTurnDelay
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-top: 3
|
||||
minimum: 0
|
||||
maximum: 300
|
||||
|
||||
$mobile:
|
||||
visible: false
|
||||
|
||||
Label
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
id: walkCtrlTurnDelayLabel
|
||||
margin-top: 10
|
||||
$mobile:
|
||||
visible: false
|
||||
@onSetup: |
|
||||
local value = modules.client_options.getOption('walkTurnDelay')
|
||||
self:setText(tr('Walk delay after ctrl turn: %s ms', value))
|
||||
|
||||
OptionScrollbar
|
||||
id: walkCtrlTurnDelay
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-top: 3
|
||||
minimum: 0
|
||||
maximum: 300
|
||||
$mobile:
|
||||
visible: false
|
||||
|
||||
Label
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
id: walkStairsDelayLabel
|
||||
margin-top: 10
|
||||
@onSetup: |
|
||||
local value = modules.client_options.getOption('walkStairsDelay')
|
||||
self:setText(tr('Walk delay after floor change: %s ms', value))
|
||||
$mobile:
|
||||
visible: false
|
||||
|
||||
OptionScrollbar
|
||||
id: walkStairsDelay
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-top: 3
|
||||
minimum: 0
|
||||
maximum: 300
|
||||
$mobile:
|
||||
visible: false
|
||||
|
||||
Label
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
id: walkTeleportDelayLabel
|
||||
margin-top: 10
|
||||
@onSetup: |
|
||||
local value = modules.client_options.getOption('walkTeleportDelay')
|
||||
self:setText(tr('Walk delay after teleport: %s ms', value))
|
||||
$mobile:
|
||||
visible: false
|
||||
|
||||
OptionScrollbar
|
||||
id: walkTeleportDelay
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-top: 3
|
||||
minimum: 0
|
||||
maximum: 300
|
||||
$mobile:
|
||||
visible: false
|
||||
|
||||
Button
|
||||
id: changeLocale
|
||||
!text: tr('Change language')
|
||||
@onClick: modules.client_locales.createWindow()
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: prev.left
|
||||
margin-top: 12
|
||||
width: 120
|
||||
Panel
|
||||
height: 30
|
||||
margin-top: 10
|
||||
|
||||
Button
|
||||
id: changeLocale
|
||||
!text: tr('Change language')
|
||||
@onClick: modules.client_locales.createWindow()
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
width: 150
|
||||
|
@@ -1,36 +1,22 @@
|
||||
Panel
|
||||
OptionPanel
|
||||
Label
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
text-wrap: false
|
||||
@onSetup: |
|
||||
self:setText(tr("GPU: ") .. g_graphics.getRenderer())
|
||||
|
||||
Label
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
text-wrap: false
|
||||
@onSetup: |
|
||||
self:setText(tr("Version: ") .. g_graphics.getVersion())
|
||||
|
||||
HorizontalSeparator
|
||||
id: separator
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin: 5 5 5 5
|
||||
|
||||
OptionCheckBox
|
||||
id: vsync
|
||||
!text: tr('Enable vertical synchronization')
|
||||
!tooltip: tr('Limits FPS (usually to 60)')
|
||||
@onSetup: |
|
||||
if g_window.getPlatformType() == 'WIN32-EGL' then
|
||||
self:setEnabled(false)
|
||||
self:setText(tr('Enable vertical synchronization') .. " " .. tr('(OpenGL only)'))
|
||||
end
|
||||
|
||||
OptionCheckBox
|
||||
id: showFps
|
||||
@@ -47,17 +33,11 @@ Panel
|
||||
|
||||
Label
|
||||
margin-top: 12
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
id: optimizationLevelLabel
|
||||
!text: tr("Optimization level")
|
||||
|
||||
ComboBox
|
||||
id: optimizationLevel
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 3
|
||||
margin-right: 2
|
||||
margin-left: 2
|
||||
@@ -70,12 +50,13 @@ Panel
|
||||
self:addOption("High")
|
||||
self:addOption("Maximum")
|
||||
|
||||
Label
|
||||
!text: tr('High/Maximum optimization level may cause visual defects.')
|
||||
margin-top: 5
|
||||
|
||||
Label
|
||||
id: backgroundFrameRateLabel
|
||||
!text: tr('Game framerate limit: %s', 'max')
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 12
|
||||
@onSetup: |
|
||||
local value = modules.client_options.getOption('backgroundFrameRate')
|
||||
@@ -87,18 +68,12 @@ Panel
|
||||
|
||||
OptionScrollbar
|
||||
id: backgroundFrameRate
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 3
|
||||
minimum: 10
|
||||
maximum: 201
|
||||
|
||||
Label
|
||||
id: ambientLightLabel
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 6
|
||||
@onSetup: |
|
||||
local value = modules.client_options.getOption('ambientLight')
|
||||
@@ -106,9 +81,6 @@ Panel
|
||||
|
||||
OptionScrollbar
|
||||
id: ambientLight
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 3
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
@@ -116,10 +88,9 @@ Panel
|
||||
Label
|
||||
id: tips
|
||||
margin-top: 20
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
text-auto-resize: true
|
||||
text-align: left
|
||||
text-wrap: true
|
||||
!text: tr("If you have FPS issues:\n- Use OpenGL version (_gl)\n- Disable vertical synchronization\n- Set higher optimization level\n- Lower screen resolution\nOr report it via email to otclient@otclient.ovh")
|
||||
!text: tr("If you have FPS issues:\n- Use OpenGL version (_gl)\n- Disable vertical synchronization\n- Set higher optimization level\n- Lower screen resolution\nOr report it on forum: http://otclient.net")
|
||||
$mobile:
|
||||
visible: false
|
@@ -1,20 +1,18 @@
|
||||
Panel
|
||||
OptionPanel
|
||||
Label
|
||||
width: 130
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
id: layoutLabel
|
||||
!text: tr("Layout (change requries client restart)")
|
||||
$mobile:
|
||||
visible: false
|
||||
|
||||
ComboBox
|
||||
id: layout
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 3
|
||||
margin-right: 2
|
||||
margin-left: 2
|
||||
$mobile:
|
||||
visible: false
|
||||
@onOptionChange: modules.client_options.setOption(self:getId(), self:getCurrentOption().text)
|
||||
@onSetup: |
|
||||
self:addOption("Default")
|
||||
@@ -28,11 +26,17 @@ Panel
|
||||
id: classicView
|
||||
!text: tr('Classic view')
|
||||
margin-top: 5
|
||||
|
||||
$mobile:
|
||||
visible: false
|
||||
|
||||
OptionCheckBox
|
||||
id: cacheMap
|
||||
!text: tr('Cache map (for non-classic view)')
|
||||
|
||||
$mobile:
|
||||
visible: false
|
||||
|
||||
OptionCheckBox
|
||||
id: actionBar1
|
||||
!text: tr("Show first action bar")
|
||||
@@ -57,6 +61,8 @@ Panel
|
||||
OptionCheckBox
|
||||
id: displayHealthOnTop
|
||||
!text: tr('Display creature health bars above texts')
|
||||
$mobile:
|
||||
visible: false
|
||||
|
||||
OptionCheckBox
|
||||
id: hidePlayerBars
|
||||
@@ -65,6 +71,8 @@ Panel
|
||||
OptionCheckBox
|
||||
id: displayMana
|
||||
!text: tr('Show player mana bar')
|
||||
$mobile:
|
||||
visible: false
|
||||
|
||||
OptionCheckBox
|
||||
id: topHealtManaBar
|
||||
@@ -73,93 +81,92 @@ Panel
|
||||
OptionCheckBox
|
||||
id: showHealthManaCircle
|
||||
!text: tr('Show health and mana circle')
|
||||
$mobile:
|
||||
visible: false
|
||||
|
||||
OptionCheckBox
|
||||
id: highlightThingsUnderCursor
|
||||
!text: tr('Highlight things under cursor')
|
||||
|
||||
Label
|
||||
margin-top: 5
|
||||
width: 90
|
||||
anchors.left: parent.left
|
||||
anchors.top: prev.bottom
|
||||
id: leftPanelsLabel
|
||||
!text: tr("Left panels")
|
||||
|
||||
Label
|
||||
width: 90
|
||||
anchors.left: prev.right
|
||||
anchors.top: prev.top
|
||||
id: rightPanelsLabel
|
||||
!text: tr("Right panels")
|
||||
|
||||
Label
|
||||
width: 130
|
||||
anchors.left: prev.right
|
||||
anchors.top: prev.top
|
||||
id: backpackPanelLabel
|
||||
!text: tr("Container's panel")
|
||||
!tooltip: tr("Open new containers in selected panel")
|
||||
|
||||
ComboBox
|
||||
id: leftPanels
|
||||
anchors.left: leftPanelsLabel.left
|
||||
anchors.right: leftPanelsLabel.right
|
||||
anchors.top: leftPanelsLabel.bottom
|
||||
Panel
|
||||
height: 40
|
||||
margin-top: 3
|
||||
margin-right: 20
|
||||
@onOptionChange: modules.client_options.setOption(self:getId(), self.currentIndex)
|
||||
@onSetup: |
|
||||
self:addOption("0")
|
||||
self:addOption("1")
|
||||
self:addOption("2")
|
||||
self:addOption("3")
|
||||
self:addOption("4")
|
||||
|
||||
Label
|
||||
width: 90
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
id: leftPanelsLabel
|
||||
!text: tr("Left panels")
|
||||
|
||||
ComboBox
|
||||
id: rightPanels
|
||||
anchors.left: rightPanelsLabel.left
|
||||
anchors.right: rightPanelsLabel.right
|
||||
anchors.top: rightPanelsLabel.bottom
|
||||
margin-top: 3
|
||||
margin-right: 20
|
||||
@onOptionChange: modules.client_options.setOption(self:getId(), self.currentIndex)
|
||||
@onSetup: |
|
||||
self:addOption("1")
|
||||
self:addOption("2")
|
||||
self:addOption("3")
|
||||
self:addOption("4")
|
||||
Label
|
||||
width: 90
|
||||
anchors.left: prev.right
|
||||
anchors.top: prev.top
|
||||
id: rightPanelsLabel
|
||||
!text: tr("Right panels")
|
||||
|
||||
ComboBox
|
||||
id: containerPanel
|
||||
anchors.left: backpackPanelLabel.left
|
||||
anchors.right: backpackPanelLabel.right
|
||||
anchors.top: backpackPanelLabel.bottom
|
||||
margin-top: 3
|
||||
@onOptionChange: modules.client_options.setOption(self:getId(), self.currentIndex)
|
||||
@onSetup: |
|
||||
self:addOption("1st left panel")
|
||||
self:addOption("2nd left panel")
|
||||
self:addOption("3rd left panel")
|
||||
self:addOption("4th left panel")
|
||||
self:addOption("1st right panel")
|
||||
self:addOption("2nd right panel")
|
||||
self:addOption("3rd right panel")
|
||||
self:addOption("4th right panel")
|
||||
Label
|
||||
width: 130
|
||||
anchors.left: prev.right
|
||||
anchors.top: prev.top
|
||||
id: backpackPanelLabel
|
||||
!text: tr("Container's panel")
|
||||
!tooltip: tr("Open new containers in selected panel")
|
||||
|
||||
ComboBox
|
||||
id: leftPanels
|
||||
anchors.left: leftPanelsLabel.left
|
||||
anchors.right: leftPanelsLabel.right
|
||||
anchors.top: leftPanelsLabel.bottom
|
||||
margin-top: 3
|
||||
margin-right: 20
|
||||
@onOptionChange: modules.client_options.setOption(self:getId(), self.currentIndex)
|
||||
@onSetup: |
|
||||
self:addOption("0")
|
||||
self:addOption("1")
|
||||
self:addOption("2")
|
||||
self:addOption("3")
|
||||
self:addOption("4")
|
||||
|
||||
ComboBox
|
||||
id: rightPanels
|
||||
anchors.left: rightPanelsLabel.left
|
||||
anchors.right: rightPanelsLabel.right
|
||||
anchors.top: rightPanelsLabel.bottom
|
||||
margin-top: 3
|
||||
margin-right: 20
|
||||
@onOptionChange: modules.client_options.setOption(self:getId(), self.currentIndex)
|
||||
@onSetup: |
|
||||
self:addOption("1")
|
||||
self:addOption("2")
|
||||
self:addOption("3")
|
||||
self:addOption("4")
|
||||
|
||||
ComboBox
|
||||
id: containerPanel
|
||||
anchors.left: backpackPanelLabel.left
|
||||
anchors.right: backpackPanelLabel.right
|
||||
anchors.top: backpackPanelLabel.bottom
|
||||
margin-top: 3
|
||||
@onOptionChange: modules.client_options.setOption(self:getId(), self.currentIndex)
|
||||
@onSetup: |
|
||||
self:addOption("1st left panel")
|
||||
self:addOption("2nd left panel")
|
||||
self:addOption("3rd left panel")
|
||||
self:addOption("4th left panel")
|
||||
self:addOption("1st right panel")
|
||||
self:addOption("2nd right panel")
|
||||
self:addOption("3rd right panel")
|
||||
self:addOption("4th right panel")
|
||||
|
||||
Label
|
||||
margin-top: 3
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
id: crosshairLabel
|
||||
!text: tr("Crosshair")
|
||||
|
||||
ComboBox
|
||||
id: crosshair
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 3
|
||||
margin-right: 2
|
||||
margin-left: 2
|
||||
@@ -171,9 +178,6 @@ Panel
|
||||
|
||||
Label
|
||||
id: floorFadingLabel
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 6
|
||||
@onSetup: |
|
||||
local value = modules.client_options.getOption('floorFading')
|
||||
@@ -181,17 +185,11 @@ Panel
|
||||
|
||||
OptionScrollbar
|
||||
id: floorFading
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 3
|
||||
minimum: 0
|
||||
maximum: 2000
|
||||
|
||||
Label
|
||||
id: floorFadingLabel2
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 6
|
||||
!text: (tr('Floor fading doesn\'t work with enabled light'))
|
||||
|
@@ -4,9 +4,9 @@ local defaultOptions = {
|
||||
showFps = true,
|
||||
showPing = true,
|
||||
fullscreen = false,
|
||||
classicView = true,
|
||||
classicView = not g_app.isMobile(),
|
||||
cacheMap = false,
|
||||
classicControl = true,
|
||||
classicControl = not g_app.isMobile(),
|
||||
smartWalk = false,
|
||||
dash = false,
|
||||
autoChaseOverride = true,
|
||||
@@ -18,9 +18,9 @@ local defaultOptions = {
|
||||
showPrivateMessagesInConsole = true,
|
||||
showPrivateMessagesOnScreen = true,
|
||||
rightPanels = 1,
|
||||
leftPanels = 2,
|
||||
leftPanels = g_app.isMobile() and 1 or 2,
|
||||
containerPanel = 8,
|
||||
backgroundFrameRate = 100,
|
||||
backgroundFrameRate = 60,
|
||||
enableAudio = true,
|
||||
enableMusicSound = false,
|
||||
musicSoundVolume = 100,
|
||||
@@ -42,10 +42,7 @@ local defaultOptions = {
|
||||
dontStretchShrink = false,
|
||||
turnDelay = 30,
|
||||
hotkeyDelay = 30,
|
||||
|
||||
ignoreServerDirection = true,
|
||||
realDirection = false,
|
||||
|
||||
|
||||
wsadWalking = false,
|
||||
walkFirstStepDelay = 200,
|
||||
walkTurnDelay = 100,
|
||||
@@ -104,20 +101,23 @@ function init()
|
||||
audioPanel = g_ui.loadUI('audio')
|
||||
optionsTabBar:addTab(tr('Audio'), audioPanel, '/images/optionstab/audio')
|
||||
|
||||
extrasPanel = g_ui.createWidget('Panel')
|
||||
extrasPanel = g_ui.createWidget('OptionPanel')
|
||||
for _, v in ipairs(g_extras.getAll()) do
|
||||
local extrasButton = g_ui.createWidget('OptionCheckBox')
|
||||
extrasButton:setId(v)
|
||||
extrasButton:setText(g_extras.getDescription(v))
|
||||
extrasPanel:addChild(extrasButton)
|
||||
end
|
||||
if not g_game.getFeature(GameNoDebug) then
|
||||
if not g_game.getFeature(GameNoDebug) and not g_app.isMobile() then
|
||||
optionsTabBar:addTab(tr('Extras'), extrasPanel, '/images/optionstab/extras')
|
||||
end
|
||||
|
||||
optionsButton = modules.client_topmenu.addLeftButton('optionsButton', tr('Options'), '/images/topbuttons/options', toggle)
|
||||
audioButton = modules.client_topmenu.addLeftButton('audioButton', tr('Audio'), '/images/topbuttons/audio', function() toggleOption('enableAudio') end)
|
||||
|
||||
if g_app.isMobile() then
|
||||
audioButton:hide()
|
||||
end
|
||||
|
||||
addEvent(function() setup() end)
|
||||
|
||||
connect(g_game, { onGameStart = online,
|
||||
@@ -316,10 +316,6 @@ function setOption(key, value, force)
|
||||
if modules.game_console and modules.game_console.consoleToggleChat:isChecked() ~= value then
|
||||
modules.game_console.consoleToggleChat:setChecked(value)
|
||||
end
|
||||
--elseif key == 'ignoreServerDirection' then
|
||||
-- g_game.ignoreServerDirection(value)
|
||||
--elseif key == 'realDirection' then
|
||||
-- g_game.showRealDirection(value)
|
||||
elseif key == 'hotkeyDelay' then
|
||||
generalPanel:getChildById('hotkeyDelayLabel'):setText(tr('Hotkey delay: %s ms', value))
|
||||
elseif key == 'walkFirstStepDelay' then
|
||||
|
@@ -2,25 +2,23 @@ OptionCheckBox < CheckBox
|
||||
@onCheckChange: modules.client_options.setOption(self:getId(), self:isChecked())
|
||||
height: 16
|
||||
|
||||
$first:
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
|
||||
$!first:
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 2
|
||||
|
||||
OptionScrollbar < HorizontalScrollBar
|
||||
step: 1
|
||||
@onValueChange: modules.client_options.setOption(self:getId(), self:getValue())
|
||||
|
||||
OptionPanel < Panel
|
||||
layout:
|
||||
type: verticalBox
|
||||
|
||||
MainWindow
|
||||
id: optionsWindow
|
||||
!text: tr('Options')
|
||||
size: 480 460
|
||||
size: 490 500
|
||||
$mobile:
|
||||
size: 490 360
|
||||
|
||||
@onEnter: modules.client_options.hide()
|
||||
@onEscape: modules.client_options.hide()
|
||||
|
@@ -57,7 +57,7 @@ function terminate()
|
||||
removeEvent(monitorEvent)
|
||||
end
|
||||
|
||||
function onMiniWindowClose()
|
||||
function onClose()
|
||||
statsButton:setOn(false)
|
||||
end
|
||||
|
||||
@@ -67,6 +67,8 @@ function toggle()
|
||||
statsButton:setOn(false)
|
||||
else
|
||||
statsWindow:show()
|
||||
statsWindow:raise()
|
||||
statsWindow:focus()
|
||||
statsButton:setOn(true)
|
||||
end
|
||||
end
|
||||
@@ -173,7 +175,7 @@ function update()
|
||||
return
|
||||
end
|
||||
|
||||
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.sleepTime:setText("GFPS: " .. g_app.getGraphicsFps() .. " PFPS: " .. g_app.getProcessingFps() .. " 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)
|
||||
|
@@ -24,6 +24,12 @@ MainWindow
|
||||
margin: 0 0 0 0
|
||||
padding: 25 3 3 3
|
||||
opacity: 0.9
|
||||
|
||||
@onEnter: modules.client_stats.toggle()
|
||||
@onEscape: modules.client_stats.toggle()
|
||||
|
||||
$mobile:
|
||||
size: 550 300
|
||||
|
||||
ScrollablePanel
|
||||
id: debugPanel
|
||||
@@ -42,17 +48,6 @@ MainWindow
|
||||
id: luaRamUsage
|
||||
text: -
|
||||
|
||||
DebugLabel
|
||||
!text: tr('Render')
|
||||
|
||||
DebugText
|
||||
id: adaptiveRender
|
||||
text: -
|
||||
|
||||
DebugText
|
||||
id: render
|
||||
text: -
|
||||
|
||||
DebugText
|
||||
id: atlas
|
||||
text: -
|
||||
@@ -71,6 +66,17 @@ MainWindow
|
||||
id: mainStats
|
||||
text: -
|
||||
|
||||
DebugLabel
|
||||
!text: tr('Render')
|
||||
|
||||
DebugText
|
||||
id: adaptiveRender
|
||||
text: -
|
||||
|
||||
DebugText
|
||||
id: render
|
||||
text: -
|
||||
|
||||
DebugLabel
|
||||
!text: tr('Dispatcher')
|
||||
|
||||
|
@@ -34,7 +34,7 @@ function init()
|
||||
loaded_files = {}
|
||||
for _,file in pairs(files) do
|
||||
if g_resources.isFileType(file, 'otfont') then
|
||||
g_ui.importFont('/layouts/' .. layout .. '/fonts/' .. file)
|
||||
g_fonts.importFont('/layouts/' .. layout .. '/fonts/' .. file)
|
||||
loaded_files[file] = true
|
||||
end
|
||||
end
|
||||
@@ -47,24 +47,6 @@ function init()
|
||||
end
|
||||
end
|
||||
|
||||
if layout:len() > 0 then
|
||||
files = g_resources.listDirectoryFiles('/layouts/' .. layout .. '/particles')
|
||||
loaded_files = {}
|
||||
for _,file in pairs(files) do
|
||||
if g_resources.isFileType(file, 'otps') then
|
||||
g_ui.importParticle('/layouts/' .. layout .. '/particles/' .. file)
|
||||
loaded_files[file] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
files = g_resources.listDirectoryFiles('/data/particles')
|
||||
for _,file in pairs(files) do
|
||||
if g_resources.isFileType(file, 'otps') and not loaded_files[file] then
|
||||
g_particles.importParticle('/data/particles/' .. file)
|
||||
end
|
||||
end
|
||||
|
||||
g_mouse.loadCursors('/data/cursors/cursors')
|
||||
if layout:len() > 0 and g_resources.directoryExists('/layouts/' .. layout .. '/cursors/cursors') then
|
||||
g_mouse.loadCursors('/layouts/' .. layout .. '/cursors/cursors')
|
||||
|
@@ -313,6 +313,13 @@ function addLine(text, color)
|
||||
table.insert(cachedLines, {text=text, color=color})
|
||||
end
|
||||
|
||||
function terminalPrint(value)
|
||||
if type(value) == "table" then
|
||||
return print(json.encode(value, 2))
|
||||
end
|
||||
print(tostring(value))
|
||||
end
|
||||
|
||||
function executeCommand(command)
|
||||
if command == nil or #string.gsub(command, '\n', '') == 0 then return end
|
||||
|
||||
@@ -337,7 +344,7 @@ function executeCommand(command)
|
||||
-- detect and convert commands with simple syntax
|
||||
local realCommand
|
||||
if string.sub(command, 1, 1) == '=' then
|
||||
realCommand = 'print(tostring(' .. string.sub(command,2) .. '))'
|
||||
realCommand = 'modules.client_terminal.terminalPrint(' .. string.sub(command,2) .. ')'
|
||||
else
|
||||
realCommand = command
|
||||
end
|
||||
|
@@ -94,6 +94,7 @@ UIWindow
|
||||
border-width-left: 0
|
||||
border-width-top: 0
|
||||
multiline: false
|
||||
text-auto-submit: true
|
||||
|
||||
$on:
|
||||
border-width-left: 1
|
||||
|
@@ -42,7 +42,7 @@ function show(text, options, callback) -- callback = function(newText)
|
||||
elseif type(text) == 'nil' then
|
||||
text = ''
|
||||
elseif type(text) ~= 'string' then
|
||||
return error("Invalid text type for game_textedit: " .. type(text))
|
||||
return error("Invalid text type for client_textedit: " .. type(text))
|
||||
end
|
||||
if type(options) == 'function' then
|
||||
local tmp = callback
|
||||
@@ -113,20 +113,21 @@ function show(text, options, callback) -- callback = function(newText)
|
||||
window.text:setCursorPos(-1)
|
||||
end
|
||||
end
|
||||
if type(options.range) == 'table' or (type(options.validation) == 'string' and options.validation:len() > 0) then
|
||||
window.buttons.ok:disable()
|
||||
window.text.onTextChange = function(widget, text)
|
||||
if validate(text) then
|
||||
window.buttons.ok:enable()
|
||||
else
|
||||
window.buttons.ok:disable()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
window.text:setText(text)
|
||||
window.text:setCursorPos(-1)
|
||||
|
||||
window.text.onTextChange = function(widget, text)
|
||||
if validate(text) then
|
||||
window.buttons.ok:enable()
|
||||
if g_app.isMobile() then
|
||||
doneFunc()
|
||||
end
|
||||
else
|
||||
window.buttons.ok:disable()
|
||||
end
|
||||
end
|
||||
|
||||
if type(options.width) == 'number' then
|
||||
window:setWidth(options.width)
|
||||
end
|
||||
@@ -134,6 +135,14 @@ function show(text, options, callback) -- callback = function(newText)
|
||||
activeWindow = window
|
||||
activeWindow:raise()
|
||||
activeWindow:focus()
|
||||
if g_app.isMobile() then
|
||||
window.text:focus()
|
||||
local flags = 0
|
||||
if options.multiline then
|
||||
flags = 1
|
||||
end
|
||||
g_window.showTextEditor(window:getText(), window.description:getText(), window.text:getText(), flags)
|
||||
end
|
||||
return activeWindow
|
||||
end
|
||||
|
@@ -1,10 +1,9 @@
|
||||
Module
|
||||
name: game_textedit
|
||||
description: Allow to edit text
|
||||
name: client_textedit
|
||||
description: Shows window which allows to edit text
|
||||
author: OTClientV8
|
||||
website: https://github.com/OTCv8/otclientv8
|
||||
sandboxed: true
|
||||
dependencies: [ game_interface ]
|
||||
scripts: [ textedit ]
|
||||
@onLoad: init()
|
||||
@onUnload: terminate()
|
@@ -27,7 +27,6 @@ TextEditWindow < MainWindow
|
||||
Label
|
||||
id: description
|
||||
text-align: center
|
||||
text: description
|
||||
margin-bottom: 5
|
||||
visible: false
|
||||
text-wrap: true
|
||||
@@ -48,10 +47,14 @@ SinglelineTextEditWindow < TextEditWindow
|
||||
|
||||
MultilineTextEditWindow < TextEditWindow
|
||||
width: 600
|
||||
$mobile:
|
||||
width: 500
|
||||
|
||||
Panel
|
||||
id: textPanel
|
||||
height: 400
|
||||
$mobile:
|
||||
height: 300
|
||||
|
||||
MultilineTextEdit
|
||||
id: text
|
@@ -1,316 +0,0 @@
|
||||
Updater = { }
|
||||
|
||||
Updater.maxRetries = 5
|
||||
|
||||
--[[
|
||||
HOW IT WORKS:
|
||||
1. init
|
||||
2. show
|
||||
3. generateChecksum and get checksums from url
|
||||
4. compareChecksums
|
||||
5. download files with different chekcums
|
||||
6. call c++ update function
|
||||
]]--
|
||||
|
||||
local filesUrl = ""
|
||||
|
||||
local updaterWindow = nil
|
||||
local initialPanel = nil
|
||||
local updatePanel = nil
|
||||
local progressBar = nil
|
||||
local updateProgressBar = nil
|
||||
local downloadStatusLabel = nil
|
||||
local downloadProgressBar = nil
|
||||
local downloadRetries = 0
|
||||
|
||||
local generateChecksumsEvent = nil
|
||||
local updateableFiles = nil
|
||||
local binaryChecksum = nil
|
||||
local binaryFile = ""
|
||||
local fileChecksums = {}
|
||||
local checksumIter = 0
|
||||
local downloadIter = 0
|
||||
local aborted = false
|
||||
local statusData = nil
|
||||
local thingsUpdate = {}
|
||||
local toUpdate = {}
|
||||
local thingsUpdateOptionalError = nil
|
||||
|
||||
local function onDownload(path, checksum, err)
|
||||
if aborted then
|
||||
return
|
||||
end
|
||||
|
||||
if err then
|
||||
if downloadRetries > Updater.maxRetries then
|
||||
return updateError("Can't download file: " .. path .. ".\nError: " .. err)
|
||||
else
|
||||
downloadRetries = downloadRetries + 1
|
||||
return downloadNextFile(true)
|
||||
end
|
||||
end
|
||||
if statusData["files"][path] == nil then
|
||||
return updateError("Invalid file path: " .. path)
|
||||
elseif statusData["files"][path] ~= checksum then
|
||||
return updateError("Invalid file checksum.\nFile: " .. path .. "\nShould be:\n" .. statusData["files"][path] .. "\nIs:\n" .. checksum)
|
||||
end
|
||||
downloadIter = downloadIter + 1
|
||||
updateProgressBar:setPercent(math.ceil((100 * downloadIter) / #toUpdate))
|
||||
downloadProgressBar:setPercent(100)
|
||||
downloadProgressBar:setText("")
|
||||
downloadNextFile(false)
|
||||
end
|
||||
|
||||
local function onDownloadProgress(progress, speed)
|
||||
downloadProgressBar:setPercent(progress)
|
||||
downloadProgressBar:setText(speed .. " kbps")
|
||||
end
|
||||
|
||||
local function gotStatus(data, err)
|
||||
if err then
|
||||
return updateError(err)
|
||||
end
|
||||
if data["error"] ~= nil and data["error"]:len() > 0 then
|
||||
return updateError(data["error"])
|
||||
end
|
||||
if data["url"] == nil or data["files"] == nil or data["binary"] == nil then
|
||||
return updateError("Invalid json data from server")
|
||||
end
|
||||
if data["things"] ~= nil then
|
||||
for file, checksum in pairs(data["things"]) do
|
||||
if #checksum > 1 then
|
||||
for thingtype, thingdata in pairs(thingsUpdate) do
|
||||
if string.match(file:lower(), thingdata[1]:lower()) then
|
||||
data["files"][file] = checksum
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
statusData = data
|
||||
if checksumIter == 100 then
|
||||
compareChecksums()
|
||||
end
|
||||
end
|
||||
|
||||
-- public functions
|
||||
function Updater.init()
|
||||
updaterWindow = g_ui.displayUI('updater')
|
||||
updaterWindow:hide()
|
||||
|
||||
initialPanel = updaterWindow:getChildById('initialPanel')
|
||||
updatePanel = updaterWindow:getChildById('updatePanel')
|
||||
progressBar = initialPanel:getChildById('progressBar')
|
||||
updateProgressBar = updatePanel:getChildById('updateProgressBar')
|
||||
downloadStatusLabel = updatePanel:getChildById('downloadStatusLabel')
|
||||
downloadProgressBar = updatePanel:getChildById('downloadProgressBar')
|
||||
updatePanel:hide()
|
||||
|
||||
scheduleEvent(Updater.show, 200)
|
||||
end
|
||||
|
||||
function Updater.terminate()
|
||||
updaterWindow:destroy()
|
||||
updaterWindow = nil
|
||||
|
||||
removeEvent(generateChecksumsEvent)
|
||||
end
|
||||
|
||||
local function clear()
|
||||
removeEvent(generateChecksumsEvent)
|
||||
|
||||
updateableFiles = nil
|
||||
binaryChecksum = nil
|
||||
binaryFile = ""
|
||||
fileChecksums = {}
|
||||
checksumIter = 0
|
||||
downloadIter = 0
|
||||
aborted = false
|
||||
statusData = nil
|
||||
toUpdate = {}
|
||||
progressBar:setPercent(0)
|
||||
updateProgressBar:setPercent(0)
|
||||
downloadProgressBar:setPercent(0)
|
||||
downloadProgressBar:setText("")
|
||||
end
|
||||
|
||||
function Updater.show()
|
||||
if not g_resources.isLoadedFromArchive() or Services.updater == nil or Services.updater:len() < 4 then
|
||||
return Updater.hide()
|
||||
end
|
||||
if updaterWindow:isVisible() then
|
||||
return
|
||||
end
|
||||
updaterWindow:show()
|
||||
updaterWindow:raise()
|
||||
updaterWindow:focus()
|
||||
if EnterGame then
|
||||
EnterGame.hide()
|
||||
end
|
||||
|
||||
clear()
|
||||
|
||||
updateableFiles = g_resources.listUpdateableFiles()
|
||||
if #updateableFiles < 1 then
|
||||
return updateError("Can't get list of files")
|
||||
end
|
||||
binaryChecksum = g_resources.selfChecksum():lower()
|
||||
if binaryChecksum:len() ~= 32 then
|
||||
return updateError("Invalid binary checksum: " .. binaryChecksum)
|
||||
end
|
||||
|
||||
local data = {
|
||||
version = APP_VERSION,
|
||||
platform = g_window.getPlatformType(),
|
||||
uid = G.UUID,
|
||||
build_version = g_app.getVersion(),
|
||||
build_revision = g_app.getBuildRevision(),
|
||||
build_commit = g_app.getBuildCommit(),
|
||||
build_date = g_app.getBuildDate(),
|
||||
os = g_app.getOs(),
|
||||
os_name = g_platform.getOSName()
|
||||
}
|
||||
HTTP.postJSON(Services.updater, data, gotStatus)
|
||||
if generateChecksumsEvent == nil then
|
||||
generateChecksumsEvent = scheduleEvent(generateChecksum, 5)
|
||||
end
|
||||
end
|
||||
|
||||
function Updater.isVisible()
|
||||
return updaterWindow:isVisible()
|
||||
end
|
||||
|
||||
function Updater.updateThings(things, optionalError)
|
||||
thingsUpdate = things
|
||||
thingsUpdateOptionalError = optionalError
|
||||
Updater:show()
|
||||
end
|
||||
|
||||
function Updater.hide()
|
||||
updaterWindow:hide()
|
||||
if thingsUpdateOptionalError then
|
||||
local msgbox = displayErrorBox("Updater error", thingsUpdateOptionalError:trim())
|
||||
msgbox.onOk = function() if EnterGame then EnterGame.show() end end
|
||||
thingsUpdateOptionalError = nil
|
||||
elseif EnterGame then
|
||||
EnterGame.show()
|
||||
end
|
||||
end
|
||||
|
||||
function Updater.abort()
|
||||
aborted = true
|
||||
Updater:hide()
|
||||
end
|
||||
|
||||
function generateChecksum()
|
||||
local entries = #updateableFiles
|
||||
local fromEntry = math.floor((checksumIter) * (entries / 100))
|
||||
local toEntry = math.floor((checksumIter + 1) * (entries / 100))
|
||||
if checksumIter == 99 then
|
||||
toEntry = #updateableFiles
|
||||
end
|
||||
for i=fromEntry+1,toEntry do
|
||||
local fileName = updateableFiles[i]
|
||||
fileChecksums[fileName] = g_resources.fileChecksum(fileName):lower()
|
||||
end
|
||||
|
||||
checksumIter = checksumIter + 1
|
||||
if checksumIter == 100 then
|
||||
generateChecksumsEvent = nil
|
||||
gotChecksums()
|
||||
else
|
||||
progressBar:setPercent(math.ceil(checksumIter * 0.95))
|
||||
generateChecksumsEvent = scheduleEvent(generateChecksum, 5)
|
||||
end
|
||||
end
|
||||
|
||||
function gotChecksums()
|
||||
if statusData ~= nil then
|
||||
compareChecksums()
|
||||
end
|
||||
end
|
||||
|
||||
function compareChecksums()
|
||||
for file, checksum in pairs(statusData["files"]) do
|
||||
checksum = checksum:lower()
|
||||
if file == statusData["binary"] then
|
||||
if binaryChecksum ~= checksum then
|
||||
binaryFile = file
|
||||
table.insert(toUpdate, binaryFile)
|
||||
end
|
||||
else
|
||||
local localChecksum = fileChecksums[file]
|
||||
if localChecksum ~= checksum then
|
||||
table.insert(toUpdate, file)
|
||||
end
|
||||
end
|
||||
end
|
||||
if #toUpdate == 0 then
|
||||
return upToDate()
|
||||
end
|
||||
-- outdated
|
||||
filesUrl = statusData["url"]
|
||||
initialPanel:hide()
|
||||
updatePanel:show()
|
||||
updatePanel:getChildById('updateStatusLabel'):setText(tr("Updating %i files", #toUpdate))
|
||||
updaterWindow:setHeight(190)
|
||||
downloadNextFile(false)
|
||||
end
|
||||
|
||||
function upToDate()
|
||||
Updater.hide()
|
||||
end
|
||||
|
||||
function updateError(err)
|
||||
Updater.hide()
|
||||
local msgbox = displayErrorBox("Updater error", err)
|
||||
msgbox.onOk = function() if EnterGame then EnterGame.show() end end
|
||||
end
|
||||
|
||||
function urlencode(url)
|
||||
url = url:gsub("\n", "\r\n")
|
||||
url = url:gsub("([^%w ])", function(c) string.format("%%%02X", string.byte(c)) end)
|
||||
url = url:gsub(" ", "+")
|
||||
return url
|
||||
end
|
||||
|
||||
function downloadNextFile(retry)
|
||||
if aborted then
|
||||
return
|
||||
end
|
||||
|
||||
updaterWindow:show()
|
||||
updaterWindow:raise()
|
||||
updaterWindow:focus()
|
||||
|
||||
if downloadIter == #toUpdate then
|
||||
return downloadingFinished()
|
||||
end
|
||||
|
||||
if retry then
|
||||
retry = " (" .. downloadRetries .. " retry)"
|
||||
else
|
||||
retry = ""
|
||||
end
|
||||
|
||||
local file = toUpdate[downloadIter + 1]
|
||||
downloadStatusLabel:setText(tr("Downloading %i of %i%s:\n%s", downloadIter + 1, #toUpdate, retry, file))
|
||||
downloadProgressBar:setPercent(0)
|
||||
downloadProgressBar:setText("")
|
||||
HTTP.download(filesUrl .. urlencode(file), file, onDownload, onDownloadProgress)
|
||||
end
|
||||
|
||||
function downloadingFinished()
|
||||
thingsUpdateOptionalError = nil
|
||||
UIMessageBox.display(tr("Success"), tr("Download complate.\nUpdating client..."), {}, nil, nil)
|
||||
scheduleEvent(function()
|
||||
local files = {}
|
||||
for file, checksum in pairs(statusData["files"]) do
|
||||
table.insert(files, file)
|
||||
end
|
||||
g_settings.save()
|
||||
g_resources.updateClient(files, binaryFile)
|
||||
g_app.quick_exit()
|
||||
end, 1000)
|
||||
end
|
@@ -1,75 +0,0 @@
|
||||
StaticMainWindow
|
||||
id: updaterWindow
|
||||
!text: tr('Updater')
|
||||
height: 125
|
||||
width: 300
|
||||
|
||||
Panel
|
||||
id: initialPanel
|
||||
layout:
|
||||
type: verticalBox
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
margin: 0 5 5 5
|
||||
|
||||
Label
|
||||
id: statusLabel
|
||||
!text: tr('Checking for updates')
|
||||
text-align: center
|
||||
|
||||
ProgressBar
|
||||
id: progressBar
|
||||
height: 15
|
||||
background-color: #4444ff
|
||||
margin-bottom: 10
|
||||
margin-top: 10
|
||||
|
||||
Button
|
||||
!text: tr('Cancel')
|
||||
margin-left: 70
|
||||
margin-right: 70
|
||||
@onClick: Updater.abort()
|
||||
|
||||
Panel
|
||||
id: updatePanel
|
||||
layout:
|
||||
type: verticalBox
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
margin: 0 5 5 5
|
||||
|
||||
Label
|
||||
id: updateStatusLabel
|
||||
!text: tr('Updating')
|
||||
text-align: center
|
||||
|
||||
ProgressBar
|
||||
id: updateProgressBar
|
||||
height: 15
|
||||
background-color: #4444ff
|
||||
margin-bottom: 10
|
||||
margin-top: 10
|
||||
|
||||
Label
|
||||
id: downloadStatusLabel
|
||||
!text: tr('Downloading:')
|
||||
text-align: center
|
||||
margin-top: 5
|
||||
height: 25
|
||||
|
||||
ProgressBar
|
||||
id: downloadProgressBar
|
||||
height: 15
|
||||
background-color: #4444ff
|
||||
margin-bottom: 10
|
||||
margin-top: 10
|
||||
|
||||
Button
|
||||
!text: tr('Cancel')
|
||||
margin-left: 70
|
||||
margin-right: 70
|
||||
@onClick: Updater.abort()
|
176
modules/corelib/base64.lua
Normal file
176
modules/corelib/base64.lua
Normal file
@@ -0,0 +1,176 @@
|
||||
--[[
|
||||
|
||||
base64 -- v1.5.1 public domain Lua base64 encoder/decoder
|
||||
no warranty implied; use at your own risk
|
||||
|
||||
Needs bit32.extract function. If not present it's implemented using BitOp
|
||||
or Lua 5.3 native bit operators. For Lua 5.1 fallbacks to pure Lua
|
||||
implementation inspired by Rici Lake's post:
|
||||
http://ricilake.blogspot.co.uk/2007/10/iterating-bits-in-lua.html
|
||||
|
||||
author: Ilya Kolbin (iskolbin@gmail.com)
|
||||
url: github.com/iskolbin/lbase64
|
||||
|
||||
COMPATIBILITY
|
||||
|
||||
Lua 5.1, 5.2, 5.3, LuaJIT
|
||||
|
||||
LICENSE
|
||||
|
||||
See end of file for license information.
|
||||
|
||||
--]]
|
||||
|
||||
|
||||
base64 = {}
|
||||
|
||||
local extract = _G.bit32 and _G.bit32.extract
|
||||
if not extract then
|
||||
if _G.bit then
|
||||
local shl, shr, band = _G.bit.lshift, _G.bit.rshift, _G.bit.band
|
||||
extract = function( v, from, width )
|
||||
return band( shr( v, from ), shl( 1, width ) - 1 )
|
||||
end
|
||||
elseif _G._VERSION >= "Lua 5.3" then
|
||||
extract = load[[return function( v, from, width )
|
||||
return ( v >> from ) & ((1 << width) - 1)
|
||||
end]]()
|
||||
else
|
||||
extract = function( v, from, width )
|
||||
local w = 0
|
||||
local flag = 2^from
|
||||
for i = 0, width-1 do
|
||||
local flag2 = flag + flag
|
||||
if v % flag2 >= flag then
|
||||
w = w + 2^i
|
||||
end
|
||||
flag = flag2
|
||||
end
|
||||
return w
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function base64.makeencoder( s62, s63, spad )
|
||||
local encoder = {}
|
||||
for b64code, char in pairs{[0]='A','B','C','D','E','F','G','H','I','J',
|
||||
'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y',
|
||||
'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n',
|
||||
'o','p','q','r','s','t','u','v','w','x','y','z','0','1','2',
|
||||
'3','4','5','6','7','8','9',s62 or '+',s63 or'/',spad or'='} do
|
||||
encoder[b64code] = char:byte()
|
||||
end
|
||||
return encoder
|
||||
end
|
||||
|
||||
function base64.makedecoder( s62, s63, spad )
|
||||
local decoder = {}
|
||||
for b64code, charcode in pairs( base64.makeencoder( s62, s63, spad )) do
|
||||
decoder[charcode] = b64code
|
||||
end
|
||||
return decoder
|
||||
end
|
||||
|
||||
local DEFAULT_ENCODER = base64.makeencoder()
|
||||
local DEFAULT_DECODER = base64.makedecoder()
|
||||
|
||||
local char, concat = string.char, table.concat
|
||||
|
||||
function base64.encode( str, encoder, usecaching )
|
||||
encoder = encoder or DEFAULT_ENCODER
|
||||
local t, k, n = {}, 1, #str
|
||||
local lastn = n % 3
|
||||
local cache = {}
|
||||
for i = 1, n-lastn, 3 do
|
||||
local a, b, c = str:byte( i, i+2 )
|
||||
local v = a*0x10000 + b*0x100 + c
|
||||
local s
|
||||
if usecaching then
|
||||
s = cache[v]
|
||||
if not s then
|
||||
s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)])
|
||||
cache[v] = s
|
||||
end
|
||||
else
|
||||
s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)])
|
||||
end
|
||||
t[k] = s
|
||||
k = k + 1
|
||||
end
|
||||
if lastn == 2 then
|
||||
local a, b = str:byte( n-1, n )
|
||||
local v = a*0x10000 + b*0x100
|
||||
t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[64])
|
||||
elseif lastn == 1 then
|
||||
local v = str:byte( n )*0x10000
|
||||
t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[64], encoder[64])
|
||||
end
|
||||
return concat( t )
|
||||
end
|
||||
|
||||
function base64.decode( b64, decoder, usecaching )
|
||||
decoder = decoder or DEFAULT_DECODER
|
||||
local pattern = '[^%w%+%/%=]'
|
||||
if decoder then
|
||||
local s62, s63
|
||||
for charcode, b64code in pairs( decoder ) do
|
||||
if b64code == 62 then s62 = charcode
|
||||
elseif b64code == 63 then s63 = charcode
|
||||
end
|
||||
end
|
||||
pattern = ('[^%%w%%%s%%%s%%=]'):format( char(s62), char(s63) )
|
||||
end
|
||||
b64 = b64:gsub( pattern, '' )
|
||||
local cache = usecaching and {}
|
||||
local t, k = {}, 1
|
||||
local n = #b64
|
||||
local padding = b64:sub(-2) == '==' and 2 or b64:sub(-1) == '=' and 1 or 0
|
||||
for i = 1, padding > 0 and n-4 or n, 4 do
|
||||
local a, b, c, d = b64:byte( i, i+3 )
|
||||
local s
|
||||
if usecaching then
|
||||
local v0 = a*0x1000000 + b*0x10000 + c*0x100 + d
|
||||
s = cache[v0]
|
||||
if not s then
|
||||
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]
|
||||
s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8))
|
||||
cache[v0] = s
|
||||
end
|
||||
else
|
||||
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]
|
||||
s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8))
|
||||
end
|
||||
t[k] = s
|
||||
k = k + 1
|
||||
end
|
||||
if padding == 1 then
|
||||
local a, b, c = b64:byte( n-3, n-1 )
|
||||
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40
|
||||
t[k] = char( extract(v,16,8), extract(v,8,8))
|
||||
elseif padding == 2 then
|
||||
local a, b = b64:byte( n-3, n-2 )
|
||||
local v = decoder[a]*0x40000 + decoder[b]*0x1000
|
||||
t[k] = char( extract(v,16,8))
|
||||
end
|
||||
return concat( t )
|
||||
end
|
||||
|
||||
--[[
|
||||
Copyright (c) 2018 Ilya Kolbin
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
--]]
|
@@ -28,6 +28,7 @@ Module
|
||||
dofile 'outputmessage'
|
||||
dofile 'orderedtable'
|
||||
|
||||
dofile 'base64'
|
||||
dofile 'json'
|
||||
dofile 'http'
|
||||
|
@@ -108,6 +108,7 @@ function HTTP.cancel(operationId)
|
||||
if not g_http or not g_http.cancel then
|
||||
return
|
||||
end
|
||||
HTTP.operations[operationId] = nil
|
||||
return g_http.cancel(operationId)
|
||||
end
|
||||
|
||||
|
@@ -249,7 +249,7 @@ function table.encodeStringPairList(t)
|
||||
end
|
||||
|
||||
function table.decodeStringPairList(l)
|
||||
local ret = {}
|
||||
local ret = {}
|
||||
local r = regexMatch(l, "(?:^|\\n)([^:^\n]{1,20}):?(.*)(?:$|\\n)")
|
||||
local multiline = ""
|
||||
local multilineKey = ""
|
||||
|
@@ -46,11 +46,11 @@ function UIPopupMenu:onGeometryChange(oldRect, newRect)
|
||||
local ymax = parent:getY() + parent:getHeight()
|
||||
local xmax = parent:getX() + parent:getWidth()
|
||||
if newRect.y + newRect.height > ymax then
|
||||
local newy = newRect.y - newRect.height
|
||||
local newy = ymax - newRect.height
|
||||
if newy > 0 and newy + newRect.height < ymax then self:setY(newy) end
|
||||
end
|
||||
if newRect.x + newRect.width > xmax then
|
||||
local newx = newRect.x - newRect.width
|
||||
local newx = xmax - newRect.width
|
||||
if newx > 0 and newx + newRect.width < xmax then self:setX(newx) end
|
||||
end
|
||||
self:bindRectToParent()
|
||||
|
@@ -29,6 +29,9 @@ local function calcValues(self)
|
||||
end
|
||||
|
||||
local px = math.max(proportion * pxrange, 6)
|
||||
if g_app.isMobile() then
|
||||
px = math.max(proportion * pxrange, 24)
|
||||
end
|
||||
px = px - px % 2 + 1
|
||||
|
||||
local offset = 0
|
||||
|
@@ -38,7 +38,7 @@ function exit()
|
||||
end
|
||||
|
||||
function quit()
|
||||
g_app.quit()
|
||||
g_app.exit()
|
||||
end
|
||||
|
||||
function connect(object, arg1, arg2, arg3)
|
||||
|
22
modules/crash_reporter/crash_reporter.lua
Normal file
22
modules/crash_reporter/crash_reporter.lua
Normal file
@@ -0,0 +1,22 @@
|
||||
local CRASH_FILE = "exception.dmp"
|
||||
|
||||
function init()
|
||||
if g_resources.fileExists(CRASH_FILE) then
|
||||
local crashLog = g_resources.readFileContents(CRASH_FILE)
|
||||
local clientLog = g_logger.getLastLog()
|
||||
HTTP.post(Services.crash, {
|
||||
version = APP_VERSION,
|
||||
build = g_app.getVersion(),
|
||||
os = g_app.getOs(),
|
||||
platform = g_window.getPlatformType(),
|
||||
crash = base64.encode(crashLog),
|
||||
log = base64.encode(clientLog)
|
||||
}, function(data, err)
|
||||
if err then
|
||||
return g_logger.error("Error while reporting crash report: " .. err)
|
||||
end
|
||||
g_resources.deleteFile(CRASH_FILE)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
8
modules/crash_reporter/crash_reporter.otmod
Normal file
8
modules/crash_reporter/crash_reporter.otmod
Normal file
@@ -0,0 +1,8 @@
|
||||
Module
|
||||
name: crash_reporter
|
||||
description: Sends crash log to remote server
|
||||
author: otclient@otclient.ovh
|
||||
website: otclient.ovh
|
||||
reloadable: false
|
||||
scripts: [ crash_reporter ]
|
||||
@onLoad: init()
|
@@ -24,7 +24,7 @@ ActionColors = {
|
||||
}
|
||||
|
||||
function init()
|
||||
local bottomPanel = modules.game_interface.getBottomPanel()
|
||||
local bottomPanel = modules.game_interface.getActionPanel()
|
||||
actionPanel1 = g_ui.loadUI('actionbar', bottomPanel)
|
||||
bottomPanel:moveChildToIndex(actionPanel1, 1)
|
||||
actionPanel2 = g_ui.loadUI('actionbar', bottomPanel)
|
||||
@@ -167,6 +167,7 @@ function setupAction(action)
|
||||
action.text:setText(config.text)
|
||||
action:setBorderColor(ActionColors.text)
|
||||
action.item:setOn(true) -- removes background
|
||||
action.item:setItemId(0)
|
||||
if Spells then
|
||||
local spell, profile = Spells.getSpellByWords(config.text:lower())
|
||||
action.spell = spell
|
||||
@@ -225,7 +226,7 @@ end
|
||||
|
||||
function updateAction(action, newConfig)
|
||||
local config = action.config
|
||||
if newConfig.hotkey and type(config.hotkey) == 'string' and config.hotkey:len() > 0 then
|
||||
if type(config.hotkey) == 'string' and config.hotkey:len() > 0 then
|
||||
local gameRootPanel = modules.game_interface.getRootPanel()
|
||||
g_keyboard.unbindKeyPress(config.hotkey, action.callback, gameRootPanel)
|
||||
end
|
||||
@@ -236,7 +237,7 @@ function updateAction(action, newConfig)
|
||||
end
|
||||
|
||||
function actionOnMouseRelease(action, mousePosition, mouseButton)
|
||||
if mouseButton == MouseRightButton then
|
||||
if mouseButton == MouseRightButton or not action.item:isOn() then
|
||||
local menu = g_ui.createWidget('PopupMenu')
|
||||
menu:setGameMenu(true)
|
||||
if action.item:getItemId() > 0 then
|
||||
@@ -256,7 +257,7 @@ function actionOnMouseRelease(action, mousePosition, mouseButton)
|
||||
end
|
||||
menu:addSeparator()
|
||||
menu:addOption(tr('Set text'), function()
|
||||
modules.game_textedit.singlelineEditor(action.config.text or "", function(newText)
|
||||
modules.client_textedit.singlelineEditor(action.config.text or "", function(newText)
|
||||
updateAction(action, {text=newText, item=0})
|
||||
end)
|
||||
end)
|
||||
@@ -362,7 +363,31 @@ function executeAction(action, ticks)
|
||||
local actionType = action.config.actionType
|
||||
|
||||
if type(action.config.text) == 'string' and action.config.text:len() > 0 then
|
||||
modules.game_console.sendMessage(action.config.text)
|
||||
if g_app.isMobile() then -- turn to direction of targer
|
||||
local target = g_game.getAttackingCreature()
|
||||
if target then
|
||||
local pos = g_game.getLocalPlayer():getPosition()
|
||||
local tpos = target:getPosition()
|
||||
if pos and tpos then
|
||||
local offx = tpos.x - pos.x
|
||||
local offy = tpos.y - pos.y
|
||||
if offy < 0 and offx <= 0 and math.abs(offx) < math.abs(offy) then
|
||||
g_game.turn(Directions.North)
|
||||
elseif offy > 0 and offx >= 0 and math.abs(offx) < math.abs(offy) then
|
||||
g_game.turn(Directions.South)
|
||||
elseif offx < 0 and offy <= 0 and math.abs(offx) > math.abs(offy) then
|
||||
g_game.turn(Directions.West)
|
||||
elseif offx > 0 and offy >= 0 and math.abs(offx) > math.abs(offy) then
|
||||
g_game.turn(Directions.East)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if modules.game_interface.isChatVisible() then
|
||||
modules.game_console.sendMessage(action.config.text)
|
||||
else
|
||||
g_game.talk(action.config.text)
|
||||
end
|
||||
action.actionDelayTo = g_clock.millis() + actionDelay
|
||||
elseif action.item:getItemId() > 0 then
|
||||
if actionType == ActionTypes.USE then
|
||||
|
@@ -55,17 +55,11 @@ ActionButton < Panel
|
||||
|
||||
Panel
|
||||
id: actionBar
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
image-source: /images/ui/panel_map
|
||||
focusable: false
|
||||
image-source: /images/ui/panel_side
|
||||
image-border: 4
|
||||
margin-top: -1
|
||||
|
||||
$first:
|
||||
anchors.top: parent.top
|
||||
|
||||
$!first:
|
||||
anchors.top: prev.bottom
|
||||
|
||||
$on:
|
||||
height: 40
|
||||
visible: true
|
||||
@@ -78,9 +72,12 @@ Panel
|
||||
id: prevButton
|
||||
icon: /images/game/console/leftarrow
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
margin-left: 1
|
||||
|
||||
margin-top: 1
|
||||
margin-bottom: 2
|
||||
|
||||
Panel
|
||||
id: tabBar
|
||||
anchors.top: parent.top
|
||||
@@ -89,15 +86,17 @@ Panel
|
||||
anchors.right: next.left
|
||||
margin-right: 3
|
||||
margin-top: 2
|
||||
clipping: true
|
||||
|
||||
clipping: true
|
||||
|
||||
TabButton
|
||||
id: nextButton
|
||||
icon: /images/game/console/rightarrow
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
margin-right: 1
|
||||
margin-top: 1
|
||||
margin-bottom: 2
|
||||
|
||||
|
||||
ActionAssignWindow < MainWindow
|
||||
|
@@ -1,3 +1,2 @@
|
||||
BattleButton < CreatureButton
|
||||
&isBattleButton: true
|
||||
optimized: true
|
@@ -1,70 +0,0 @@
|
||||
TargetBot.Creature.attack = function(params, targets, isLooting) -- params {config, creature, danger, priority}
|
||||
if player:isWalking() then
|
||||
lastWalk = now
|
||||
end
|
||||
|
||||
local config = params.config
|
||||
local creature = params.creature
|
||||
|
||||
if g_game.getAttackingCreature() ~= creature then
|
||||
g_game.attack(creature)
|
||||
end
|
||||
|
||||
if not isLooting then -- walk only when not looting
|
||||
TargetBot.Creature.walk(creature, config, targets)
|
||||
end
|
||||
|
||||
-- attacks
|
||||
local mana = player:getMana()
|
||||
if config.useGroupAttack and config.groupAttackSpell:len() > 1 and mana > config.minManaGroup then
|
||||
local creatures = g_map.getSpectatorsInRange(player:getPosition(), false, config.groupAttackRadius, config.groupAttackRadius)
|
||||
local playersAround = false
|
||||
local monsters = 0
|
||||
for _, creature in ipairs(creatures) do
|
||||
if not creature:isLocalPlayer() and creature:isPlayer() then
|
||||
playersAround = true
|
||||
elseif creature:isMonster() then
|
||||
monsters = monsters + 1
|
||||
end
|
||||
end
|
||||
if monsters >= config.groupAttackTargets and (not playersAround or config.groupAttackIgnorePlayers) then
|
||||
if TargetBot.sayAttackSpell(config.groupAttackSpell, config.groupAttackDelay) then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
if config.useSpellAttack and config.attackSpell:len() > 1 and mana > config.minMana then
|
||||
if TargetBot.sayAttackSpell(config.attackSpell, config.attackSpellDelay) then
|
||||
return
|
||||
end
|
||||
end
|
||||
if config.useRuneAttack and config.attackRune > 100 then
|
||||
if TargetBot.useAttackItem(config.attackRune, 0, creature, config.attackRuneDelay) then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
TargetBot.Creature.walk = function(creature, config, targets)
|
||||
-- luring
|
||||
if config.lure and not (config.chase and creature:getHealthPercent() < 30) then
|
||||
local monsters = 0
|
||||
if targets < config.lureCount then
|
||||
local path = findPath(player:getPosition(), creature:getPosition(), 5, {ignoreNonPathable=true, precision=2})
|
||||
if path then
|
||||
return TargetBot.walkTo(creature:getPosition(), 10, {marginMin=5, marginMax=6, ignoreNonPathable=true})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local currentDistance = findPath(player:getPosition(), creature:getPosition(), 10, {ignoreCreatures=true, ignoreNonPathable=true, ignoreCost=true})
|
||||
if config.chase and (creature:getHealthPercent() < 30 or not config.keepDistance) then
|
||||
if #currentDistance > 1 then
|
||||
return TargetBot.walkTo(creature:getPosition(), 10, {ignoreNonPathable=true, precision=1})
|
||||
end
|
||||
elseif config.keepDistance then
|
||||
if #currentDistance ~= config.keepDistanceRange and #currentDistance ~= config.keepDistanceRange + 1 then
|
||||
return TargetBot.walkTo(creature:getPosition(), 10, {ignoreNonPathable=true, marginMin=config.keepDistanceRange, marginMax=config.keepDistanceRange + 1})
|
||||
end
|
||||
end
|
||||
end
|
@@ -21,7 +21,7 @@ end
|
||||
local actionRetries = 0
|
||||
local prevActionResult = true
|
||||
cavebotMacro = macro(20, function()
|
||||
if TargetBot and TargetBot.isActive() then
|
||||
if TargetBot and TargetBot.isActive() and not TargetBot.isCaveBotActionAllowed() then
|
||||
return -- target bot or looting is working, wait
|
||||
end
|
||||
|
@@ -1,5 +1,5 @@
|
||||
-- main tab
|
||||
VERSION = "1.1"
|
||||
VERSION = "1.2"
|
||||
|
||||
UI.Label("Config version: " .. VERSION)
|
||||
|
||||
@@ -14,7 +14,7 @@ UI.Button("Discord", function()
|
||||
end)
|
||||
|
||||
UI.Button("Forum", function()
|
||||
g_platform.openUrl("https://otland.net/forums/otclient.494/")
|
||||
g_platform.openUrl("https://otclient.net/")
|
||||
end)
|
||||
|
||||
UI.Button("Help & Tutorials", function()
|
@@ -0,0 +1,112 @@
|
||||
TargetBot.Creature.attack = function(params, targets, isLooting) -- params {config, creature, danger, priority}
|
||||
if player:isWalking() then
|
||||
lastWalk = now
|
||||
end
|
||||
|
||||
local config = params.config
|
||||
local creature = params.creature
|
||||
|
||||
if g_game.getAttackingCreature() ~= creature then
|
||||
g_game.attack(creature)
|
||||
end
|
||||
|
||||
if not isLooting then -- walk only when not looting
|
||||
TargetBot.Creature.walk(creature, config, targets)
|
||||
end
|
||||
|
||||
-- attacks
|
||||
local mana = player:getMana()
|
||||
if config.useGroupAttack and config.groupAttackSpell:len() > 1 and mana > config.minManaGroup then
|
||||
local creatures = g_map.getSpectatorsInRange(player:getPosition(), false, config.groupAttackRadius, config.groupAttackRadius)
|
||||
local playersAround = false
|
||||
local monsters = 0
|
||||
for _, creature in ipairs(creatures) do
|
||||
if not creature:isLocalPlayer() and creature:isPlayer() and (not config.groupAttackIgnoreParty or creature:getShield() <= 2) then
|
||||
playersAround = true
|
||||
elseif creature:isMonster() then
|
||||
monsters = monsters + 1
|
||||
end
|
||||
end
|
||||
if monsters >= config.groupAttackTargets and (not playersAround or config.groupAttackIgnorePlayers) then
|
||||
if TargetBot.sayAttackSpell(config.groupAttackSpell, config.groupAttackDelay) then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if config.useGroupAttackRune and config.groupAttackRune > 100 then
|
||||
local creatures = g_map.getSpectatorsInRange(creature:getPosition(), false, config.groupRuneAttackRadius, config.groupRuneAttackRadius)
|
||||
local playersAround = false
|
||||
local monsters = 0
|
||||
for _, creature in ipairs(creatures) do
|
||||
if not creature:isLocalPlayer() and creature:isPlayer() and (not config.groupAttackIgnoreParty or creature:getShield() <= 2) then
|
||||
playersAround = true
|
||||
elseif creature:isMonster() then
|
||||
monsters = monsters + 1
|
||||
end
|
||||
end
|
||||
if monsters >= config.groupRuneAttackTargets and (not playersAround or config.groupAttackIgnorePlayers) then
|
||||
if TargetBot.useAttackItem(config.groupAttackRune, 0, creature, config.groupRuneAttackDelay) then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
if config.useSpellAttack and config.attackSpell:len() > 1 and mana > config.minMana then
|
||||
if TargetBot.sayAttackSpell(config.attackSpell, config.attackSpellDelay) then
|
||||
return
|
||||
end
|
||||
end
|
||||
if config.useRuneAttack and config.attackRune > 100 then
|
||||
if TargetBot.useAttackItem(config.attackRune, 0, creature, config.attackRuneDelay) then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
TargetBot.Creature.walk = function(creature, config, targets)
|
||||
local cpos = creature:getPosition()
|
||||
local pos = player:getPosition()
|
||||
|
||||
-- luring
|
||||
if (config.lure or config.lureCavebot) and not (config.chase and creature:getHealthPercent() < 30) then
|
||||
local monsters = 0
|
||||
if targets < config.lureCount then
|
||||
if config.lureCavebot then
|
||||
return TargetBot.allowCaveBot(200)
|
||||
else
|
||||
local path = findPath(pos, cpos, 5, {ignoreNonPathable=true, precision=2})
|
||||
if path then
|
||||
return TargetBot.walkTo(cpos, 10, {marginMin=5, marginMax=6, ignoreNonPathable=true})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local currentDistance = findPath(pos, cpos, 10, {ignoreCreatures=true, ignoreNonPathable=true, ignoreCost=true})
|
||||
if config.chase and (creature:getHealthPercent() < 30 or not config.keepDistance) then
|
||||
if #currentDistance > 1 then
|
||||
return TargetBot.walkTo(cpos, 10, {ignoreNonPathable=true, precision=1})
|
||||
end
|
||||
elseif config.keepDistance then
|
||||
if #currentDistance ~= config.keepDistanceRange and #currentDistance ~= config.keepDistanceRange + 1 then
|
||||
return TargetBot.walkTo(cpos, 10, {ignoreNonPathable=true, marginMin=config.keepDistanceRange, marginMax=config.keepDistanceRange + 1})
|
||||
end
|
||||
end
|
||||
|
||||
if config.avoidAttacks then
|
||||
local diffx = cpos.x - pos.x
|
||||
local diffy = cpos.y - pos.y
|
||||
local candidates = {}
|
||||
if math.abs(diffx) == 1 and diffy == 0 then
|
||||
candidates = {{x=pos.x, y=pos.y-1, z=pos.z}, {x=pos.x, y=pos.y+1, z=pos.z}}
|
||||
elseif diffx == 0 and math.abs(diffy) == 1 then
|
||||
candidates = {{x=pos.x-1, y=pos.y, z=pos.z}, {x=pos.x+1, y=pos.y, z=pos.z}}
|
||||
end
|
||||
for _, candidate in ipairs(candidates) do
|
||||
local tile = g_map.getTile(candidate)
|
||||
if tile and tile:isWalkable() then
|
||||
return TargetBot.walkTo(candidate, 2, {ignoreNonPathable=true})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@@ -82,18 +82,24 @@ TargetBot.Creature.edit = function(config, callback) -- callback = function(newC
|
||||
addScrollBar("groupAttackRadius", "Radius of group attack spell", 1, 7, 1)
|
||||
addScrollBar("groupAttackDelay", "Group attack spell delay", 200, 60000, 5000)
|
||||
addScrollBar("runeAttackDelay", "Rune attack delay", 200, 5000, 2000)
|
||||
addScrollBar("groupRuneAttackTargets", "Min. targets for group rune attack", 1, 10, 2)
|
||||
addScrollBar("groupRuneAttackRadius", "Radius of group rune attack", 1, 7, 1)
|
||||
addScrollBar("groupRuneAttackDelay", "Group rune attack delay", 200, 60000, 5000)
|
||||
|
||||
addCheckBox("chase", "Chase", true)
|
||||
addCheckBox("keepDistance", "Keep Distance", false)
|
||||
addCheckBox("lure", "Lure", false)
|
||||
-- addCheckBox("avoidAttacks", "Avoid attacks", true)
|
||||
addCheckBox("lureCavebot", "Lure using cavebot", false)
|
||||
addCheckBox("avoidAttacks", "Avoid wave attacks", false)
|
||||
|
||||
addCheckBox("useSpellAttack", "Use attack spell", false)
|
||||
addTextEdit("attackSpell", "Attack spell", "")
|
||||
addCheckBox("useGroupAttack", "Use group attack spell", false)
|
||||
addCheckBox("groupAttackIgnorePlayers", "Ignore players in group attack", false)
|
||||
addTextEdit("groupAttackSpell", "Group attack spell", "")
|
||||
addCheckBox("useRuneAttack", "Use attack rune", false)
|
||||
addItem("attackRune", "Attack rune:", 0)
|
||||
|
||||
addCheckBox("useGroupAttack", "Use group attack spell", false)
|
||||
addTextEdit("groupAttackSpell", "Group attack spell", "")
|
||||
addCheckBox("useGroupAttackRune", "Use group attack rune", false)
|
||||
addItem("groupAttackRune", "Group attack rune:", 0)
|
||||
addCheckBox("groupAttackIgnorePlayers", "Ignore players in group attack", false)
|
||||
addCheckBox("groupAttackIgnoreParty", "Ignore party in group attack", false)
|
||||
end
|
@@ -1,6 +1,6 @@
|
||||
TargetBotCreatureEditorScrollBar < Panel
|
||||
height: 35
|
||||
margin-top: 7
|
||||
height: 28
|
||||
margin-top: 3
|
||||
|
||||
Label
|
||||
id: text
|
||||
@@ -14,7 +14,7 @@ TargetBotCreatureEditorScrollBar < Panel
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 5
|
||||
margin-top: 3
|
||||
minimum: 0
|
||||
maximum: 10
|
||||
step: 1
|
||||
@@ -64,7 +64,7 @@ TargetBotCreatureEditorCheckBox < BotSwitch
|
||||
TargetBotCreatureEditorWindow < MainWindow
|
||||
text: TargetBot creature editor
|
||||
width: 500
|
||||
height: 650
|
||||
height: 600
|
||||
|
||||
Label
|
||||
anchors.left: parent.left
|
@@ -145,14 +145,14 @@ TargetBot.Looting.process = function(targets, dangerLevel)
|
||||
end
|
||||
|
||||
local container = tile:getTopUseThing()
|
||||
if not container or container:getId() ~= loot.container then
|
||||
if not container or not container:isContainer() then
|
||||
table.remove(TargetBot.Looting.list, 1)
|
||||
return true
|
||||
end
|
||||
|
||||
g_game.open(container)
|
||||
waitTill = now + 1000 -- give it 1s to open
|
||||
waitingForContainer = loot.container
|
||||
waitingForContainer = container:getId()
|
||||
loot.tries = loot.tries + 10
|
||||
|
||||
return true
|
||||
@@ -213,9 +213,9 @@ TargetBot.Looting.lootContainer = function(lootContainers, container)
|
||||
-- loot items
|
||||
local nextContainer = nil
|
||||
for i, item in ipairs(container:getItems()) do
|
||||
if item:isContainer() then
|
||||
if item:isContainer() and not itemsById[item:getId()] then
|
||||
nextContainer = item
|
||||
elseif itemsById[item:getId()] or ui.everyItem:isOn() then
|
||||
elseif itemsById[item:getId()] or (ui.everyItem:isOn() and not item:isContainer()) then
|
||||
item.lootTries = (item.lootTries or 0) + 1
|
||||
if item.lootTries < 5 then -- if can't be looted within 0.5s then skip it
|
||||
return TargetBot.Looting.lootItem(lootContainers, item)
|
@@ -1,6 +1,7 @@
|
||||
local targetbotMacro = nil
|
||||
local config = nil
|
||||
local lastAction = 0
|
||||
local cavebotAllowance = 0
|
||||
|
||||
-- ui
|
||||
local configWidget = UI.Config()
|
||||
@@ -72,6 +73,8 @@ targetbotMacro = macro(100, function()
|
||||
TargetBot.Creature.attack(highestPriorityParams, targets, looting)
|
||||
if lootingStatus:len() > 0 then
|
||||
TargetBot.setStatus("Attack & " .. lootingStatus)
|
||||
elseif cavebotAllowance > now then
|
||||
TargetBot.setStatus("Luring using CaveBot")
|
||||
else
|
||||
TargetBot.setStatus("Attacking")
|
||||
end
|
||||
@@ -148,6 +151,10 @@ TargetBot.isActive = function() -- return true if attacking or looting takes pla
|
||||
return lastAction + 300 > now
|
||||
end
|
||||
|
||||
TargetBot.isCaveBotActionAllowed = function()
|
||||
return cavebotAllowance > now
|
||||
end
|
||||
|
||||
TargetBot.setStatus = function(text)
|
||||
return ui.status.right:setText(text)
|
||||
end
|
||||
@@ -187,6 +194,10 @@ TargetBot.save = function()
|
||||
config.save(data)
|
||||
end
|
||||
|
||||
TargetBot.allowCaveBot = function(time)
|
||||
cavebotAllowance = now + time
|
||||
end
|
||||
|
||||
-- attacks
|
||||
local lastSpell = 0
|
||||
local lastAttackSpell = 0
|
@@ -72,6 +72,42 @@ macro(1000, "Stack items", function()
|
||||
end
|
||||
end)
|
||||
|
||||
macro(10000, "Anti Kick", function()
|
||||
local dir = player:getDirection()
|
||||
turn((dir + 1) % 4)
|
||||
turn(dir)
|
||||
end)
|
||||
|
||||
UI.Separator()
|
||||
UI.Label("Drop items:")
|
||||
if type(storage.dropItems) ~= "table" then
|
||||
storage.dropItems = {283, 284, 285}
|
||||
end
|
||||
|
||||
local foodContainer = UI.Container(function(widget, items)
|
||||
storage.dropItems = items
|
||||
end, true)
|
||||
foodContainer:setHeight(35)
|
||||
foodContainer:setItems(storage.dropItems)
|
||||
|
||||
macro(5000, "drop items", function()
|
||||
if not storage.dropItems[1] then return end
|
||||
if TargetBot and TargetBot.isActive() then return end -- pause when attacking
|
||||
for _, container in pairs(g_game.getContainers()) do
|
||||
for __, item in ipairs(container:getItems()) do
|
||||
for i, dropItem in ipairs(storage.dropItems) do
|
||||
if item:getId() == dropItem.id then
|
||||
if item:isStackable() then
|
||||
return g_game.move(item, player:getPosition(), item:getCount())
|
||||
else
|
||||
return g_game.move(item, player:getPosition(), dropItem.count) -- count is also subtype
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
UI.Separator()
|
||||
|
||||
UI.Label("Mana training")
|
||||
@@ -80,6 +116,7 @@ if type(storage.manaTrain) ~= "table" then
|
||||
end
|
||||
|
||||
local manatrainmacro = macro(1000, function()
|
||||
if TargetBot and TargetBot.isActive() then return end -- pause when attacking
|
||||
local mana = math.min(100, math.floor(100 * (player:getMana() / player:getMaxMana())))
|
||||
if storage.manaTrain.max >= mana and mana >= storage.manaTrain.min then
|
||||
say(storage.manaTrain.text)
|
@@ -81,6 +81,7 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, relo
|
||||
context.gcinfo = gcinfo
|
||||
context.tr = tr
|
||||
context.json = json
|
||||
context.base64 = base64
|
||||
context.regexMatch = regexMatch
|
||||
context.getDistanceBetween = function(p1, p2)
|
||||
return math.max(math.abs(p1.x - p2.x), math.abs(p1.y - p2.y))
|
||||
|
@@ -1,5 +1,7 @@
|
||||
local context = G.botContext
|
||||
|
||||
context.getMapView = function() return modules.game_interface.getMapPanel() end
|
||||
context.getMapPanel = context.getMapView
|
||||
context.zoomIn = function() modules.game_interface.getMapPanel():zoomIn() end
|
||||
context.zoomOut = function() modules.game_interface.getMapPanel():zoomOut() end
|
||||
|
||||
|
@@ -80,7 +80,7 @@ UI.Container = function(callback, unique, parent, widget)
|
||||
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()})
|
||||
table.insert(items, {id=child:getItemId(), count=child:getItemCountOrSubType()})
|
||||
duplicates[child:getItemId()] = true
|
||||
end
|
||||
end
|
||||
|
@@ -14,7 +14,7 @@ UI.EditorWindow = function(text, options, callback)
|
||||
validation = text (regex)
|
||||
examples = {{name, text}, {name, text}}
|
||||
]]--
|
||||
local window = modules.game_textedit.edit(text, options, callback)
|
||||
local window = modules.client_textedit.edit(text, options, callback)
|
||||
window.botWidget = true
|
||||
return window
|
||||
end
|
||||
|
@@ -679,7 +679,7 @@ Panel
|
||||
refreshConfig()
|
||||
end
|
||||
ui.add.onClick = function()
|
||||
modules.game_textedit.multilineEditor("Target list editor", "name:Config name", function(newText)
|
||||
modules.client_textedit.multilineEditor("Target list editor", "name:Config name", function(newText)
|
||||
table.insert(context.storage.attacking.configs, newText)
|
||||
context.storage.attacking.activeConfig = #context.storage.attacking.configs
|
||||
refreshConfig()
|
||||
@@ -689,7 +689,7 @@ Panel
|
||||
if not context.storage.attacking.activeConfig or not context.storage.attacking.configs[context.storage.attacking.activeConfig] then
|
||||
return
|
||||
end
|
||||
modules.game_textedit.multilineEditor("Target list editor", context.storage.attacking.configs[context.storage.attacking.activeConfig], function(newText)
|
||||
modules.client_textedit.multilineEditor("Target list editor", context.storage.attacking.configs[context.storage.attacking.activeConfig], function(newText)
|
||||
context.storage.attacking.configs[context.storage.attacking.activeConfig] = newText
|
||||
refreshConfig()
|
||||
end)
|
||||
|
@@ -266,7 +266,7 @@ Panel
|
||||
refreshConfig()
|
||||
end
|
||||
ui.add.onClick = function()
|
||||
modules.game_textedit.multilineEditor("Looting editor", "name:Config name", function(newText)
|
||||
modules.client_textedit.multilineEditor("Looting editor", "name:Config name", function(newText)
|
||||
table.insert(context.storage.looting.configs, newText)
|
||||
context.storage.looting.activeConfig = #context.storage.looting.configs
|
||||
refreshConfig()
|
||||
@@ -276,7 +276,7 @@ Panel
|
||||
if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then
|
||||
return
|
||||
end
|
||||
modules.game_textedit.multilineEditor("Looting editor", context.storage.looting.configs[context.storage.looting.activeConfig], function(newText)
|
||||
modules.client_textedit.multilineEditor("Looting editor", context.storage.looting.configs[context.storage.looting.activeConfig], function(newText)
|
||||
context.storage.looting.configs[context.storage.looting.activeConfig] = newText
|
||||
refreshConfig()
|
||||
end)
|
||||
|
@@ -312,7 +312,7 @@ Panel
|
||||
end
|
||||
end
|
||||
ui.add.onClick = function()
|
||||
modules.game_textedit.multilineEditor("Waypoints editor", "name:Config name\nlabel:start\n", function(newText)
|
||||
modules.client_textedit.multilineEditor("Waypoints editor", "name:Config name\nlabel:start\n", function(newText)
|
||||
table.insert(context.storage.cavebot.configs, newText)
|
||||
context.storage.cavebot.activeConfig = #context.storage.cavebot.configs
|
||||
refreshConfig()
|
||||
@@ -322,7 +322,7 @@ Panel
|
||||
if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then
|
||||
return
|
||||
end
|
||||
modules.game_textedit.multilineEditor("Waypoints editor", context.storage.cavebot.configs[context.storage.cavebot.activeConfig], function(newText)
|
||||
modules.client_textedit.multilineEditor("Waypoints editor", context.storage.cavebot.configs[context.storage.cavebot.activeConfig], function(newText)
|
||||
context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = newText
|
||||
refreshConfig()
|
||||
end)
|
||||
@@ -428,7 +428,7 @@ Panel
|
||||
return
|
||||
end
|
||||
local pos = context.player:getPosition()
|
||||
modules.game_textedit.singlelineEditor("" .. pos.x .. "," .. pos.y .. "," .. pos.z, function(newText)
|
||||
modules.client_textedit.singlelineEditor("" .. pos.x .. "," .. pos.y .. "," .. pos.z, function(newText)
|
||||
context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\ngoto:" .. newText
|
||||
refreshConfig(true)
|
||||
end)
|
||||
@@ -439,7 +439,7 @@ Panel
|
||||
return
|
||||
end
|
||||
local pos = context.player:getPosition()
|
||||
modules.game_textedit.singlelineEditor("" .. pos.x .. "," .. pos.y .. "," .. pos.z, function(newText)
|
||||
modules.client_textedit.singlelineEditor("" .. pos.x .. "," .. pos.y .. "," .. pos.z, function(newText)
|
||||
context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nuse:" .. newText
|
||||
refreshConfig(true)
|
||||
end)
|
||||
@@ -450,7 +450,7 @@ Panel
|
||||
return
|
||||
end
|
||||
local pos = context.player:getPosition()
|
||||
modules.game_textedit.singlelineEditor("ITEMID," .. pos.x .. "," .. pos.y .. "," .. pos.z, function(newText)
|
||||
modules.client_textedit.singlelineEditor("ITEMID," .. pos.x .. "," .. pos.y .. "," .. pos.z, function(newText)
|
||||
context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nusewith:" .. newText
|
||||
refreshConfig(true)
|
||||
end)
|
||||
@@ -460,7 +460,7 @@ Panel
|
||||
if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then
|
||||
return
|
||||
end
|
||||
modules.game_textedit.singlelineEditor("1000", function(newText)
|
||||
modules.client_textedit.singlelineEditor("1000", function(newText)
|
||||
context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nwait:" .. newText
|
||||
refreshConfig(true)
|
||||
end)
|
||||
@@ -470,7 +470,7 @@ Panel
|
||||
if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then
|
||||
return
|
||||
end
|
||||
modules.game_textedit.singlelineEditor("text", function(newText)
|
||||
modules.client_textedit.singlelineEditor("text", function(newText)
|
||||
context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nsay:" .. newText
|
||||
refreshConfig(true)
|
||||
end)
|
||||
@@ -480,7 +480,7 @@ Panel
|
||||
if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then
|
||||
return
|
||||
end
|
||||
modules.game_textedit.singlelineEditor("text", function(newText)
|
||||
modules.client_textedit.singlelineEditor("text", function(newText)
|
||||
context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nnpc:" .. newText
|
||||
refreshConfig(true)
|
||||
end)
|
||||
@@ -490,7 +490,7 @@ Panel
|
||||
if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then
|
||||
return
|
||||
end
|
||||
modules.game_textedit.singlelineEditor("label name", function(newText)
|
||||
modules.client_textedit.singlelineEditor("label name", function(newText)
|
||||
context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nlabel:" .. newText
|
||||
refreshConfig(true)
|
||||
end)
|
||||
@@ -500,7 +500,7 @@ Panel
|
||||
if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then
|
||||
return
|
||||
end
|
||||
modules.game_textedit.singlelineEditor("creature name", function(newText)
|
||||
modules.client_textedit.singlelineEditor("creature name", function(newText)
|
||||
context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nfollow:" .. newText
|
||||
refreshConfig(true)
|
||||
end)
|
||||
@@ -510,7 +510,7 @@ Panel
|
||||
if not context.storage.cavebot.activeConfig or not context.storage.cavebot.configs[context.storage.cavebot.activeConfig] then
|
||||
return
|
||||
end
|
||||
modules.game_textedit.multilineEditor("Add function", "function(waypoints)\n -- your lua code, function is executed if previous goto was successful or is just after label\n\n -- must return true to execute next command, otherwise will run in loop till correct return\n return true\nend", function(newText)
|
||||
modules.client_textedit.multilineEditor("Add function", "function(waypoints)\n -- your lua code, function is executed if previous goto was successful or is just after label\n\n -- must return true to execute next command, otherwise will run in loop till correct return\n return true\nend", function(newText)
|
||||
context.storage.cavebot.configs[context.storage.cavebot.activeConfig] = context.storage.cavebot.configs[context.storage.cavebot.activeConfig] .. "\nfunction:" .. newText
|
||||
refreshConfig(true)
|
||||
end)
|
||||
|
@@ -29,7 +29,7 @@ BotItem < Item
|
||||
&editable: true
|
||||
|
||||
BotTextEdit < TextEdit
|
||||
@onClick: modules.game_textedit.show(self)
|
||||
@onClick: modules.client_textedit.show(self)
|
||||
text-align: center
|
||||
multiline: false
|
||||
focusable: false
|
||||
@@ -52,6 +52,8 @@ BotPanel < Panel
|
||||
vertical-scrollbar: botPanelScroll
|
||||
layout:
|
||||
type: verticalBox
|
||||
$mobile:
|
||||
margin-right: 16
|
||||
|
||||
BotSmallScrollBar
|
||||
id: botPanelScroll
|
||||
|
@@ -22,7 +22,6 @@ BotIcon < UIWidget
|
||||
margin-top: 0
|
||||
size: 48 48
|
||||
phantom: true
|
||||
optimized: true
|
||||
|
||||
UIWidget
|
||||
id: status
|
||||
|
@@ -194,6 +194,7 @@ function toggleChat()
|
||||
end
|
||||
|
||||
function enableChat(temporarily)
|
||||
if g_app.isMobile() then return end
|
||||
if consoleToggleChat:isChecked() then
|
||||
return consoleToggleChat:setChecked(false)
|
||||
end
|
||||
@@ -225,6 +226,7 @@ function enableChat(temporarily)
|
||||
end
|
||||
|
||||
function disableChat(temporarily)
|
||||
if g_app.isMobile() then return end
|
||||
if not consoleToggleChat:isChecked() then
|
||||
return consoleToggleChat:setChecked(true)
|
||||
end
|
||||
@@ -591,6 +593,29 @@ function getHighlightedText(text)
|
||||
return tmpData
|
||||
end
|
||||
|
||||
function getNewHighlightedText(text, color, highlightColor)
|
||||
local tmpData = {}
|
||||
|
||||
for i, part in ipairs(text:split("{")) do
|
||||
if i == 1 then
|
||||
table.insert(tmpData, part)
|
||||
table.insert(tmpData, color)
|
||||
else
|
||||
for j, part2 in ipairs(part:split("}")) do
|
||||
if j == 1 then
|
||||
table.insert(tmpData, part2)
|
||||
table.insert(tmpData, highlightColor)
|
||||
else
|
||||
table.insert(tmpData, part2)
|
||||
table.insert(tmpData, color)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return tmpData
|
||||
end
|
||||
|
||||
function addTabText(text, speaktype, tab, creatureName)
|
||||
if not tab or tab.locked or not text or #text == 0 then return end
|
||||
|
||||
@@ -1113,16 +1138,16 @@ function onTalk(name, level, mode, message, channelId, creaturePos)
|
||||
-- Remove curly braces from screen message
|
||||
local staticMessage = message
|
||||
if isNpcMode then
|
||||
local highlightData = getHighlightedText(staticMessage)
|
||||
if #highlightData > 0 then
|
||||
for i = 1, #highlightData / 3 do
|
||||
local dataBlock = { _start = highlightData[(i-1)*3+1], _end = highlightData[(i-1)*3+2], words = highlightData[(i-1)*3+3] }
|
||||
staticMessage = staticMessage:gsub("{"..dataBlock.words.."}", dataBlock.words)
|
||||
end
|
||||
local highlightData = getNewHighlightedText(staticMessage, speaktype.color, "#1f9ffe")
|
||||
if #highlightData > 2 then
|
||||
staticText:addColoredMessage(name, mode, highlightData)
|
||||
else
|
||||
staticText:addMessage(name, mode, staticMessage)
|
||||
end
|
||||
staticText:setColor(speaktype.color)
|
||||
else
|
||||
staticText:addMessage(name, mode, staticMessage)
|
||||
end
|
||||
staticText:addMessage(name, mode, staticMessage)
|
||||
g_map.addThing(staticText, creaturePos, -1)
|
||||
end
|
||||
|
||||
|
@@ -50,7 +50,11 @@ function init()
|
||||
|
||||
if not healthInfoWindow.forceOpen then
|
||||
healthInfoButton = modules.client_topmenu.addRightGameToggleButton('healthInfoButton', tr('Health Information'), '/images/topbuttons/healthinfo', toggle)
|
||||
healthInfoButton:setOn(true)
|
||||
if g_app.isMobile() then
|
||||
healthInfoButton:hide()
|
||||
else
|
||||
healthInfoButton:setOn(true)
|
||||
end
|
||||
end
|
||||
|
||||
healthBar = healthInfoWindow:recursiveGetChildById('healthBar')
|
||||
@@ -89,6 +93,11 @@ function init()
|
||||
hideExperience()
|
||||
|
||||
healthInfoWindow:setup()
|
||||
|
||||
if g_app.isMobile() then
|
||||
healthInfoWindow:close()
|
||||
healthInfoButton:setOn(false)
|
||||
end
|
||||
end
|
||||
|
||||
function terminate()
|
||||
@@ -166,8 +175,9 @@ function onHealthChange(localPlayer, health, maxHealth)
|
||||
|
||||
local healthPercent = math.floor(g_game.getLocalPlayer():getHealthPercent())
|
||||
local Yhppc = math.floor(208 * (1 - (healthPercent / 100)))
|
||||
local rect = { x = 0, y = Yhppc, width = 63, height = 208 }
|
||||
local rect = { x = 0, y = Yhppc, width = 63, height = 208 - Yhppc + 1 }
|
||||
healthCircleFront:setImageClip(rect)
|
||||
healthCircleFront:setImageRect(rect)
|
||||
|
||||
if healthPercent > 92 then
|
||||
healthCircleFront:setImageColor("#00BC00FF")
|
||||
@@ -182,14 +192,13 @@ function onHealthChange(localPlayer, health, maxHealth)
|
||||
else
|
||||
healthCircleFront:setImageColor("#850C0CFF")
|
||||
end
|
||||
|
||||
healthCircleFront:setMarginTop(Yhppc)
|
||||
end
|
||||
|
||||
function onManaChange(localPlayer, mana, maxMana)
|
||||
if mana > maxMana then
|
||||
maxMana = mana
|
||||
end
|
||||
|
||||
manaBar:setText(comma_value(mana) .. ' / ' .. comma_value(maxMana))
|
||||
manaBar:setTooltip(tr(manaTooltip, mana, maxMana))
|
||||
manaBar:setValue(mana, 0, maxMana)
|
||||
@@ -198,10 +207,10 @@ function onManaChange(localPlayer, mana, maxMana)
|
||||
topManaBar:setTooltip(tr(manaTooltip, mana, maxMana))
|
||||
topManaBar:setValue(mana, 0, maxMana)
|
||||
|
||||
local Ymppc = math.floor(208 * (1 - (math.floor((g_game.getLocalPlayer():getMaxMana() - (g_game.getLocalPlayer():getMaxMana() - g_game.getLocalPlayer():getMana())) * 100 / g_game.getLocalPlayer():getMaxMana()) / 100)))
|
||||
local rect = { x = 0, y = Ymppc, width = 63, height = 208 }
|
||||
local Ymppc = math.floor(208 * (1 - (math.floor((maxMana - (maxMana - mana)) * 100 / maxMana) / 100)))
|
||||
local rect = { x = 0, y = Ymppc, width = 63, height = 208 - Ymppc + 1 }
|
||||
manaCircleFront:setImageClip(rect)
|
||||
manaCircleFront:setMarginTop(Ymppc)
|
||||
manaCircleFront:setImageRect(rect)
|
||||
end
|
||||
|
||||
function onLevelChange(localPlayer, value, percent)
|
||||
@@ -276,8 +285,18 @@ function setExperienceTooltip(tooltip)
|
||||
end
|
||||
|
||||
function onOverlayGeometryChange()
|
||||
if g_app.isMobile() then
|
||||
topHealthBar:setMarginTop(35)
|
||||
topManaBar:setMarginTop(35)
|
||||
local width = overlay:getWidth()
|
||||
local margin = width / 3 + 10
|
||||
topHealthBar:setMarginLeft(margin)
|
||||
topManaBar:setMarginRight(margin)
|
||||
return
|
||||
end
|
||||
|
||||
local classic = g_settings.getBoolean("classicView")
|
||||
local minMargin = 100
|
||||
local minMargin = 40
|
||||
if classic then
|
||||
topHealthBar:setMarginTop(15)
|
||||
topManaBar:setMarginTop(15)
|
||||
|
@@ -51,9 +51,9 @@ function setupExtraHotkeys(combobox)
|
||||
end
|
||||
local battlePanel = modules.game_battle.battlePanel
|
||||
local attackedCreature = g_game.getAttackingCreature()
|
||||
local prevChild = battlePanel:getLastChild()
|
||||
local prevChild = nil
|
||||
for i, child in ipairs(battlePanel:getChildren()) do
|
||||
if not child.creature or child:isDisabled() then
|
||||
if not child.creature or child:isDisabled() or child:isHidden() then
|
||||
break
|
||||
end
|
||||
if child.creature == attackedCreature then
|
||||
|
@@ -43,7 +43,9 @@ configValueChanged = false
|
||||
|
||||
-- public functions
|
||||
function init()
|
||||
hotkeysButton = modules.client_topmenu.addLeftGameButton('hotkeysButton', tr('Hotkeys') .. ' (Ctrl+K)', '/images/topbuttons/hotkeys', toggle, false, 7)
|
||||
if not g_app.isMobile() then
|
||||
hotkeysButton = modules.client_topmenu.addLeftGameButton('hotkeysButton', tr('Hotkeys') .. ' (Ctrl+K)', '/images/topbuttons/hotkeys', toggle, false, 7)
|
||||
end
|
||||
g_keyboard.bindKeyDown('Ctrl+K', toggle)
|
||||
hotkeysWindow = g_ui.displayUI('hotkeys_manager')
|
||||
hotkeysWindow:setVisible(false)
|
||||
@@ -104,7 +106,9 @@ function terminate()
|
||||
unload()
|
||||
|
||||
hotkeysWindow:destroy()
|
||||
hotkeysButton:destroy()
|
||||
if hotkeysButton then
|
||||
hotkeysButton:destroy()
|
||||
end
|
||||
mouseGrabberWidget:destroy()
|
||||
end
|
||||
|
||||
|
@@ -3,6 +3,8 @@ gameMapPanel = nil
|
||||
gameRightPanels = nil
|
||||
gameLeftPanels = nil
|
||||
gameBottomPanel = nil
|
||||
gameActionPanel = nil
|
||||
gameLeftActions = nil
|
||||
logoutButton = nil
|
||||
mouseGrabberWidget = nil
|
||||
countWindow = nil
|
||||
@@ -44,6 +46,8 @@ function init()
|
||||
gameRightPanels = gameRootPanel:getChildById('gameRightPanels')
|
||||
gameLeftPanels = gameRootPanel:getChildById('gameLeftPanels')
|
||||
gameBottomPanel = gameRootPanel:getChildById('gameBottomPanel')
|
||||
gameActionPanel = gameRootPanel:getChildById('gameActionPanel')
|
||||
gameLeftActions = gameRootPanel:getChildById('gameLeftActions')
|
||||
connect(gameLeftPanel, { onVisibilityChange = onLeftPanelVisibilityChange })
|
||||
|
||||
logoutButton = modules.client_topmenu.addLeftButton('logoutButton', tr('Exit'),
|
||||
@@ -52,6 +56,7 @@ function init()
|
||||
|
||||
gameRightPanels:addChild(g_ui.createWidget('GameSidePanel'))
|
||||
|
||||
setupLeftActions()
|
||||
refreshViewMode()
|
||||
|
||||
bindKeys()
|
||||
@@ -389,12 +394,12 @@ function createThingMenu(menuPosition, lookThing, useThing, creatureThing)
|
||||
local classic = modules.client_options.getOption('classicControl')
|
||||
local shortcut = nil
|
||||
|
||||
if not classic then shortcut = '(Shift)' else shortcut = nil end
|
||||
if not classic and not g_app.isMobile() then shortcut = '(Shift)' else shortcut = nil end
|
||||
if lookThing then
|
||||
menu:addOption(tr('Look'), function() g_game.look(lookThing) end, shortcut)
|
||||
end
|
||||
|
||||
if not classic then shortcut = '(Ctrl)' else shortcut = nil end
|
||||
if not classic and not g_app.isMobile() then shortcut = '(Ctrl)' else shortcut = nil end
|
||||
if useThing then
|
||||
if useThing:isContainer() then
|
||||
if useThing:getParentContainer() then
|
||||
@@ -470,7 +475,7 @@ function createThingMenu(menuPosition, lookThing, useThing, creatureThing)
|
||||
|
||||
else
|
||||
local localPosition = localPlayer:getPosition()
|
||||
if not classic then shortcut = '(Alt)' else shortcut = nil end
|
||||
if not classic and not g_app.isMobile() then shortcut = '(Alt)' else shortcut = nil end
|
||||
if creatureThing:getPosition().z == localPosition.z then
|
||||
if g_game.getAttackingCreature() ~= creatureThing then
|
||||
menu:addOption(tr('Attack'), function() g_game.attack(creatureThing) end, shortcut)
|
||||
@@ -565,7 +570,68 @@ end
|
||||
function processMouseAction(menuPosition, mouseButton, autoWalkPos, lookThing, useThing, creatureThing, attackCreature, marking)
|
||||
local keyboardModifiers = g_keyboard.getModifiers()
|
||||
|
||||
if not modules.client_options.getOption('classicControl') then
|
||||
if g_app.isMobile() then
|
||||
if mouseButton == MouseRightButton then
|
||||
createThingMenu(menuPosition, lookThing, useThing, creatureThing)
|
||||
return true
|
||||
end
|
||||
if mouseButton ~= MouseLeftButton then
|
||||
return false
|
||||
end
|
||||
local action = getLeftAction()
|
||||
if action == "look" then
|
||||
if lookThing then
|
||||
resetLeftActions()
|
||||
g_game.look(lookThing)
|
||||
return true
|
||||
end
|
||||
return true
|
||||
elseif action == "use" then
|
||||
if useThing then
|
||||
resetLeftActions()
|
||||
if useThing:isContainer() then
|
||||
if useThing:getParentContainer() then
|
||||
g_game.open(useThing, useThing:getParentContainer())
|
||||
else
|
||||
g_game.open(useThing)
|
||||
end
|
||||
return true
|
||||
elseif useThing:isMultiUse() then
|
||||
startUseWith(useThing)
|
||||
return true
|
||||
else
|
||||
g_game.use(useThing)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return true
|
||||
elseif action == "attack" then
|
||||
if attackCreature and attackCreature ~= player then
|
||||
resetLeftActions()
|
||||
g_game.attack(attackCreature)
|
||||
return true
|
||||
elseif creatureThing and creatureThing ~= player and creatureThing:getPosition().z == autoWalkPos.z then
|
||||
resetLeftActions()
|
||||
g_game.attack(creatureThing)
|
||||
return true
|
||||
end
|
||||
return true
|
||||
elseif action == "follow" then
|
||||
if attackCreature and attackCreature ~= player then
|
||||
resetLeftActions()
|
||||
g_game.follow(attackCreature)
|
||||
return true
|
||||
elseif creatureThing and creatureThing ~= player and creatureThing:getPosition().z == autoWalkPos.z then
|
||||
resetLeftActions()
|
||||
g_game.follow(creatureThing)
|
||||
return true
|
||||
end
|
||||
return true
|
||||
elseif not autoWalkPos and useThing then
|
||||
createThingMenu(menuPosition, lookThing, useThing, creatureThing)
|
||||
return true
|
||||
end
|
||||
elseif not modules.client_options.getOption('classicControl') then
|
||||
if keyboardModifiers == KeyboardNoModifier and mouseButton == MouseRightButton then
|
||||
createThingMenu(menuPosition, lookThing, useThing, creatureThing)
|
||||
return true
|
||||
@@ -595,9 +661,7 @@ function processMouseAction(menuPosition, mouseButton, autoWalkPos, lookThing, u
|
||||
g_game.attack(creatureThing)
|
||||
return true
|
||||
end
|
||||
|
||||
-- classic control
|
||||
else
|
||||
else -- classic control
|
||||
if useThing and keyboardModifiers == KeyboardNoModifier and mouseButton == MouseRightButton and not g_mouse.isPressed(MouseLeftButton) then
|
||||
local player = g_game.getLocalPlayer()
|
||||
if attackCreature and attackCreature ~= player then
|
||||
@@ -810,8 +874,12 @@ function getBottomPanel()
|
||||
return gameBottomPanel
|
||||
end
|
||||
|
||||
function getActionPanel()
|
||||
return gameActionPanel
|
||||
end
|
||||
|
||||
function refreshViewMode()
|
||||
local classic = g_settings.getBoolean("classicView")
|
||||
local classic = g_settings.getBoolean("classicView") and not g_app.isMobile()
|
||||
local rightPanels = g_settings.getNumber("rightPanels") - gameRightPanels:getChildCount()
|
||||
local leftPanels = g_settings.getNumber("leftPanels") - 1 - gameLeftPanels:getChildCount()
|
||||
|
||||
@@ -838,9 +906,9 @@ function refreshViewMode()
|
||||
return
|
||||
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 })
|
||||
local minimumWidth = (g_settings.getNumber("rightPanels") + g_settings.getNumber("leftPanels") - 1) * 200 + 200
|
||||
minimumWidth = math.max(minimumWidth, g_resources.getLayout() == "mobile" and 640 or 800)
|
||||
g_window.setMinimumSize({ width = minimumWidth, height = (g_resources.getLayout() == "mobile" and 360 or 600)})
|
||||
if g_window.getWidth() < minimumWidth then
|
||||
local oldPos = g_window.getPosition()
|
||||
local size = { width = minimumWidth, height = g_window.getHeight() }
|
||||
@@ -868,30 +936,21 @@ function refreshViewMode()
|
||||
gameMapPanel:setMarginLeft(0)
|
||||
gameMapPanel:setMarginRight(0)
|
||||
gameMapPanel:setMarginTop(0)
|
||||
else
|
||||
gameLeftPanels:setMarginTop(modules.client_topmenu.getTopMenu():getHeight() - gameLeftPanels:getPaddingTop())
|
||||
gameRightPanels:setMarginTop(modules.client_topmenu.getTopMenu():getHeight() - gameRightPanels:getPaddingTop())
|
||||
end
|
||||
|
||||
gameMapPanel:setVisibleDimension({ width = 15, height = 11 })
|
||||
|
||||
if classic then
|
||||
g_game.changeMapAwareRange(19, 15)
|
||||
if not modules.client_topmenu.getTopMenu().hideIngame then
|
||||
gameRootPanel:addAnchor(AnchorTop, 'topMenu', AnchorBottom)
|
||||
end
|
||||
gameMapPanel:addAnchor(AnchorLeft, 'gameLeftPanels', AnchorRight)
|
||||
gameMapPanel:addAnchor(AnchorRight, 'gameRightPanels', AnchorLeft)
|
||||
gameMapPanel:addAnchor(AnchorBottom, 'gameBottomPanel', AnchorTop)
|
||||
gameMapPanel:addAnchor(AnchorBottom, 'gameActionPanel', AnchorTop)
|
||||
gameMapPanel:setKeepAspectRatio(true)
|
||||
gameMapPanel:setLimitVisibleRange(false)
|
||||
gameMapPanel:setZoom(11)
|
||||
gameMapPanel:setOn(false) -- frame
|
||||
|
||||
gameBottomPanel:addAnchor(AnchorLeft, 'gameLeftPanels', AnchorRight)
|
||||
gameBottomPanel:addAnchor(AnchorRight, 'gameRightPanels', AnchorLeft)
|
||||
|
||||
modules.client_topmenu.getTopMenu():setImageColor('white')
|
||||
gameBottomPanel:setImageColor('white')
|
||||
|
||||
if modules.game_console then
|
||||
modules.game_console.switchMode(false)
|
||||
@@ -899,13 +958,19 @@ function refreshViewMode()
|
||||
else
|
||||
g_game.changeMapAwareRange(31, 21)
|
||||
gameMapPanel:fill('parent')
|
||||
gameRootPanel:fill('parent')
|
||||
gameMapPanel:setKeepAspectRatio(false)
|
||||
gameMapPanel:setLimitVisibleRange(false)
|
||||
gameMapPanel:setZoom(15)
|
||||
gameMapPanel:setOn(true)
|
||||
if g_app.isMobile() then
|
||||
gameMapPanel:setZoom(11)
|
||||
else
|
||||
gameMapPanel:setZoom(15)
|
||||
end
|
||||
|
||||
modules.client_topmenu.getTopMenu():setImageColor('#ffffff66')
|
||||
|
||||
if g_app.isMobile() then
|
||||
gameMapPanel:setMarginTop(-32)
|
||||
end
|
||||
if modules.game_console then
|
||||
modules.game_console.switchMode(true)
|
||||
end
|
||||
@@ -925,7 +990,9 @@ function limitZoom()
|
||||
limitedZoom = true
|
||||
end
|
||||
|
||||
function updateSize()
|
||||
function updateSize()
|
||||
if g_app.isMobile() then return end
|
||||
|
||||
local classic = g_settings.getBoolean("classicView")
|
||||
local height = gameMapPanel:getHeight()
|
||||
local width = gameMapPanel:getWidth()
|
||||
@@ -984,3 +1051,45 @@ function updateSize()
|
||||
gameMapPanel:setMarginLeft(extraMargin)
|
||||
gameMapPanel:setMarginRight(extraMargin) ]]
|
||||
end
|
||||
|
||||
function setupLeftActions()
|
||||
if not g_app.isMobile() then return end
|
||||
for _, widget in ipairs(gameLeftActions:getChildren()) do
|
||||
widget.image:setChecked(false)
|
||||
widget.onClick = function()
|
||||
if widget.image:isChecked() then
|
||||
widget.image:setChecked(false)
|
||||
return
|
||||
end
|
||||
resetLeftActions()
|
||||
widget.image:setChecked(true)
|
||||
end
|
||||
end
|
||||
if not gameLeftActions.chat then return end
|
||||
gameLeftActions.chat.onClick = function()
|
||||
if gameBottomPanel:getHeight() <= 5 then
|
||||
gameBottomPanel:setHeight(90)
|
||||
else
|
||||
gameBottomPanel:setHeight(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function resetLeftActions()
|
||||
for _, widget in ipairs(gameLeftActions:getChildren()) do
|
||||
widget.image:setChecked(false)
|
||||
end
|
||||
end
|
||||
|
||||
function getLeftAction()
|
||||
for _, widget in ipairs(gameLeftActions:getChildren()) do
|
||||
if widget.image:isChecked() then
|
||||
return widget:getId()
|
||||
end
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
function isChatVisible()
|
||||
return gameBottomPanel:getHeight() >= 5
|
||||
end
|
@@ -1,16 +1,17 @@
|
||||
GameSidePanel < UIMiniWindowContainer
|
||||
image-source: /images/ui/panel_side
|
||||
image-border: 4
|
||||
padding: 4
|
||||
padding: 3
|
||||
padding-top: 0
|
||||
width: 198
|
||||
focusable: false
|
||||
on: true
|
||||
layout:
|
||||
type: verticalBox
|
||||
//spacing: 1
|
||||
|
||||
GameBottomPanel < Panel
|
||||
$mobile:
|
||||
padding: 0
|
||||
width: 200
|
||||
|
||||
|
||||
GameMapPanel < UIGameMap
|
||||
padding: 4
|
||||
@@ -19,6 +20,24 @@ GameMapPanel < UIGameMap
|
||||
|
||||
$on:
|
||||
padding: 0
|
||||
|
||||
GameAction < UIButton
|
||||
size: 64 64
|
||||
phantom: false
|
||||
|
||||
UIButton
|
||||
id: image
|
||||
size: 48 48
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
phantom: true
|
||||
$checked:
|
||||
opacity: 1.0
|
||||
background: #00FF0033
|
||||
$!checked:
|
||||
opacity: 0.6
|
||||
background: alpha
|
||||
|
||||
|
||||
UIWidget
|
||||
id: gameRootPanel
|
||||
@@ -33,11 +52,46 @@ UIWidget
|
||||
focusable: false
|
||||
|
||||
Panel
|
||||
id: gameLeftPanels
|
||||
id: gameLeftActions
|
||||
focusable: false
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
width: 64
|
||||
|
||||
$!mobile:
|
||||
visible: false
|
||||
width: 0
|
||||
|
||||
layout:
|
||||
type: verticalBox
|
||||
fit-children: true
|
||||
|
||||
GameAction
|
||||
id: use
|
||||
@onSetup: self.image:setImageSource("/images/game/mobile/use")
|
||||
GameAction
|
||||
id: attack
|
||||
@onSetup: self.image:setImageSource("/images/game/mobile/attack")
|
||||
GameAction
|
||||
id: follow
|
||||
@onSetup: self.image:setImageSource("/images/game/mobile/follow")
|
||||
GameAction
|
||||
id: look
|
||||
@onSetup: self.image:setImageSource("/images/game/mobile/look")
|
||||
GameAction
|
||||
id: chat
|
||||
@onSetup: self.image:setImageSource("/images/game/mobile/chat")
|
||||
|
||||
Panel
|
||||
id: gameLeftPanels
|
||||
focusable: false
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
focusable: false
|
||||
$!mobile:
|
||||
anchors.left: parent.left
|
||||
$mobile:
|
||||
anchors.left: gameLeftActions.right
|
||||
|
||||
layout:
|
||||
type: horizontalBox
|
||||
fit-children: true
|
||||
@@ -59,19 +113,48 @@ UIWidget
|
||||
anchors.left: gameLeftPanels.right
|
||||
anchors.right: gameRightPanels.left
|
||||
anchors.bottom: parent.bottom
|
||||
height: 5
|
||||
relative-margin: bottom
|
||||
margin-bottom: 180
|
||||
@canUpdateMargin: function(self, newMargin) if modules.client_options.getOption('dontStretchShrink') then return self:getMarginBottom() end return math.max(math.min(newMargin, self:getParent():getHeight() - 300), 80) end
|
||||
@onGeometryChange: function(self) self:setMarginBottom(math.min(math.max(self:getParent():getHeight() - 300, 80), self:getMarginBottom())) end
|
||||
|
||||
GameBottomPanel
|
||||
id: gameBottomPanel
|
||||
anchors.left: bottomSplitter.left
|
||||
anchors.right: bottomSplitter.right
|
||||
anchors.top: bottomSplitter.top
|
||||
anchors.bottom: parent.bottom
|
||||
margin-top: 3
|
||||
margin-bottom: 150
|
||||
@canUpdateMargin: function(self, newMargin) if modules.client_options.getOption('dontStretchShrink') then return self:getMarginBottom() end return math.max(math.min(newMargin, self:getParent():getHeight() - 150), 80) end
|
||||
@onGeometryChange: function(self) self:setMarginBottom(math.min(math.max(self:getParent():getHeight() - 150, 80), self:getMarginBottom())) end
|
||||
|
||||
$mobile:
|
||||
visible: false
|
||||
|
||||
Panel
|
||||
id: gameActionPanel
|
||||
phantom: true
|
||||
focusable: false
|
||||
|
||||
$!mobile:
|
||||
anchors.left: bottomSplitter.left
|
||||
anchors.right: bottomSplitter.right
|
||||
anchors.top: bottomSplitter.top
|
||||
margin-top: 3
|
||||
|
||||
$mobile:
|
||||
anchors.left: gameLeftPanels.right
|
||||
anchors.right: gameRightPanels.left
|
||||
anchors.bottom: gameBottomPanel.top
|
||||
|
||||
layout:
|
||||
type: verticalBox
|
||||
fit-children: true
|
||||
|
||||
Panel
|
||||
id: gameBottomPanel
|
||||
$!mobile:
|
||||
anchors.left: gameActionPanel.left
|
||||
anchors.right: gameActionPanel.right
|
||||
anchors.top: gameActionPanel.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
$mobile:
|
||||
anchors.left: gameLeftPanels.right
|
||||
anchors.right: gameRightPanels.left
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
UIWidget
|
||||
id: mouseGrabber
|
||||
focusable: false
|
||||
|
@@ -34,7 +34,7 @@ Module
|
||||
- game_walking
|
||||
- game_shop
|
||||
- game_itemselector
|
||||
- game_textedit
|
||||
- client_textedit
|
||||
- game_actionbar
|
||||
- game_prey
|
||||
- game_imbuing
|
||||
|
@@ -1136,10 +1136,12 @@ function Market.loadMarketItems(category)
|
||||
if category == MarketCategory.All then
|
||||
-- loop all categories
|
||||
for category = MarketCategory.First, MarketCategory.Last do
|
||||
for i = 1, #marketItems[category] do
|
||||
local item = marketItems[category][i]
|
||||
if isItemValid(item, category, searchFilter) then
|
||||
table.insert(currentItems, item)
|
||||
if marketItems[category] then
|
||||
for i = 1, #marketItems[category] do
|
||||
local item = marketItems[category][i]
|
||||
if isItemValid(item, category, searchFilter) then
|
||||
table.insert(currentItems, item)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@@ -18,7 +18,7 @@ NPCItemBox < UICheckBox
|
||||
anchors.top: parent.top
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
image-color: #ffffffff
|
||||
margin-top: 5
|
||||
margin-top: 3
|
||||
|
||||
$checked on:
|
||||
border-color: #ffffff
|
||||
@@ -33,7 +33,7 @@ NPCItemBox < UICheckBox
|
||||
MainWindow
|
||||
id: npcWindow
|
||||
!text: tr('NPC Trade')
|
||||
size: 550 515
|
||||
size: 550 340
|
||||
@onEscape: modules.game_npctrade.closeNpcTrade()
|
||||
|
||||
TabButton
|
||||
@@ -45,8 +45,7 @@ MainWindow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
margin-right: 5
|
||||
margin-top: 5
|
||||
margin-top: 0
|
||||
|
||||
TabButton
|
||||
id: sellTab
|
||||
@@ -55,15 +54,13 @@ MainWindow
|
||||
anchors.left: parent.horizontalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
margin-left: 5
|
||||
margin-top: 5
|
||||
|
||||
FlatPanel
|
||||
height: 250
|
||||
height: 150
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 10
|
||||
margin-top: 5
|
||||
|
||||
VerticalScrollBar
|
||||
id: itemsPanelListScrollBar
|
||||
@@ -75,7 +72,7 @@ MainWindow
|
||||
|
||||
ScrollablePanel
|
||||
id: itemsPanel
|
||||
height: 250
|
||||
height: 200
|
||||
anchors.left: parent.left
|
||||
anchors.right: prev.left
|
||||
anchors.top: parent.top
|
||||
@@ -91,30 +88,19 @@ MainWindow
|
||||
|
||||
FlatPanel
|
||||
id: setupPanel
|
||||
height: 140
|
||||
height: 105
|
||||
enabled: false
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.horizontalCenter
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 10
|
||||
margin-top: 5
|
||||
margin-right: 5
|
||||
image-color: #ffffff88
|
||||
|
||||
Label
|
||||
!text: tr('Id') .. ':'
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
margin-top: 5
|
||||
margin-left: 5
|
||||
width: 85
|
||||
|
||||
NPCOfferLabel
|
||||
id: id
|
||||
|
||||
Label
|
||||
!text: tr('Name') .. ':'
|
||||
anchors.left: parent.left
|
||||
anchors.top: prev.bottom
|
||||
anchors.top: parent.top
|
||||
margin-top: 5
|
||||
margin-left: 5
|
||||
width: 85
|
||||
@@ -122,11 +108,23 @@ MainWindow
|
||||
NPCOfferLabel
|
||||
id: name
|
||||
|
||||
Label
|
||||
!text: tr('Id') .. ':'
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
margin-top: 5
|
||||
margin-left: 5
|
||||
margin-left: 195
|
||||
width: 15
|
||||
|
||||
NPCOfferLabel
|
||||
id: id
|
||||
|
||||
Label
|
||||
!text: tr('Price') .. ':'
|
||||
anchors.left: parent.left
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 5
|
||||
margin-top: 3
|
||||
margin-left: 5
|
||||
width: 85
|
||||
|
||||
@@ -137,7 +135,7 @@ MainWindow
|
||||
!text: tr('Your Money') .. ':'
|
||||
anchors.left: parent.left
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 5
|
||||
margin-top: 3
|
||||
margin-left: 5
|
||||
width: 85
|
||||
|
||||
@@ -149,7 +147,7 @@ MainWindow
|
||||
!text: tr('Weight') .. ':'
|
||||
anchors.left: parent.left
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 5
|
||||
margin-top: 3
|
||||
margin-left: 5
|
||||
width: 85
|
||||
|
||||
@@ -161,7 +159,7 @@ MainWindow
|
||||
!text: tr('Your Capacity') .. ':'
|
||||
anchors.left: parent.left
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 5
|
||||
margin-top: 3
|
||||
margin-left: 5
|
||||
width: 85
|
||||
|
||||
@@ -173,7 +171,7 @@ MainWindow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 5
|
||||
margin-top: 3
|
||||
margin-left: 5
|
||||
margin-right: 5
|
||||
show-value: true
|
||||
@@ -184,7 +182,7 @@ MainWindow
|
||||
|
||||
FlatPanel
|
||||
id: buyOptions
|
||||
height: 140
|
||||
height: 80
|
||||
anchors.top: prev.top
|
||||
anchors.left: parent.horizontalCenter
|
||||
anchors.right: parent.right
|
||||
@@ -217,7 +215,7 @@ MainWindow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-left: 5
|
||||
margin-top: 5
|
||||
margin-top: 3
|
||||
@onCheckChange: modules.game_npctrade.onBuyWithBackpackChange()
|
||||
|
||||
CheckBox
|
||||
@@ -227,7 +225,7 @@ MainWindow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-left: 5
|
||||
margin-top: 5
|
||||
margin-top: 3
|
||||
@onCheckChange: modules.game_npctrade.onIgnoreCapacityChange()
|
||||
|
||||
CheckBox
|
||||
@@ -237,7 +235,7 @@ MainWindow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-left: 5
|
||||
margin-top: 5
|
||||
margin-top: 3
|
||||
visible: false
|
||||
checked: true
|
||||
@onCheckChange: modules.game_npctrade.onIgnoreEquippedChange()
|
||||
@@ -249,7 +247,7 @@ MainWindow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
margin-left: 5
|
||||
margin-top: 5
|
||||
margin-top: 3
|
||||
visible: false
|
||||
checked: true
|
||||
@onCheckChange: modules.game_npctrade.onShowAllItemsChange()
|
||||
|
@@ -5,7 +5,7 @@ PrevMountButton < PreviousButton
|
||||
|
||||
MainWindow
|
||||
!text: tr('Select Outfit')
|
||||
size: 338 375
|
||||
size: 338 355
|
||||
|
||||
@onEnter: modules.game_outfit.accept()
|
||||
@onEscape: modules.game_outfit.destroy()
|
||||
@@ -158,24 +158,21 @@ MainWindow
|
||||
num-columns: 19
|
||||
num-lines: 7
|
||||
|
||||
// Action Button Section
|
||||
HorizontalSeparator
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: next.top
|
||||
margin-bottom: 5
|
||||
margin-top: 5
|
||||
|
||||
Button
|
||||
id: randomizeButton
|
||||
!text: tr('Randomize')
|
||||
!tooltip: tr('Randomize characters outfit')
|
||||
width: 75
|
||||
anchors.left: prev.left
|
||||
anchors.top: prev.bottom
|
||||
margin-right: 16
|
||||
@onClick: modules.game_outfit.randomize()
|
||||
|
||||
HorizontalSeparator
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: next.top
|
||||
margin-bottom: 10
|
||||
margin-top: 5
|
||||
anchors.bottom: parent.bottom
|
||||
@onClick: modules.game_outfit.randomize()
|
||||
|
||||
Button
|
||||
id: outfitOkButton
|
||||
|
@@ -4,9 +4,11 @@ questLineWindow = nil
|
||||
function init()
|
||||
g_ui.importStyle('questlogwindow')
|
||||
g_ui.importStyle('questlinewindow')
|
||||
|
||||
questLogButton = modules.client_topmenu.addLeftGameButton('questLogButton', tr('Quest Log'), '/images/topbuttons/questlog', function() g_game.requestQuestLog() end, false, 8)
|
||||
|
||||
|
||||
if not g_app.isMobile() then
|
||||
questLogButton = modules.client_topmenu.addLeftGameButton('questLogButton', tr('Quest Log'), '/images/topbuttons/questlog', function() g_game.requestQuestLog() end, false, 8)
|
||||
end
|
||||
|
||||
connect(g_game, { onQuestLog = onGameQuestLog,
|
||||
onQuestLine = onGameQuestLine,
|
||||
onGameEnd = destroyWindows})
|
||||
@@ -18,7 +20,9 @@ function terminate()
|
||||
onGameEnd = destroyWindows})
|
||||
|
||||
destroyWindows()
|
||||
questLogButton:destroy()
|
||||
if questLogButton then
|
||||
questLogButton:destroy()
|
||||
end
|
||||
end
|
||||
|
||||
function destroyWindows()
|
||||
|
@@ -23,6 +23,8 @@ QuestLogWindow < MainWindow
|
||||
!text: tr('Quest Log')
|
||||
size: 500 400
|
||||
@onEscape: self:destroy()
|
||||
$mobile:
|
||||
size: 500 350
|
||||
|
||||
TextList
|
||||
id: questList
|
||||
|
@@ -530,8 +530,8 @@ function buyConfirmed()
|
||||
sendAction("buy", selectedOffer)
|
||||
if g_game.getFeature(GameIngameStore) and selectedOffer.id and not otcv8shop then
|
||||
local offerName = selectedOffer.title:lower()
|
||||
if string.find(offerName, "name") and string.find(offerName, "change") and modules.game_textedit then
|
||||
modules.game_textedit.singlelineEditor("", function(newName)
|
||||
if string.find(offerName, "name") and string.find(offerName, "change") and modules.client_textedit then
|
||||
modules.client_textedit.singlelineEditor("", function(newName)
|
||||
if newName:len() == 0 then
|
||||
return
|
||||
end
|
||||
|
@@ -39,7 +39,6 @@ ShopCategoryCreature < ShopCategory
|
||||
margin-bottom: 2
|
||||
margin-left: 2
|
||||
size: 32 32
|
||||
raw: true
|
||||
|
||||
ShopCategoryImage < ShopCategory
|
||||
Label
|
||||
@@ -118,7 +117,6 @@ ShopOfferCreature < ShopOffer
|
||||
margin-bottom: 4
|
||||
margin-left: 2
|
||||
size: 48 48
|
||||
raw: true
|
||||
|
||||
ShopOfferImage < ShopOffer
|
||||
Label
|
||||
|
@@ -375,8 +375,11 @@ function walk(dir, ticks)
|
||||
elseif player:isServerWalking() then
|
||||
g_game.stop()
|
||||
return
|
||||
elseif not toTile then
|
||||
player:lockWalk(100) -- bug fix for missing stairs down on map
|
||||
return
|
||||
else
|
||||
player:lockWalk(200)
|
||||
return -- not walkable tile
|
||||
end
|
||||
end
|
||||
|
||||
|
@@ -4,10 +4,6 @@ Module
|
||||
author: OTClient team
|
||||
website: https://github.com/edubart/otclient
|
||||
|
||||
dependencies:
|
||||
- game_features
|
||||
- game_things
|
||||
|
||||
@onLoad: |
|
||||
dofile 'const'
|
||||
dofile 'util'
|
||||
|
@@ -86,6 +86,13 @@ function ProtocolLogin:sendLoginPacket()
|
||||
if self.getLoginExtendedData then
|
||||
local data = self:getLoginExtendedData()
|
||||
msg:addString(data)
|
||||
else
|
||||
msg:addString("OTCv8")
|
||||
local version = g_app.getVersion():split(" ")[1]:gsub("%.", "")
|
||||
if version:len() == 2 then
|
||||
version = version .. "0"
|
||||
end
|
||||
msg:addU16(tonumber(version))
|
||||
end
|
||||
|
||||
local paddingBytes = g_crypt.rsaGetSize() - (msg:getMessageSize() - offset)
|
||||
|
@@ -26,8 +26,11 @@ function UIItem:onDrop(widget, mousePos, forced)
|
||||
if not item or not item:isItem() then return false end
|
||||
|
||||
if self.selectable then
|
||||
self:setItem(Item.create(item:getId(), item:getCountOrSubType()))
|
||||
return
|
||||
if item:isPickupable() then
|
||||
self:setItem(Item.create(item:getId(), item:getCountOrSubType()))
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local toPos = self.position
|
||||
@@ -85,7 +88,7 @@ function UIItem:onMouseRelease(mousePosition, mouseButton)
|
||||
local item = self:getItem()
|
||||
if not item or not self:containsPoint(mousePosition) then return false end
|
||||
|
||||
if modules.client_options.getOption('classicControl') and
|
||||
if modules.client_options.getOption('classicControl') and not g_app.isMobile() and
|
||||
((g_mouse.isPressed(MouseLeftButton) and mouseButton == MouseRightButton) or
|
||||
(g_mouse.isPressed(MouseRightButton) and mouseButton == MouseLeftButton)) then
|
||||
g_game.look(item)
|
||||
|
276
modules/updater/updater.lua
Normal file
276
modules/updater/updater.lua
Normal file
@@ -0,0 +1,276 @@
|
||||
Updater = { }
|
||||
|
||||
Updater.maxRetries = 5
|
||||
|
||||
--[[
|
||||
|
||||
]]--
|
||||
|
||||
local updaterWindow
|
||||
local loadModulesFunction
|
||||
local scheduledEvent
|
||||
local httpOperationId = 0
|
||||
|
||||
local function onLog(level, message, time)
|
||||
if level == LogError then
|
||||
Updater.error(message)
|
||||
g_logger.setOnLog(nil)
|
||||
end
|
||||
end
|
||||
|
||||
local function initAppWindow()
|
||||
if g_resources.getLayout() == "mobile" then
|
||||
g_window.setMinimumSize({ width = 640, height = 360 })
|
||||
else
|
||||
g_window.setMinimumSize({ width = 800, height = 640 })
|
||||
end
|
||||
|
||||
-- window size
|
||||
local size = { width = 1024, height = 600 }
|
||||
size = g_settings.getSize('window-size', size)
|
||||
g_window.resize(size)
|
||||
|
||||
-- window position, default is the screen center
|
||||
local displaySize = g_window.getDisplaySize()
|
||||
local defaultPos = { x = (displaySize.width - size.width)/2,
|
||||
y = (displaySize.height - size.height)/2 }
|
||||
local pos = g_settings.getPoint('window-pos', defaultPos)
|
||||
pos.x = math.max(pos.x, 0)
|
||||
pos.y = math.max(pos.y, 0)
|
||||
g_window.move(pos)
|
||||
|
||||
-- window maximized?
|
||||
local maximized = g_settings.getBoolean('window-maximized', false)
|
||||
if maximized then g_window.maximize() end
|
||||
|
||||
g_window.setTitle(g_app.getName())
|
||||
g_window.setIcon('/images/clienticon')
|
||||
|
||||
if g_app.isMobile() then
|
||||
scheduleEvent(function()
|
||||
g_app.scale(5.0)
|
||||
end, 10)
|
||||
end
|
||||
end
|
||||
|
||||
local function loadModules()
|
||||
if loadModulesFunction then
|
||||
local tmpLoadFunc = loadModulesFunction
|
||||
loadModulesFunction = nil
|
||||
tmpLoadFunc()
|
||||
end
|
||||
end
|
||||
|
||||
local function downloadFiles(url, files, index, retries, doneCallback)
|
||||
local entry = files[index]
|
||||
if not entry then -- finished
|
||||
return doneCallback()
|
||||
end
|
||||
local file = entry[1]
|
||||
local file_checksum = entry[2]
|
||||
|
||||
if retries > 0 then
|
||||
updaterWindow.downloadStatus:setText(tr("Downloading (%i retry):\n%s", retries, file))
|
||||
else
|
||||
updaterWindow.downloadStatus:setText(tr("Downloading:\n%s", file))
|
||||
end
|
||||
updaterWindow.downloadProgress:setPercent(0)
|
||||
updaterWindow.mainProgress:setPercent(math.floor(100 * index / #files))
|
||||
|
||||
httpOperationId = HTTP.download(url .. file, file,
|
||||
function (file, checksum, err)
|
||||
if not err and checksum ~= file_checksum then
|
||||
err = "Invalid checksum of: " .. file .. ".\nShould be " .. file_checksum .. ", is: " .. checksum
|
||||
end
|
||||
if err then
|
||||
if retries >= Updater.maxRetries then
|
||||
Updater.error("Can't download file: " .. file .. ".\nError: " .. err)
|
||||
else
|
||||
scheduledEvent = scheduleEvent(function()
|
||||
downloadFiles(url, files, index, retries + 1, doneCallback)
|
||||
end, 250)
|
||||
end
|
||||
return
|
||||
end
|
||||
downloadFiles(url, files, index + 1, 0, doneCallback)
|
||||
end,
|
||||
function (progress, speed)
|
||||
updaterWindow.downloadProgress:setPercent(progress)
|
||||
updaterWindow.downloadProgress:setText(speed .. " kbps")
|
||||
end)
|
||||
end
|
||||
|
||||
local function updateFiles(data, keepCurrentFiles)
|
||||
if type(data) ~= "table" then
|
||||
return Updater.error("Invalid data from updater api (not table)")
|
||||
end
|
||||
if type(data["error"]) == 'string' and data["error"]:len() > 0 then
|
||||
return Updater.error(data["error"])
|
||||
end
|
||||
if not data["files"] or type(data["url"]) ~= 'string' or data["url"]:len() < 4 then
|
||||
return Updater.error("Invalid data from updater api: " .. json.encode(data, 2))
|
||||
end
|
||||
if data["keepFiles"] then
|
||||
keepCurrentFiles = true
|
||||
end
|
||||
|
||||
local newFiles = false
|
||||
local finalFiles = {}
|
||||
local localFiles = g_resources.filesChecksums()
|
||||
local toUpdate = {}
|
||||
-- keep all files or files from data/things
|
||||
for file, checksum in pairs(localFiles) do
|
||||
if keepCurrentFiles or string.find(file, "data/things") then
|
||||
table.insert(finalFiles, file)
|
||||
end
|
||||
end
|
||||
-- update files
|
||||
for file, checksum in pairs(data["files"]) do
|
||||
table.insert(finalFiles, file)
|
||||
if not localFiles[file] or localFiles[file] ~= checksum then
|
||||
table.insert(toUpdate, {file, checksum})
|
||||
newFiles = true
|
||||
end
|
||||
end
|
||||
-- update binary
|
||||
local binary = nil
|
||||
if type(data["binary"]) == "table" and data["binary"]["file"]:len() > 1 then
|
||||
local selfChecksum = g_resources.selfChecksum()
|
||||
if selfChecksum:len() > 0 and selfChecksum ~= data["binary"]["checksum"] then
|
||||
binary = data["binary"]["file"]
|
||||
table.insert(toUpdate, {binary, data["binary"]["checksum"]})
|
||||
end
|
||||
end
|
||||
|
||||
if #toUpdate == 0 then -- nothing to update
|
||||
updaterWindow.mainProgress:setPercent(100)
|
||||
scheduledEvent = scheduleEvent(Updater.abort, 20)
|
||||
return
|
||||
end
|
||||
|
||||
-- update of some files require full client restart
|
||||
local forceRestart = false
|
||||
local reloadModules = false
|
||||
local forceRestartPattern = {"init.lua", "corelib", "updater", "otmod"}
|
||||
for _, file in ipairs(toUpdate) do
|
||||
for __, pattern in ipairs(forceRestartPattern) do
|
||||
if string.find(file[1], pattern) then
|
||||
forceRestart = true
|
||||
end
|
||||
if not string.find(file[1], "data/things") then
|
||||
reloadModules = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
updaterWindow.status:setText(tr("Updating %i files", #toUpdate))
|
||||
updaterWindow.mainProgress:setPercent(0)
|
||||
updaterWindow.downloadProgress:setPercent(0)
|
||||
updaterWindow.downloadProgress:show()
|
||||
updaterWindow.downloadStatus:show()
|
||||
updaterWindow.changeUrlButton:hide()
|
||||
downloadFiles(data["url"], toUpdate, 1, 0, function()
|
||||
updaterWindow.status:setText(tr("Updating client (may take few seconds)"))
|
||||
updaterWindow.mainProgress:setPercent(100)
|
||||
updaterWindow.downloadProgress:hide()
|
||||
updaterWindow.downloadStatus:hide()
|
||||
scheduledEvent = scheduleEvent(function()
|
||||
local restart = binary or (not loadModulesFunction and reloadModules) or forceRestart
|
||||
if newFiles then
|
||||
g_resources.updateData(finalFiles, not restart)
|
||||
end
|
||||
if binary then
|
||||
g_resources.updateExecutable(binary)
|
||||
end
|
||||
if restart then
|
||||
g_app.restart()
|
||||
else
|
||||
if reloadModules then
|
||||
g_modules.reloadModules()
|
||||
end
|
||||
Updater.abort()
|
||||
end
|
||||
end, 100)
|
||||
end)
|
||||
end
|
||||
|
||||
-- public functions
|
||||
function Updater.init(loadModulesFunc)
|
||||
g_logger.setOnLog(onLog)
|
||||
loadModulesFunction = loadModulesFunc
|
||||
initAppWindow()
|
||||
Updater.check()
|
||||
end
|
||||
|
||||
function Updater.terminate()
|
||||
loadModulesFunction = nil
|
||||
Updater.abort()
|
||||
end
|
||||
|
||||
function Updater.abort()
|
||||
HTTP.cancel(httpOperationId)
|
||||
removeEvent(scheduledEvent)
|
||||
if updaterWindow then
|
||||
updaterWindow:destroy()
|
||||
updaterWindow = nil
|
||||
end
|
||||
loadModules()
|
||||
end
|
||||
|
||||
function Updater.check(args)
|
||||
if updaterWindow then return end
|
||||
|
||||
updaterWindow = g_ui.displayUI('updater')
|
||||
updaterWindow:show()
|
||||
updaterWindow:focus()
|
||||
updaterWindow:raise()
|
||||
|
||||
local updateData = nil
|
||||
local function progressUpdater(value)
|
||||
removeEvent(scheduledEvent)
|
||||
if value == 100 then
|
||||
return Updater.error(tr("Timeout"))
|
||||
end
|
||||
if updateData and (value > 60 or (not g_app.isMobile() or not ALLOW_CUSTOM_SERVERS or not loadModulesFunc)) then -- gives 3s to set custom updater for mobile version
|
||||
return updateFiles(updateData)
|
||||
end
|
||||
scheduledEvent = scheduleEvent(function() progressUpdater(value + 1) end, 50)
|
||||
updaterWindow.mainProgress:setPercent(value)
|
||||
end
|
||||
progressUpdater(0)
|
||||
|
||||
httpOperationId = HTTP.postJSON(Services.updater, {
|
||||
version = APP_VERSION,
|
||||
build = g_app.getVersion(),
|
||||
os = g_app.getOs(),
|
||||
platform = g_window.getPlatformType(),
|
||||
args = args or {}
|
||||
}, function(data, err)
|
||||
if err then
|
||||
return Updater.error(err)
|
||||
end
|
||||
updateData = data
|
||||
end)
|
||||
end
|
||||
|
||||
function Updater.error(message)
|
||||
removeEvent(scheduledEvent)
|
||||
if not updaterWindow then return end
|
||||
displayErrorBox(tr("Updater Error"), message).onOk = function()
|
||||
Updater.abort()
|
||||
end
|
||||
end
|
||||
|
||||
function Updater.changeUrl()
|
||||
removeEvent(scheduledEvent)
|
||||
modules.client_textedit.edit(Services.updater, {title="Enter updater url", width=300}, function(newUrl)
|
||||
if updaterWindow and newUrl:len() > 4 then
|
||||
Services.updater = newUrl
|
||||
end
|
||||
if updaterWindow then
|
||||
updaterWindow:destroy()
|
||||
updaterWindow = nil
|
||||
end
|
||||
Updater.check()
|
||||
end)
|
||||
end
|
@@ -1,9 +1,12 @@
|
||||
Module
|
||||
name: client_updater
|
||||
name: updater
|
||||
description: Updates client
|
||||
author: otclient@otclient.ovh
|
||||
website: otclient.ovh
|
||||
reloadable: false
|
||||
scripts: [ updater ]
|
||||
@onLoad: Updater.init()
|
||||
dependencies: [ client_locales, client_styles, client_textedit ]
|
||||
@onUnload: Updater.terminate()
|
||||
|
||||
load-later:
|
||||
- client_background
|
53
modules/updater/updater.otui
Normal file
53
modules/updater/updater.otui
Normal file
@@ -0,0 +1,53 @@
|
||||
StaticMainWindow
|
||||
id: updaterWindow
|
||||
!text: tr('Updater')
|
||||
width: 350
|
||||
|
||||
layout:
|
||||
type: verticalBox
|
||||
fit-children: true
|
||||
|
||||
Label
|
||||
id: status
|
||||
!text: tr('Checking for updates')
|
||||
text-align: center
|
||||
|
||||
ProgressBar
|
||||
id: mainProgress
|
||||
height: 15
|
||||
background-color: #4444ff
|
||||
margin-bottom: 5
|
||||
margin-top: 5
|
||||
|
||||
Label
|
||||
id: downloadStatus
|
||||
text-align: center
|
||||
text-auto-resize: true
|
||||
text-wrap: true
|
||||
visible: false
|
||||
|
||||
ProgressBar
|
||||
id: downloadProgress
|
||||
height: 15
|
||||
background-color: #4444ff
|
||||
margin-bottom: 5
|
||||
margin-top: 5
|
||||
visible: false
|
||||
|
||||
Button
|
||||
id: changeUrlButton
|
||||
margin-left: 50
|
||||
margin-right: 50
|
||||
margin-top: 5
|
||||
margin-bottom: 10
|
||||
!text: tr('Change updater URL')
|
||||
@onClick: Updater.changeUrl()
|
||||
$!mobile:
|
||||
visible: false
|
||||
|
||||
Button
|
||||
margin-left: 90
|
||||
margin-right: 90
|
||||
!text: tr('Cancel')
|
||||
@onClick: Updater.abort()
|
||||
|
Reference in New Issue
Block a user