Version 2.1 - imbuements, wrap/unwrap, 4 byte header, packet compression and other features

This commit is contained in:
OTCv8 2020-03-13 23:35:44 +01:00
parent dbfad99ca4
commit b58076a675
36 changed files with 1740 additions and 251 deletions

View File

@ -1,26 +1,27 @@
local entergameWindow
local characterGroup
local outfitGroup
local protocol
local infoBox
local loadingBox
function init()
if not USE_NEW_ENERGAME then return end
entergameWindow = g_ui.displayUI('entergamev2')
--entergameWindow.news:hide()
--entergameWindow.quick:hide()
entergameWindow.news:hide()
entergameWindow.quick:hide()
entergameWindow.registration:hide()
entergameWindow.characters:hide()
entergameWindow.createcharacter:hide()
entergameWindow.settings:hide()
-- entergame
entergameWindow.entergame.register.onClick = function()
entergameWindow.registration:show()
entergameWindow.entergame:hide()
end
entergameWindow.entergame.mainPanel.button.onClick = function()
entergameWindow.entergame:hide()
entergameWindow.characters:show()
g_game.setClientVersion(1099) -- for tests
end
entergameWindow.entergame.mainPanel.button.onClick = login
-- registration
entergameWindow.registration.back.onClick = function()
@ -29,44 +30,455 @@ function init()
end
-- characters
--- outfits
entergameWindow.characters.mainPanel.showOutfits.onClick = function()
local status = not (entergameWindow.characters.mainPanel.showOutfits:isOn())
g_settings.set('showOutfits', status)
entergameWindow.characters.mainPanel.showOutfits:setOn(status)
if status then
entergameWindow.characters.mainPanel.outfitsPanel:show()
entergameWindow.characters.mainPanel.outfitsScroll:show()
entergameWindow.characters.mainPanel.charactersPanel:hide()
entergameWindow.characters.mainPanel.charactersScroll:hide()
else
entergameWindow.characters.mainPanel.outfitsPanel:hide()
entergameWindow.characters.mainPanel.outfitsScroll:hide()
entergameWindow.characters.mainPanel.charactersPanel:show()
entergameWindow.characters.mainPanel.charactersScroll:show()
end
end
local showOutfits = g_settings.getBoolean("showOutfits", false)
entergameWindow.characters.mainPanel.showOutfits:setOn(showOutfits)
if showOutfits then
entergameWindow.characters.mainPanel.charactersPanel:hide()
entergameWindow.characters.mainPanel.charactersScroll:hide()
else
entergameWindow.characters.mainPanel.outfitsPanel:hide()
entergameWindow.characters.mainPanel.outfitsScroll:hide()
end
--- auto reconnect
entergameWindow.characters.mainPanel.autoReconnect.onClick = function()
local status = (not entergameWindow.characters.mainPanel.autoReconnect:isOn())
g_settings.set('autoReconnect', status)
entergameWindow.characters.mainPanel.autoReconnect:setOn(status)
end
local autoReconnect = g_settings.getBoolean("autoReconnect", true)
entergameWindow.characters.mainPanel.autoReconnect:setOn(autoReconnect)
--- buttons
entergameWindow.characters.logout.onClick = function()
protocol:logout()
entergameWindow.characters:hide()
entergameWindow.entergame:show()
entergameWindow.entergame.mainPanel.account:setText("")
entergameWindow.entergame.mainPanel.password:setText("")
end
entergameWindow.characters.createcharacter.onClick = function()
entergameWindow.characters:hide()
entergameWindow.createcharacter:show()
entergameWindow.createcharacter.mainPanel.name:setText("")
end
entergameWindow.characters.mainPanel.autoReconnect.onClick = function()
entergameWindow.characters.mainPanel.autoReconnect:setOn(not entergameWindow.characters.mainPanel.autoReconnect:isOn())
end
entergameWindow.characters.settings.onClick = function()
entergameWindow.characters:hide()
entergameWindow.settings:show()
end
-- create character
entergameWindow.createcharacter.back.onClick = function()
entergameWindow.createcharacter:hide()
entergameWindow.characters:show()
end
-- tests
characterGroup = UIRadioGroup.create()
for i=1,20 do
local character = g_ui.createWidget('EntergameCharacter', entergameWindow.characters.mainPanel.charactersPanel)
characterGroup:addWidget(character)
character.outfit:setOutfit({feet=10,legs=10,body=176,type=129,auxType=0,addons=3,head=48})
entergameWindow.createcharacter.mainPanel.createButton.onClick = createcharacter
entergameWindow.settings.back.onClick = function()
entergameWindow.settings:hide()
entergameWindow.characters:show()
end
characterGroup:selectWidget(entergameWindow.characters.mainPanel.charactersPanel:getFirstChild())
characterGroup:getSelectedWidget()
entergameWindow.settings.mainPanel.updateButton.onClick = updateSettings
for i=1,100 do
local l = g_ui.createWidget("NewsLabel", entergameWindow.news.content)
l:setText("test xxx ssss eeee uu u llel " .. i)
-- pick server
local server = nil
if type(Servers) == "table" then
for name, url in pairs(Servers) do
server = url
end
elseif type(Servers) == "string" then
server = Servers
elseif type(Server) == "string" then
server = Server
end
if not server then
message("Configuration error", "You must set server url in init.lua!\nExample:\nServer = \"ws://otclient.ovh:8000\"")
return
end
-- init protocol
-- token is random string
local session = g_crypt.sha1Encode("" .. math.random() .. g_clock.realMicros() .. tostring(G.UUID) .. g_platform.getCPUName() .. g_platform.getProcessId())
protocol = EnterGameV2Protocol.new(session)
if not protocol:setUrl(server) then
return message("Configuration error", "Invalid url for entergamev2:\n" .. server)
end
protocol.onLogin = onLogin
protocol.onLogout = logout
protocol.onMessage = serverMessage
protocol.onLoading = showLoading
protocol.onQAuth = updateQAuth
protocol.onCharacters = updateCharacters
protocol.onNews = updateNews
protocol.onMotd = updateMotd
protocol.onCharacterCreate = onCharacterCreate
-- game stuff
connect(g_game, { onLoginError = onLoginError,
onLoginToken = onLoginToken ,
onUpdateNeeded = onUpdateNeeded,
onConnectionError = onConnectionError,
onGameStart = onGameStart,
onGameEnd = onGameEnd,
onLoginWait = onLoginWait,
onLogout = onLogout
})
if g_game.isOnline() then
onGameStart()
end
end
function terminate()
if not USE_NEW_ENERGAME then return end
if protocol then
protocol:destroy()
protocol = nil
end
if infoBox then
infoBox:destroy()
infoBox = nil
end
if loadingBox then
loadingBox:destroy()
loadingBox = nil
end
if characterGroup then
characterGroup:destroy()
characterGroup = nil
end
if outfitGroup then
outfitGroup:destroy()
outfitGroup = nil
end
entergameWindow:destroy()
entergameWindow = nil
disconnect(g_game, { onLoginError = onLoginError,
onLoginToken = onLoginToken ,
onUpdateNeeded = onUpdateNeeded,
onConnectionError = onConnectionError,
onGameStart = onGameStart,
onGameEnd = onGameEnd,
onLoginWait = onLoginWait,
onLogout = onLogout
})
end
function show()
end
function hide()
end
function message(title, text)
if infoBox then
infoBox:destroy()
end
infoBox = displayInfoBox(title, text)
infoBox.onDestroy = function(widget)
if widget == infoBox then
infoBox = nil
end
end
infoBox:show()
infoBox:raise()
infoBox:focus()
end
function showLoading(titie, text)
if loadingBox then
loadingBox:destroy()
end
local callback = function() end -- do nothing
loadingBox = displayGeneralBox(titie, text, {}, callback, callback)
loadingBox.onDestroy = function(widget)
if widget == loadingBox then
loadingBox = nil
end
end
loadingBox:show()
loadingBox:raise()
loadingBox:focus()
end
function serverMessage(title, text)
return message(title, text)
end
function updateCharacters(characters)
if outfitGroup then
outfitGroup:destroy()
end
if characterGroup then
characterGroup:destroy()
end
entergameWindow.characters.mainPanel.charactersPanel:destroyChildren()
entergameWindow.characters.mainPanel.outfitsPanel:destroyChildren()
outfitGroup = UIRadioGroup.create()
characterGroup = UIRadioGroup.create()
for i, character in ipairs(characters) do
local characterWidget = g_ui.createWidget('EntergameCharacter', entergameWindow.characters.mainPanel.charactersPanel)
characterGroup:addWidget(characterWidget)
local outfitWidget = g_ui.createWidget('EntergameBigCharacter', entergameWindow.characters.mainPanel.outfitsPanel)
outfitGroup:addWidget(outfitWidget)
for i, widget in ipairs({characterWidget, outfitWidget}) do
widget.character = character
widget.outfit:setOutfit(character["outfit"])
widget.line1:setText(character["line1"])
widget.line2:setText(character["line2"])
widget.line3:setText(character["line3"])
end
end
if #characters > 1 then
characterGroup:selectWidget(entergameWindow.characters.mainPanel.charactersPanel:getFirstChild())
outfitGroup:selectWidget(entergameWindow.characters.mainPanel.outfitsPanel:getFirstChild())
end
end
function updateQAuth(token)
if not token or token:len() == 0 then
return entergameWindow.quick:hide()
end
entergameWindow.quick:show()
entergameWindow.quick.qrcode:setQRCode(token, 1)
entergameWindow.quick.qrcode.onClick = function()
g_platform.openUrl(token)
end
entergameWindow.quick.quathlogo.onClick = entergameWindow.quick.qrcode.onClick
end
function updateNews(news)
if not news or #news == 0 then
return entergameWindow.news:hide()
end
entergameWindow.news:show()
entergameWindow.news.content:destroyChildren()
for i, entry in ipairs(news) do
local title = entry["title"]
local text = entry["text"]
local image = entry["image"]
if title then
local newsLabel = g_ui.createWidget('NewsLabel', entergameWindow.news.content)
newsLabel:setText(title)
end
if text ~= nil then
local newsText = g_ui.createWidget('NewsText', entergameWindow.news.content)
newsText:setText(text)
end
end
end
function updateMotd(text)
if not text or text:len() == 0 then
return entergameWindow.characters.mainPanel.motd:hide()
end
entergameWindow.characters.mainPanel.motd:show()
entergameWindow.characters.mainPanel.motd:setText(text)
end
function login()
local account = entergameWindow.entergame.mainPanel.account:getText()
local password = entergameWindow.entergame.mainPanel.password:getText()
entergameWindow.entergame:hide()
showLoading("Login", "Connecting to server...")
protocol:login(account, password, "")
end
function onLogin(data)
if loadingBox then
loadingBox:destroy()
loadingBox = nil
end
if data["error"] and data["error"]:len() > 0 then
entergameWindow.entergame:show()
return message("Login error", data["error"])
end
local incorrectThings = validateThings(data["things"])
if incorrectThings:len() > 0 then
entergameWindow.entergame:show()
return message("Login error - missing things", incorrectThings)
end
if infoBox then
infoBox:destroy()
end
local version = data["version"]
G.clientVersion = version
g_game.setClientVersion(version)
g_game.setProtocolVersion(g_game.getClientProtocolVersion(version))
g_game.setCustomOs(-1) -- disable custom os
local customProtocol = data["customProtocol"]
g_game.setCustomProtocolVersion(0)
if type(customProtocol) == 'number' then
g_game.setCustomProtocolVersion(customProtocol)
end
local email = data["email"]
local security = data["security"]
entergameWindow.settings.mainPanel.email:setText(email)
entergameWindow.settings.mainPanel.security:setCurrentIndex(math.max(1, security))
entergameWindow.characters:show()
entergameWindow.entergame:hide()
end
function logout()
if not entergameWindow.characters:isVisible() and not entergameWindow.createcharacter:isVisible() then
return
end
entergameWindow.characters:hide()
entergameWindow.createcharacter:hide()
entergameWindow.entergame:show()
message("Information", "Session expired, you has been logged out.")
end
function validateThings(things)
local incorrectThings = ""
local missingFiles = false
local versionForMissingFiles = 0
if things ~= nil then
local thingsNode = {}
for thingtype, thingdata in pairs(things) do
thingsNode[thingtype] = thingdata[1]
if not g_resources.fileExists("/data/things/" .. thingdata[1]) then
incorrectThings = incorrectThings .. "Missing file: " .. thingdata[1] .. "\n"
missingFiles = true
versionForMissingFiles = thingdata[1]:split("/")[1]
else
local localChecksum = g_resources.fileChecksum("/data/things/" .. thingdata[1]):lower()
if localChecksum ~= thingdata[2]:lower() and #thingdata[2] > 1 then
if g_resources.isLoadedFromArchive() then -- ignore checksum if it's test/debug version
incorrectThings = incorrectThings .. "Invalid checksum of file: " .. thingdata[1] .. " (is " .. localChecksum .. ", should be " .. thingdata[2]:lower() .. ")\n"
end
end
end
end
g_settings.setNode("things", thingsNode)
else
g_settings.setNode("things", {})
end
if missingFiles then
incorrectThings = incorrectThings .. "\nYou should open data/things and create directory " .. versionForMissingFiles ..
".\nIn this directory (data/things/" .. versionForMissingFiles .. ") you should put missing\nfiles (Tibia.dat and Tibia.spr) " ..
"from correct Tibia version."
end
return incorrectThings
end
function doGameLogin()
local selected = nil
if entergameWindow.characters.mainPanel.charactersPanel:isVisible() then
selected = characterGroup:getSelectedWidget()
else
selected = outfitGroup:getSelectedWidget()
end
if not selected then
return message("Entergame error", "Please select character")
end
local character = selected.character
if not g_game.getFeature(GameSessionKey) then
g_game.enableFeature(GameSessionKey)
end
g_game.loginWorld("", "", character.worldName, character.worldHost, character.worldPort, character.name, "", protocol.session)
end
function onLoginError(err)
message("Login error", err)
end
function onLoginToken()
end
function onUpdateNeeded(signature)
end
function onConnectionError(message, code)
end
function onGameStart()
entergameWindow:hide()
end
function onGameEnd()
entergameWindow:show()
end
function onLoginWait(message, time)
end
function onLogout()
end
function createcharacter()
local name = entergameWindow.createcharacter.mainPanel.name:getText()
local gender = entergameWindow.createcharacter.mainPanel.gender:getCurrentOption().text
local vocation = entergameWindow.createcharacter.mainPanel.vocation:getCurrentOption().text
local town = entergameWindow.createcharacter.mainPanel.town:getCurrentOption().text
if name:len() < 3 or name:len() > 20 then
return message("Error", "Invalid character name")
end
protocol:createCharacter(name, gender, vocation, town)
showLoading("Creating character", "Creating new character...")
end
function onCharacterCreate(err, msg)
if loadingBox then
loadingBox:destroy()
loadingBox = nil
end
if err then
return message("Error", err)
end
message("Success", msg)
entergameWindow.createcharacter:hide()
entergameWindow.characters:show()
end
function updateSettings()
local email = entergameWindow.settings.mainPanel.email:getText()
local security = entergameWindow.settings.mainPanel.security.currentIndex
protocol:updateSettings({
email=email,
security=security
})
entergameWindow.settings:hide()
entergameWindow.characters:show()
end

View File

@ -4,7 +4,7 @@ Module
author: otclient.ovh
website: http://otclient.ovh
sandboxed: true
scripts: [ entergamev2 ]
scripts: [ protocol, entergamev2 ]
@onLoad: init()
@onUnload: terminate()

View File

@ -60,7 +60,7 @@ EnterGame < Panel
id: mainPanel
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
size: 250 210
size: 230 210
!text: tr("Enter Game")
padding-top: 36
padding-left: 16
@ -130,7 +130,7 @@ EnterGame < Panel
anchors.horizontalCenter: prev.horizontalCenter
anchors.top: prev.bottom
margin-top: 20
size: 200 50
size: 290 50
image-source: /images/ui/window_headless
image-border: 6
padding-top: 8
@ -138,9 +138,18 @@ EnterGame < Panel
Button
id: register
anchors.verticalCenter: buttons.verticalCenter
anchors.horizontalCenter: buttons.horizontalCenter
anchors.left: buttons.left
margin-left: 10
!text: tr("Create account")
size: 160 30
size: 130 30
Button
id: register
anchors.verticalCenter: buttons.verticalCenter
anchors.right: buttons.right
margin-right: 10
!text: tr("Casts")
size: 130 30
Registration < Panel
anchors.fill: parent
@ -226,7 +235,6 @@ Registration < Panel
margin-top: 10
margin-left: 50
margin-right: 50
@onClick: EnterGame.doLogin()
EnterGamePanel
id: buttons
@ -261,7 +269,6 @@ QuickLogin < EnterGamePanel
image-fixed-ratio: true
image-smooth: false
margin-top: 5
qr: 123
UIButton
id: quathlogo
@ -319,7 +326,6 @@ Characters < Panel
anchors.right: parent.right
text-auto-resize: true
text-wrap: true
text: This is motd ;)
text-align: center
HorizontalSeparator
@ -328,22 +334,45 @@ Characters < Panel
anchors.right: parent.right
anchors.top: prev.bottom
margin-top: 5
height: 10
ScrollablePanel
id: charactersPanel
id: outfitsPanel
layout:
type: grid
cell-size: 100 100
cell-size: 125 125
cell-spacing: 1
flow: true
vertical-scrollbar: charactersScroll
vertical-scrollbar: outfitsScroll
background-color: #444444
anchors.top: prev.bottom
anchors.bottom: bottomSeparator.top
anchors.left: parent.left
anchors.right: parent.right
margin-bottom: 10
margin-right: 12
margin-bottom: 5
margin-top: 5
VerticalScrollBar
id: outfitsScroll
anchors.top: outfitsPanel.top
anchors.bottom: outfitsPanel.bottom
anchors.left: outfitsPanel.right
step: 14
pixels-scroll: true
ScrollablePanel
id: charactersPanel
layout:
type: verticalBox
vertical-scrollbar: charactersScroll
background-color: #444444
anchors.top: motdSeparator.bottom
anchors.bottom: bottomSeparator.top
anchors.left: parent.left
anchors.right: parent.right
margin-right: 12
margin-bottom: 5
margin-top: 5
VerticalScrollBar
id: charactersScroll
@ -351,14 +380,14 @@ Characters < Panel
anchors.bottom: charactersPanel.bottom
anchors.left: charactersPanel.right
step: 14
pixels-scroll: true
pixels-scroll: true
HorizontalSeparator
id: bottomSeparator
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 35
margin-bottom: 30
Button
id: autoReconnect
@ -370,7 +399,7 @@ Characters < Panel
$!on:
image-color: red
!text: tr('Auto reconnect: Off')
Button
id: showOutfits
anchors.horizontalCenter: parent.horizontalCenter
@ -378,10 +407,10 @@ Characters < Panel
width: 140
$on:
!text: tr("Show outfits")
!text: tr("Hide big outfits")
$!on:
!text: tr("Hide outfits")
!text: tr("Show big outfits")
Button
id: connect
@ -389,6 +418,7 @@ Characters < Panel
anchors.bottom: parent.bottom
!text: tr("Connect")
width: 140
@onClick: modules.client_entergamev2.doGameLogin()
EnterGamePanel
id: buttons
@ -409,10 +439,10 @@ Characters < Panel
size: 140 30
Button
id: casts
id: settings
anchors.verticalCenter: buttons.verticalCenter
anchors.horizontalCenter: buttons.horizontalCenter
!text: tr("Casts")
!text: tr("Account settings")
size: 140 30
Button
@ -423,10 +453,11 @@ Characters < Panel
!text: tr("Logout")
size: 140 30
EntergameCharacter < UIButton
EntergameBigCharacter < UIButton
border-width: 1
padding: 1 1 1 1
@onClick: self:setChecked(true)
@onDoubleClick: modules.client_entergamev2.doGameLogin()
$checked:
border-color: #ffffff
@ -438,7 +469,7 @@ EntergameCharacter < UIButton
id: outfit
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
size: 48 48
size: 70 70
margin-bottom: 3
phantom: true
@ -447,39 +478,99 @@ EntergameCharacter < UIButton
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
text: Dagusia Druid
text-align: center
text-wrap: false
height: 16
font: terminus-10px
border-width-bottom: 1
border-color: #00000077
phantom: true
Label
id: line2
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
text: Level: 666
text-align: center
text-wrap: false
height: 16
font: terminus-10px
border-width-bottom: 1
border-color: #00000077
phantom: true
Label
id: line3
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
text: World: nemezis
text-align: center
text-wrap: false
height: 16
font: terminus-10px
border-width-bottom: 1
border-color: #00000077
phantom: true
EntergameCharacter < UIButton
padding: 3 3 3 3
@onClick: self:setChecked(true)
@onDoubleClick: modules.client_entergamev2.doGameLogin()
height: 34
$checked:
background-color: #333333
$!checked:
background-color: #555555
UICreature
id: outfit
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
size: 32 32
margin-top: -2
margin-bottom: -2
phantom: true
UILabel
id: line1
anchors.left: prev.right
anchors.top: parent.top
anchors.bottom: parent.bottom
margin-left: 6
width: 150
text-align: left
phantom: true
VerticalSeparator
anchors.left: prev.right
anchors.top: parent.top
anchors.bottom: parent.bottom
UILabel
id: line2
anchors.left: prev.right
anchors.top: parent.top
anchors.bottom: parent.bottom
width: 150
text-align: center
phantom: true
VerticalSeparator
anchors.left: prev.right
anchors.top: parent.top
anchors.bottom: parent.bottom
UILabel
id: line3
anchors.left: prev.right
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
text-align: center
phantom: true
CreateCharacter < Panel
anchors.fill: parent
@ -503,12 +594,32 @@ CreateCharacter < Panel
text-auto-resize: true
TextEdit
id: accountNameTextEdit
id: name
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
margin-top: 2
MenuLabel
!text: tr('Gender')
anchors.left: prev.left
anchors.top: prev.bottom
margin-top: 8
text-auto-resize: true
ComboBox
id: gender
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
menu-scroll: true
menu-height: 125
menu-scroll-step: 25
margin-right: 3
@onSetup: |
self:addOption("Male")
self:addOption("Female")
MenuLabel
!text: tr('Vocation')
anchors.left: prev.left
@ -516,40 +627,42 @@ CreateCharacter < Panel
margin-top: 8
text-auto-resize: true
TextEdit
id: emailTextEdit
ComboBox
id: vocation
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
margin-top: 2
MenuLabel
!text: tr('Password')
anchors.left: prev.left
anchors.top: prev.bottom
margin-top: 8
text-auto-resize: true
PasswordTextEdit
id: accountPasswordTextEdit
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
margin-top: 2
menu-scroll: true
menu-height: 125
menu-scroll-step: 25
margin-right: 3
@onSetup: |
self:addOption("Sorcerer")
self:addOption("Druid")
self:addOption("Paladin")
self:addOption("Knight")
MenuLabel
!text: tr('Password confirmation')
!text: tr('Town')
anchors.left: prev.left
anchors.top: prev.bottom
margin-top: 8
text-auto-resize: true
PasswordTextEdit
id: accountPasswordTextEdit
ComboBox
id: town
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
margin-top: 2
menu-scroll: true
menu-height: 125
menu-scroll-step: 25
margin-right: 3
@onSetup: |
self:addOption("Carlin")
self:addOption("Edron")
self:addOption("Thais")
self:addOption("Venore")
HorizontalSeparator
anchors.left: parent.left
@ -558,6 +671,7 @@ CreateCharacter < Panel
margin-top: 9
Button
id: createButton
!text: tr('Create character')
anchors.left: parent.left
anchors.right: parent.right
@ -565,7 +679,89 @@ CreateCharacter < Panel
margin-top: 10
margin-left: 50
margin-right: 50
@onClick: EnterGame.doLogin()
EnterGamePanel
id: buttons
anchors.horizontalCenter: prev.horizontalCenter
anchors.top: prev.bottom
margin-top: 20
size: 200 50
image-source: /images/ui/window_headless
image-border: 6
padding-top: 8
Button
id: back
anchors.verticalCenter: buttons.verticalCenter
anchors.horizontalCenter: buttons.horizontalCenter
!text: tr("Back")
size: 160 30
AccountSettings < Panel
anchors.fill: parent
id: settings
EnterGamePanel
id: mainPanel
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
size: 250 173
!text: tr("Account settings")
padding-top: 36
padding-left: 16
padding-right: 16
padding-bottom: 16
MenuLabel
!text: tr('Email')
anchors.left: parent.left
anchors.top: parent.top
text-auto-resize: true
TextEdit
id: email
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
margin-top: 2
MenuLabel
!text: tr('Security level')
anchors.left: prev.left
anchors.top: prev.bottom
margin-top: 8
text-auto-resize: true
ComboBox
id: security
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
menu-scroll: true
menu-height: 125
menu-scroll-step: 25
margin-right: 3
@onSetup: |
self:addOption("Low")
self:addOption("Medium")
self:addOption("High")
HorizontalSeparator
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
margin-top: 9
Button
id: updateButton
!text: tr('Update settings')
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
margin-top: 10
margin-left: 50
margin-right: 50
EnterGamePanel
id: buttons
@ -597,3 +793,4 @@ Panel
Registration
Characters
CreateCharacter
AccountSettings

View File

@ -0,0 +1,228 @@
EnterGameV2Protocol = {}
EnterGameV2Protocol.__index = EnterGameV2Protocol
EnterGameV2Protocol.new = function(session)
if type(session) ~= 'string' then
return error("You need to specify string session for EnterGameV2Protocol")
end
local data = {}
data.socket = nil
data.terminated = false
data.reconnectEvent = nil
data.connected = false
data.session = session
data.sendQueue = {}
data.sendQueueMsgId = 1
data.loginTimeoutEvent = nil
setmetatable(data, EnterGameV2Protocol)
return data
end
function EnterGameV2Protocol:destroy()
self.terminated = true
self.sendQueue = {}
if self.loginTimeoutEvent then
removeEvent(self.loginTimeoutEvent)
self.loginTimeoutEvent = nil
end
self:reset()
end
function EnterGameV2Protocol:reset()
self.connected = false
if self.reconnectEvent then
removeEvent(self.reconnectEvent)
self.reconnectEvent = nil
end
if self.socket then
self.socket.close()
self.socket = nil
end
end
function EnterGameV2Protocol:setUrl(url)
if self.terminated then
return false
end
self:reset()
if self.url ~= url then
self.sendQueue = {}
self.sendQueueMsgId = 1
if self.loginTimeoutEvent then
removeEvent(self.loginTimeoutEvent)
self.loginTimeoutEvent = nil
end
end
if type(url) ~= 'string' or not url:lower():find("ws") then
g_logger.error("Invalid url for EnterGameV2Protocol:\n" .. url)
return false
end
self.url = url
self.socket = HTTP.WebSocketJSON(url, {
onOpen = function(message, websocketId)
if self.terminated or not self.socket or self.socket.id ~= websocketId then return end
self.connected = true
self:sendFirstMessage()
end,
onMessage = function(message, websocketId)
if self.terminated or not self.socket or self.socket.id ~= websocketId then return end
self:onSocketMessage(message)
end,
onClose = function(message, websocketId)
if self.terminated or not self.socket or self.socket.id ~= websocketId then return end
self.connected = false
self:scheduleReconnect()
end,
onError = function(message, websocketId)
if self.terminated or not self.socket or self.socket.id ~= websocketId then return end
self.connected = false
self:scheduleReconnect()
end
})
return true
end
function EnterGameV2Protocol:isConnected()
return self.socket and self.connected
end
function EnterGameV2Protocol:scheduleReconnect()
if self.socket then
self.connected = false
self.socket.close()
self.socket = nil
end
if self.terminated then return end
if self.reconnectEvent then return end
self.reconnectEvent = scheduleEvent(function() self:reconnect() end, 500)
end
function EnterGameV2Protocol:login(account, password, token, callback)
self:send({
type="login",
account=account,
password=password,
token=token,
})
if self.loginTimeoutEvent then
removeEvent(self.loginTimeoutEvent)
end
self.loginTimeoutEvent = scheduleEvent(function()
self.loginTimeoutEvent = nil
self.onLogin({error="Connection timeout"})
end, 10000)
end
function EnterGameV2Protocol:logout()
self:send({
type="logout"
})
end
function EnterGameV2Protocol:register(name, email, password, callback)
end
function EnterGameV2Protocol:createCharacter(name, gender, vocation, town)
self:send({
type="createcharacter",
name=name,
gender=gender,
vocation=vocation,
town=town
})
end
function EnterGameV2Protocol:updateSettings(settings)
self:send({
type="settings",
settings=settings
})
end
-- private functions
function EnterGameV2Protocol:reconnect()
if #self.sendQueue > 1 then
self.sendQueue = {} -- TEMPORARY
end
self.reconnectEvent = nil
if self.terminated then return end
self:setUrl(self.url)
end
function EnterGameV2Protocol:send(data)
if type(data) ~= "table" then
return error("data should be table")
end
data["id"] = self.sendQueueMsgId
table.insert(self.sendQueue, {id=self.sendQueueMsgId, msg=json.encode(data)})
self.sendQueueMsgId = self.sendQueueMsgId + 1
if self.socket then
self.socket.send(self.sendQueue[#self.sendQueue].msg)
end
end
function EnterGameV2Protocol:sendFirstMessage()
self.socket.send({type="init", session=self.session})
for i, msg in ipairs(self.sendQueue) do
self.socket.send(msg.msg)
end
end
function EnterGameV2Protocol:onSocketMessage(message)
local lastId = message["lastId"]
if type(lastId) == 'number' then -- clear send queue
while #self.sendQueue > 0 do
local id = self.sendQueue[1].id
if id < lastId then
break
end
table.remove(self.sendQueue, 1)
end
end
if message["type"] == "ping" then
self.socket.send({type="ping"})
elseif message["type"] == "login" then
if self.loginTimeoutEvent then
removeEvent(self.loginTimeoutEvent)
self.loginTimeoutEvent = nil
end
if self.onLogin then
self.onLogin(message)
end
elseif message["type"] == "logout" then
if self.onLogout then
self.onLogout()
end
elseif message["type"] == "qauth" then
if self.onQAuth then
self.onQAuth(message["token"])
end
elseif message["type"] == "characters" then
if self.onCharacters then
self.onCharacters(message["characters"])
end
elseif message["type"] == "message" then
if self.onMessage then
self.onMessage(message["title"], message["text"])
end
elseif message["type"] == "loading" then
if self.onMessage then
self.onLoading(message["title"], message["text"])
end
elseif message["type"] == "news" then
if self.onNews then
self.onNews(message["news"])
end
elseif message["type"] == "motd" then
if self.onMotd then
self.onMotd(message["text"])
end
elseif message["type"] == "createcharacter" then
if self.onMessage then
self.onCharacterCreate(message["error"], message["message"])
end
end
end

View File

@ -170,7 +170,7 @@ function update()
return
end
statsWindow.debugPanel.sleepTime:setText("Sleep: " .. math.round(g_stats.getSleepTime() / math.max(1, g_clock.micros() - lastSleepTimeReset), 2) .. "%")
statsWindow.debugPanel.sleepTime:setText("Sleep: " .. math.round(g_stats.getSleepTime() / math.max(1, g_clock.micros() - lastSleepTimeReset), 2) .. "%, Packets: " .. g_game.getRecivedPacketsCount() .. " , " .. (g_game.getRecivedPacketsSize() / 1024) .. " KB")
statsWindow.debugPanel.luaRamUsage:setText("Ram usage by lua: " .. gcinfo() .. " kb")
local adaptive = "Adaptive: " .. g_adaptiveRenderer.getLevel() .. " | " .. g_adaptiveRenderer.getDebugInfo()
adaptiveRender:setText(adaptive)

View File

@ -141,6 +141,7 @@ function init()
terminalWindow.onDoubleClick = popWindow
terminalButton = modules.client_topmenu.addLeftButton('terminalButton', tr('Terminal') .. ' (Ctrl + T)', '/images/topbuttons/terminal', toggle)
terminalButton:setOn(false)
g_keyboard.bindKeyDown('Ctrl+T', toggle)
commandHistory = g_settings.getList('terminal-history')
@ -197,7 +198,7 @@ function terminate()
g_keyboard.unbindKeyDown('Ctrl+T')
g_logger.setOnLog(nil)
terminalWindow:destroy()
--terminalButton:destroy()
terminalButton:destroy()
commandEnv = nil
_G.terminalLines = allLines
end

View File

@ -209,4 +209,79 @@ function table.equal(t1,t2,ignore_mt)
if v1 == nil or not table.equal(v1,v2) then return false end
end
return true
end
function table.isList(t)
local size = #t
return table.size(t) == size and size > 0
end
function table.isStringList(t)
if not table.isList(t) then return false end
for k,v in ipairs(t) do
if type(v) ~= 'string' then
return false
end
end
return true
end
function table.isStringPairList(t)
if not table.isList(t) then return false end
for k,v in ipairs(t) do
if type(v) ~= 'table' or #v ~= 2 or type(v[1]) ~= 'string' or type(v[2]) ~= 'string' then
return false
end
end
return true
end
function table.encodeStringPairList(t)
local ret = ""
for k,v in ipairs(t) do
if v[2]:find("\n") then
ret = ret .. v[1] .. ":[[\n" .. v[2] .. "\n]]\n"
else
ret = ret .. v[1] .. ":" .. v[2] .. "\n"
end
end
return ret end
function table.decodeStringPairList(l)
local ret = {}
local r = regexMatch(l, "^([^:^\n]{1,20}):?(.*)$")
local multiline = ""
local multilineKey = ""
local multilineActive = false
for k,v in ipairs(r) do
if multilineActive then
local endPos = v[1]:find("%]%]")
if endPos then
if endPos > 1 then
table.insert(ret, {multilineKey, multiline .. "\n" .. v[1]:sub(1, endPos - 1)})
else
table.insert(ret, {multilineKey, multiline})
end
multilineActive = false
multiline = ""
multilineKey = ""
else
if multiline:len() == 0 then
multiline = v[1]
else
multiline = multiline .. "\n" .. v[1]
end
end
else
local bracketPos = v[3]:find("%[%[")
if bracketPos == 1 then -- multiline begin
multiline = v[3]:sub(bracketPos + 2)
multilineActive = true
multilineKey = v[2]
elseif v[2]:len() > 0 and v[3]:len() > 0 then
table.insert(ret, {v[2], v[3]})
end
end
end
return ret
end

View File

@ -144,7 +144,7 @@ function setupAction(index, action, config)
if config then
action.hotkey = config.hotkey
if action.hotkey then
if action.hotkey and action.hotkey:len() > 0 then
local gameRootPanel = modules.game_interface.getRootPanel()
g_keyboard.bindKeyPress(action.hotkey, action.callback, gameRootPanel)
end
@ -300,11 +300,11 @@ function actionOnMouseRelease(action, mousePosition, mouseButton)
end
assignWindow.addButton.onClick = function()
local gameRootPanel = modules.game_interface.getRootPanel()
if action.hotkey then
if action.hotkey and action.hotkey:len() > 0 then
g_keyboard.unbindKeyPress(action.hotkey, action.callback, gameRootPanel)
end
action.hotkey = assignWindow.comboPreview.keyCombo
if action.hotkey then
if action.hotkey and action.hotkey:len() > 0 then
g_keyboard.bindKeyPress(action.hotkey, action.callback, gameRootPanel)
end
action.hotkeyLabel:setText(action.hotkey or "")
@ -318,7 +318,7 @@ function actionOnMouseRelease(action, mousePosition, mouseButton)
action.text:setText("")
action.hotkeyLabel:setText("")
local gameRootPanel = modules.game_interface.getRootPanel()
if action.hotkey then
if action.hotkey and action.hotkey:len() > 0 then
g_keyboard.unbindKeyPress(action.hotkey, action.callback, gameRootPanel)
end
action.hotkey = nil

View File

@ -24,6 +24,7 @@ function init()
g_ui.importStyle("ui/panels.otui")
g_ui.importStyle("ui/config.otui")
g_ui.importStyle("ui/icons.otui")
g_ui.importStyle("ui/container.otui")
connect(g_game, {
onGameStart = online,
@ -195,7 +196,7 @@ function refresh()
-- run script
local status, result = pcall(function()
return executeBot(configName, botStorage, botTabs, message, save, botWebSockets) end
return executeBot(configName, botStorage, botTabs, message, save, refresh, botWebSockets) end
)
if not status then
return onError(result)

View File

@ -1,4 +1,4 @@
function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, websockets)
function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, reloadCallback, websockets)
-- load lua and otui files
local configFiles = g_resources.listDirectoryFiles("/bot/" .. config, true, false)
local luaFiles = {}
@ -21,16 +21,18 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, webs
local context = {}
context.configDir = "/bot/".. config
context.tabs = tabs
context.panel = context.tabs:addTab("Main", g_ui.createWidget('BotPanel')).tabPanel.content
context.mainTab = context.tabs:addTab("Main", g_ui.createWidget('BotPanel')).tabPanel.content
context.panel = context.mainTab
context.saveConfig = saveConfigCallback
context._websockets = websockets
context.reload = reloadCallback
context.storage = storage
if context.storage._macros == nil then
context.storage._macros = {} -- active macros
end
-- macros, hotkeys, scheduler, icons, callbacks
-- websockets, macros, hotkeys, scheduler, icons, callbacks
context._websockets = websockets
context._macros = {}
context._hotkeys = {}
context._scheduler = {}
@ -71,9 +73,10 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, webs
context.tonumber = tonumber
context.type = type
context.pcall = pcall
context.load = function(str) return load(str, nil, nil, context) end
context.load = function(str) return assert(load(str, nil, nil, context)) end
context.loadstring = context.load
context.assert = assert
context.dofile = function(file) assert(load(g_resources.readFileContents("/bot/" .. config .. "/" .. file), file, nil, context))() end
context.gcinfo = gcinfo
context.tr = tr
context.json = json
@ -129,7 +132,8 @@ function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, webs
-- run lua script
for i, file in ipairs(luaFiles) do
assert(load(g_resources.readFileContents(file), file, nil, context))()
assert(load(g_resources.readFileContents(file), file, nil, context))()
context.panel = context.mainTab -- reset default tab
end
return {

View File

@ -1,5 +1,5 @@
--[[
Config. create. load and save config file (.json)
Config - create, load and save config file (.json / .cfg)
Used by cavebot and other things
]]--
@ -22,7 +22,32 @@ Config.list = function(dir)
return contex.error("Can't create config dir: " .. context.configDir .. "/" .. dir)
end
end
return g_resources.listDirectoryFiles(context.configDir .. "/" .. dir)
local list = g_resources.listDirectoryFiles(context.configDir .. "/" .. dir)
local correctList = {}
for k,v in ipairs(list) do -- filter files
local nv = v:gsub(".json", ""):gsub(".cfg", "")
if nv ~= v then
table.insert(correctList, nv)
end
end
return correctList
end
-- load config from string insteaf of dile
Config.parse = function(data)
local status, result = pcall(function()
return json.decode(data)
end)
if status and type(result) == 'table' then
return result
end
local status, result = pcall(function()
return table.decodeStringPairList(data)
end)
if status and type(result) == 'table' then
return result
end
return context.error("Invalid config format")
end
Config.load = function(dir, name)
@ -31,63 +56,154 @@ Config.load = function(dir, name)
return json.decode(g_resources.readFileContents(file))
end
file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg"
if g_resources.fileExists(file) then -- load cfg
return table.decodeStringPairList(g_resources.readFileContents(file))
end
return context.error("Config " .. file .. " doesn't exist")
end
Config.loadRaw = function(dir, name)
local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json"
if g_resources.fileExists(file) then -- load json
return g_resources.readFileContents(file)
end
file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg"
if g_resources.fileExists(file) then -- load cfg
return g_resources.readFileContents(file)
end
return context.error("Config " .. file .. " doesn't exist")
end
Config.save = function(dir, name, value)
Config.save = function(dir, name, value, forcedExtension)
if not Config.exist(dir) then
if not Config.create(dir) then
return contex.error("Can't create config dir: " .. context.configDir .. "/" .. dir)
end
end
if type(value) ~= 'table' then
return context.error("Invalid config value type: " .. type(value) .. ", should be table")
end
local file = context.configDir .. "/" .. dir .. "/" .. name
if type(value) == 'string' then -- cfg
g_resources.writeFileContents(file .. ".cfg", value)
elseif type(value) == 'table' then -- json
if (table.isStringPairList(value) and forcedExtension ~= "json") or forcedExtension == "cfg" then -- cfg
g_resources.writeFileContents(file .. ".cfg", table.encodeStringPairList(value))
else
g_resources.writeFileContents(file .. ".json", json.encode(value))
end
return context.error("Invalid config value type: " .. type(value))
return true
end
Config.remove = function(dir, name)
local file = context.configDir .. "/" .. dir .. "/" .. name .. ".json"
local ret = false
if g_resources.fileExists(file) then
return g_resources.deleteFile(file)
g_resources.deleteFile(file)
ret = true
end
file = context.configDir .. "/" .. dir .. "/" .. name .. ".cfg"
if g_resources.fileExists(file) then
return g_resources.deleteFile(file)
g_resources.deleteFile(file)
ret = true
end
return ret
end
-- setup is used for BotConfig widget
-- not done yet
Config.setup = function(dir, widget, callback)
local refresh = function()
--
Config.setup = function(dir, widget, configExtension, callback)
if type(dir) ~= 'string' or dir:len() == 0 then
return context.error("Invalid config dir")
end
if not Config.exist(dir) and not Config.create(dir) then
return context.error("Can't create config dir: " .. dir)
end
if type(context.storage._configs) ~= "table" then
context.storage._configs = {}
end
if type(context.storage._configs[dir]) ~= "table" then
context.storage._configs[dir] = {
enabled = false,
selected = ""
}
else
widget.switch:setOn(context.storage._configs[dir].enabled)
end
local isRefreshing = false
local refresh = function()
isRefreshing = true
local configs = Config.list(dir)
local configIndex = 1
widget.list:clear()
for v,k in ipairs(configs) do
widget.list:addOption(k)
if k == context.storage._configs[dir].selected then
configIndex = v
end
end
local data = nil
if #configs > 0 then
widget.list:setCurrentIndex(configIndex)
context.storage._configs[dir].selected = widget.list:getCurrentOption().text
data = Config.load(dir, configs[configIndex])
else
context.storage._configs[dir].selected = nil
end
context.storage._configs[dir].enabled = widget.switch:isOn()
isRefreshing = false
callback(context.storage._configs[dir].selected, widget.switch:isOn(), data)
end
widget.list.onOptionChange = function(widget)
if not isRefreshing then
context.storage._configs[dir].selected = widget:getCurrentOption().text
refresh()
end
end
widget.switch.onClick = function()
widget.switch:setOn(not widget.switch:isOn())
refresh()
end
widget.add = function()
widget.add.onClick = function()
context.UI.SinglelineEditorWindow("config_name", function(name)
name = name:gsub("%s+", "_")
if name:len() == 0 or name:len() >= 30 or name:find("/") or name:find("\\") then
return context.error("Invalid config name")
end
local file = context.configDir .. "/" .. dir .. "/" .. name .. "." .. configExtension
if g_resources.fileExists(file) then
return context.error("Config " .. name .. " already exist")
end
g_resources.writeFileContents(file, "")
context.storage._configs[dir].selected = name
widget.switch:setOn(false)
refresh()
end)
end
widget.edit = function()
widget.edit.onClick = function()
local name = context.storage._configs[dir].selected
if not name then return end
context.UI.MultilineEditorWindow("Config editor - " .. name .. " in " .. dir,
Config.loadRaw(dir, name), function(newValue)
local data = Config.parse(newValue)
Config.save(dir, name, data, configExtension)
refresh()
end)
end
widget.remove = function()
widget.remove.onClick = function()
local name = context.storage._configs[dir].selected
if not name then return end
context.UI.ConfirmationWindow("Config removal", "Do you want to remove config " .. name .. " from " .. dir .. "?", function()
Config.remove(dir, name)
widget.switch:setOn(false)
refresh()
end)
end
--local configs = Config.list(dir)
--widget.list.
refresh()
return {
isOn = function()
@ -96,18 +212,35 @@ Config.setup = function(dir, widget, callback)
isOff = function()
return not widget.switch:isOn()
end,
enable = function()
setOn = function(val)
if val == false then
if widget.switch:isOn() then
widget.switch:onClick()
end
return
end
if not widget.switch:isOn() then
widget.switch:onClick()
end
end,
disable = function()
setOff = function(val)
if val == false then
if not widget.switch:isOn() then
widget.switch:onClick()
end
return
end
if widget.switch:isOn() then
widget.switch:onClick()
end
end,
save = function()
end
save = function(data)
Config.save(dir, context.storage._configs[dir].selected, data, configExtension)
end,
refresh = refresh,
reload = refresh,
getActiveConfigName = function()
return context.storage._configs[dir].selected
end
}
end

View File

@ -31,6 +31,7 @@ context.addIcon = function(id, options, callback)
local config = context.storage._icons[id]
local widget = g_ui.createWidget("BotIcon", panel)
widget.botWidget = true
widget.botIcon = true
if type(config.x) ~= 'number' and type(config.y) ~= 'number' then
if type(options.x) == 'number' and type(options.y) == 'number' then
@ -138,22 +139,22 @@ context.addIcon = function(id, options, callback)
config.x = math.min(1, math.max(0, x / width))
config.y = math.min(1, math.max(0, y / height))
widget:addAnchor(AnchorHorizontalCenter, 'parent', AnchorHorizontalCenter)
widget:addAnchor(AnchorVerticalCenter, 'parent', AnchorVerticalCenter)
widget:setMarginTop(height * (-0.5 + config.y))
widget:setMarginTop(math.max(height * (-0.5) - parent:getMarginTop(), height * (-0.5 + config.y)))
widget:setMarginLeft(width * (-0.5 + config.x))
return true
end
end
widget.onGeometryChange = function(widget, oldRect, newRect)
widget.onGeometryChange = function(widget)
if widget:isDragging() then return end
local parent = widget:getParent()
local parentRect = parent:getRect()
local width = parentRect.width - widget:getWidth()
local height = parentRect.height - widget:getHeight()
widget:setMarginTop(-parent:getMarginTop() + height * (-0.5 + config.y))
widget:setMarginTop(math.max(height * (-0.5) - parent:getMarginTop(), height * (-0.5 + config.y)))
widget:setMarginLeft(width * (-0.5 + config.x))
end

View File

@ -183,7 +183,13 @@ context.findPath = function(startPos, destPos, maxDist, params)
end
context.getPath = context.findPath
-- also works as autoWalk(dirs) where dirs is a list eg.: {1,2,3,0,1,1,2,}
context.autoWalk = function(destination, maxDist, params)
if type(destination) == "table" and table.isList(destination) and not maxDist and not params then
g_game.autoWalk(destination, {x=0,y=0,z=0})
return true
end
-- Available params same as for findPath
local path = context.findPath(context.player:getPosition(), destination, maxDist, params)
if not path then

View File

@ -1,114 +1,12 @@
local context = G.botContext
if type(context.UI) ~= "table" then
context.UI = {}
end
local UI = context.UI
context.setupUI = function(otml, parent)
UI.createWidget = function(name, parent)
if parent == nil then
parent = context.panel
end
local widget = g_ui.loadUIFromString(otml, parent)
widget.botWidget = true
return widget
return g_ui.createWidget(name, parent)
end
context.importStyle = function(otml)
return g_ui.importStyleFromString(otml)
end
context.addTab = function(name)
local tab = context.tabs:getTab(name)
if tab then -- return existing tab
return tab.tabPanel.content
end
context.tabs:setOn(true)
local newTab = context.tabs:addTab(name, g_ui.createWidget('BotPanel')).tabPanel.content
if #(context.tabs.tabs) > 5 then
for k,tab in pairs(context.tabs.tabs) do
tab:setPadding(3)
tab:setFont('small-9px')
end
end
return newTab
end
context.getTab = context.addTab
context.addSwitch = function(id, text, onClickCallback, parent)
if not parent then
parent = context.panel
end
local switch = g_ui.createWidget('BotSwitch', parent)
switch:setId(id)
switch:setText(text)
switch.onClick = onClickCallback
return switch
end
context.addButton = function(id, text, onClickCallback, parent)
if not parent then
parent = context.panel
end
local button = g_ui.createWidget('BotButton', parent)
button:setId(id)
button:setText(text)
button.onClick = onClickCallback
return button
end
context.addLabel = function(id, text, parent)
if not parent then
parent = context.panel
end
local label = g_ui.createWidget('BotLabel', parent)
label:setId(id)
label:setText(text)
return label
end
context.addTextEdit = function(id, text, onTextChangeCallback, parent)
if not parent then
parent = context.panel
end
local widget = g_ui.createWidget('BotTextEdit', parent)
widget:setId(id)
widget.onTextChange = onTextChangeCallback
widget:setText(text)
return widget
end
context.addSeparator = function(id, parent)
if not parent then
parent = context.panel
end
local separator = g_ui.createWidget('BotSeparator', parent)
separator:setId(id)
return separator
end
context._addMacroSwitch = function(name, keys, parent)
if not parent then
parent = context.panel
end
local text = name
if keys:len() > 0 then
text = name .. " [" .. keys .. "]"
end
local switch = context.addSwitch("macro_" .. #context._macros, text, function(widget)
context.storage._macros[name] = not context.storage._macros[name]
widget:setOn(context.storage._macros[name])
end, parent)
switch:setOn(context.storage._macros[name])
return switch
end
context._addHotkeySwitch = function(name, keys, parent)
if not parent then
parent = context.panel
end
local text = name
if keys:len() > 0 then
text = name .. " [" .. keys .. "]"
end
local switch = context.addSwitch("hotkey_" .. #context._hotkeys, text, nil, parent)
switch:setOn(false)
return switch
end

View File

@ -0,0 +1,81 @@
local context = G.botContext
if type(context.UI) ~= "table" then
context.UI = {}
end
local UI = context.UI
UI.Config = function(parent)
return UI.createWidget("BotConfig", parent)
end
-- call :setItems(table) to set items, call :getItems() to get them
-- unique if true, won't allow duplicates
-- callback (can be nil) gets table with new item list, eg: {{id=2160, count=1}, {id=268, count=100}, {id=269, count=20}}
UI.Container = function(callback, unique, parent)
local widget = UI.createWidget("BotContainer", parent)
local oldItems = {}
local updateItems = function()
local items = widget:getItems()
widget:setItems(items)
-- callback part
if not callback then return end
local somethingNew = false
for i, item in ipairs(items) do
if type(oldItems[i]) ~= "table" then
somethingNew = true
break
end
if oldItems[i].id ~= item.id or oldItems[i].count ~= item.count then
somethingNew = true
break
end
end
if somethingNew then
oldItems = items
callback(items)
end
end
widget.setItems = function(self, items)
if type(self) == 'table' then
items = self
end
local itemsToShow = math.max(10, #items + 2)
if itemsToShow % 5 ~= 0 then
itemsToShow = itemsToShow + 5 - itemsToShow % 5
end
widget.items:destroyChildren()
for i = 1, itemsToShow do
local widget = g_ui.createWidget("BotItem", widget.items)
if type(items[i]) == 'number' then
items[i] = {id=items[i], count=1}
end
if type(items[i]) == 'table' then
widget:setItem(Item.create(items[i].id, items[i].count))
end
end
oldItems = items
for i, child in ipairs(widget.items:getChildren()) do
child.onItemChange = updateItems
end
end
widget.getItems = function()
local items = {}
local duplicates = {}
for i, child in ipairs(widget.items:getChildren()) do
if child:getItemId() >= 100 then
if not duplicates[child:getItemId()] or not unique then
table.insert(items, {id=child:getItemId(), count=child:getItemCount()})
duplicates[child:getItemId()] = true
end
end
end
return items
end
return widget
end

View File

@ -0,0 +1,132 @@
local context = G.botContext
context.createWidget = function(name, parent)
if parent == nil then
parent = context.panel
end
g_ui.createWidget(name, parent)
end
context.setupUI = function(otml, parent)
if parent == nil then
parent = context.panel
end
local widget = g_ui.loadUIFromString(otml, parent)
widget.botWidget = true
return widget
end
context.importStyle = function(otml)
if type(otml) ~= "string" then
return error("Invalid parameter for importStyle, should be string")
end
if otml:find(".otui") and not otml:find("\n") then
return g_ui.importStyle(context.configDir .. "/" .. otml)
end
return g_ui.importStyleFromString(otml)
end
context.addTab = function(name)
local tab = context.tabs:getTab(name)
if tab then -- return existing tab
return tab.tabPanel.content
end
context.tabs:setOn(true)
local newTab = context.tabs:addTab(name, g_ui.createWidget('BotPanel')).tabPanel.content
if #(context.tabs.tabs) > 5 then
for k,tab in pairs(context.tabs.tabs) do
tab:setPadding(3)
tab:setFont('small-9px')
end
end
return newTab
end
context.getTab = context.addTab
context.setDefaultTab = function(name)
local tab = context.addTab(name)
context.panel = tab
end
context.addSwitch = function(id, text, onClickCallback, parent)
if not parent then
parent = context.panel
end
local switch = g_ui.createWidget('BotSwitch', parent)
switch:setId(id)
switch:setText(text)
switch.onClick = onClickCallback
return switch
end
context.addButton = function(id, text, onClickCallback, parent)
if not parent then
parent = context.panel
end
local button = g_ui.createWidget('BotButton', parent)
button:setId(id)
button:setText(text)
button.onClick = onClickCallback
return button
end
context.addLabel = function(id, text, parent)
if not parent then
parent = context.panel
end
local label = g_ui.createWidget('BotLabel', parent)
label:setId(id)
label:setText(text)
return label
end
context.addTextEdit = function(id, text, onTextChangeCallback, parent)
if not parent then
parent = context.panel
end
local widget = g_ui.createWidget('BotTextEdit', parent)
widget:setId(id)
widget.onTextChange = onTextChangeCallback
widget:setText(text)
return widget
end
context.addSeparator = function(id, parent)
if not parent then
parent = context.panel
end
local separator = g_ui.createWidget('BotSeparator', parent)
separator:setId(id)
return separator
end
context._addMacroSwitch = function(name, keys, parent)
if not parent then
parent = context.panel
end
local text = name
if keys:len() > 0 then
text = name .. " [" .. keys .. "]"
end
local switch = context.addSwitch("macro_" .. #context._macros, text, function(widget)
context.storage._macros[name] = not context.storage._macros[name]
widget:setOn(context.storage._macros[name])
end, parent)
switch:setOn(context.storage._macros[name])
return switch
end
context._addHotkeySwitch = function(name, keys, parent)
if not parent then
parent = context.panel
end
local text = name
if keys:len() > 0 then
text = name .. " [" .. keys .. "]"
end
local switch = context.addSwitch("hotkey_" .. #context._hotkeys, text, nil, parent)
switch:setOn(false)
return switch
end

View File

@ -0,0 +1,30 @@
local context = G.botContext
if type(context.UI) ~= "table" then
context.UI = {}
end
local UI = context.UI
UI.SinglelineEditorWindow = function(text, callback)
return modules.game_textedit.singlelineEditor(text, callback)
end
UI.MultilineEditorWindow = function(description, test, callback)
return modules.game_textedit.multilineEditor(description, test, callback)
end
UI.ConfirmationWindow = function(title, question, callback)
local window = nil
local onConfirm = function()
window:destroy()
callback()
end
local closeWindow = function()
window:destroy()
end
window = context.displayGeneralBox(title, question, {
{ text=tr('Yes'), callback=onConfirm },
{ text=tr('No'), callback=closeWindow },
anchor=AnchorHorizontalCenter}, onConfirm, closeWindow)
window.botWidget = true
return window
end

View File

@ -1,8 +1,6 @@
BotConfig < Panel
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: 40
id: botConfig
height: 45
ComboBox
id: list
@ -31,21 +29,21 @@ BotConfig < Panel
anchors.top: prev.bottom
anchors.left: parent.left
text: Add
width: 58
height: 17
width: 59
height: 20
Button
id: edit
anchors.top: prev.top
anchors.horizontalCenter: parent.horizontalCenter
text: Edit
width: 58
height: 17
width: 59
height: 20
Button
id: remove
anchors.top: prev.top
anchors.right: parent.right
text: Remove
width: 58
height: 17
width: 59
height: 20

View File

@ -0,0 +1,19 @@
BotContainer < Panel
height: 68
ScrollablePanel
id: items
anchors.fill: parent
vertical-scrollbar: scroll
layout:
type: grid
cell-size: 34 34
flow: true
BotSmallScrollBar
id: scroll
anchors.top: prev.top
anchors.bottom: prev.bottom
anchors.right: parent.right
step: 10
pixels-scroll: true

View File

@ -8,6 +8,9 @@ end
function updateFeatures(version)
g_game.resetFeatures()
if version <= 0 then
return
end
-- you can add custom features here, list of them is in the modules\gamelib\const.lua
g_game.enableFeature(GameBot)

View File

@ -0,0 +1,39 @@
function init()
connect(g_game, {
onImbuementWindow = onImbuementWindow,
onCloseImbuementWindow = onCloseImbuementWindow
})
end
function terminate()
disconnect(g_game, {
onImbuementWindow = onImbuementWindow,
onCloseImbuementWindow = onCloseImbuementWindow
})
end
function onImbuementWindow(itemId, slots, activeSlots, imbuements, needItems)
print("window " .. slots)
for i, slot in pairs(activeSlots) do
local duration = slot.duration
local removalCost = slot.removalCost
local imbuement = slot.imbuement
for i, source in pairs(imbuement.sources) do
print(source.description, source.item:getId(), source.item:getCount())
end
end
for i, imbuement in ipairs(imbuements) do
for i, source in pairs(imbuement.sources) do
print(source.description, source.item:getId(), source.item:getCount())
end
end
for i, item in ipairs(needItems) do
print(item:getId(), item:getCount())
end
end
function onCloseImbuementWindow()
print("close")
end

View File

@ -0,0 +1,9 @@
Module
name: game_imbuement
description: Imbuement
author: otclient.ovh
website: http://otclient.ovh
sandboxed: true
scripts: [ imbuement ]
@onLoad: init()
@onUnload: terminate()

View File

@ -0,0 +1,199 @@
PreySelectionLabel < Label
font: verdana-11px-monochrome
background-color: alpha
text-offset: 34 0
margin-right: 5
text-align: center
text-wrap: true
focusable: true
height: 34
$focus:
background-color: #00000055
color: #ffffff
UICreature
id: creature
size: 32 32
anchors.top: parent.top
anchors.left: parent.left
margin-top: 1
PreySlot < Panel
width: 190
height: 280
border: 1 black
padding: 5
Label
id: title
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
text-align: center
!text: tr("Prey Inactive")
TextList
id: list
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
margin-top: 15
margin-right: 10
vertical-scrollbar: listScrollbar
height: 150
visible: false
focusable: false
VerticalScrollBar
id: listScrollbar
anchors.top: prev.top
anchors.bottom: prev.bottom
anchors.right: parent.right
pixels-scroll: true
visible: false
step: 10
UICreature
id: creature
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
margin-top: 40
height: 64
width: 64
visible: false
Label
id: description
margin-top: 30
margin-left: 5
margin-right: 5
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
text-auto-resize: true
text-align: center
text-wrap: true
visible: false
Label
id: bonuses
margin-top: 5
margin-left: 5
margin-right: 5
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
text-auto-resize: true
text-align: center
text-wrap: true
visible: false
Button
id: button
margin-top: 5
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
margin-left: 10
margin-right: 10
visible: false
Label
id: bottomLabel
margin-left: 5
margin-right: 5
margin-bottom: 5
anchors.bottom: next.top
anchors.left: parent.left
anchors.right: parent.right
text-auto-resize: true
text-align: center
text-wrap: true
visible: false
Button
id: bottomButton
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
margin-left: 10
margin-right: 10
text: 11
visible: false
$hidden:
height: 0
$!hidden:
height: 22
MainWindow
id: preyWindow
!text: tr('Preys')
size: 600 405
background-color: #AAAAAA
@onEscape: modules.game_prey.hide()
PreySlot
id: slot1
anchors.left: parent.left
anchors.top: parent.top
PreySlot
id: slot2
anchors.left: prev.right
anchors.top: prev.top
PreySlot
id: slot3
anchors.left: prev.right
anchors.top: prev.top
HorizontalSeparator
id: mainSeparator
anchors.top: slot3.bottom
anchors.left: parent.left
anchors.right: parent.right
margin-top: 5
Label
id: rerollPrice
anchors.top: mainSeparator.bottom
anchors.left: parent.left
margin-top: 5
width: 190
height: 30
text-align: center
Label
id: balance
anchors.top: mainSeparator.bottom
anchors.left: prev.right
margin-top: 5
width: 190
height: 30
text-align: center
Label
id: bonusRerolls
anchors.top: mainSeparator.bottom
anchors.left: prev.right
margin-top: 5
width: 190
height: 30
text-align: center
HorizontalSeparator
anchors.bottom: buttonCancel.top
anchors.left: parent.left
anchors.right: parent.right
margin-bottom: 5
Button
id: buttonCancel
!text: tr('Close')
width: 64
anchors.right: parent.right
anchors.bottom: parent.bottom
@onClick: modules.game_prey.hide()

View File

@ -414,6 +414,12 @@ function createThingMenu(menuPosition, lookThing, useThing, creatureThing)
if useThing:isRotateable() then
menu:addOption(tr('Rotate'), function() g_game.rotate(useThing) end)
end
if useThing:isWrapable() then
menu:addOption(tr('Wrap'), function() g_game.wrap(useThing) end)
end
if useThing:isUnwrapable() then
menu:addOption(tr('Unwrap'), function() g_game.wrap(useThing) end)
end
if g_game.getFeature(GameBrowseField) and useThing:getPosition().x ~= 0xffff then
menu:addOption(tr('Browse Field'), function() g_game.browseField(useThing:getPosition()) end)
@ -832,10 +838,7 @@ function refreshViewMode()
return
end
local minimumWidth = (g_settings.getNumber("rightPanels") + g_settings.getNumber("leftPanels") - 1) * 200
if classic then
minimumWidth = minimumWidth + 400
end
local minimumWidth = (g_settings.getNumber("rightPanels") + g_settings.getNumber("leftPanels") - 1) * 200 + 300
minimumWidth = math.max(minimumWidth, 800)
g_window.setMinimumSize({ width = minimumWidth, height = 600 })
if g_window.getWidth() < minimumWidth then
@ -900,7 +903,7 @@ function refreshViewMode()
gameRootPanel:fill('parent')
gameMapPanel:setKeepAspectRatio(false)
gameMapPanel:setLimitVisibleRange(false)
gameMapPanel:setZoom(14)
gameMapPanel:setZoom(15)
modules.client_topmenu.getTopMenu():setImageColor('#ffffff66')
@ -939,10 +942,10 @@ function updateSize()
local dwidth = dimenstion.width
local tileSize = rheight / dheight
local maxWidth = tileSize * (awareRange.width + 1)
if g_game.getFeature(GameChangeMapAwareRange) then
local maxWidth = tileSize * (awareRange.width - 1)
if g_game.getFeature(GameChangeMapAwareRange) and g_game.getFeature(GameNewWalking) then
maxWidth = tileSize * (awareRange.width - 1)
end
gameMapPanel:setMarginTop(-tileSize * 2)
gameMapPanel:setMarginTop(-tileSize)
if g_settings.getBoolean("cacheMap") then
gameMapPanel:setMarginLeft(0)
gameMapPanel:setMarginRight(0)
@ -956,6 +959,13 @@ function updateSize()
modules.game_textmessage.messagesPanel:setMarginTop(-gameMapPanel:getMarginTop())
end
if modules.game_bot then
for i, child in ipairs(gameMapPanel:getChildren()) do
if child.botIcon and child.onGeometryChange then
child.onGeometryChange(child)
end
end
end
end
--[[

View File

@ -36,6 +36,7 @@ Module
- game_textedit
- game_actionbar
- game_prey
- game_imbuement
- game_bot
@onLoad: init()
@onUnload: terminate()

View File

@ -35,8 +35,7 @@ function show(itemWidget)
end
end
local doneFunc = function()
itemWidget:setItemId(window.item:getItemId())
itemWidget:setItemCount(window.item:getItemCount())
itemWidget:setItem(Item.create(window.item:getItemId(), window.item:getItemCount()))
destroy()
end
@ -45,8 +44,7 @@ function show(itemWidget)
window.onEnter = doneFunc
window.onEscape = destroy
window.item:setItemId(itemWidget:getItemId())
window.item:setItemCount(itemWidget:getItemCount())
window.item:setItem(Item.create(itemWidget:getItemId(), itemWidget:getItemCount()))
window.itemId:setValue(itemWidget:getItemId())
if itemWidget:getItemCount() > 1 then

View File

@ -18,6 +18,7 @@ MainWindow
margin-top: 15
margin-left: 22
padding: 4 4 4 4
size: 96 96
fixed-creature-size: true
Label
@ -44,13 +45,14 @@ MainWindow
enabled: true
@onClick: modules.game_outfit.previousOutfitType()
Creature
Creature
id: mountCreatureBox
anchors.top: parent.top
anchors.right: parent.right
margin-top: 15
margin-right: 22
padding: 4 4 4 4
size: 96 96
fixed-creature-size: true
Label

View File

@ -317,6 +317,11 @@ function processMessage(data)
local title = tr(data["title"])
local msg = data["msg"]
msgWindow = displayInfoBox(title, msg)
msgWindow.onDestroy = function(widget)
if widget == msgWindow then
msgWindow = nil
end
end
msgWindow:show()
msgWindow:raise()
msgWindow:focus()

View File

@ -165,6 +165,8 @@ GameMinimapLimitedToSingleFloor = 81
GameDoubleLevel = 83
GameDoubleSoul = 84
GameDoublePlayerGoodsMoney = 85
GameCreatureWalkthrough = 86 -- add Walkthrough for versions less than 854, unpass = msg->getU8(); in protocolgameparse.cpp
GameDoubleTradeMoney = 87
GameNewWalking = 90
GameSlowerManualWalking = 91
@ -174,9 +176,14 @@ GameBiggerMapCache = 96
GameForceLight = 97
GameNoDebug = 98
GameBotProtection = 99
GameFasterAnimations = 101
LastGameFeature = 110
GameFasterAnimations = 101
GameCenteredOutfits = 102
GamePacketSizeU32 = 110
GamePacketCompression = 111
LastGameFeature = 120
TextColors = {
red = '#f55e5e', --'#c83200'

View File

@ -131,6 +131,10 @@ function ProtocolLogin:sendLoginPacket()
msg:encryptRsa()
end
if g_game.getFeature(GamePacketSizeU32) then
self:enableBigPackets()
end
if g_game.getFeature(GameProtocolChecksum) then
self:enableChecksum()
end

View File

@ -26,11 +26,7 @@ function UIItem:onDrop(widget, mousePos, forced)
if not item or not item:isItem() then return false end
if self.selectable then
self:setItemId(item:getId())
self:setItemCount(item:getCount())
if item:getSubType() > 1 then
self:setItemSubType(item:getSubType())
end
self:setItem(Item.create(item:getId(), item:getCount()))
return
end

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.