2
LICENSE
@ -1,6 +1,6 @@
|
||||
OTClient is made available under the MIT License
|
||||
|
||||
Copyright (c) 2010-2012 OTClient <https://github.com/edubart/otclient>
|
||||
Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -41,6 +41,7 @@ A package with all required libraries for compiling OTClient on Windows can be f
|
||||
In short, if you need to compile OTClient, follow these tutorials:
|
||||
* [Compiling on Windows](https://github.com/edubart/otclient/wiki/Compiling-on-Windows)
|
||||
* [Compiling on Linux](https://github.com/edubart/otclient/wiki/Compiling-on-Linux)
|
||||
* [Compiling on OS X](https://github.com/edubart/otclient/wiki/Compiling-on-Mac-OS-X)
|
||||
|
||||
|
||||
|
||||
|
BIN
data/images/game/skull_socket.png
Normal file
After Width: | Height: | Size: 338 B |
BIN
data/images/game/slots/ammo-blessed.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
data/images/game/slots/back-blessed.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
data/images/game/slots/body-blessed.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
data/images/game/slots/feet-blessed.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
data/images/game/slots/finger-blessed.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
data/images/game/slots/head-blessed.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
data/images/game/slots/left-hand-blessed.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
data/images/game/slots/legs-blessed.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
data/images/game/slots/neck-blessed.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
data/images/game/slots/right-hand-blessed.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
data/images/topbuttons/unjustifiedpoints.png
Normal file
After Width: | Height: | Size: 421 B |
BIN
data/images/ui/item-blessed.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
@ -4,6 +4,10 @@ locale = {
|
||||
charset = "cp1252",
|
||||
languageName = "Deutsch",
|
||||
|
||||
formatNumbers = true,
|
||||
decimalSeperator = ',',
|
||||
thousandsSeperator = ' ',
|
||||
|
||||
translation = {
|
||||
["1a) Offensive Name"] = false,
|
||||
["1b) Invalid Name Format"] = false,
|
||||
|
@ -3,6 +3,10 @@ locale = {
|
||||
charset = "cp1252",
|
||||
languageName = "English",
|
||||
|
||||
formatNumbers = true,
|
||||
decimalSeperator = '.',
|
||||
thousandsSeperator = ',',
|
||||
|
||||
-- translations are not needed because everything is already in english
|
||||
translation = {}
|
||||
}
|
||||
|
@ -7,6 +7,10 @@ locale = {
|
||||
charset = "cp1252",
|
||||
languageName = "Español",
|
||||
|
||||
formatNumbers = true,
|
||||
decimalSeperator = ',',
|
||||
thousandsSeperator = '.',
|
||||
|
||||
translation = {
|
||||
["1a) Offensive Name"] = "1a) Nombre ofensivo",
|
||||
["1b) Invalid Name Format"] = "1b) Formato invalido para nombre",
|
||||
|
@ -2,6 +2,10 @@ locale = {
|
||||
name = "pl",
|
||||
languageName = "Polski",
|
||||
|
||||
formatNumbers = true,
|
||||
decimalSeperator = '.',
|
||||
thousandsSeperator = ' ',
|
||||
|
||||
translation = {
|
||||
["1a) Offensive Name"] = "1a) Obrazliwe Imie",
|
||||
["1b) Invalid Name Format"] = "1b) Niepoprawny Format Imienia",
|
||||
|
@ -3,6 +3,10 @@ locale = {
|
||||
charset = "cp1252",
|
||||
languageName = "Português",
|
||||
|
||||
formatNumbers = true,
|
||||
decimalSeperator = ',',
|
||||
thousandsSeperator = '.',
|
||||
|
||||
-- As traduções devem vir sempre em ordem alfabética.
|
||||
translation = {
|
||||
["%d of experience per hour"] = "%d de experiência por hora",
|
||||
|
@ -5,6 +5,10 @@ locale = {
|
||||
charset = "cp1252",
|
||||
languageName = "Svenska",
|
||||
|
||||
formatNumbers = true,
|
||||
decimalSeperator = ',',
|
||||
thousandsSeperator = ' ',
|
||||
|
||||
translation = {
|
||||
["1a) Offensive Name"] = "1a) Offensivt Namn",
|
||||
["1b) Invalid Name Format"] = "1b) Ogiltigt Namnformat",
|
||||
|
@ -8,6 +8,7 @@ Button < UIButton
|
||||
image-clip: 0 0 22 23
|
||||
image-border: 3
|
||||
padding: 5 10 5 10
|
||||
opacity: 1.0
|
||||
|
||||
$hover !disabled:
|
||||
image-clip: 0 23 22 23
|
||||
@ -45,6 +46,7 @@ NextButton < UIButton
|
||||
size: 12 21
|
||||
image-source: /images/ui/arrow_horizontal
|
||||
image-clip: 12 0 12 21
|
||||
image-color: #ffffff
|
||||
|
||||
$hover !disabled:
|
||||
image-clip: 12 21 12 21
|
||||
@ -53,12 +55,13 @@ NextButton < UIButton
|
||||
image-clip: 12 21 12 21
|
||||
|
||||
$disabled:
|
||||
image-color: #dfdfdf55
|
||||
image-color: #dfdfdf88
|
||||
|
||||
PreviousButton < UIButton
|
||||
size: 12 21
|
||||
image-source: /images/ui/arrow_horizontal
|
||||
image-clip: 0 0 12 21
|
||||
image-color: #ffffff
|
||||
|
||||
$hover !disabled:
|
||||
image-clip: 0 21 12 21
|
||||
@ -67,7 +70,7 @@ PreviousButton < UIButton
|
||||
image-clip: 0 21 12 21
|
||||
|
||||
$disabled:
|
||||
image-color: #dfdfdf55
|
||||
image-color: #dfdfdf88
|
||||
|
||||
AddButton < UIButton
|
||||
size: 20 20
|
||||
|
@ -27,7 +27,7 @@ MoveableTabBarButton < UIButton
|
||||
color: #dfdfdf
|
||||
|
||||
$on !checked:
|
||||
color: #dfdfdf
|
||||
color: #de6f6f
|
||||
|
||||
TabBar < UITabBar
|
||||
size: 80 21
|
||||
@ -36,10 +36,11 @@ TabBar < UITabBar
|
||||
anchors.fill: parent
|
||||
TabBarPanel < Panel
|
||||
TabBarButton < UIButton
|
||||
size: 22 23
|
||||
size: 20 21
|
||||
image-source: /images/ui/tabbutton_square
|
||||
image-source: /images/ui/tabbutton_square
|
||||
image-color: #dfdfdf
|
||||
image-clip: 0 0 22 23
|
||||
image-clip: 0 0 20 21
|
||||
image-border: 3
|
||||
image-border-bottom: 0
|
||||
icon-color: #dfdfdf
|
||||
@ -55,7 +56,7 @@ TabBarButton < UIButton
|
||||
margin-left: 5
|
||||
|
||||
$hover !checked:
|
||||
image-clip: 0 23 22 23
|
||||
image-clip: 0 21 20 21
|
||||
color: #dfdfdf
|
||||
|
||||
$disabled:
|
||||
@ -63,7 +64,7 @@ TabBarButton < UIButton
|
||||
icon-color: #dfdfdf
|
||||
|
||||
$checked:
|
||||
image-clip: 0 46 22 23
|
||||
image-clip: 0 42 20 21
|
||||
color: #dfdfdf
|
||||
|
||||
$on !checked:
|
||||
@ -73,6 +74,14 @@ TabBarRounded < TabBar
|
||||
TabBarRoundedPanel < TabBarPanel
|
||||
TabBarRoundedButton < TabBarButton
|
||||
image-source: /images/ui/tabbutton_rounded
|
||||
size: 22 23
|
||||
image-clip: 0 0 22 23
|
||||
|
||||
$hover !checked:
|
||||
image-clip: 0 23 22 23
|
||||
|
||||
$checked:
|
||||
image-clip: 0 46 22 23
|
||||
|
||||
TabBarVertical < UITabBar
|
||||
width: 96
|
||||
|
@ -1,26 +1,62 @@
|
||||
Table < UITable
|
||||
layout: verticalBox
|
||||
header-column-style: HeaderTableColumn
|
||||
header-row-style: HeaderTableRow
|
||||
header-column-style: TableHeaderColumn
|
||||
header-row-style: TableHeaderRow
|
||||
column-style: TableColumn
|
||||
row-style: TableRow
|
||||
|
||||
TableData < UIScrollArea
|
||||
layout: verticalBox
|
||||
|
||||
TableRow < Label
|
||||
TableRow < UITableRow
|
||||
layout: horizontalBox
|
||||
height: 10
|
||||
text-wrap: true
|
||||
focusable: true
|
||||
even-background-color: alpha
|
||||
odd-background-color: #00000022
|
||||
|
||||
$focus:
|
||||
background-color: #294f6d
|
||||
color: #ffffff
|
||||
|
||||
TableColumn < Label
|
||||
width: 30
|
||||
text-wrap: true
|
||||
focusable: false
|
||||
|
||||
TableHeaderRow < Label
|
||||
layout: horizontalBox
|
||||
focusable: false
|
||||
height: 10
|
||||
text-wrap: true
|
||||
|
||||
TableHeaderColumn < Button
|
||||
width: 30
|
||||
TableHeaderColumn < UITableHeaderColumn
|
||||
font: verdana-11px-antialised
|
||||
background-color: alpha
|
||||
color: #dfdfdfff
|
||||
height: 23
|
||||
focusable: true
|
||||
text-offset: 0 0
|
||||
image-source: /images/ui/button
|
||||
image-color: #dfdfdf
|
||||
image-clip: 0 0 22 23
|
||||
image-border: 3
|
||||
padding: 5 10 5 10
|
||||
enabled: false
|
||||
focusable: false
|
||||
|
||||
$hover !disabled:
|
||||
image-clip: 0 23 22 23
|
||||
|
||||
$pressed:
|
||||
image-clip: 0 46 22 23
|
||||
text-offset: 1 1
|
||||
|
||||
$disabled:
|
||||
color: #dfdfdf88
|
||||
opacity: 0.8
|
||||
|
||||
SortableTableHeaderColumn < TableHeaderColumn
|
||||
enabled: true
|
||||
focusable: true
|
@ -27,7 +27,7 @@ local function tryLogin(charInfo, tries)
|
||||
|
||||
CharacterList.hide()
|
||||
|
||||
g_game.loginWorld(G.account, G.password, charInfo.worldName, charInfo.worldHost, charInfo.worldPort, charInfo.characterName)
|
||||
g_game.loginWorld(G.account, G.password, charInfo.worldName, charInfo.worldHost, charInfo.worldPort, charInfo.characterName, G.authenticatorToken, G.sessionKey)
|
||||
|
||||
loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to game server...'))
|
||||
connect(loadBox, { onCancel = function()
|
||||
@ -109,6 +109,16 @@ function onGameLoginError(message)
|
||||
end
|
||||
end
|
||||
|
||||
function onGameLoginToken(unknown)
|
||||
CharacterList.destroyLoadBox()
|
||||
-- TODO: make it possible to enter a new token here / prompt token
|
||||
errorBox = displayErrorBox(tr("Two-Factor Authentification"), 'A new authentification token is required.\nPlease login again.')
|
||||
errorBox.onOk = function()
|
||||
errorBox = nil
|
||||
EnterGame.show()
|
||||
end
|
||||
end
|
||||
|
||||
function onGameConnectionError(message, code)
|
||||
CharacterList.destroyLoadBox()
|
||||
local text = translateNetworkError(code, g_game.getProtocolGame() and g_game.getProtocolGame():isConnecting(), message)
|
||||
@ -131,6 +141,7 @@ end
|
||||
-- public functions
|
||||
function CharacterList.init()
|
||||
connect(g_game, { onLoginError = onGameLoginError })
|
||||
connect(g_game, { onLoginToken = onGameLoginToken })
|
||||
connect(g_game, { onUpdateNeeded = onGameUpdateNeeded })
|
||||
connect(g_game, { onConnectionError = onGameConnectionError })
|
||||
connect(g_game, { onGameStart = CharacterList.destroyLoadBox })
|
||||
@ -144,6 +155,7 @@ end
|
||||
|
||||
function CharacterList.terminate()
|
||||
disconnect(g_game, { onLoginError = onGameLoginError })
|
||||
disconnect(g_game, { onLoginToken = onGameLoginToken })
|
||||
disconnect(g_game, { onUpdateNeeded = onGameUpdateNeeded })
|
||||
disconnect(g_game, { onConnectionError = onGameConnectionError })
|
||||
disconnect(g_game, { onGameStart = CharacterList.destroyLoadBox })
|
||||
|
@ -16,7 +16,7 @@ CharacterWidget < UIWidget
|
||||
|
||||
Label
|
||||
id: name
|
||||
color: #aaaaaa
|
||||
color: #bbbbbb
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
font: verdana-11px-monochrome
|
||||
@ -29,8 +29,7 @@ CharacterWidget < UIWidget
|
||||
|
||||
Label
|
||||
id: worldName
|
||||
color: #ffffff
|
||||
color: #aaaaaa
|
||||
color: #bbbbbb
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
margin-right: 5
|
||||
@ -45,16 +44,21 @@ CharacterWidget < UIWidget
|
||||
MainWindow
|
||||
id: charactersWindow
|
||||
!text: tr('Character List')
|
||||
size: 250 248
|
||||
visible: false
|
||||
@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)
|
||||
if g_game.getFeature(GamePreviewState) then
|
||||
self:setSize({width = 350, height = 400})
|
||||
else
|
||||
self:setSize({width = 250, height = 248})
|
||||
end
|
||||
|
||||
TextList
|
||||
id: characters
|
||||
background-color: #565656
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: characterListScrollBar.left
|
||||
|
@ -33,10 +33,17 @@ local function onMotd(protocol, motd)
|
||||
end
|
||||
end
|
||||
|
||||
local function onSessionKey(protocol, sessionKey)
|
||||
G.sessionKey = sessionKey
|
||||
end
|
||||
|
||||
local function onCharacterList(protocol, characters, account, otui)
|
||||
-- Try add server to the server list
|
||||
ServerList.add(G.host, G.port, g_game.getClientVersion())
|
||||
|
||||
-- Save 'Stay logged in' setting
|
||||
g_settings.set('staylogged', enterGame:getChildById('stayLoggedBox'):isChecked())
|
||||
|
||||
if enterGame:getChildById('rememberPasswordBox'):isChecked() then
|
||||
local account = g_crypt.encrypt(G.account)
|
||||
local password = g_crypt.encrypt(G.password)
|
||||
@ -59,6 +66,12 @@ local function onCharacterList(protocol, characters, account, otui)
|
||||
loadBox:destroy()
|
||||
loadBox = nil
|
||||
|
||||
for _, characterInfo in pairs(characters) do
|
||||
if characterInfo.previewState and characterInfo.previewState ~= PreviewState.Default then
|
||||
characterInfo.worldName = characterInfo.worldName .. ', Preview'
|
||||
end
|
||||
end
|
||||
|
||||
CharacterList.create(characters, account, otui)
|
||||
CharacterList.show()
|
||||
|
||||
@ -103,9 +116,10 @@ function EnterGame.init()
|
||||
local password = g_settings.get('password')
|
||||
local host = g_settings.get('host')
|
||||
local port = g_settings.get('port')
|
||||
local stayLogged = g_settings.getBoolean('staylogged')
|
||||
local autologin = g_settings.getBoolean('autologin')
|
||||
local clientVersion = g_settings.getInteger('client-version')
|
||||
if clientVersion == 0 then clientVersion = 860 end
|
||||
if clientVersion == 0 then clientVersion = 1074 end
|
||||
|
||||
if port == nil or port == 0 then port = 7171 end
|
||||
|
||||
@ -115,6 +129,7 @@ function EnterGame.init()
|
||||
enterGame:getChildById('serverHostTextEdit'):setText(host)
|
||||
enterGame:getChildById('serverPortTextEdit'):setText(port)
|
||||
enterGame:getChildById('autoLoginBox'):setChecked(autologin)
|
||||
enterGame:getChildById('stayLoggedBox'):setChecked(stayLogged)
|
||||
|
||||
clientBox = enterGame:getChildById('clientComboBox')
|
||||
for _, proto in pairs(g_game.getSupportedClients()) do
|
||||
@ -122,6 +137,10 @@ function EnterGame.init()
|
||||
end
|
||||
clientBox:setCurrentOption(clientVersion)
|
||||
|
||||
EnterGame.toggleAuthenticatorToken(clientVersion, true)
|
||||
EnterGame.toggleStayLoggedBox(clientVersion, true)
|
||||
connect(clientBox, { onOptionChange = EnterGame.onClientVersionChange })
|
||||
|
||||
enterGame:hide()
|
||||
|
||||
if g_app.isRunning() and not g_game.isOnline() then
|
||||
@ -146,6 +165,7 @@ end
|
||||
|
||||
function EnterGame.terminate()
|
||||
g_keyboard.unbindKeyDown('Ctrl+G')
|
||||
disconnect(clientBox, { onOptionChange = EnterGame.onClientVersionChange })
|
||||
enterGame:destroy()
|
||||
enterGame = nil
|
||||
enterGameButton:destroy()
|
||||
@ -204,14 +224,80 @@ end
|
||||
function EnterGame.clearAccountFields()
|
||||
enterGame:getChildById('accountNameTextEdit'):clearText()
|
||||
enterGame:getChildById('accountPasswordTextEdit'):clearText()
|
||||
enterGame:getChildById('authenticatorTokenTextEdit'):clearText()
|
||||
enterGame:getChildById('accountNameTextEdit'):focus()
|
||||
g_settings.remove('account')
|
||||
g_settings.remove('password')
|
||||
end
|
||||
|
||||
function EnterGame.toggleAuthenticatorToken(clientVersion, init)
|
||||
local enabled = (clientVersion >= 1072)
|
||||
if enabled == enterGame.authenticatorEnabled then
|
||||
return
|
||||
end
|
||||
|
||||
enterGame:getChildById('authenticatorTokenLabel'):setOn(enabled)
|
||||
enterGame:getChildById('authenticatorTokenTextEdit'):setOn(enabled)
|
||||
|
||||
local newHeight = enterGame:getHeight()
|
||||
local newY = enterGame:getY()
|
||||
if enabled then
|
||||
newY = newY - enterGame.authenticatorHeight
|
||||
newHeight = newHeight + enterGame.authenticatorHeight
|
||||
else
|
||||
newY = newY + enterGame.authenticatorHeight
|
||||
newHeight = newHeight - enterGame.authenticatorHeight
|
||||
end
|
||||
|
||||
if not init then
|
||||
enterGame:breakAnchors()
|
||||
enterGame:setY(newY)
|
||||
enterGame:bindRectToParent()
|
||||
end
|
||||
enterGame:setHeight(newHeight)
|
||||
|
||||
enterGame.authenticatorEnabled = enabled
|
||||
end
|
||||
|
||||
function EnterGame.toggleStayLoggedBox(clientVersion, init)
|
||||
local enabled = (clientVersion >= 1074)
|
||||
if enabled == enterGame.stayLoggedBoxEnabled then
|
||||
return
|
||||
end
|
||||
|
||||
enterGame:getChildById('stayLoggedBox'):setOn(enabled)
|
||||
|
||||
local newHeight = enterGame:getHeight()
|
||||
local newY = enterGame:getY()
|
||||
if enabled then
|
||||
newY = newY - enterGame.stayLoggedBoxHeight
|
||||
newHeight = newHeight + enterGame.stayLoggedBoxHeight
|
||||
else
|
||||
newY = newY + enterGame.stayLoggedBoxHeight
|
||||
newHeight = newHeight - enterGame.stayLoggedBoxHeight
|
||||
end
|
||||
|
||||
if not init then
|
||||
enterGame:breakAnchors()
|
||||
enterGame:setY(newY)
|
||||
enterGame:bindRectToParent()
|
||||
end
|
||||
enterGame:setHeight(newHeight)
|
||||
|
||||
enterGame.stayLoggedBoxEnabled = enabled
|
||||
end
|
||||
|
||||
function EnterGame.onClientVersionChange(comboBox, text, data)
|
||||
local clientVersion = tonumber(text)
|
||||
EnterGame.toggleAuthenticatorToken(clientVersion)
|
||||
EnterGame.toggleStayLoggedBox(clientVersion)
|
||||
end
|
||||
|
||||
function EnterGame.doLogin()
|
||||
G.account = enterGame:getChildById('accountNameTextEdit'):getText()
|
||||
G.password = enterGame:getChildById('accountPasswordTextEdit'):getText()
|
||||
G.authenticatorToken = enterGame:getChildById('authenticatorTokenTextEdit'):getText()
|
||||
G.stayLogged = enterGame:getChildById('stayLoggedBox'):isChecked()
|
||||
G.host = enterGame:getChildById('serverHostTextEdit'):getText()
|
||||
G.port = tonumber(enterGame:getChildById('serverPortTextEdit'):getText())
|
||||
local clientVersion = tonumber(clientBox:getText())
|
||||
@ -230,6 +316,7 @@ function EnterGame.doLogin()
|
||||
protocolLogin = ProtocolLogin.create()
|
||||
protocolLogin.onLoginError = onError
|
||||
protocolLogin.onMotd = onMotd
|
||||
protocolLogin.onSessionKey = onSessionKey
|
||||
protocolLogin.onCharacterList = onCharacterList
|
||||
protocolLogin.onUpdateNeeded = onUpdateNeeded
|
||||
|
||||
@ -240,12 +327,12 @@ function EnterGame.doLogin()
|
||||
EnterGame.show()
|
||||
end })
|
||||
|
||||
g_game.chooseRsa(G.host)
|
||||
g_game.setClientVersion(clientVersion)
|
||||
g_game.setProtocolVersion(g_game.getClientProtocolVersion(clientVersion))
|
||||
g_game.chooseRsa(G.host)
|
||||
|
||||
if modules.game_things.isLoaded() then
|
||||
protocolLogin:login(G.host, G.port, G.account, G.password)
|
||||
protocolLogin:login(G.host, G.port, G.account, G.password, G.authenticatorToken, G.stayLogged)
|
||||
else
|
||||
loadBox:destroy()
|
||||
loadBox = nil
|
||||
@ -266,6 +353,7 @@ function EnterGame.setDefaultServer(host, port, protocol)
|
||||
local clientLabel = enterGame:getChildById('clientLabel')
|
||||
local accountTextEdit = enterGame:getChildById('accountNameTextEdit')
|
||||
local passwordTextEdit = enterGame:getChildById('accountPasswordTextEdit')
|
||||
local authenticatorTokenTextEdit = enterGame:getChildById('authenticatorTokenTextEdit')
|
||||
|
||||
if hostTextEdit:getText() ~= host then
|
||||
hostTextEdit:setText(host)
|
||||
@ -273,6 +361,7 @@ function EnterGame.setDefaultServer(host, port, protocol)
|
||||
clientBox:setCurrentOption(protocol)
|
||||
accountTextEdit:setText('')
|
||||
passwordTextEdit:setText('')
|
||||
authenticatorTokenTextEdit:setText('')
|
||||
end
|
||||
end
|
||||
|
||||
@ -286,6 +375,16 @@ function EnterGame.setUniqueServer(host, port, protocol, windowWidth, windowHeig
|
||||
portTextEdit:setVisible(false)
|
||||
portTextEdit:setHeight(0)
|
||||
|
||||
local authenticatorTokenTextEdit = enterGame:getChildById('authenticatorTokenTextEdit')
|
||||
authenticatorTokenTextEdit:setText('')
|
||||
authenticatorTokenTextEdit:setOn(false)
|
||||
local authenticatorTokenLabel = enterGame:getChildById('authenticatorTokenLabel')
|
||||
authenticatorTokenLabel:setOn(false)
|
||||
|
||||
local stayLoggedBox = enterGame:getChildById('stayLoggedBox')
|
||||
stayLoggedBox:setChecked(false)
|
||||
stayLoggedBox:setOn(false)
|
||||
|
||||
clientBox:setCurrentOption(protocol)
|
||||
clientBox:setVisible(false)
|
||||
clientBox:setHeight(0)
|
||||
@ -306,11 +405,11 @@ function EnterGame.setUniqueServer(host, port, protocol, windowWidth, windowHeig
|
||||
serverListButton:setWidth(0)
|
||||
|
||||
local rememberPasswordBox = enterGame:getChildById('rememberPasswordBox')
|
||||
rememberPasswordBox:setMarginTop(-5)
|
||||
rememberPasswordBox:setMarginTop(-8)
|
||||
|
||||
if not windowWidth then windowWidth = 236 end
|
||||
enterGame:setWidth(windowWidth)
|
||||
if not windowHeight then windowHeight = 200 end
|
||||
if not windowHeight then windowHeight = 210 end
|
||||
enterGame:setHeight(windowHeight)
|
||||
end
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
EnterGameWindow < MainWindow
|
||||
!text: tr('Enter Game')
|
||||
size: 236 274
|
||||
size: 236 298
|
||||
|
||||
EnterGameButton < Button
|
||||
width: 64
|
||||
@ -21,6 +21,10 @@ ServerListButton < UIButton
|
||||
|
||||
EnterGameWindow
|
||||
id: enterGame
|
||||
&authenticatorEnabled: false
|
||||
&authenticatorHeight: 44
|
||||
&stayLoggedBoxEnabled: false
|
||||
&stayLoggedBoxHeight: 24
|
||||
@onEnter: EnterGame.doLogin()
|
||||
|
||||
MenuLabel
|
||||
@ -50,6 +54,52 @@ EnterGameWindow
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 2
|
||||
|
||||
MenuLabel
|
||||
id: authenticatorTokenLabel
|
||||
!text: tr('Authenticator Token')
|
||||
anchors.left: prev.left
|
||||
anchors.top: prev.bottom
|
||||
text-auto-resize: true
|
||||
margin-top: -12
|
||||
visible: false
|
||||
|
||||
$on:
|
||||
visible: true
|
||||
margin-top: 8
|
||||
|
||||
TextEdit
|
||||
id: authenticatorTokenTextEdit
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: -22
|
||||
visible: false
|
||||
max-length: 8
|
||||
|
||||
$on:
|
||||
visible: true
|
||||
margin-top: 2
|
||||
|
||||
CheckBox
|
||||
id: stayLoggedBox
|
||||
!text: tr('Stay logged during session')
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 6
|
||||
margin-top: -16
|
||||
visible: false
|
||||
|
||||
$on:
|
||||
visible: true
|
||||
margin-top: 8
|
||||
|
||||
HorizontalSeparator
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 8
|
||||
|
||||
MenuLabel
|
||||
id: serverLabel
|
||||
!text: tr('Server')
|
||||
@ -132,16 +182,24 @@ EnterGameWindow
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 2
|
||||
|
||||
HorizontalSeparator
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 6
|
||||
|
||||
EnterGameButton
|
||||
!text: tr('Ok')
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.top: prev.bottom
|
||||
margin-top: 4
|
||||
@onClick: EnterGame.doLogin()
|
||||
|
||||
Label
|
||||
id: serverInfoLabel
|
||||
font: verdana-11px-rounded
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.top: prev.top
|
||||
anchors.left: parent.left
|
||||
margin-top: 5
|
||||
color: green
|
||||
text-auto-resize: true
|
||||
text-auto-resize: true
|
@ -166,16 +166,20 @@ end
|
||||
-- global function used to translate texts
|
||||
function _G.tr(text, ...)
|
||||
if currentLocale then
|
||||
if tonumber(text) then
|
||||
-- todo: use locale information to calculate this. also detect floating numbers
|
||||
if tonumber(text) and currentLocale.formatNumbers then
|
||||
local number = tostring(text):split('.')
|
||||
local out = ''
|
||||
local number = tostring(text):reverse()
|
||||
for i=1,#number do
|
||||
out = out .. number:sub(i, i)
|
||||
local reverseNumber = number[1]:reverse()
|
||||
for i=1,#reverseNumber do
|
||||
out = out .. reverseNumber:sub(i, i)
|
||||
if i % 3 == 0 and i ~= #number then
|
||||
out = out .. ','
|
||||
out = out .. currentLocale.thousandsSeperator
|
||||
end
|
||||
end
|
||||
|
||||
if number[2] then
|
||||
out = number[2] .. currentLocale.decimalSeperator .. out
|
||||
end
|
||||
return out:reverse()
|
||||
elseif tostring(text) then
|
||||
local translation = currentLocale.translation[text]
|
||||
|
@ -30,6 +30,10 @@ local allLines = {}
|
||||
|
||||
-- private functions
|
||||
local function navigateCommand(step)
|
||||
if commandTextEdit:isMultiline() then
|
||||
return
|
||||
end
|
||||
|
||||
local numCommands = #commandHistory
|
||||
if numCommands > 0 then
|
||||
currentHistoryIndex = math.min(math.max(currentHistoryIndex + step, 0), numCommands)
|
||||
@ -96,16 +100,29 @@ local function completeCommand()
|
||||
end
|
||||
end
|
||||
|
||||
local function doCommand()
|
||||
local currentCommand = commandTextEdit:getText()
|
||||
local function doCommand(textWidget)
|
||||
local currentCommand = textWidget:getText()
|
||||
executeCommand(currentCommand)
|
||||
|
||||
if commandTextEdit then
|
||||
commandTextEdit:clearText()
|
||||
end
|
||||
textWidget:clearText()
|
||||
return true
|
||||
end
|
||||
|
||||
local function addNewline(textWidget)
|
||||
if not textWidget:isOn() then
|
||||
textWidget:setOn(true)
|
||||
end
|
||||
textWidget:appendText('\n')
|
||||
end
|
||||
|
||||
local function onCommandChange(textWidget, newText, oldText)
|
||||
local _, newLineCount = string.gsub(newText, '\n', '\n')
|
||||
textWidget:setHeight((newLineCount + 1) * textWidget.baseHeight)
|
||||
|
||||
if newLineCount == 0 and textWidget:isOn() then
|
||||
textWidget:setOn(false)
|
||||
end
|
||||
end
|
||||
|
||||
local function onLog(level, message, time)
|
||||
if disabled then return end
|
||||
-- avoid logging while reporting logs (would cause a infinite loop)
|
||||
@ -129,6 +146,8 @@ function init()
|
||||
commandHistory = g_settings.getList('terminal-history')
|
||||
|
||||
commandTextEdit = terminalWindow:getChildById('commandTextEdit')
|
||||
commandTextEdit:setHeight(commandTextEdit.baseHeight)
|
||||
connect(commandTextEdit, {onTextChange = onCommandChange})
|
||||
g_keyboard.bindKeyPress('Up', function() navigateCommand(1) end, commandTextEdit)
|
||||
g_keyboard.bindKeyPress('Down', function() navigateCommand(-1) end, commandTextEdit)
|
||||
g_keyboard.bindKeyPress('Ctrl+C',
|
||||
@ -138,6 +157,7 @@ function init()
|
||||
return true
|
||||
end, commandTextEdit)
|
||||
g_keyboard.bindKeyDown('Tab', completeCommand, commandTextEdit)
|
||||
g_keyboard.bindKeyPress('Shift+Enter', addNewline, commandTextEdit)
|
||||
g_keyboard.bindKeyDown('Enter', doCommand, commandTextEdit)
|
||||
g_keyboard.bindKeyDown('Escape', hide, terminalWindow)
|
||||
|
||||
@ -293,7 +313,7 @@ function addLine(text, color)
|
||||
end
|
||||
|
||||
function executeCommand(command)
|
||||
if command == nil or #command == 0 then return end
|
||||
if command == nil or #string.gsub(command, '\n', '') == 0 then return end
|
||||
|
||||
-- add command line
|
||||
addLine("> " .. command, "#ffffff")
|
||||
|
@ -47,7 +47,7 @@ UIWindow
|
||||
anchors.left: parent.left
|
||||
anchors.right: terminalScroll.left
|
||||
anchors.top: terminalScroll.top
|
||||
anchors.bottom: commandSymbolLabel.top
|
||||
anchors.bottom: commandTextEdit.top
|
||||
layout:
|
||||
type: verticalBox
|
||||
align-bottom: true
|
||||
@ -80,14 +80,25 @@ UIWindow
|
||||
|
||||
UITextEdit
|
||||
id: commandTextEdit
|
||||
height: 12
|
||||
background: #aaaaaa11
|
||||
border-color: #aaaaaa88
|
||||
&baseHeight: 12
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: commandSymbolLabel.right
|
||||
anchors.right: parent.right
|
||||
anchors.right: terminalScroll.left
|
||||
margin-left: 1
|
||||
padding-left: 2
|
||||
font: terminus-10px
|
||||
selection-color: black
|
||||
selection-background-color: white
|
||||
border-width-left: 0
|
||||
border-width-top: 0
|
||||
multiline: false
|
||||
|
||||
$on:
|
||||
border-width-left: 1
|
||||
border-width-top: 1
|
||||
multiline: true
|
||||
|
||||
ResizeBorder
|
||||
id: bottomResizeBorder
|
||||
|
@ -17,19 +17,19 @@ local function updateMargins(tabBar, ignored)
|
||||
end
|
||||
|
||||
local function updateNavigation(tabBar)
|
||||
if prevNavigation then
|
||||
if tabBar.prevNavigation then
|
||||
if #tabBar.preTabs > 0 or table.find(tabBar.tabs, tabBar.currentTab) ~= 1 then
|
||||
prevNavigation:enable()
|
||||
tabBar.prevNavigation:enable()
|
||||
else
|
||||
prevNavigation:disable()
|
||||
tabBar.prevNavigation:disable()
|
||||
end
|
||||
end
|
||||
|
||||
if nextNavigation then
|
||||
if tabBar.nextNavigation then
|
||||
if #tabBar.postTabs > 0 or table.find(tabBar.tabs, tabBar.currentTab) ~= #tabBar.tabs then
|
||||
nextNavigation:enable()
|
||||
tabBar.nextNavigation:enable()
|
||||
else
|
||||
nextNavigation:disable()
|
||||
tabBar.nextNavigation:disable()
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -187,7 +187,7 @@ local function onTabDragMove(tab, mousePos, mouseMoved)
|
||||
end
|
||||
|
||||
local function tabBlink(tab, step)
|
||||
step = step or 0
|
||||
local step = step or 0
|
||||
tab:setOn(not tab:isOn())
|
||||
|
||||
removeEvent(tab.blinkEvent)
|
||||
@ -218,6 +218,19 @@ function UIMoveableTabBar.create()
|
||||
return tabbar
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:onDestroy()
|
||||
if self.prevNavigation then
|
||||
self.prevNavigation:disable()
|
||||
end
|
||||
|
||||
if self.nextNavigation then
|
||||
self.nextNavigation:disable()
|
||||
end
|
||||
|
||||
self.nextNavigation = nil
|
||||
self.prevNavigation = nil
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:setContentWidget(widget)
|
||||
self.contentWidget = widget
|
||||
if #self.tabs > 0 then
|
||||
@ -467,14 +480,14 @@ function UIMoveableTabBar:getCurrentTab()
|
||||
end
|
||||
|
||||
function UIMoveableTabBar:setNavigation(prevButton, nextButton)
|
||||
prevNavigation = prevButton
|
||||
nextNavigation = nextButton
|
||||
self.prevNavigation = prevButton
|
||||
self.nextNavigation = nextButton
|
||||
|
||||
if prevNavigation then
|
||||
prevNavigation.onClick = function() self:selectPrevTab() end
|
||||
if self.prevNavigation then
|
||||
self.prevNavigation.onClick = function() self:selectPrevTab() end
|
||||
end
|
||||
if nextNavigation then
|
||||
nextNavigation.onClick = function() self:selectNextTab() end
|
||||
if self.nextNavigation then
|
||||
self.nextNavigation.onClick = function() self:selectNextTab() end
|
||||
end
|
||||
updateNavigation(self)
|
||||
end
|
||||
|
@ -8,6 +8,7 @@ function UIPopupMenu.create()
|
||||
local layout = UIVerticalLayout.create(menu)
|
||||
layout:setFitChildren(true)
|
||||
menu:setLayout(layout)
|
||||
menu.isGameMenu = false
|
||||
return menu
|
||||
end
|
||||
|
||||
@ -34,6 +35,7 @@ function UIPopupMenu:display(pos)
|
||||
rootWidget:addChild(self)
|
||||
self:setPosition(pos)
|
||||
self:grabMouse()
|
||||
self:focus()
|
||||
--self:grabKeyboard()
|
||||
currentMenu = self
|
||||
end
|
||||
@ -76,6 +78,10 @@ function UIPopupMenu:addSeparator()
|
||||
g_ui.createWidget(self:getStyleName() .. 'Separator', self)
|
||||
end
|
||||
|
||||
function UIPopupMenu:setGameMenu(state)
|
||||
self.isGameMenu = state
|
||||
end
|
||||
|
||||
function UIPopupMenu:onDestroy()
|
||||
if currentMenu == self then
|
||||
currentMenu = nil
|
||||
@ -105,4 +111,12 @@ local function onRootGeometryUpdate()
|
||||
currentMenu:destroy()
|
||||
end
|
||||
end
|
||||
connect(rootWidget, { onGeometryChange = onRootGeometryUpdate} )
|
||||
|
||||
local function onGameEnd()
|
||||
if currentMenu and currentMenu.isGameMenu then
|
||||
currentMenu:destroy()
|
||||
end
|
||||
end
|
||||
|
||||
connect(rootWidget, { onGeometryChange = onRootGeometryUpdate })
|
||||
connect(g_game, { onGameEnd = onGameEnd } )
|
||||
|
@ -12,7 +12,7 @@ function UISpinBox.create()
|
||||
spinbox.step = 1
|
||||
spinbox.firstchange = true
|
||||
spinbox.mouseScroll = true
|
||||
spinbox:setText("0")
|
||||
spinbox:setText("1")
|
||||
spinbox:setValue(1)
|
||||
return spinbox
|
||||
end
|
||||
@ -23,7 +23,7 @@ function UISpinBox:onSetup()
|
||||
end
|
||||
|
||||
function UISpinBox:onMouseWheel(mousePos, direction)
|
||||
if not self.mouseScroll then
|
||||
if not self.mouseScroll then
|
||||
return false
|
||||
end
|
||||
if direction == MouseWheelUp then
|
||||
@ -66,7 +66,15 @@ function UISpinBox:onTextChange(text, oldText)
|
||||
end
|
||||
|
||||
function UISpinBox:onValueChange(value)
|
||||
-- nothing todo
|
||||
-- nothing to do
|
||||
end
|
||||
|
||||
function UISpinBox:onFocusChange(focused)
|
||||
if not focused then
|
||||
if self:getText():len() == 0 then
|
||||
self:setText(self.minimum)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UISpinBox:onStyleApply(styleName, styleNode)
|
||||
@ -109,14 +117,16 @@ function UISpinBox:down()
|
||||
self:setValue(self.value - self.step)
|
||||
end
|
||||
|
||||
function UISpinBox:setValue(value)
|
||||
function UISpinBox:setValue(value, dontSignal)
|
||||
value = value or 0
|
||||
value = math.max(math.min(self.maximum, value), self.minimum)
|
||||
|
||||
if value == self.value then return end
|
||||
|
||||
self.value = value
|
||||
if self:getText():len() > 0 then
|
||||
self:setText(value)
|
||||
end
|
||||
self.value = value
|
||||
|
||||
local upButton = self:getChildById('up')
|
||||
local downButton = self:getChildById('down')
|
||||
@ -127,7 +137,9 @@ function UISpinBox:setValue(value)
|
||||
downButton:setEnabled(self.maximum ~= self.minimum and self.value ~= self.minimum)
|
||||
end
|
||||
|
||||
signalcall(self.onValueChange, self, value)
|
||||
if not dontSignal then
|
||||
signalcall(self.onValueChange, self, value)
|
||||
end
|
||||
end
|
||||
|
||||
function UISpinBox:getValue()
|
||||
|
@ -38,6 +38,7 @@ function UITabBar:addTab(text, panel, icon)
|
||||
end
|
||||
|
||||
local tab = g_ui.createWidget(self:getStyleName() .. 'Button', self.buttonsPanel)
|
||||
|
||||
panel.isTab = true
|
||||
tab.tabPanel = panel
|
||||
tab.tabBar = self
|
||||
|
@ -3,33 +3,45 @@
|
||||
TODO:
|
||||
* Make table headers more robust.
|
||||
* Get dynamic row heights working with text wrapping.
|
||||
* Every second row different background color applied.
|
||||
]]
|
||||
|
||||
TABLE_SORTING_ASC = 0
|
||||
TABLE_SORTING_DESC = 1
|
||||
|
||||
UITable = extends(UIWidget, "UITable")
|
||||
|
||||
local HEADER_ID = 'row0'
|
||||
|
||||
-- Initialize default values
|
||||
function UITable.create()
|
||||
local table = UITable.internalCreate()
|
||||
table.headerRow = nil
|
||||
table.headerColumns = {}
|
||||
table.dataSpace = nil
|
||||
table.rows = {}
|
||||
table.rowBaseStyle = nil
|
||||
table.columns = {}
|
||||
table.columnWidth = {}
|
||||
table.columBaseStyle = nil
|
||||
table.headerRowBaseStyle = nil
|
||||
table.headerColumnBaseStyle = nil
|
||||
table.selectedRow = nil
|
||||
table.defaultColumnWidth = 80
|
||||
table.sortColumn = -1
|
||||
table.sortType = TABLE_SORTING_ASC
|
||||
table.autoSort = false
|
||||
|
||||
return table
|
||||
end
|
||||
|
||||
-- Clear table values
|
||||
function UITable:onDestroy()
|
||||
for k,row in pairs(self.rows) do
|
||||
for _,row in pairs(self.rows) do
|
||||
row.onClick = nil
|
||||
end
|
||||
self.rows = {}
|
||||
self.columns = {}
|
||||
self.headerRow = {}
|
||||
self.headerRow = nil
|
||||
self.headerColumns = {}
|
||||
self.columnWidth = {}
|
||||
self.selectedRow = nil
|
||||
|
||||
if self.dataSpace then
|
||||
@ -38,36 +50,58 @@ function UITable:onDestroy()
|
||||
end
|
||||
end
|
||||
|
||||
-- Detect if a header is already defined
|
||||
function UITable:onSetup()
|
||||
local header = self:getChildById('header')
|
||||
if header then
|
||||
self:setHeader(header)
|
||||
end
|
||||
end
|
||||
|
||||
-- Parse table related styles
|
||||
function UITable:onStyleApply(styleName, styleNode)
|
||||
for name, value in pairs(styleNode) do
|
||||
if name == 'table-data' then
|
||||
addEvent(function()
|
||||
self:setTableData(self:getParent():getChildById(value))
|
||||
end)
|
||||
elseif name == 'column-style' then
|
||||
addEvent(function()
|
||||
self:setColumnStyle(value)
|
||||
end)
|
||||
elseif name == 'row-style' then
|
||||
addEvent(function()
|
||||
self:setRowStyle(value)
|
||||
end)
|
||||
elseif name == 'header-column-style' then
|
||||
addEvent(function()
|
||||
self:setHeaderColumnStyle(value)
|
||||
end)
|
||||
elseif name == 'header-row-style' then
|
||||
addEvent(function()
|
||||
self:setHeaderRowStyle(value)
|
||||
end)
|
||||
if value ~= false then
|
||||
if name == 'table-data' then
|
||||
addEvent(function()
|
||||
self:setTableData(self:getParent():getChildById(value))
|
||||
end)
|
||||
elseif name == 'column-style' then
|
||||
addEvent(function()
|
||||
self:setColumnStyle(value)
|
||||
end)
|
||||
elseif name == 'row-style' then
|
||||
addEvent(function()
|
||||
self:setRowStyle(value)
|
||||
end)
|
||||
elseif name == 'header-column-style' then
|
||||
addEvent(function()
|
||||
self:setHeaderColumnStyle(value)
|
||||
end)
|
||||
elseif name == 'header-row-style' then
|
||||
addEvent(function()
|
||||
self:setHeaderRowStyle(value)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UITable:setColumnWidth(width)
|
||||
if self:hasHeader() then return end
|
||||
self.columnWidth = width
|
||||
end
|
||||
|
||||
function UITable:setDefaultColumnWidth(width)
|
||||
self.defaultColumnWidth = width
|
||||
end
|
||||
|
||||
-- Check if the table has a header
|
||||
function UITable:hasHeader()
|
||||
return self.headerRow ~= nil
|
||||
end
|
||||
|
||||
-- Clear all rows
|
||||
function UITable:clearData()
|
||||
if not self.dataSpace then
|
||||
return
|
||||
@ -78,16 +112,42 @@ function UITable:clearData()
|
||||
self.rows = {}
|
||||
end
|
||||
|
||||
function UITable:addHeaderRow(data)
|
||||
-- Set existing child as header
|
||||
function UITable:setHeader(headerWidget)
|
||||
self:removeHeader()
|
||||
|
||||
if self.dataSpace then
|
||||
local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop()
|
||||
self.dataSpace:applyStyle({ height = newHeight })
|
||||
end
|
||||
|
||||
self.headerColumns = {}
|
||||
self.columnWidth = {}
|
||||
for colId, column in pairs(headerWidget:getChildren()) do
|
||||
column.colId = colId
|
||||
column.table = self
|
||||
table.insert(self.columnWidth, column:getWidth())
|
||||
table.insert(self.headerColumns, column)
|
||||
end
|
||||
|
||||
self.headerRow = headerWidget
|
||||
end
|
||||
|
||||
-- Create and add header from table data
|
||||
function UITable:addHeader(data)
|
||||
if not data or type(data) ~= 'table' then
|
||||
g_logger.error('UITable:addHeaderRow - table columns must be provided in a table')
|
||||
return
|
||||
end
|
||||
|
||||
self:removeHeader()
|
||||
|
||||
-- build header columns
|
||||
local columns = {}
|
||||
for _, column in pairs(data) do
|
||||
for colId, column in pairs(data) do
|
||||
local col = g_ui.createWidget(self.headerColumnBaseStyle)
|
||||
col.colId = colId
|
||||
col.table = self
|
||||
for type, value in pairs(column) do
|
||||
if type == 'width' then
|
||||
col:setWidth(value)
|
||||
@ -104,26 +164,37 @@ function UITable:addHeaderRow(data)
|
||||
|
||||
-- create a new header
|
||||
local headerRow = g_ui.createWidget(self.headerRowBaseStyle, self)
|
||||
local newHeight = (self.dataSpace:getHeight()-headerRow:getHeight())-self.dataSpace:getMarginTop()
|
||||
local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop()
|
||||
self.dataSpace:applyStyle({ height = newHeight })
|
||||
|
||||
headerRow:setId(HEADER_ID)
|
||||
headerRow:setId('header')
|
||||
self.headerColumns = {}
|
||||
self.columnWidth = {}
|
||||
for _, column in pairs(columns) do
|
||||
headerRow:addChild(column)
|
||||
self.columns[HEADER_ID] = column
|
||||
table.insert(self.columnWidth, column:getWidth())
|
||||
table.insert(self.headerColumns, column)
|
||||
end
|
||||
|
||||
headerRow.onClick = function(headerRow) self:selectRow(headerRow) end
|
||||
self.headerRow = headerRow
|
||||
return headerRow
|
||||
end
|
||||
|
||||
function UITable:removeHeaderRow()
|
||||
self.headerRow:destroy()
|
||||
self.headerRow = nil
|
||||
-- Remove header
|
||||
function UITable:removeHeader()
|
||||
if self:hasHeader() then
|
||||
if self.dataSpace then
|
||||
local newHeight = self.dataSpace:getHeight()+self.headerRow:getHeight()+self.dataSpace:getMarginTop()
|
||||
self.dataSpace:applyStyle({ height = newHeight })
|
||||
end
|
||||
self.headerColumns = {}
|
||||
self.columnWidth = {}
|
||||
self.headerRow:destroy()
|
||||
self.headerRow = nil
|
||||
end
|
||||
end
|
||||
|
||||
function UITable:addRow(data, ref, height)
|
||||
function UITable:addRow(data, height)
|
||||
if not self.dataSpace then
|
||||
g_logger.error('UITable:addRow - table data space has not been set, cannot add rows.')
|
||||
return
|
||||
@ -134,41 +205,123 @@ function UITable:addRow(data, ref, height)
|
||||
end
|
||||
|
||||
local row = g_ui.createWidget(self.rowBaseStyle)
|
||||
if ref then row.ref = ref end
|
||||
row.table = self
|
||||
if height then row:setHeight(height) end
|
||||
|
||||
local rowId = #self.rows
|
||||
row:setId('row'..(rowId < 1 and 1 or rowId))
|
||||
local rowId = #self.rows + 1
|
||||
row.rowId = rowId
|
||||
row:setId('row'..rowId)
|
||||
row:updateBackgroundColor()
|
||||
|
||||
for _, column in pairs(data) do
|
||||
self.columns[rowId] = {}
|
||||
for colId, column in pairs(data) do
|
||||
local col = g_ui.createWidget(self.columBaseStyle, row)
|
||||
for type, value in pairs(column) do
|
||||
if type == 'width' then
|
||||
col:setWidth(value)
|
||||
elseif type == 'height' then
|
||||
col:setHeight(value)
|
||||
elseif type == 'text' then
|
||||
col:setText(value)
|
||||
end
|
||||
if column.width then
|
||||
col:setWidth(column.width)
|
||||
else
|
||||
col:setWidth(self.columnWidth[colId] or self.defaultColumnWidth)
|
||||
end
|
||||
self.columns[rowId] = col
|
||||
if column.height then
|
||||
col:setHeight(column.height)
|
||||
end
|
||||
if column.text then
|
||||
col:setText(column.text)
|
||||
end
|
||||
if column.sortvalue then
|
||||
col.sortvalue = column.sortvalue
|
||||
else
|
||||
col.sortvalue = column.text or 0
|
||||
end
|
||||
table.insert(self.columns[rowId], col)
|
||||
end
|
||||
|
||||
row.onFocusChange = function(row, focused)
|
||||
if focused then self:selectRow(row) end
|
||||
end
|
||||
self.dataSpace:addChild(row)
|
||||
|
||||
table.insert(self.rows, row)
|
||||
|
||||
if self.autoSort then
|
||||
self:sort()
|
||||
end
|
||||
|
||||
return row
|
||||
end
|
||||
|
||||
-- Update row indices and background color
|
||||
function UITable:updateRows()
|
||||
for rowId = 1, #self.rows do
|
||||
local row = self.rows[rowId]
|
||||
row.rowId = rowId
|
||||
row:setId('row'..rowId)
|
||||
row:updateBackgroundColor()
|
||||
end
|
||||
end
|
||||
|
||||
-- Removes the given row widget from the table
|
||||
function UITable:removeRow(row)
|
||||
if self.selectedRow == row then
|
||||
self:selectRow(nil)
|
||||
end
|
||||
row.onClick = nil
|
||||
table.removevalue(self.rows, row)
|
||||
row.table = nil
|
||||
table.remove(self.columns, row.rowId)
|
||||
table.remove(self.rows, row.rowId)
|
||||
self.dataSpace:removeChild(row)
|
||||
self:updateRows()
|
||||
end
|
||||
|
||||
function UITable:toggleSorting(enabled)
|
||||
self.autoSort = enabled
|
||||
end
|
||||
|
||||
function UITable:setSorting(colId, sortType)
|
||||
self.headerColumns[colId]:focus()
|
||||
|
||||
if sortType then
|
||||
self.sortType = sortType
|
||||
elseif self.sortColumn == colId then
|
||||
if self.sortType == TABLE_SORTING_ASC then
|
||||
self.sortType = TABLE_SORTING_DESC
|
||||
else
|
||||
self.sortType = TABLE_SORTING_ASC
|
||||
end
|
||||
else
|
||||
self.sortType = TABLE_SORTING_ASC
|
||||
end
|
||||
self.sortColumn = colId
|
||||
end
|
||||
|
||||
function UITable:sort()
|
||||
if self.sortColumn <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
if self.sortType == TABLE_SORTING_ASC then
|
||||
table.sort(self.rows, function(rowA, b)
|
||||
return rowA:getChildByIndex(self.sortColumn).sortvalue < b:getChildByIndex(self.sortColumn).sortvalue
|
||||
end)
|
||||
else
|
||||
table.sort(self.rows, function(rowA, b)
|
||||
return rowA:getChildByIndex(self.sortColumn).sortvalue > b:getChildByIndex(self.sortColumn).sortvalue
|
||||
end)
|
||||
end
|
||||
|
||||
if self.dataSpace then
|
||||
for _, child in pairs(self.dataSpace:getChildren()) do
|
||||
self.dataSpace:removeChild(child)
|
||||
end
|
||||
end
|
||||
|
||||
self:updateRows()
|
||||
self.columns = {}
|
||||
for _, row in pairs(self.rows) do
|
||||
if self.dataSpace then
|
||||
self.dataSpace:addChild(row)
|
||||
end
|
||||
|
||||
self.columns[row.rowId] = {}
|
||||
for _, column in pairs(row:getChildren()) do
|
||||
table.insert(self.columns[row.rowId], column)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UITable:selectRow(selectedRow)
|
||||
@ -189,21 +342,34 @@ function UITable:selectRow(selectedRow)
|
||||
end
|
||||
|
||||
function UITable:setTableData(tableData)
|
||||
local headerHeight = 0
|
||||
if self.headerRow then
|
||||
headerHeight = self.headerRow:getHeight()
|
||||
end
|
||||
|
||||
self.dataSpace = tableData
|
||||
self.dataSpace:applyStyle({ height = self:getHeight() })
|
||||
self.dataSpace:applyStyle({ height = self:getHeight()-headerHeight-self:getMarginTop() })
|
||||
end
|
||||
|
||||
function UITable:setRowStyle(style)
|
||||
function UITable:setRowStyle(style, dontUpdate)
|
||||
self.rowBaseStyle = style
|
||||
for _, row in pairs(self.rows) do
|
||||
row:setStyle(style)
|
||||
|
||||
if not dontUpdate then
|
||||
for _, row in pairs(self.rows) do
|
||||
row:setStyle(style)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UITable:setColumnStyle(style)
|
||||
function UITable:setColumnStyle(style, dontUpdate)
|
||||
self.columBaseStyle = style
|
||||
for _, col in pairs(self.columns) do
|
||||
col:setStyle(style)
|
||||
|
||||
if not dontUpdate then
|
||||
for _, columns in pairs(self.columns) do
|
||||
for _, col in pairs(columns) do
|
||||
col:setStyle(style)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -216,7 +382,51 @@ end
|
||||
|
||||
function UITable:setHeaderColumnStyle(style)
|
||||
self.headerColumnBaseStyle = style
|
||||
if table.haskey(HEADER_ID) then
|
||||
self.columns[HEADER_ID]:setStyle(style)
|
||||
for _, col in pairs(self.headerColumns) do
|
||||
col:setStyle(style)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
UITableRow = extends(UIWidget, "UITableRow")
|
||||
|
||||
function UITableRow:onFocusChange(focused)
|
||||
if focused then
|
||||
if self.table then self.table:selectRow(self) end
|
||||
end
|
||||
end
|
||||
|
||||
function UITableRow:onStyleApply(styleName, styleNode)
|
||||
for name,value in pairs(styleNode) do
|
||||
if name == 'even-background-color' then
|
||||
self.evenBackgroundColor = value
|
||||
elseif name == 'odd-background-color' then
|
||||
self.oddBackgroundColor = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function UITableRow:updateBackgroundColor()
|
||||
self.backgroundColor = nil
|
||||
|
||||
local isEven = (self.rowId % 2 == 0)
|
||||
if isEven and self.evenBackgroundColor then
|
||||
self.backgroundColor = self.evenBackgroundColor
|
||||
elseif not isEven and self.oddBackgroundColor then
|
||||
self.backgroundColor = self.oddBackgroundColor
|
||||
end
|
||||
|
||||
if self.backgroundColor then
|
||||
self:mergeStyle({ ['background-color'] = self.backgroundColor })
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
UITableHeaderColumn = extends(UIButton, "UITableHeaderColumn")
|
||||
|
||||
function UITableHeaderColumn:onClick()
|
||||
if self.table then
|
||||
self.table:setSorting(self.colId)
|
||||
self.table:sort()
|
||||
end
|
||||
end
|
||||
|
@ -313,7 +313,7 @@ function signalcall(param, ...)
|
||||
perror(ret)
|
||||
end
|
||||
end
|
||||
elseif func ~= nil then
|
||||
elseif param ~= nil then
|
||||
error('attempt to call a non function value')
|
||||
end
|
||||
return false
|
||||
|
@ -15,7 +15,8 @@ fightModeRadioGroup = nil
|
||||
pvpModeRadioGroup = nil
|
||||
|
||||
function init()
|
||||
combatControlsButton = modules.client_topmenu.addRightGameToggleButton('combatControlsButton', tr('Combat Controls'), '/images/topbuttons/combatcontrols', toggle)
|
||||
combatControlsButton = modules.client_topmenu.addRightGameToggleButton('combatControlsButton',
|
||||
tr('Combat Controls'), '/images/topbuttons/combatcontrols', toggle)
|
||||
combatControlsButton:setOn(true)
|
||||
combatControlsWindow = g_ui.loadUI('combatcontrols', modules.game_interface.getRightPanel())
|
||||
combatControlsWindow:disableResize()
|
||||
|
@ -3,7 +3,7 @@ SpeakTypesSettings = {
|
||||
say = { speakType = MessageModes.Say, color = '#FFFF00' },
|
||||
whisper = { speakType = MessageModes.Whisper, color = '#FFFF00' },
|
||||
yell = { speakType = MessageModes.Yell, color = '#FFFF00' },
|
||||
broadcast = { speakType = MessageModes.GamemasterPrivateFrom, color = '#F55E5E' },
|
||||
broadcast = { speakType = MessageModes.GamemasterBroadcast, color = '#F55E5E' },
|
||||
private = { speakType = MessageModes.PrivateTo, color = '#5FF7F7', private = true },
|
||||
privateRed = { speakType = MessageModes.GamemasterTo, color = '#F55E5E', private = true },
|
||||
privatePlayerToPlayer = { speakType = MessageModes.PrivateTo, color = '#9F9DFD', private = true },
|
||||
@ -164,12 +164,18 @@ function enableChat()
|
||||
|
||||
g_keyboard.unbindKeyUp("Space")
|
||||
g_keyboard.unbindKeyUp("Enter")
|
||||
g_keyboard.unbindKeyUp("Escape")
|
||||
|
||||
gameInterface.unbindWalkKey("W")
|
||||
gameInterface.unbindWalkKey("D")
|
||||
gameInterface.unbindWalkKey("S")
|
||||
gameInterface.unbindWalkKey("A")
|
||||
|
||||
gameInterface.unbindWalkKey("E")
|
||||
gameInterface.unbindWalkKey("Q")
|
||||
gameInterface.unbindWalkKey("C")
|
||||
gameInterface.unbindWalkKey("Z")
|
||||
|
||||
consoleToggleChat:setTooltip(tr("Disable chat mode, allow to walk using ASDW"))
|
||||
end
|
||||
|
||||
@ -187,12 +193,18 @@ function disableChat()
|
||||
end
|
||||
g_keyboard.bindKeyUp("Space", quickFunc)
|
||||
g_keyboard.bindKeyUp("Enter", quickFunc)
|
||||
g_keyboard.bindKeyUp("Escape", quickFunc)
|
||||
|
||||
gameInterface.bindWalkKey("W", North)
|
||||
gameInterface.bindWalkKey("D", East)
|
||||
gameInterface.bindWalkKey("S", South)
|
||||
gameInterface.bindWalkKey("A", West)
|
||||
|
||||
gameInterface.bindWalkKey("E", NorthEast)
|
||||
gameInterface.bindWalkKey("Q", NorthWest)
|
||||
gameInterface.bindWalkKey("C", SouthEast)
|
||||
gameInterface.bindWalkKey("Z", SouthWest)
|
||||
|
||||
consoleToggleChat:setTooltip(tr("Enable chat mode"))
|
||||
end
|
||||
|
||||
@ -233,7 +245,13 @@ function terminate()
|
||||
violationWindow:destroy()
|
||||
end
|
||||
|
||||
consoleTabBar = nil
|
||||
consoleContentPanel = nil
|
||||
consoleToggleChat = nil
|
||||
consoleTextEdit = nil
|
||||
|
||||
consolePanel:destroy()
|
||||
consolePanel = nil
|
||||
ownPrivateName = nil
|
||||
|
||||
Console = nil
|
||||
@ -288,11 +306,14 @@ function clear()
|
||||
channels = {}
|
||||
|
||||
consoleTabBar:removeTab(defaultTab)
|
||||
defaultTab = nil
|
||||
consoleTabBar:removeTab(serverTab)
|
||||
serverTab = nil
|
||||
|
||||
local npcTab = consoleTabBar:getTab('NPCs')
|
||||
if npcTab then
|
||||
consoleTabBar:removeTab(npcTab)
|
||||
npcTab = nil
|
||||
end
|
||||
|
||||
if violationReportTab then
|
||||
@ -578,6 +599,7 @@ end
|
||||
|
||||
function processChannelTabMenu(tab, mousePos, mouseButton)
|
||||
local menu = g_ui.createWidget('PopupMenu')
|
||||
menu:setGameMenu(true)
|
||||
|
||||
channelName = tab:getText()
|
||||
if tab ~= defaultTab and tab ~= serverTab then
|
||||
@ -597,6 +619,7 @@ end
|
||||
function processMessageMenu(mousePos, mouseButton, creatureName, text, label, tab)
|
||||
if mouseButton == MouseRightButton then
|
||||
local menu = g_ui.createWidget('PopupMenu')
|
||||
menu:setGameMenu(true)
|
||||
if creatureName and #creatureName > 0 then
|
||||
if creatureName ~= g_game.getCharacterName() then
|
||||
menu:addOption(tr('Message to ' .. creatureName), function () g_game.openPrivateChannel(creatureName) end)
|
||||
@ -687,7 +710,7 @@ function sendMessage(message, tab)
|
||||
end
|
||||
|
||||
-- player used whisper
|
||||
local chatCommandMessage = message:match("^%#[w|W] (.*)")
|
||||
chatCommandMessage = message:match("^%#[w|W] (.*)")
|
||||
if chatCommandMessage ~= nil then
|
||||
chatCommandSayMode = 'whisper'
|
||||
message = chatCommandMessage
|
||||
@ -695,12 +718,27 @@ function sendMessage(message, tab)
|
||||
end
|
||||
|
||||
-- player say
|
||||
local chatCommandMessage = message:match("^%#[s|S] (.*)")
|
||||
chatCommandMessage = message:match("^%#[s|S] (.*)")
|
||||
if chatCommandMessage ~= nil then
|
||||
chatCommandSayMode = 'say'
|
||||
message = chatCommandMessage
|
||||
channel = 0
|
||||
end
|
||||
|
||||
-- player red talk on channel
|
||||
chatCommandMessage = message:match("^%#[c|C] (.*)")
|
||||
if chatCommandMessage ~= nil then
|
||||
chatCommandSayMode = 'channelRed'
|
||||
message = chatCommandMessage
|
||||
end
|
||||
|
||||
-- player broadcast
|
||||
chatCommandMessage = message:match("^%#[b|B] (.*)")
|
||||
if chatCommandMessage ~= nil then
|
||||
chatCommandSayMode = 'broadcast'
|
||||
message = chatCommandMessage
|
||||
channel = 0
|
||||
end
|
||||
|
||||
local findIni, findEnd, chatCommandInitial, chatCommandPrivate, chatCommandEnd, chatCommandMessage = message:find("([%*%@])(.+)([%*%@])(.*)")
|
||||
if findIni ~= nil and findIni == 1 then -- player used private chat command
|
||||
|
@ -442,7 +442,10 @@ end
|
||||
|
||||
function createThingMenu(menuPosition, lookThing, useThing, creatureThing)
|
||||
if not g_game.isOnline() then return end
|
||||
|
||||
local menu = g_ui.createWidget('PopupMenu')
|
||||
menu:setGameMenu(true)
|
||||
|
||||
local classic = modules.client_options.getOption('classicControl')
|
||||
local shortcut = nil
|
||||
|
||||
|
@ -30,5 +30,6 @@ Module
|
||||
- game_spelllist
|
||||
- game_cooldown
|
||||
- game_modaldialog
|
||||
- game_unjustifiedpoints
|
||||
@onLoad: init()
|
||||
@onUnload: terminate()
|
||||
|
@ -17,7 +17,10 @@ inventoryButton = nil
|
||||
purseButton = nil
|
||||
|
||||
function init()
|
||||
connect(LocalPlayer, { onInventoryChange = onInventoryChange })
|
||||
connect(LocalPlayer, {
|
||||
onInventoryChange = onInventoryChange,
|
||||
onBlessingsChange = onBlessingsChange
|
||||
})
|
||||
connect(g_game, { onGameStart = refresh })
|
||||
|
||||
g_keyboard.bindKeyDown('Ctrl+I', toggle)
|
||||
@ -43,7 +46,10 @@ function init()
|
||||
end
|
||||
|
||||
function terminate()
|
||||
disconnect(LocalPlayer, { onInventoryChange = onInventoryChange })
|
||||
disconnect(LocalPlayer, {
|
||||
onInventoryChange = onInventoryChange,
|
||||
onBlessingsChange = onBlessingsChange
|
||||
})
|
||||
disconnect(g_game, { onGameStart = refresh })
|
||||
|
||||
g_keyboard.unbindKeyDown('Ctrl+I')
|
||||
@ -52,6 +58,15 @@ function terminate()
|
||||
inventoryButton:destroy()
|
||||
end
|
||||
|
||||
function toggleAdventurerStyle(hasBlessing)
|
||||
for slot = InventorySlotFirst, InventorySlotLast do
|
||||
local itemWidget = inventoryPanel:getChildById('slot' .. slot)
|
||||
if itemWidget then
|
||||
itemWidget:setOn(hasBlessing)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function refresh()
|
||||
local player = g_game.getLocalPlayer()
|
||||
for i = InventorySlotFirst, InventorySlotPurse do
|
||||
@ -60,6 +75,7 @@ function refresh()
|
||||
else
|
||||
onInventoryChange(player, i, nil)
|
||||
end
|
||||
toggleAdventurerStyle(player and Bit.hasBit(player:getBlessings(), Blessings.Adventurer) or false)
|
||||
end
|
||||
|
||||
purseButton:setVisible(g_game.getFeature(GamePurseSlot))
|
||||
@ -92,10 +108,17 @@ function onInventoryChange(player, slot, item, oldItem)
|
||||
|
||||
local itemWidget = inventoryPanel:getChildById('slot' .. slot)
|
||||
if item then
|
||||
itemWidget:setStyle('Item')
|
||||
itemWidget:setStyle('InventoryItem')
|
||||
itemWidget:setItem(item)
|
||||
else
|
||||
itemWidget:setStyle(InventorySlotStyles[slot])
|
||||
itemWidget:setItem(nil)
|
||||
end
|
||||
end
|
||||
|
||||
function onBlessingsChange(player, blessings, oldBlessings)
|
||||
local hasAdventurerBlessing = Bit.hasBit(blessings, Blessings.Adventurer)
|
||||
if hasAdventurerBlessing ~= Bit.hasBit(oldBlessings, Blessings.Adventurer) then
|
||||
toggleAdventurerStyle(hasAdventurerBlessing)
|
||||
end
|
||||
end
|
@ -1,54 +1,76 @@
|
||||
InventoryItem < Item
|
||||
$on:
|
||||
image-source: /images/ui/item-blessed
|
||||
|
||||
HeadSlot < InventoryItem
|
||||
id: slot1
|
||||
image-source: /images/game/slots/head
|
||||
&position: {x=65535, y=1, z=0}
|
||||
$on:
|
||||
image-source: /images/game/slots/head-blessed
|
||||
|
||||
BodySlot < InventoryItem
|
||||
id: slot4
|
||||
image-source: /images/game/slots/body
|
||||
&position: {x=65535, y=4, z=0}
|
||||
$on:
|
||||
image-source: /images/game/slots/body-blessed
|
||||
|
||||
LegSlot < InventoryItem
|
||||
id: slot7
|
||||
image-source: /images/game/slots/legs
|
||||
&position: {x=65535, y=7, z=0}
|
||||
$on:
|
||||
image-source: /images/game/slots/legs-blessed
|
||||
|
||||
FeetSlot < InventoryItem
|
||||
id: slot8
|
||||
image-source: /images/game/slots/feet
|
||||
&position: {x=65535, y=8, z=0}
|
||||
$on:
|
||||
image-source: /images/game/slots/feet-blessed
|
||||
|
||||
NeckSlot < InventoryItem
|
||||
id: slot2
|
||||
image-source: /images/game/slots/neck
|
||||
&position: {x=65535, y=2, z=0}
|
||||
$on:
|
||||
image-source: /images/game/slots/neck-blessed
|
||||
|
||||
LeftSlot < InventoryItem
|
||||
id: slot6
|
||||
image-source: /images/game/slots/left-hand
|
||||
&position: {x=65535, y=6, z=0}
|
||||
$on:
|
||||
image-source: /images/game/slots/left-hand-blessed
|
||||
|
||||
FingerSlot < InventoryItem
|
||||
id: slot9
|
||||
image-source: /images/game/slots/finger
|
||||
&position: {x=65535, y=9, z=0}
|
||||
$on:
|
||||
image-source: /images/game/slots/finger-blessed
|
||||
|
||||
BackSlot < InventoryItem
|
||||
id: slot3
|
||||
image-source: /images/game/slots/back
|
||||
&position: {x=65535, y=3, z=0}
|
||||
$on:
|
||||
image-source: /images/game/slots/back-blessed
|
||||
|
||||
RightSlot < InventoryItem
|
||||
id: slot5
|
||||
image-source: /images/game/slots/right-hand
|
||||
&position: {x=65535, y=5, z=0}
|
||||
$on:
|
||||
image-source: /images/game/slots/right-hand-blessed
|
||||
|
||||
AmmoSlot < InventoryItem
|
||||
id: slot10
|
||||
image-source: /images/game/slots/ammo
|
||||
&position: {x=65535, y=10, z=0}
|
||||
$on:
|
||||
image-source: /images/game/slots/ammo-blessed
|
||||
|
||||
PurseButton < Button
|
||||
id: purseButton
|
||||
|
@ -13,12 +13,8 @@
|
||||
* Clean up the interface building
|
||||
- Add a new market interface file to handle building?
|
||||
|
||||
* Add offer table column ordering.
|
||||
- Player Name, Amount, Total Price, Peice Price and Ends At
|
||||
|
||||
* Extend information features
|
||||
- Hover over offers for purchase information (balance after transaction, etc)
|
||||
- Display out of trend market offers based on their previous statistics (like cipsoft does)
|
||||
]]
|
||||
|
||||
Market = {}
|
||||
@ -75,17 +71,10 @@ information = {}
|
||||
currentItems = {}
|
||||
lastCreatedOffer = 0
|
||||
fee = 0
|
||||
averagePrice = 0
|
||||
|
||||
loaded = false
|
||||
|
||||
local offerTableHeader = {
|
||||
{['text'] = 'Player Name', ['width'] = 100},
|
||||
{['text'] = 'Amount', ['width'] = 60},
|
||||
{['text'] = 'Total Price', ['width'] = 90},
|
||||
{['text'] = 'Piece Price', ['width'] = 80},
|
||||
{['text'] = 'Ends at', ['width'] = 120}
|
||||
}
|
||||
|
||||
local function isItemValid(item, category, searchFilter)
|
||||
if not item or not item.marketData then
|
||||
return false
|
||||
@ -110,7 +99,7 @@ local function isItemValid(item, category, searchFilter)
|
||||
local filterDepot = filterButtons[MarketFilters.Depot]:isChecked()
|
||||
|
||||
if slotFilter then
|
||||
if slotFilter ~= 255 and item.ptr:getClothSlot() ~= slotFilter then
|
||||
if slotFilter ~= 255 and item.thingType:getClothSlot() ~= slotFilter then
|
||||
return false
|
||||
end
|
||||
end
|
||||
@ -124,7 +113,7 @@ local function isItemValid(item, category, searchFilter)
|
||||
return false
|
||||
end
|
||||
end
|
||||
if filterDepot and Market.depotContains(item.ptr:getId()) <= 0 then
|
||||
if filterDepot and Market.getDepotCount(item.marketData.tradeAs) <= 0 then
|
||||
return false
|
||||
end
|
||||
if searchFilter then
|
||||
@ -147,15 +136,15 @@ end
|
||||
|
||||
local function clearFilters()
|
||||
for _, filter in pairs(filterButtons) do
|
||||
if filter and filter:isChecked() then
|
||||
filter:setChecked(false)
|
||||
if filter and filter:isChecked() ~= filter.default then
|
||||
filter:setChecked(filter.default)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function clearFee()
|
||||
feeLabel:setText('')
|
||||
fee = 0
|
||||
fee = 20
|
||||
end
|
||||
|
||||
local function refreshTypeList()
|
||||
@ -163,13 +152,13 @@ local function refreshTypeList()
|
||||
offerTypeList:addOption('Buy')
|
||||
|
||||
if Market.isItemSelected() then
|
||||
if Market.depotContains(selectedItem.item.ptr:getId()) > 0 then
|
||||
if Market.getDepotCount(selectedItem.item.marketData.tradeAs) > 0 then
|
||||
offerTypeList:addOption('Sell')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function addOffer(offer, type)
|
||||
local function addOffer(offer, offerType)
|
||||
if not offer then
|
||||
return false
|
||||
end
|
||||
@ -179,26 +168,57 @@ local function addOffer(offer, type)
|
||||
local price = offer:getPrice()
|
||||
local timestamp = offer:getTimeStamp()
|
||||
|
||||
buyOfferTable:toggleSorting(false)
|
||||
sellOfferTable:toggleSorting(false)
|
||||
|
||||
if amount < 1 then return false end
|
||||
if type == MarketAction.Buy then
|
||||
if offerType == MarketAction.Buy then
|
||||
local data = {
|
||||
{['text'] = player, ['width'] = 100},
|
||||
{['text'] = amount, ['width'] = 60},
|
||||
{['text'] = price*amount, ['width'] = 90},
|
||||
{['text'] = price, ['width'] = 80},
|
||||
{['text'] = string.gsub(os.date('%c', timestamp), " ", " "), ['width'] = 120}
|
||||
{text = player},
|
||||
{text = amount},
|
||||
{text = price*amount},
|
||||
{text = price},
|
||||
{text = string.gsub(os.date('%c', timestamp), " ", " ")}
|
||||
}
|
||||
buyOfferTable:addRow(data, id)
|
||||
|
||||
if offer.warn then
|
||||
buyOfferTable:setColumnStyle('OfferTableWarningColumn', true)
|
||||
end
|
||||
|
||||
local row = buyOfferTable:addRow(data)
|
||||
row.ref = id
|
||||
|
||||
if offer.warn then
|
||||
row:setTooltip(tr('This offer is 25%% below the average market price'))
|
||||
buyOfferTable:setColumnStyle('OfferTableColumn', true)
|
||||
end
|
||||
else
|
||||
local data = {
|
||||
{['text'] = player, ['width'] = 100},
|
||||
{['text'] = amount, ['width'] = 60},
|
||||
{['text'] = price*amount, ['width'] = 90},
|
||||
{['text'] = price, ['width'] = 80},
|
||||
{['text'] = string.gsub(os.date('%c', timestamp), " ", " "), ['width'] = 120}
|
||||
{text = player},
|
||||
{text = amount},
|
||||
{text = price*amount},
|
||||
{text = price},
|
||||
{text = string.gsub(os.date('%c', timestamp), " ", " "), sortvalue = timestamp}
|
||||
}
|
||||
sellOfferTable:addRow(data, id)
|
||||
|
||||
if offer.warn then
|
||||
sellOfferTable:setColumnStyle('OfferTableWarningColumn', true)
|
||||
end
|
||||
|
||||
local row = sellOfferTable:addRow(data)
|
||||
row.ref = id
|
||||
|
||||
if offer.warn then
|
||||
row:setTooltip(tr('This offer is 25%% above the average market price'))
|
||||
sellOfferTable:setColumnStyle('OfferTableColumn', true)
|
||||
end
|
||||
end
|
||||
|
||||
buyOfferTable:toggleSorting(false)
|
||||
sellOfferTable:toggleSorting(false)
|
||||
buyOfferTable:sort()
|
||||
sellOfferTable:sort()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
@ -206,12 +226,17 @@ local function mergeOffer(offer)
|
||||
if not offer then
|
||||
return false
|
||||
end
|
||||
|
||||
local id = offer:getId()
|
||||
local type = offer:getType()
|
||||
local offerType = offer:getType()
|
||||
local amount = offer:getAmount()
|
||||
local replaced = false
|
||||
|
||||
if type == MarketAction.Buy then
|
||||
if offerType == MarketAction.Buy then
|
||||
if averagePrice > 0 then
|
||||
offer.warn = offer:getPrice() <= averagePrice - math.floor(averagePrice / 4)
|
||||
end
|
||||
|
||||
for i = 1, #marketOffers[MarketAction.Buy] do
|
||||
local o = marketOffers[MarketAction.Buy][i]
|
||||
-- replace existing offer
|
||||
@ -224,6 +249,10 @@ local function mergeOffer(offer)
|
||||
table.insert(marketOffers[MarketAction.Buy], offer)
|
||||
end
|
||||
else
|
||||
if averagePrice > 0 then
|
||||
offer.warn = offer:getPrice() >= averagePrice + math.floor(averagePrice / 4)
|
||||
end
|
||||
|
||||
for i = 1, #marketOffers[MarketAction.Sell] do
|
||||
local o = marketOffers[MarketAction.Sell][i]
|
||||
-- replace existing offer
|
||||
@ -250,7 +279,9 @@ local function updateOffers(offers)
|
||||
|
||||
-- clear existing offer data
|
||||
buyOfferTable:clearData()
|
||||
buyOfferTable:setSorting(4, TABLE_SORTING_DESC)
|
||||
sellOfferTable:clearData()
|
||||
sellOfferTable:setSorting(4, TABLE_SORTING_ASC)
|
||||
|
||||
sellButton:setEnabled(false)
|
||||
buyButton:setEnabled(false)
|
||||
@ -274,8 +305,8 @@ local function updateDetails(itemId, descriptions, purchaseStats, saleStats)
|
||||
detailsTable:clearData()
|
||||
for k, desc in pairs(descriptions) do
|
||||
local columns = {
|
||||
{['text'] = getMarketDescriptionName(desc[1])..':'},
|
||||
{['text'] = desc[2], ['width'] = 330}
|
||||
{text = getMarketDescriptionName(desc[1])..':'},
|
||||
{text = desc[2]}
|
||||
}
|
||||
detailsTable:addRow(columns)
|
||||
end
|
||||
@ -283,11 +314,13 @@ local function updateDetails(itemId, descriptions, purchaseStats, saleStats)
|
||||
-- update sale item statistics
|
||||
sellStatsTable:clearData()
|
||||
if table.empty(saleStats) then
|
||||
sellStatsTable:addRow({{['text'] = 'No information'}})
|
||||
sellStatsTable:addRow({{text = 'No information'}})
|
||||
else
|
||||
local offerAmount = 0
|
||||
local transactions, totalPrice, highestPrice, lowestPrice = 0, 0, 0, 0
|
||||
for _, stat in pairs(saleStats) do
|
||||
if not stat:isNull() then
|
||||
offerAmount = offerAmount + 1
|
||||
transactions = transactions + stat:getTransactions()
|
||||
totalPrice = totalPrice + stat:getTotalPrice()
|
||||
local newHigh = stat:getHighestPrice()
|
||||
@ -301,28 +334,30 @@ local function updateDetails(itemId, descriptions, purchaseStats, saleStats)
|
||||
end
|
||||
end
|
||||
end
|
||||
sellStatsTable:addRow({{['text'] = 'Total Transations:'},
|
||||
{['text'] = transactions, ['width'] = 270}})
|
||||
|
||||
sellStatsTable:addRow({{['text'] = 'Highest Price:'},
|
||||
{['text'] = highestPrice, ['width'] = 270}})
|
||||
|
||||
if totalPrice > 0 and transactions > 0 then
|
||||
sellStatsTable:addRow({{['text'] = 'Average Price:'},
|
||||
{['text'] = math.floor(totalPrice/transactions), ['width'] = 270}})
|
||||
if offerAmount >= 5 and transactions >= 10 then
|
||||
averagePrice = math.round(totalPrice / transactions)
|
||||
else
|
||||
sellStatsTable:addRow({{['text'] = 'Average Price:'},
|
||||
{['text'] = 0, ['width'] = 270}})
|
||||
averagePrice = 0
|
||||
end
|
||||
|
||||
sellStatsTable:addRow({{['text'] = 'Lowest Price:'},
|
||||
{['text'] = lowestPrice, ['width'] = 270}})
|
||||
sellStatsTable:addRow({{text = 'Total Transations:'}, {text = transactions}})
|
||||
sellStatsTable:addRow({{text = 'Highest Price:'}, {text = highestPrice}})
|
||||
|
||||
if totalPrice > 0 and transactions > 0 then
|
||||
sellStatsTable:addRow({{text = 'Average Price:'},
|
||||
{text = math.floor(totalPrice/transactions)}})
|
||||
else
|
||||
sellStatsTable:addRow({{text = 'Average Price:'}, {text = 0}})
|
||||
end
|
||||
|
||||
sellStatsTable:addRow({{text = 'Lowest Price:'}, {text = lowestPrice}})
|
||||
end
|
||||
|
||||
-- update buy item statistics
|
||||
buyStatsTable:clearData()
|
||||
if table.empty(purchaseStats) then
|
||||
buyStatsTable:addRow({{['text'] = 'No information'}})
|
||||
buyStatsTable:addRow({{text = 'No information'}})
|
||||
else
|
||||
local transactions, totalPrice, highestPrice, lowestPrice = 0, 0, 0, 0
|
||||
for _, stat in pairs(purchaseStats) do
|
||||
@ -340,22 +375,18 @@ local function updateDetails(itemId, descriptions, purchaseStats, saleStats)
|
||||
end
|
||||
end
|
||||
end
|
||||
buyStatsTable:addRow({{['text'] = 'Total Transations:'},
|
||||
{['text'] = transactions, ['width'] = 270}})
|
||||
|
||||
buyStatsTable:addRow({{['text'] = 'Highest Price:'},
|
||||
{['text'] = highestPrice, ['width'] = 270}})
|
||||
buyStatsTable:addRow({{text = 'Total Transations:'},{text = transactions}})
|
||||
buyStatsTable:addRow({{text = 'Highest Price:'}, {text = highestPrice}})
|
||||
|
||||
if totalPrice > 0 and transactions > 0 then
|
||||
buyStatsTable:addRow({{['text'] = 'Average Price:'},
|
||||
{['text'] = math.floor(totalPrice/transactions), ['width'] = 270}})
|
||||
buyStatsTable:addRow({{text = 'Average Price:'},
|
||||
{text = math.floor(totalPrice/transactions)}})
|
||||
else
|
||||
buyStatsTable:addRow({{['text'] = 'Average Price:'},
|
||||
{['text'] = 0, ['width'] = 270}})
|
||||
buyStatsTable:addRow({{text = 'Average Price:'}, {text = 0}})
|
||||
end
|
||||
|
||||
buyStatsTable:addRow({{['text'] = 'Lowest Price:'},
|
||||
{['text'] = lowestPrice, ['width'] = 270}})
|
||||
buyStatsTable:addRow({{text = 'Lowest Price:'}, {text = lowestPrice}})
|
||||
end
|
||||
end
|
||||
|
||||
@ -365,12 +396,12 @@ local function updateSelectedItem(widget)
|
||||
|
||||
Market.resetCreateOffer()
|
||||
if Market.isItemSelected() then
|
||||
selectedItem:setItem(selectedItem.item.ptr)
|
||||
selectedItem:setItem(selectedItem.item.displayItem)
|
||||
nameLabel:setText(selectedItem.item.marketData.name)
|
||||
clearOffers()
|
||||
|
||||
Market.enableCreateOffer(true) -- update offer types
|
||||
MarketProtocol.sendMarketBrowse(selectedItem.item.ptr:getId()) -- send browsed msg
|
||||
MarketProtocol.sendMarketBrowse(selectedItem.item.marketData.tradeAs) -- send browsed msg
|
||||
else
|
||||
Market.clearSelectedItem()
|
||||
end
|
||||
@ -400,55 +431,69 @@ local function updateFee(price, amount)
|
||||
feeLabel:resizeToText()
|
||||
end
|
||||
|
||||
local function openAmountWindow(callback, type, actionText)
|
||||
local actionText = actionText or ''
|
||||
if not Market.isOfferSelected(type) then
|
||||
local function destroyAmountWindow()
|
||||
if amountWindow then
|
||||
amountWindow:destroy()
|
||||
amountWindow = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function openAmountWindow(callback, actionType, actionText)
|
||||
if not Market.isOfferSelected(actionType) then
|
||||
return
|
||||
end
|
||||
|
||||
amountWindow = g_ui.createWidget('AmountWindow', rootWidget)
|
||||
amountWindow:lock()
|
||||
local item = selectedOffer[type]:getItem()
|
||||
|
||||
local max = selectedOffer[type]:getAmount(item:getId())
|
||||
if type == MarketAction.Sell then
|
||||
local depot = Market.depotContains(item:getId())
|
||||
if max > depot then
|
||||
max = depot
|
||||
local offer = selectedOffer[actionType]
|
||||
local item = offer:getItem()
|
||||
|
||||
local maximum = offer:getAmount()
|
||||
if actionType == MarketAction.Sell then
|
||||
local depot = Market.getDepotCount(item:getId())
|
||||
if maximum > depot then
|
||||
maximum = depot
|
||||
end
|
||||
else
|
||||
maximum = math.min(maximum, math.floor(information.balance / offer:getPrice()))
|
||||
end
|
||||
|
||||
if item:isStackable() then
|
||||
maximum = math.min(maximum, MarketMaxAmountStackable)
|
||||
else
|
||||
maximum = math.min(maximum, MarketMaxAmount)
|
||||
end
|
||||
|
||||
local itembox = amountWindow:getChildById('item')
|
||||
itembox:setItemId(item:getId())
|
||||
itembox:setText(1)
|
||||
|
||||
local scrollbar = amountWindow:getChildById('amountScrollBar')
|
||||
scrollbar:setText(tostring(selectedOffer[type]:getPrice())..'gp')
|
||||
scrollbar:setMaximum(max)
|
||||
scrollbar:setMinimum(1)
|
||||
scrollbar:setValue(1)
|
||||
scrollbar:setText(offer:getPrice()..'gp')
|
||||
|
||||
scrollbar.onValueChange = function(widget, value)
|
||||
widget:setText(tostring(value*selectedOffer[type]:getPrice())..'gp')
|
||||
itembox:setText(tostring(value))
|
||||
widget:setText((value*offer:getPrice())..'gp')
|
||||
itembox:setText(value)
|
||||
end
|
||||
|
||||
scrollbar:setRange(1, maximum)
|
||||
scrollbar:setValue(1)
|
||||
|
||||
local okButton = amountWindow:getChildById('buttonOk')
|
||||
if actionText ~= '' then
|
||||
if actionText then
|
||||
okButton:setText(actionText)
|
||||
end
|
||||
|
||||
local okFunc = function()
|
||||
local counter = selectedOffer[type]:getCounter()
|
||||
local timestamp = selectedOffer[type]:getTimeStamp()
|
||||
local counter = offer:getCounter()
|
||||
local timestamp = offer:getTimeStamp()
|
||||
callback(scrollbar:getValue(), timestamp, counter)
|
||||
okButton:getParent():destroy()
|
||||
amountWindow = nil
|
||||
destroyAmountWindow()
|
||||
end
|
||||
|
||||
local cancelButton = amountWindow:getChildById('buttonCancel')
|
||||
local cancelFunc = function()
|
||||
cancelButton:getParent():destroy()
|
||||
amountWindow = nil
|
||||
destroyAmountWindow()
|
||||
end
|
||||
|
||||
amountWindow.onEnter = okFunc
|
||||
@ -492,7 +537,7 @@ local function onSelectBuyOffer(table, selectedRow, previousSelectedRow)
|
||||
for _, offer in pairs(marketOffers[MarketAction.Buy]) do
|
||||
if offer:isEqual(selectedRow.ref) then
|
||||
selectedOffer[MarketAction.Sell] = offer
|
||||
if Market.depotContains(offer:getItem():getId()) > 0 then
|
||||
if Market.getDepotCount(offer:getItem():getId()) > 0 then
|
||||
sellButton:setEnabled(true)
|
||||
else
|
||||
sellButton:setEnabled(false)
|
||||
@ -535,74 +580,84 @@ local function onChangeSlotFilter(combobox, option)
|
||||
end
|
||||
|
||||
local function onChangeOfferType(combobox, option)
|
||||
local id = selectedItem.item.ptr:getId()
|
||||
local item = selectedItem.item
|
||||
local maximum = item.thingType:isStackable() and MarketMaxAmountStackable or MarketMaxAmount
|
||||
|
||||
if option == 'Sell' then
|
||||
local max = Market.depotContains(id)
|
||||
amountEdit:setMaximum(max)
|
||||
maximum = math.min(maximum, Market.getDepotCount(item.marketData.tradeAs))
|
||||
amountEdit:setMaximum(maximum)
|
||||
else
|
||||
amountEdit:setMaximum(999999)
|
||||
amountEdit:setMaximum(maximum)
|
||||
end
|
||||
end
|
||||
|
||||
local function onTotalPriceChange()
|
||||
local totalPrice = totalPriceEdit:getValue()
|
||||
local piecePrice = piecePriceEdit:getValue()
|
||||
local amount = amountEdit:getValue()
|
||||
local totalPrice = totalPriceEdit:getValue()
|
||||
local piecePrice = math.floor(totalPrice/amount)
|
||||
|
||||
piecePriceEdit:setValue(math.floor(totalPrice/amount))
|
||||
piecePriceEdit:setValue(piecePrice, true)
|
||||
if Market.isItemSelected() then
|
||||
updateFee(totalPrice, amount)
|
||||
updateFee(piecePrice, amount)
|
||||
end
|
||||
end
|
||||
|
||||
local function onPiecePriceChange()
|
||||
local amount = amountEdit:getValue()
|
||||
local totalPrice = totalPriceEdit:getValue()
|
||||
local piecePrice = piecePriceEdit:getValue()
|
||||
local amount = amountEdit:getValue()
|
||||
|
||||
totalPriceEdit:setValue(piecePrice*amount)
|
||||
totalPriceEdit:setValue(piecePrice*amount, true)
|
||||
if Market.isItemSelected() then
|
||||
updateFee(totalPrice, amount)
|
||||
updateFee(piecePrice, amount)
|
||||
end
|
||||
end
|
||||
|
||||
local function onAmountChange()
|
||||
local totalPrice = totalPriceEdit:getValue()
|
||||
local piecePrice = piecePriceEdit:getValue()
|
||||
local amount = amountEdit:getValue()
|
||||
local piecePrice = piecePriceEdit:getValue()
|
||||
local totalPrice = piecePrice * amount
|
||||
|
||||
piecePriceEdit:setValue(math.floor(totalPrice/amount))
|
||||
totalPriceEdit:setValue(piecePrice*amount)
|
||||
totalPriceEdit:setValue(piecePrice*amount, true)
|
||||
if Market.isItemSelected() then
|
||||
updateFee(totalPrice, amount)
|
||||
updateFee(piecePrice, amount)
|
||||
end
|
||||
end
|
||||
|
||||
local function initMarketItems(category)
|
||||
local function onMarketMessage(messageMode, message)
|
||||
Market.displayMessage(message)
|
||||
end
|
||||
|
||||
local function initMarketItems()
|
||||
for c = MarketCategory.First, MarketCategory.Last do
|
||||
marketItems[c] = {}
|
||||
end
|
||||
|
||||
-- save a list of items which are already added
|
||||
local itemSet = {}
|
||||
|
||||
-- populate all market items
|
||||
local types = g_things.findThingTypeByAttr(ThingAttrMarket, 0)
|
||||
for i = 1, #types do
|
||||
local t = types[i]
|
||||
local itemType = types[i]
|
||||
|
||||
local newItem = Item.create(t:getId())
|
||||
if newItem then
|
||||
local marketData = t:getMarketData()
|
||||
if not table.empty(marketData) then
|
||||
if marketData.category == category or category == MarketCategory.All then
|
||||
local item = Item.create(itemType:getId())
|
||||
if item then
|
||||
local marketData = itemType:getMarketData()
|
||||
if not table.empty(marketData) and not itemSet[marketData.tradeAs] then
|
||||
-- Some items use a different sprite in Market
|
||||
item:setId(marketData.showAs)
|
||||
|
||||
-- create new item block
|
||||
local item = {
|
||||
ptr = newItem,
|
||||
-- create new marketItem block
|
||||
local marketItem = {
|
||||
displayItem = item,
|
||||
thingType = itemType,
|
||||
marketData = marketData
|
||||
}
|
||||
|
||||
-- add new market item
|
||||
table.insert(marketItems[marketData.category], item)
|
||||
end
|
||||
table.insert(marketItems[marketData.category], marketItem)
|
||||
itemSet[marketData.tradeAs] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -624,8 +679,10 @@ local function initInterface()
|
||||
browsePanel = g_ui.loadUI('ui/marketoffers/browse')
|
||||
selectionTabBar:addTab(tr('Browse'), browsePanel)
|
||||
|
||||
overviewPanel = g_ui.loadUI('ui/marketoffers/overview')
|
||||
selectionTabBar:addTab(tr('Overview'), overviewPanel)
|
||||
-- Currently not used
|
||||
-- "Reserved for more functionality later"
|
||||
--overviewPanel = g_ui.loadUI('ui/marketoffers/overview')
|
||||
--selectionTabBar:addTab(tr('Overview'), overviewPanel)
|
||||
|
||||
displaysTabBar = marketOffersPanel:getChildById('rightTabBar')
|
||||
displaysTabBar:setContentWidget(marketOffersPanel:getChildById('rightTabContent'))
|
||||
@ -665,7 +722,6 @@ local function initInterface()
|
||||
-- setup selected item
|
||||
nameLabel = marketOffersPanel:getChildById('nameLabel')
|
||||
selectedItem = marketOffersPanel:getChildById('selectedItem')
|
||||
selectedItem.item = {}
|
||||
|
||||
-- setup create new offer
|
||||
totalPriceEdit = marketOffersPanel:getChildById('totalPriceEdit')
|
||||
@ -690,6 +746,14 @@ local function initInterface()
|
||||
filterButtons[MarketFilters.Depot] = browsePanel:getChildById('filterDepot')
|
||||
filterButtons[MarketFilters.SearchAll] = browsePanel:getChildById('filterSearchAll')
|
||||
|
||||
-- set filter default values
|
||||
clearFilters()
|
||||
|
||||
-- hook filters
|
||||
for _, filter in pairs(filterButtons) do
|
||||
filter.onCheckChange = Market.updateCurrentItems
|
||||
end
|
||||
|
||||
searchEdit = browsePanel:getChildById('searchEdit')
|
||||
categoryList = browsePanel:getChildById('categoryComboBox')
|
||||
subCategoryList = browsePanel:getChildById('subCategoryComboBox')
|
||||
@ -722,6 +786,13 @@ local function initInterface()
|
||||
sellStatsTable = itemStatsPanel:recursiveGetChildById('sellStatsTable')
|
||||
buyOfferTable.onSelectionChange = onSelectBuyOffer
|
||||
sellOfferTable.onSelectionChange = onSelectSellOffer
|
||||
|
||||
buyStatsTable:setColumnWidth({120, 270})
|
||||
sellStatsTable:setColumnWidth({120, 270})
|
||||
detailsTable:setColumnWidth({80, 330})
|
||||
|
||||
buyOfferTable:setSorting(4, TABLE_SORTING_DESC)
|
||||
sellOfferTable:setSorting(4, TABLE_SORTING_ASC)
|
||||
end
|
||||
|
||||
function init()
|
||||
@ -734,6 +805,8 @@ function init()
|
||||
offerExhaust[MarketAction.Sell] = 10
|
||||
offerExhaust[MarketAction.Buy] = 20
|
||||
|
||||
registerMessageMode(MessageModes.Market, onMarketMessage)
|
||||
|
||||
protocol.initProtocol()
|
||||
connect(g_game, { onGameEnd = Market.reset })
|
||||
connect(g_game, { onGameEnd = Market.close })
|
||||
@ -744,10 +817,15 @@ function init()
|
||||
end
|
||||
|
||||
function terminate()
|
||||
Market.close()
|
||||
|
||||
unregisterMessageMode(MessageModes.Market, onMarketMessage)
|
||||
|
||||
protocol.terminateProtocol()
|
||||
disconnect(g_game, { onGameEnd = Market.reset })
|
||||
disconnect(g_game, { onGameEnd = Market.close })
|
||||
|
||||
destroyAmountWindow()
|
||||
marketWindow:destroy()
|
||||
|
||||
Market = nil
|
||||
@ -763,9 +841,16 @@ function Market.reset()
|
||||
end
|
||||
end
|
||||
|
||||
function Market.displayMessage(message)
|
||||
if marketWindow:isHidden() then return end
|
||||
|
||||
local infoBox = displayInfoBox(tr('Market Error'), message)
|
||||
infoBox:lock()
|
||||
end
|
||||
|
||||
function Market.clearSelectedItem()
|
||||
if Market.isItemSelected() then
|
||||
Market.resetCreateOffer()
|
||||
Market.resetCreateOffer(true)
|
||||
offerTypeList:clearOptions()
|
||||
offerTypeList:setText('Please Select')
|
||||
offerTypeList:setEnabled(false)
|
||||
@ -787,22 +872,15 @@ function Market.clearSelectedItem()
|
||||
end
|
||||
|
||||
function Market.isItemSelected()
|
||||
return selectedItem and not table.empty(selectedItem.item) and selectedItem.item.ptr
|
||||
return selectedItem and selectedItem.item
|
||||
end
|
||||
|
||||
function Market.isOfferSelected(type)
|
||||
return selectedOffer[type] and not selectedOffer[type]:isNull()
|
||||
end
|
||||
|
||||
function Market.depotContains(itemId)
|
||||
local count = 0
|
||||
for i = 1, #information.depotItems do
|
||||
local item = information.depotItems[i]
|
||||
if item and item.ptr:getId() == itemId then
|
||||
count = count + item.ptr:getCount()
|
||||
end
|
||||
end
|
||||
return count
|
||||
function Market.getDepotCount(itemId)
|
||||
return information.depotItems[itemId] or 0
|
||||
end
|
||||
|
||||
function Market.enableCreateOffer(enable)
|
||||
@ -825,6 +903,8 @@ function Market.close(notify)
|
||||
if not marketWindow:isHidden() then
|
||||
marketWindow:hide()
|
||||
marketWindow:unlock()
|
||||
Market.clearSelectedItem(
|
||||
) Market.reset()
|
||||
if notify then
|
||||
MarketProtocol.sendMarketLeave()
|
||||
end
|
||||
@ -847,12 +927,17 @@ function Market.updateCurrentItems()
|
||||
Market.loadMarketItems(id)
|
||||
end
|
||||
|
||||
function Market.resetCreateOffer()
|
||||
function Market.resetCreateOffer(resetFee)
|
||||
piecePriceEdit:setValue(1)
|
||||
totalPriceEdit:setValue(1)
|
||||
amountEdit:setValue(1)
|
||||
refreshTypeList()
|
||||
clearFee()
|
||||
|
||||
if resetFee then
|
||||
clearFee()
|
||||
else
|
||||
updateFee(0, 0)
|
||||
end
|
||||
end
|
||||
|
||||
function Market.refreshItemsWidget(selectItem)
|
||||
@ -877,15 +962,15 @@ function Market.refreshItemsWidget(selectItem)
|
||||
itemBox.onCheckChange = Market.onItemBoxChecked
|
||||
itemBox.item = item
|
||||
|
||||
if selectItem > 0 and item.ptr:getId() == selectItem then
|
||||
if selectItem > 0 and item.marketData.tradeAs == selectItem then
|
||||
select = itemBox
|
||||
selectItem = 0
|
||||
end
|
||||
|
||||
local itemWidget = itemBox:getChildById('item')
|
||||
item.ptr:setCount(1) -- reset item count for image
|
||||
itemWidget:setItem(item.ptr)
|
||||
itemWidget:setItem(item.displayItem)
|
||||
|
||||
local amount = Market.depotContains(item.ptr:getId())
|
||||
local amount = Market.getDepotCount(item.marketData.tradeAs)
|
||||
if amount > 0 then
|
||||
itemWidget:setText(amount)
|
||||
itemBox:setTooltip('You have '.. amount ..' in your depot.')
|
||||
@ -893,8 +978,9 @@ function Market.refreshItemsWidget(selectItem)
|
||||
|
||||
radioItemSet:addWidget(itemBox)
|
||||
end
|
||||
|
||||
if select then
|
||||
select:setChecked(true)
|
||||
radioItemSet:selectWidget(select, false)
|
||||
end
|
||||
|
||||
layout:enableUpdates()
|
||||
@ -925,6 +1011,7 @@ function Market.loadMarketItems(category)
|
||||
for i = 1, #marketItems[category] do
|
||||
local item = marketItems[category][i]
|
||||
if isItemValid(item, category, searchFilter) then
|
||||
|
||||
table.insert(currentItems, item)
|
||||
end
|
||||
end
|
||||
@ -942,56 +1029,6 @@ function Market.loadMarketItems(category)
|
||||
Market.refreshItemsWidget()
|
||||
end
|
||||
|
||||
function Market.loadDepotItems(depotItems)
|
||||
information.depotItems = {}
|
||||
|
||||
local items = {}
|
||||
for i = 1, #depotItems do
|
||||
local data = depotItems[i]
|
||||
local id, count = data[1], data[2]
|
||||
|
||||
local tmpItem = Item.create(id)
|
||||
if tmpItem:isStackable() then
|
||||
if count > 100 then
|
||||
local createCount = math.floor(count/100)
|
||||
local remainder = count % 100
|
||||
if remainder > 0 then
|
||||
createCount = createCount + 1
|
||||
end
|
||||
for i = 1, createCount do
|
||||
local newItem = Item.create(id)
|
||||
if i == createCount and remainder > 0 then
|
||||
newItem:setCount(remainder)
|
||||
else
|
||||
newItem:setCount(100)
|
||||
end
|
||||
table.insert(items, newItem)
|
||||
end
|
||||
else
|
||||
local newItem = Item.create(id)
|
||||
newItem:setCount(count)
|
||||
table.insert(items, newItem)
|
||||
end
|
||||
else
|
||||
for i = 1, count do
|
||||
table.insert(items, Item.create(id))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, newItem in pairs(items) do
|
||||
local marketData = newItem:getMarketData()
|
||||
|
||||
if not table.empty(marketData) then
|
||||
local item = {
|
||||
ptr = newItem,
|
||||
marketData = marketData
|
||||
}
|
||||
table.insert(information.depotItems, item)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Market.createNewOffer()
|
||||
local type = offerTypeList:getCurrentOption().text
|
||||
if type == 'Sell' then
|
||||
@ -1003,12 +1040,10 @@ function Market.createNewOffer()
|
||||
if not Market.isItemSelected() then
|
||||
return
|
||||
end
|
||||
local item = selectedItem.item
|
||||
local spriteId = item.ptr:getId()
|
||||
|
||||
local spriteId = selectedItem.item.marketData.tradeAs
|
||||
|
||||
local piecePrice = piecePriceEdit:getValue()
|
||||
local totalPrice = totalPriceEdit:getValue()
|
||||
|
||||
local amount = amountEdit:getValue()
|
||||
local anonymous = anonymous:isChecked() and 1 or 0
|
||||
|
||||
@ -1019,7 +1054,10 @@ function Market.createNewOffer()
|
||||
errorMsg = errorMsg..'Not enough balance to create this offer.\n'
|
||||
end
|
||||
elseif type == MarketAction.Sell then
|
||||
if Market.depotContains(spriteId) < amount then
|
||||
if information.balance < fee then
|
||||
errorMsg = errorMsg..'Not enough balance to create this offer.\n'
|
||||
end
|
||||
if Market.getDepotCount(spriteId) < amount then
|
||||
errorMsg = errorMsg..'Not enough items in your depot to create this offer.\n'
|
||||
end
|
||||
end
|
||||
@ -1029,12 +1067,21 @@ function Market.createNewOffer()
|
||||
elseif piecePrice < piecePriceEdit.minimum then
|
||||
errorMsg = errorMsg..'Price is too low.\n'
|
||||
end
|
||||
|
||||
if amount > amountEdit.maximum then
|
||||
errorMsg = errorMsg..'Amount is too high.\n'
|
||||
elseif amount < amountEdit.minimum then
|
||||
errorMsg = errorMsg..'Amount is too low.\n'
|
||||
end
|
||||
|
||||
if amount * piecePrice > MarketMaxPrice then
|
||||
errorMsg = errorMsg..'Total price is too high.\n'
|
||||
end
|
||||
|
||||
if information.totalOffers >= MarketMaxOffers then
|
||||
errorMsg = errorMsg..'You cannot create more offers.\n'
|
||||
end
|
||||
|
||||
local timeCheck = os.time() - lastCreatedOffer
|
||||
if timeCheck < offerExhaust[type] then
|
||||
local waitTime = math.ceil(offerExhaust[type] - timeCheck)
|
||||
@ -1042,7 +1089,7 @@ function Market.createNewOffer()
|
||||
end
|
||||
|
||||
if errorMsg ~= '' then
|
||||
displayInfoBox('Error', errorMsg)
|
||||
Market.displayMessage(errorMsg)
|
||||
return
|
||||
end
|
||||
|
||||
@ -1060,9 +1107,6 @@ end
|
||||
|
||||
function Market.onItemBoxChecked(widget)
|
||||
if widget:isChecked() then
|
||||
if selectedItem.ref and widget ~= selectedItem.ref then
|
||||
selectedItem.ref:setChecked(false) -- temporary fix?
|
||||
end
|
||||
updateSelectedItem(widget)
|
||||
end
|
||||
end
|
||||
@ -1071,12 +1115,12 @@ end
|
||||
|
||||
function Market.onMarketEnter(depotItems, offers, balance, vocation)
|
||||
if not loaded then
|
||||
initMarketItems(MarketCategory.All)
|
||||
initMarketItems()
|
||||
loaded = true
|
||||
end
|
||||
|
||||
Market.clearSelectedItem()
|
||||
updateBalance(balance)
|
||||
averagePrice = 0
|
||||
|
||||
information.totalOffers = offers
|
||||
local player = g_game.getLocalPlayer()
|
||||
@ -1092,10 +1136,12 @@ function Market.onMarketEnter(depotItems, offers, balance, vocation)
|
||||
information.vocation = vocation
|
||||
end
|
||||
|
||||
Market.loadDepotItems(depotItems)
|
||||
-- set list of depot items
|
||||
information.depotItems = depotItems
|
||||
|
||||
-- update the items widget to match depot items
|
||||
if Market.isItemSelected() then
|
||||
local spriteId = selectedItem.item.ptr:getId()
|
||||
local spriteId = selectedItem.item.marketData.tradeAs
|
||||
MarketProtocol.silent(true) -- disable protocol messages
|
||||
Market.refreshItemsWidget(spriteId)
|
||||
MarketProtocol.silent(false) -- enable protocol messages
|
||||
@ -1107,14 +1153,6 @@ function Market.onMarketEnter(depotItems, offers, balance, vocation)
|
||||
Market.loadMarketItems(MarketCategory.First)
|
||||
end
|
||||
|
||||
-- build offer table header
|
||||
if buyOfferTable and not buyOfferTable:hasHeader() then
|
||||
buyOfferTable:addHeaderRow(offerTableHeader)
|
||||
end
|
||||
if sellOfferTable and not sellOfferTable:hasHeader() then
|
||||
sellOfferTable:addHeaderRow(offerTableHeader)
|
||||
end
|
||||
|
||||
if g_game.isOnline() then
|
||||
marketWindow:lock()
|
||||
marketWindow:show()
|
||||
|
@ -3,7 +3,7 @@ MarketWindow < MainWindow
|
||||
!text: tr('Market')
|
||||
size: 700 530
|
||||
|
||||
@onEscape: Market.close(true)
|
||||
@onEscape: Market.close()
|
||||
|
||||
// Main Panel Window
|
||||
|
||||
@ -54,6 +54,7 @@ MarketWindow < MainWindow
|
||||
Button
|
||||
id: resetButton
|
||||
!text: tr('Reset Market')
|
||||
!tooltip: tr('Reset selection, filters & search')
|
||||
anchors.top: mainTabContent.bottom
|
||||
anchors.left: mainTabContent.left
|
||||
margin-top: 5
|
||||
|
@ -50,14 +50,14 @@ local function parseMarketEnter(protocol, msg)
|
||||
vocation = msg:getU8() -- get vocation id
|
||||
end
|
||||
local offers = msg:getU8()
|
||||
local depotItems = {}
|
||||
|
||||
local depotItems = {}
|
||||
local depotCount = msg:getU16()
|
||||
for i = 1, depotCount do
|
||||
local itemId = msg:getU16() -- item id
|
||||
local itemCount = msg:getU16() -- item count
|
||||
|
||||
table.insert(depotItems, {itemId, itemCount})
|
||||
depotItems[itemId] = itemCount
|
||||
end
|
||||
|
||||
signalcall(Market.onMarketEnter, depotItems, offers, balance, vocation)
|
||||
|
@ -1,7 +1,7 @@
|
||||
AmountWindow < MainWindow
|
||||
id: amountWindow
|
||||
!text: tr('Amount')
|
||||
size: 270 80
|
||||
size: 270 90
|
||||
|
||||
Item
|
||||
id: item
|
||||
|
@ -4,14 +4,8 @@ MarketButtonBox < ButtonBoxRounded
|
||||
size: 106 22
|
||||
text-offset: 0 2
|
||||
text-align: center
|
||||
image-clip: 0 0 20 20
|
||||
image-border: 2
|
||||
|
||||
$hover !disabled:
|
||||
image-clip: 0 20 20 20
|
||||
|
||||
$checked:
|
||||
image-clip: 0 40 20 20
|
||||
color: white
|
||||
|
||||
$disabled:
|
||||
|
@ -10,15 +10,9 @@ MarketTabBarButton < TabBarButton
|
||||
margin-left: 0
|
||||
|
||||
$hover !checked:
|
||||
image-clip: 0 20 20 20
|
||||
color: white
|
||||
|
||||
$disabled:
|
||||
image-color: #ffffff66
|
||||
icon-color: #888888
|
||||
color: #ffffff
|
||||
|
||||
$checked:
|
||||
image-clip: 0 20 20 20
|
||||
color: #ffffff
|
||||
|
||||
$on !checked:
|
||||
@ -26,36 +20,24 @@ MarketTabBarButton < TabBarButton
|
||||
|
||||
MarketRightTabBar < TabBar
|
||||
MarketRightTabBarPanel < TabBarPanel
|
||||
// TODO: inherit style from TabBarButton and adjust it
|
||||
// anchors.left: none did not seem to work for me
|
||||
MarketRightTabBarButton < UIButton
|
||||
MarketRightTabBarButton < TabBarButton
|
||||
size: 20 25
|
||||
font: verdana-11px-rounded
|
||||
text-offset: 0 2
|
||||
image-source: /images/ui/tabbutton_square
|
||||
image-color: white
|
||||
image-clip: 0 0 20 20
|
||||
image-border: 3
|
||||
icon-color: white
|
||||
color: #aaaaaa
|
||||
anchors.top: parent.top
|
||||
padding: 5
|
||||
|
||||
anchors.right: prev.left
|
||||
color: #929292
|
||||
|
||||
$first:
|
||||
anchors.right: parent.right
|
||||
anchors.left: none
|
||||
|
||||
$!first:
|
||||
anchors.right: prev.left
|
||||
anchors.left: none
|
||||
|
||||
$hover !checked:
|
||||
image-clip: 0 20 20 20
|
||||
color: white
|
||||
|
||||
$disabled:
|
||||
image-color: #ffffff66
|
||||
icon-color: #888888
|
||||
color: #ffffff
|
||||
|
||||
$checked:
|
||||
image-clip: 0 20 20 20
|
||||
color: #ffffff
|
||||
|
||||
$on !checked:
|
||||
|
@ -135,8 +135,7 @@ Panel
|
||||
font: verdana-11px-rounded
|
||||
text-offset: 0 2
|
||||
anchors.top: offerTypeLabel.top
|
||||
anchors.left: prev.right
|
||||
margin-left: 32
|
||||
anchors.left: amountEdit.left
|
||||
|
||||
PreviousButton
|
||||
id: prevAmountButton
|
||||
@ -147,7 +146,7 @@ Panel
|
||||
|
||||
SpinBox
|
||||
id: amountEdit
|
||||
anchors.verticalCenter: prev.verticalCenter
|
||||
anchors.top: prev.top
|
||||
anchors.left: prev.right
|
||||
margin-left: 3
|
||||
width: 55
|
||||
@ -184,8 +183,6 @@ Panel
|
||||
Label
|
||||
id: feeLabel
|
||||
font: verdana-11px-rounded
|
||||
text-offset: 0 2
|
||||
anchors.top: createOfferButton.bottom
|
||||
anchors.right: parent.right
|
||||
margin-right: 8
|
||||
margin-top: 3
|
||||
anchors.left: createOfferButton.left
|
||||
margin: 2
|
@ -52,7 +52,7 @@ Panel
|
||||
|
||||
MarketButtonBox
|
||||
id: filterLevel
|
||||
checked: false
|
||||
&default: false
|
||||
!text: tr('Level')
|
||||
!tooltip: tr('Filter list to match your level')
|
||||
anchors.top: prev.bottom
|
||||
@ -62,11 +62,10 @@ Panel
|
||||
margin-left: 3
|
||||
width: 40
|
||||
height: 20
|
||||
@onCheckChange: Market.updateCurrentItems()
|
||||
|
||||
MarketButtonBox
|
||||
id: filterVocation
|
||||
checked: false
|
||||
&default: false
|
||||
!text: tr('Voc.')
|
||||
!tooltip: tr('Filter list to match your vocation')
|
||||
anchors.top: prev.top
|
||||
@ -75,7 +74,6 @@ Panel
|
||||
margin-left: 3
|
||||
width: 34
|
||||
height: 20
|
||||
@onCheckChange: Market.updateCurrentItems()
|
||||
|
||||
MarketComboBox
|
||||
id: slotComboBox
|
||||
@ -90,7 +88,7 @@ Panel
|
||||
|
||||
MarketButtonBox
|
||||
id: filterDepot
|
||||
checked: false
|
||||
&default: false
|
||||
!text: tr('Show Depot Only')
|
||||
!tooltip: tr('Show your depot items only')
|
||||
anchors.top: prev.bottom
|
||||
@ -99,7 +97,6 @@ Panel
|
||||
margin-top: 6
|
||||
margin-right: 3
|
||||
margin-left: 3
|
||||
@onCheckChange: Market.updateCurrentItems()
|
||||
|
||||
Panel
|
||||
id: itemsContainer
|
||||
@ -152,11 +149,10 @@ Panel
|
||||
|
||||
MarketButtonBox
|
||||
id: filterSearchAll
|
||||
checked: false
|
||||
&default: true
|
||||
!text: tr('All')
|
||||
!tooltip: tr('Search all items')
|
||||
anchors.verticalCenter: prev.verticalCenter
|
||||
anchors.left: prev.right
|
||||
anchors.right: itemsContainer.right
|
||||
margin-left: 3
|
||||
@onCheckChange: Market.updateCurrentItems()
|
||||
|
@ -1,11 +1,12 @@
|
||||
DetailsTableRow < TableRow
|
||||
font: verdana-11px-monochrome
|
||||
background-color: alpha
|
||||
focusable: true
|
||||
color: #cccccc
|
||||
height: 45
|
||||
focusable: false
|
||||
padding: 2
|
||||
even-background-color: alpha
|
||||
odd-background-color: alpha
|
||||
|
||||
DetailsTableColumn < TableColumn
|
||||
font: verdana-11px-monochrome
|
||||
|
@ -1,35 +1,27 @@
|
||||
OfferTableRow < TableRow
|
||||
font: verdana-11px-monochrome
|
||||
background-color: alpha
|
||||
focusable: true
|
||||
color: #cccccc
|
||||
height: 15
|
||||
|
||||
$focus:
|
||||
background-color: #294f6d
|
||||
color: #ffffff
|
||||
|
||||
OfferTableColumn < TableColumn
|
||||
font: verdana-11px-monochrome
|
||||
background-color: alpha
|
||||
text-offset: 5 0
|
||||
color: #cccccc
|
||||
width: 80
|
||||
focusable: false
|
||||
|
||||
OfferTableWarningColumn < OfferTableColumn
|
||||
color: #e03d3d
|
||||
|
||||
OfferTableHeaderRow < TableHeaderRow
|
||||
font: verdana-11px-monochrome
|
||||
focusable: false
|
||||
color: #cccccc
|
||||
height: 20
|
||||
|
||||
OfferTableHeaderColumn < TableHeaderColumn
|
||||
OfferTableHeaderColumn < SortableTableHeaderColumn
|
||||
font: verdana-11px-monochrome
|
||||
background-color: alpha
|
||||
text-offset: 2 0
|
||||
color: #cccccc
|
||||
width: 80
|
||||
focusable: true
|
||||
|
||||
$focus:
|
||||
background-color: #294f6d
|
||||
@ -74,8 +66,26 @@ Panel
|
||||
table-data: sellingTableData
|
||||
row-style: OfferTableRow
|
||||
column-style: OfferTableColumn
|
||||
header-row-style: OfferTableHeaderRow
|
||||
header-column-style: OfferTableHeaderColumn
|
||||
header-column-style: false
|
||||
header-row-style: false
|
||||
|
||||
OfferTableHeaderRow
|
||||
id: header
|
||||
OfferTableHeaderColumn
|
||||
!text: tr('Buyer Name')
|
||||
width: 100
|
||||
OfferTableHeaderColumn
|
||||
!text: tr('Amount')
|
||||
width: 60
|
||||
OfferTableHeaderColumn
|
||||
!text: tr('Total Price')
|
||||
width: 90
|
||||
OfferTableHeaderColumn
|
||||
!text: tr('Piece Price')
|
||||
width: 80
|
||||
OfferTableHeaderColumn
|
||||
!text: tr('Auction End')
|
||||
width: 120
|
||||
|
||||
TableData
|
||||
id: sellingTableData
|
||||
@ -129,8 +139,26 @@ Panel
|
||||
table-data: buyingTableData
|
||||
row-style: OfferTableRow
|
||||
column-style: OfferTableColumn
|
||||
header-row-style: OfferTableHeaderRow
|
||||
header-column-style: OfferTableHeaderColumn
|
||||
header-column-style: false
|
||||
header-row-style: false
|
||||
|
||||
OfferTableHeaderRow
|
||||
id: header
|
||||
OfferTableHeaderColumn
|
||||
!text: tr('Seller Name')
|
||||
width: 100
|
||||
OfferTableHeaderColumn
|
||||
!text: tr('Amount')
|
||||
width: 60
|
||||
OfferTableHeaderColumn
|
||||
!text: tr('Total Price')
|
||||
width: 90
|
||||
OfferTableHeaderColumn
|
||||
!text: tr('Piece Price')
|
||||
width: 80
|
||||
OfferTableHeaderColumn
|
||||
!text: tr('Auction End')
|
||||
width: 120
|
||||
|
||||
TableData
|
||||
id: buyingTableData
|
||||
|
@ -1,6 +1,5 @@
|
||||
StatsTableRow < TableRow
|
||||
font: verdana-11px-monochrome
|
||||
background-color: alpha
|
||||
focusable: true
|
||||
color: #cccccc
|
||||
height: 20
|
||||
@ -9,7 +8,7 @@ StatsTableRow < TableRow
|
||||
StatsTableColumn < TableColumn
|
||||
font: verdana-11px-monochrome
|
||||
background-color: alpha
|
||||
text-offset: 5 0
|
||||
text-offset: 5 3
|
||||
color: #cccccc
|
||||
width: 110
|
||||
focusable: false
|
||||
|
@ -177,6 +177,7 @@ function itemPopup(self, mousePosition, mouseButton)
|
||||
|
||||
if mouseButton == MouseRightButton then
|
||||
local menu = g_ui.createWidget('PopupMenu')
|
||||
menu:setGameMenu(true)
|
||||
menu:addOption(tr('Look'), function() return g_game.inspectNpcTrade(self:getItem()) end)
|
||||
menu:display(mousePosition)
|
||||
return true
|
||||
|
@ -25,13 +25,17 @@ mountCreature = nil
|
||||
currentMount = 1
|
||||
|
||||
function init()
|
||||
connect(g_game, { onOpenOutfitWindow = create,
|
||||
onGameEnd = destroy })
|
||||
connect(g_game, {
|
||||
onOpenOutfitWindow = create,
|
||||
onGameEnd = destroy
|
||||
})
|
||||
end
|
||||
|
||||
function terminate()
|
||||
disconnect(g_game, { onOpenOutfitWindow = create,
|
||||
onGameEnd = destroy })
|
||||
disconnect(g_game, {
|
||||
onOpenOutfitWindow = create,
|
||||
onGameEnd = destroy
|
||||
})
|
||||
destroy()
|
||||
end
|
||||
|
||||
@ -300,17 +304,11 @@ function updateOutfit()
|
||||
addon.widget:setChecked(false)
|
||||
addon.widget:setEnabled(false)
|
||||
end
|
||||
outfit.addons = 0
|
||||
|
||||
if availableAddons > 0 then
|
||||
for _, i in pairs(ADDON_SETS[availableAddons]) do
|
||||
addons[i].widget:setEnabled(true)
|
||||
end
|
||||
end
|
||||
|
||||
outfit.addons = 0
|
||||
for i = 1, #prevAddons do
|
||||
local addon = prevAddons[i]
|
||||
if addon and addons[i].widget:isEnabled() then
|
||||
addons[i].widget:setChecked(true)
|
||||
end
|
||||
end
|
||||
|
@ -1,10 +1,11 @@
|
||||
DeathWindow < MainWindow
|
||||
id: deathWindow
|
||||
!text: tr('You are dead')
|
||||
size: 350 155
|
||||
&baseWidth: 350
|
||||
&baseHeight: 15
|
||||
|
||||
Label
|
||||
!text: tr('Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nSimply click on Ok to resume your journeys!')
|
||||
id: labelText
|
||||
width: 550
|
||||
height: 140
|
||||
anchors.left: parent.left
|
||||
|
@ -1,5 +1,11 @@
|
||||
deathWindow = nil
|
||||
|
||||
local deathTexts = {
|
||||
regular = {text = 'Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nSimply click on Ok to resume your journeys!', height = 140, width = 0},
|
||||
unfair = {text = 'Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nThis death penalty has been reduced by %i%%\nbecause it was an unfair fight.\n\nSimply click on Ok to resume your journeys!', height = 185, width = 0},
|
||||
blessed = {text = 'Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back into this world\n\nThis death penalty has been reduced by 100%\nbecause you are blessed with the Adventurer\'s Blessing\n\nSimply click on Ok to resume your journeys!', height = 170, width = 90}
|
||||
}
|
||||
|
||||
function init()
|
||||
g_ui.importStyle('deathwindow')
|
||||
|
||||
@ -21,9 +27,9 @@ function reset()
|
||||
end
|
||||
end
|
||||
|
||||
function display()
|
||||
function display(deathType, penalty)
|
||||
displayDeadMessage()
|
||||
openWindow()
|
||||
openWindow(deathType, penalty)
|
||||
end
|
||||
|
||||
function displayDeadMessage()
|
||||
@ -33,12 +39,31 @@ function displayDeadMessage()
|
||||
modules.game_textmessage.displayGameMessage(tr('You are dead.'))
|
||||
end
|
||||
|
||||
function openWindow()
|
||||
function openWindow(deathType, penalty)
|
||||
if deathWindow then
|
||||
deathWindow:destroy()
|
||||
return
|
||||
end
|
||||
|
||||
deathWindow = g_ui.createWidget('DeathWindow', rootWidget)
|
||||
|
||||
local textLabel = deathWindow:getChildById('labelText')
|
||||
if deathType == DeathType.Regular then
|
||||
if penalty == 100 then
|
||||
textLabel:setText(deathTexts.regular.text)
|
||||
deathWindow:setHeight(deathWindow.baseHeight + deathTexts.regular.height)
|
||||
deathWindow:setWidth(deathWindow.baseWidth + deathTexts.regular.width)
|
||||
else
|
||||
textLabel:setText(string.format(deathTexts.unfair.text, 100 - penalty))
|
||||
deathWindow:setHeight(deathWindow.baseHeight + deathTexts.unfair.height)
|
||||
deathWindow:setWidth(deathWindow.baseWidth + deathTexts.unfair.width)
|
||||
end
|
||||
elseif deathType == DeathType.Blessed then
|
||||
textLabel:setText(deathTexts.blessed.text)
|
||||
deathWindow:setHeight(deathWindow.baseHeight + deathTexts.blessed.height)
|
||||
deathWindow:setWidth(deathWindow.baseWidth + deathTexts.blessed.width)
|
||||
end
|
||||
|
||||
local okButton = deathWindow:getChildById('buttonOk')
|
||||
local cancelButton = deathWindow:getChildById('buttonCancel')
|
||||
|
||||
|
@ -44,7 +44,6 @@ MessageTypes = {
|
||||
[MessageModes.Party] = MessageSettings.centerGreen,
|
||||
[MessageModes.PartyManagement] = MessageSettings.centerWhite,
|
||||
[MessageModes.TutorialHint] = MessageSettings.centerWhite,
|
||||
[MessageModes.Market] = MessageSettings.centerWhite,
|
||||
[MessageModes.BeyondLast] = MessageSettings.centerWhite,
|
||||
[MessageModes.Report] = MessageSettings.consoleRed,
|
||||
[MessageModes.HotkeyUse] = MessageSettings.centerGreen,
|
||||
@ -55,13 +54,19 @@ MessageTypes = {
|
||||
messagesPanel = nil
|
||||
|
||||
function init()
|
||||
connect(g_game, 'onTextMessage', displayMessage)
|
||||
for messageMode, _ in pairs(MessageTypes) do
|
||||
registerMessageMode(messageMode, displayMessage)
|
||||
end
|
||||
|
||||
connect(g_game, 'onGameEnd', clearMessages)
|
||||
messagesPanel = g_ui.loadUI('textmessage', modules.game_interface.getRootPanel())
|
||||
end
|
||||
|
||||
function terminate()
|
||||
disconnect(g_game, 'onTextMessage', displayMessage)
|
||||
for messageMode, _ in pairs(MessageTypes) do
|
||||
unregisterMessageMode(messageMode, displayMessage)
|
||||
end
|
||||
|
||||
disconnect(g_game, 'onGameEnd', clearMessages)
|
||||
clearMessages()
|
||||
messagesPanel:destroy()
|
||||
@ -75,9 +80,7 @@ function displayMessage(mode, text)
|
||||
if not g_game.isOnline() then return end
|
||||
|
||||
local msgtype = MessageTypes[mode]
|
||||
|
||||
if not msgtype then
|
||||
perror('unhandled onTextMessage message mode ' .. mode .. ': ' .. text)
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -8,6 +8,7 @@ TextMessageLabel < UILabel
|
||||
|
||||
Panel
|
||||
anchors.fill: gameMapPanel
|
||||
anchors.bottom: gameBottomPanel.top
|
||||
focusable: false
|
||||
|
||||
Panel
|
||||
|
148
modules/game_unjustifiedpoints/unjustifiedpoints.lua
Normal file
@ -0,0 +1,148 @@
|
||||
unjustifiedPointsWindow = nil
|
||||
unjustifiedPointsButton = nil
|
||||
contentsPanel = nil
|
||||
|
||||
openPvpSituationsLabel = nil
|
||||
currentSkullWidget = nil
|
||||
skullTimeLabel = nil
|
||||
|
||||
dayProgressBar = nil
|
||||
weekProgressBar = nil
|
||||
monthProgressBar = nil
|
||||
|
||||
daySkullWidget = nil
|
||||
weekSkullWidget = nil
|
||||
monthSkullWidget = nil
|
||||
|
||||
function init()
|
||||
connect(g_game, { onGameStart = online,
|
||||
onUnjustifiedPointsChange = onUnjustifiedPointsChange,
|
||||
onOpenPvpSituationsChange = onOpenPvpSituationsChange })
|
||||
connect(LocalPlayer, { onSkullChange = onSkullChange } )
|
||||
|
||||
unjustifiedPointsButton = modules.client_topmenu.addRightGameToggleButton('unjustifiedPointsButton',
|
||||
tr('Unjustified Points'), '/images/topbuttons/unjustifiedpoints', toggle)
|
||||
unjustifiedPointsButton:setOn(true)
|
||||
unjustifiedPointsButton:hide()
|
||||
|
||||
unjustifiedPointsWindow = g_ui.loadUI('unjustifiedpoints', modules.game_interface.getRightPanel())
|
||||
unjustifiedPointsWindow:disableResize()
|
||||
unjustifiedPointsWindow:setup()
|
||||
|
||||
contentsPanel = unjustifiedPointsWindow:getChildById('contentsPanel')
|
||||
|
||||
openPvpSituationsLabel = contentsPanel:getChildById('openPvpSituationsLabel')
|
||||
currentSkullWidget = contentsPanel:getChildById('currentSkullWidget')
|
||||
skullTimeLabel = contentsPanel:getChildById('skullTimeLabel')
|
||||
|
||||
dayProgressBar = contentsPanel:getChildById('dayProgressBar')
|
||||
weekProgressBar = contentsPanel:getChildById('weekProgressBar')
|
||||
monthProgressBar = contentsPanel:getChildById('monthProgressBar')
|
||||
daySkullWidget = contentsPanel:getChildById('daySkullWidget')
|
||||
weekSkullWidget = contentsPanel:getChildById('weekSkullWidget')
|
||||
monthSkullWidget = contentsPanel:getChildById('monthSkullWidget')
|
||||
|
||||
if g_game.isOnline() then
|
||||
online()
|
||||
end
|
||||
end
|
||||
|
||||
function terminate()
|
||||
disconnect(g_game, { onGameStart = online,
|
||||
onUnjustifiedPointsChange = onUnjustifiedPointsChange,
|
||||
onOpenPvpSituationsChange = onOpenPvpSituationsChange })
|
||||
disconnect(LocalPlayer, { onSkullChange = onSkullChange } )
|
||||
|
||||
unjustifiedPointsWindow:destroy()
|
||||
unjustifiedPointsButton:destroy()
|
||||
end
|
||||
|
||||
function onMiniWindowClose()
|
||||
unjustifiedPointsButton:setOn(false)
|
||||
end
|
||||
|
||||
function toggle()
|
||||
if unjustifiedPointsButton:isOn() then
|
||||
unjustifiedPointsWindow:close()
|
||||
unjustifiedPointsButton:setOn(false)
|
||||
else
|
||||
unjustifiedPointsWindow:open()
|
||||
unjustifiedPointsButton:setOn(true)
|
||||
end
|
||||
end
|
||||
|
||||
function online()
|
||||
if g_game.getFeature(GameUnjustifiedPoints) then
|
||||
unjustifiedPointsButton:show()
|
||||
else
|
||||
unjustifiedPointsButton:hide()
|
||||
unjustifiedPointsWindow:close()
|
||||
end
|
||||
|
||||
refresh()
|
||||
end
|
||||
|
||||
function refresh()
|
||||
local localPlayer = g_game.getLocalPlayer()
|
||||
|
||||
local unjustifiedPoints = g_game.getUnjustifiedPoints()
|
||||
onUnjustifiedPointsChange(unjustifiedPoints)
|
||||
|
||||
onSkullChange(localPlayer, localPlayer:getSkull())
|
||||
onOpenPvpSituationsChange(g_game.getOpenPvpSituations())
|
||||
end
|
||||
|
||||
function onSkullChange(localPlayer, skull)
|
||||
if not localPlayer:isLocalPlayer() then return end
|
||||
|
||||
if skull == SkullRed or skull == SkullBlack then
|
||||
currentSkullWidget:setIcon(getSkullImagePath(skull))
|
||||
currentSkullWidget:setTooltip('Remaining skull time')
|
||||
else
|
||||
currentSkullWidget:setIcon('')
|
||||
currentSkullWidget:setTooltip('You have no skull')
|
||||
end
|
||||
|
||||
daySkullWidget:setIcon(getSkullImagePath(getNextSkullId(skull)))
|
||||
weekSkullWidget:setIcon(getSkullImagePath(getNextSkullId(skull)))
|
||||
monthSkullWidget:setIcon(getSkullImagePath(getNextSkullId(skull)))
|
||||
end
|
||||
|
||||
function onOpenPvpSituationsChange(amount)
|
||||
openPvpSituationsLabel:setText(amount)
|
||||
end
|
||||
|
||||
local function getColorByKills(kills)
|
||||
if kills < 2 then
|
||||
return 'red'
|
||||
elseif kills < 3 then
|
||||
return 'yellow'
|
||||
end
|
||||
|
||||
return 'green'
|
||||
end
|
||||
|
||||
function onUnjustifiedPointsChange(unjustifiedPoints)
|
||||
if unjustifiedPoints.skullTime == 0 then
|
||||
skullTimeLabel:setText('No skull')
|
||||
skullTimeLabel:setTooltip('You have no skull')
|
||||
else
|
||||
skullTimeLabel:setText(unjustifiedPoints.skullTime .. ' days')
|
||||
skullTimeLabel:setTooltip('Remaining skull time')
|
||||
end
|
||||
|
||||
dayProgressBar:setValue(unjustifiedPoints.killsDay, 0, 100)
|
||||
dayProgressBar:setBackgroundColor(getColorByKills(unjustifiedPoints.killsDayRemaining))
|
||||
dayProgressBar:setTooltip(string.format('Unjustified points gained during the last 24 hours.\n%i kill%s left.', unjustifiedPoints.killsDayRemaining, (unjustifiedPoints.killsDayRemaining == 1 and '' or 's')))
|
||||
dayProgressBar:setText(string.format('%i kill%s left', unjustifiedPoints.killsDayRemaining, (unjustifiedPoints.killsDayRemaining == 1 and '' or 's')))
|
||||
|
||||
weekProgressBar:setValue(unjustifiedPoints.killsWeek, 0, 100)
|
||||
weekProgressBar:setBackgroundColor(getColorByKills(unjustifiedPoints.killsWeekRemaining))
|
||||
weekProgressBar:setTooltip(string.format('Unjustified points gained during the last 7 days.\n%i kill%s left.', unjustifiedPoints.killsWeekRemaining, (unjustifiedPoints.killsWeekRemaining == 1 and '' or 's')))
|
||||
weekProgressBar:setText(string.format('%i kill%s left', unjustifiedPoints.killsWeekRemaining, (unjustifiedPoints.killsWeekRemaining == 1 and '' or 's')))
|
||||
|
||||
monthProgressBar:setValue(unjustifiedPoints.killsMonth, 0, 100)
|
||||
monthProgressBar:setBackgroundColor(getColorByKills(unjustifiedPoints.killsMonthRemaining))
|
||||
monthProgressBar:setTooltip(string.format('Unjustified points gained during the last 30 days.\n%i kill%s left.', unjustifiedPoints.killsMonthRemaining, (unjustifiedPoints.killsMonthRemaining == 1 and '' or 's')))
|
||||
monthProgressBar:setText(string.format('%i kill%s left', unjustifiedPoints.killsMonthRemaining, (unjustifiedPoints.killsMonthRemaining == 1 and '' or 's')))
|
||||
end
|
8
modules/game_unjustifiedpoints/unjustifiedpoints.otmod
Normal file
@ -0,0 +1,8 @@
|
||||
Module
|
||||
name: game_unjustifiedpoints
|
||||
description: View unjustified points
|
||||
author: Summ
|
||||
sandboxed: true
|
||||
scripts: [ unjustifiedpoints ]
|
||||
@onLoad: init()
|
||||
@onUnload: terminate()
|
80
modules/game_unjustifiedpoints/unjustifiedpoints.otui
Normal file
@ -0,0 +1,80 @@
|
||||
SkullProgressBar < ProgressBar
|
||||
height: 13
|
||||
margin: 4 18 0 10
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
SkullWidget < UIWidget
|
||||
size: 13 13
|
||||
margin-right: 2
|
||||
anchors.right: parent.right
|
||||
image-source: /images/game/skull_socket
|
||||
|
||||
MiniWindow
|
||||
id: unjustifiedPointsWindow
|
||||
!text: tr('Unjustified Points')
|
||||
height: 114
|
||||
icon: /images/topbuttons/unjustifiedpoints
|
||||
@onClose: modules.game_unjustifiedpoints.onMiniWindowClose()
|
||||
&save: true
|
||||
|
||||
MiniWindowContents
|
||||
Label
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
!text: tr('Open PvP')
|
||||
!tooltip: tr('Open PvP Situations')
|
||||
phantom: false
|
||||
margin-top: 2
|
||||
margin-left: 10
|
||||
|
||||
Label
|
||||
id: openPvpSituationsLabel
|
||||
anchors.top: prev.bottom
|
||||
anchors.left: parent.left
|
||||
font: verdana-11px-rounded
|
||||
margin-left: 12
|
||||
phantom: false
|
||||
|
||||
Label
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
!text: tr('Skull Time')
|
||||
margin-top: 2
|
||||
margin-right: 10
|
||||
|
||||
SkullWidget
|
||||
id: currentSkullWidget
|
||||
anchors.top: prev.bottom
|
||||
margin-right: 10
|
||||
|
||||
Label
|
||||
id: skullTimeLabel
|
||||
anchors.top: prev.top
|
||||
anchors.right: prev.left
|
||||
font: verdana-11px-rounded
|
||||
margin-right: 6
|
||||
phantom: false
|
||||
|
||||
SkullProgressBar
|
||||
id: dayProgressBar
|
||||
margin-top: 10
|
||||
|
||||
SkullWidget
|
||||
id: daySkullWidget
|
||||
anchors.top: prev.top
|
||||
|
||||
SkullProgressBar
|
||||
id: weekProgressBar
|
||||
|
||||
SkullWidget
|
||||
id: weekSkullWidget
|
||||
anchors.top: prev.top
|
||||
|
||||
SkullProgressBar
|
||||
id: monthProgressBar
|
||||
|
||||
SkullWidget
|
||||
id: monthSkullWidget
|
||||
anchors.top: prev.top
|
@ -345,6 +345,7 @@ function onVipListMousePress(widget, mousePos, mouseButton)
|
||||
local vipList = vipWindow:getChildById('contentsPanel')
|
||||
|
||||
local menu = g_ui.createWidget('PopupMenu')
|
||||
menu:setGameMenu(true)
|
||||
menu:addOption(tr('Add new VIP'), function() createAddWindow() end)
|
||||
|
||||
menu:addSeparator()
|
||||
@ -377,6 +378,7 @@ function onVipListLabelMousePress(widget, mousePos, mouseButton)
|
||||
local vipList = vipWindow:getChildById('contentsPanel')
|
||||
|
||||
local menu = g_ui.createWidget('PopupMenu')
|
||||
menu:setGameMenu(true)
|
||||
menu:addOption(tr('Send Message'), function() g_game.openPrivateChannel(widget:getText()) end)
|
||||
menu:addOption(tr('Add new VIP'), function() createAddWindow() end)
|
||||
menu:addOption(tr('Edit %s', widget:getText()), function() if widget then createEditWindow(widget) end end)
|
||||
|
@ -120,6 +120,17 @@ GameSpritesAlphaChannel = 56
|
||||
GamePremiumExpiration = 57
|
||||
GameBrowseField = 58
|
||||
GameEnhancedAnimations = 59
|
||||
GameOGLInformation = 60
|
||||
GameMessageSizeCheck = 61
|
||||
GamePreviewState = 62
|
||||
GameLoginPacketEncryption = 63
|
||||
GameClientVersion = 64
|
||||
GameContentRevision = 65
|
||||
GameExperienceBonus = 66
|
||||
GameAuthenticator = 67
|
||||
GameUnjustifiedPoints = 68
|
||||
GameSessionKey = 69
|
||||
GameDeathType = 70
|
||||
|
||||
TextColors = {
|
||||
red = '#f55e5e', --'#c83200'
|
||||
@ -184,7 +195,9 @@ MessageModes = {
|
||||
RVRChannel = 46,
|
||||
RVRAnswer = 47,
|
||||
RVRContinue = 48,
|
||||
Last = 49,
|
||||
GameHighlight = 49,
|
||||
NpcFromStartBlock = 50,
|
||||
Last = 51,
|
||||
Invalid = 255,
|
||||
}
|
||||
|
||||
@ -201,7 +214,7 @@ CIPSOFT_RSA = "1321277432058722840622950990822933849527763264961655079678763618"
|
||||
"88792221429527047321331896351555606801473202394175817"
|
||||
|
||||
-- set to the latest Tibia.pic signature to make otclient compatible with official tibia
|
||||
PIC_SIGNATURE = 0x53208400
|
||||
PIC_SIGNATURE = 0x542100C1
|
||||
|
||||
OsTypes = {
|
||||
Linux = 1,
|
||||
@ -244,4 +257,25 @@ ExtendedIds = {
|
||||
NeedsUpdate = 7
|
||||
}
|
||||
|
||||
PreviewState = {
|
||||
Default = 0,
|
||||
Inactive = 1,
|
||||
Active = 2
|
||||
}
|
||||
|
||||
Blessings = {
|
||||
None = 0,
|
||||
Adventurer = 1,
|
||||
SpiritualShielding = 2,
|
||||
EmbraceOfTibia = 4,
|
||||
FireOfSuns = 8,
|
||||
WisdomOfSolitude = 16,
|
||||
SparkOfPhoenix = 32
|
||||
}
|
||||
|
||||
DeathType = {
|
||||
Regular = 0,
|
||||
Blessed = 1
|
||||
}
|
||||
|
||||
-- @}
|
||||
|
@ -35,6 +35,13 @@ NpcIconTradeQuest = 4
|
||||
|
||||
-- @}
|
||||
|
||||
function getNextSkullId(skullId)
|
||||
if skullId == SkullRed or skullId == SkullBlack then
|
||||
return SkullBlack
|
||||
end
|
||||
return SkullRed
|
||||
end
|
||||
|
||||
function getSkullImagePath(skullId)
|
||||
local path
|
||||
if skullId == SkullYellow then
|
||||
|
@ -1,7 +1,5 @@
|
||||
local currentRsa
|
||||
|
||||
function g_game.getRsa()
|
||||
return currentRsa
|
||||
return G.currentRsa
|
||||
end
|
||||
|
||||
function g_game.findPlayerItem(itemId, subType)
|
||||
@ -19,7 +17,7 @@ function g_game.findPlayerItem(itemId, subType)
|
||||
end
|
||||
|
||||
function g_game.chooseRsa(host)
|
||||
if currentRsa ~= CIPSOFT_RSA and currentRsa ~= OTSERV_RSA then return end
|
||||
if G.currentRsa ~= CIPSOFT_RSA and G.currentRsa ~= OTSERV_RSA then return end
|
||||
if host:ends('.tibia.com') or host:ends('.cipsoft.com') then
|
||||
g_game.setRsa(CIPSOFT_RSA)
|
||||
|
||||
@ -29,7 +27,7 @@ function g_game.chooseRsa(host)
|
||||
g_game.setCustomOs(OsTypes.Linux)
|
||||
end
|
||||
else
|
||||
if currentRsa == CIPSOFT_RSA then
|
||||
if G.currentRsa == CIPSOFT_RSA then
|
||||
g_game.setCustomOs(-1)
|
||||
end
|
||||
g_game.setRsa(OTSERV_RSA)
|
||||
@ -44,47 +42,49 @@ end
|
||||
function g_game.setRsa(rsa, e)
|
||||
e = e or '65537'
|
||||
g_crypt.rsaSetPublicKey(rsa, e)
|
||||
currentRsa = rsa
|
||||
G.currentRsa = rsa
|
||||
end
|
||||
|
||||
function g_game.isOfficialTibia()
|
||||
return currentRsa == CIPSOFT_RSA
|
||||
return G.currentRsa == CIPSOFT_RSA
|
||||
end
|
||||
|
||||
function g_game.getSupportedClients()
|
||||
return {
|
||||
740, 741, 750, 760, 770, 772,
|
||||
740, 741, 750, 760, 770, 772,
|
||||
780, 781, 782, 790, 792,
|
||||
|
||||
800, 810, 811, 820, 821, 822,
|
||||
830, 831, 840, 842, 850, 853,
|
||||
854, 855, 857, 860, 861, 862,
|
||||
800, 810, 811, 820, 821, 822,
|
||||
830, 831, 840, 842, 850, 853,
|
||||
854, 855, 857, 860, 861, 862,
|
||||
870, 871,
|
||||
|
||||
900, 910, 920, 931, 940, 943,
|
||||
944, 951, 952, 953, 954, 960,
|
||||
961, 963, 970, 971, 972, 973,
|
||||
980, 981, 982, 983, 984, 985,
|
||||
900, 910, 920, 931, 940, 943,
|
||||
944, 951, 952, 953, 954, 960,
|
||||
961, 963, 970, 971, 972, 973,
|
||||
980, 981, 982, 983, 984, 985,
|
||||
986,
|
||||
|
||||
1000, 1001, 1002, 1010, 1011,
|
||||
1012, 1013, 1020, 1021, 1022,
|
||||
1030, 1031, 1032, 1033, 1034,
|
||||
1035, 1036, 1037, 1038, 1039,
|
||||
1000, 1001, 1002, 1010, 1011,
|
||||
1012, 1013, 1020, 1021, 1022,
|
||||
1030, 1031, 1032, 1033, 1034,
|
||||
1035, 1036, 1037, 1038, 1039,
|
||||
1040, 1041, 1050, 1051, 1052,
|
||||
1053, 1054, 1055, 1056, 1057,
|
||||
1058, 1059, 1060, 1061
|
||||
1058, 1059, 1060, 1061, 1062,
|
||||
1063, 1064, 1070, 1071, 1072,
|
||||
1073, 1074, 1075, 1076
|
||||
}
|
||||
end
|
||||
|
||||
-- The client version and protocol version where
|
||||
-- unsynchronized for some releases, not sure if this
|
||||
-- unsynchronized for some releases, not sure if this
|
||||
-- will be the normal standard.
|
||||
|
||||
-- Client Version: Publicly given version when
|
||||
-- Client Version: Publicly given version when
|
||||
-- downloading Cipsoft client.
|
||||
|
||||
-- Protocol Version: Previously was the same as
|
||||
-- Protocol Version: Previously was the same as
|
||||
-- the client version, but was unsychronized in some
|
||||
-- releases, now it needs to be verified and added here
|
||||
-- if it does not match the client version.
|
||||
@ -92,7 +92,7 @@ end
|
||||
-- Reason for defining both: The server now requires a
|
||||
-- Client version and Protocol version from the client.
|
||||
|
||||
-- Important: Use getClientVersion for specific protocol
|
||||
-- Important: Use getClientVersion for specific protocol
|
||||
-- features to ensure we are using the proper version.
|
||||
|
||||
function g_game.getClientProtocolVersion(client)
|
||||
@ -110,4 +110,6 @@ function g_game.getClientProtocolVersion(client)
|
||||
return clients[client] or client
|
||||
end
|
||||
|
||||
g_game.setRsa(OTSERV_RSA)
|
||||
if not G.currentRsa then
|
||||
g_game.setRsa(OTSERV_RSA)
|
||||
end
|
||||
|
@ -19,6 +19,7 @@ Module
|
||||
dofile 'creature'
|
||||
dofile 'player'
|
||||
dofile 'market'
|
||||
dofile 'textmessages'
|
||||
dofile 'thing'
|
||||
dofile 'spells'
|
||||
|
||||
|
@ -1,3 +1,8 @@
|
||||
MarketMaxAmount = 2000
|
||||
MarketMaxAmountStackable = 64000
|
||||
MarketMaxPrice = 999999999
|
||||
MarketMaxOffers = 100
|
||||
|
||||
MarketAction = {
|
||||
Buy = 0,
|
||||
Sell = 1
|
||||
@ -160,8 +165,8 @@ MarketFilters = {
|
||||
SearchAll = 4
|
||||
}
|
||||
|
||||
MarketFilters.First = MarketFilters.vocation
|
||||
MarketFilters.Last = MarketFilters.depot
|
||||
MarketFilters.First = MarketFilters.Vocation
|
||||
MarketFilters.Last = MarketFilters.Depot
|
||||
|
||||
function getMarketSlotFilterId(name)
|
||||
local id = table.find(MarketSlotFilters, name)
|
||||
|
@ -2,13 +2,20 @@
|
||||
ProtocolLogin = extends(Protocol, "ProtocolLogin")
|
||||
|
||||
LoginServerError = 10
|
||||
LoginServerTokenSuccess = 12
|
||||
LoginServerTokenError = 13
|
||||
LoginServerUpdate = 17
|
||||
LoginServerMotd = 20
|
||||
LoginServerUpdateNeeded = 30
|
||||
LoginServerSessionKey = 40
|
||||
LoginServerCharacterList = 100
|
||||
LoginServerExtendedCharacterList = 101
|
||||
|
||||
function ProtocolLogin:login(host, port, accountName, accountPassword)
|
||||
-- Since 10.76
|
||||
LoginServerRetry = 10
|
||||
LoginServerErrorNew = 11
|
||||
|
||||
function ProtocolLogin:login(host, port, accountName, accountPassword, authenticatorToken, stayLogged)
|
||||
if string.len(host) == 0 or port == nil or port == 0 then
|
||||
signalcall(self.onLoginError, self, tr("You must enter a valid server address and port."))
|
||||
return
|
||||
@ -16,6 +23,8 @@ function ProtocolLogin:login(host, port, accountName, accountPassword)
|
||||
|
||||
self.accountName = accountName
|
||||
self.accountPassword = accountPassword
|
||||
self.authenticatorToken = authenticatorToken
|
||||
self.stayLogged = stayLogged
|
||||
self.connectCallback = self.sendLoginPacket
|
||||
|
||||
self:connect(host, port)
|
||||
@ -32,23 +41,28 @@ function ProtocolLogin:sendLoginPacket()
|
||||
|
||||
msg:addU16(g_game.getProtocolVersion())
|
||||
|
||||
if g_game.getClientVersion() >= 980 then
|
||||
if g_game.getFeature(GameClientVersion) then
|
||||
msg:addU32(g_game.getClientVersion())
|
||||
end
|
||||
|
||||
msg:addU32(g_things.getDatSignature())
|
||||
if g_game.getFeature(GameContentRevision) then
|
||||
msg:addU16(g_things.getContentRevision())
|
||||
msg:addU16(0)
|
||||
else
|
||||
msg:addU32(g_things.getDatSignature())
|
||||
end
|
||||
msg:addU32(g_sprites.getSprSignature())
|
||||
msg:addU32(PIC_SIGNATURE)
|
||||
|
||||
if g_game.getClientVersion() >= 980 then
|
||||
msg:addU8(0) -- clientType
|
||||
if g_game.getFeature(GamePreviewState) then
|
||||
msg:addU8(0)
|
||||
end
|
||||
|
||||
local offset = msg:getMessageSize()
|
||||
|
||||
if g_game.getClientVersion() >= 770 then
|
||||
if g_game.getFeature(GameLoginPacketEncryption) then
|
||||
-- first RSA byte must be 0
|
||||
msg:addU8(0)
|
||||
|
||||
-- xtea key
|
||||
self:generateXteaKey()
|
||||
local xteaKey = self:getXteaKey()
|
||||
@ -73,8 +87,44 @@ function ProtocolLogin:sendLoginPacket()
|
||||
|
||||
local paddingBytes = g_crypt.rsaGetSize() - (msg:getMessageSize() - offset)
|
||||
assert(paddingBytes >= 0)
|
||||
msg:addPaddingBytes(paddingBytes, 0)
|
||||
if g_game.getClientVersion() >= 770 then
|
||||
for i = 1, paddingBytes do
|
||||
msg:addU8(math.random(0, 0xff))
|
||||
end
|
||||
|
||||
if g_game.getFeature(GameLoginPacketEncryption) then
|
||||
msg:encryptRsa()
|
||||
end
|
||||
|
||||
if g_game.getFeature(GameOGLInformation) then
|
||||
msg:addU8(1) --unknown
|
||||
msg:addU8(1) --unknown
|
||||
|
||||
if g_game.getClientVersion() >= 1072 then
|
||||
msg:addString(string.format('%s %s', g_graphics.getVendor(), g_graphics.getRenderer()))
|
||||
else
|
||||
msg:addString(g_graphics.getRenderer())
|
||||
end
|
||||
msg:addString(g_graphics.getVersion())
|
||||
end
|
||||
|
||||
-- add RSA encrypted auth token
|
||||
if g_game.getFeature(GameAuthenticator) then
|
||||
offset = msg:getMessageSize()
|
||||
|
||||
-- first RSA byte must be 0
|
||||
msg:addU8(0)
|
||||
msg:addString(self.authenticatorToken)
|
||||
|
||||
if g_game.getFeature(GameSessionKey) then
|
||||
msg:addU8(booleantonumber(self.stayLogged))
|
||||
end
|
||||
|
||||
paddingBytes = g_crypt.rsaGetSize() - (msg:getMessageSize() - offset)
|
||||
assert(paddingBytes >= 0)
|
||||
for i = 1, paddingBytes do
|
||||
msg:addU8(math.random(0, 0xff))
|
||||
end
|
||||
|
||||
msg:encryptRsa()
|
||||
end
|
||||
|
||||
@ -83,7 +133,7 @@ function ProtocolLogin:sendLoginPacket()
|
||||
end
|
||||
|
||||
self:send(msg)
|
||||
if g_game.getClientVersion() >= 770 then
|
||||
if g_game.getFeature(GameLoginPacketEncryption) then
|
||||
self:enableXteaEncryption()
|
||||
end
|
||||
self:recv()
|
||||
@ -98,12 +148,20 @@ end
|
||||
function ProtocolLogin:onRecv(msg)
|
||||
while not msg:eof() do
|
||||
local opcode = msg:getU8()
|
||||
if opcode == LoginServerError then
|
||||
if opcode == LoginServerErrorNew then
|
||||
self:parseError(msg)
|
||||
elseif opcode == LoginServerError then
|
||||
self:parseError(msg)
|
||||
elseif opcode == LoginServerMotd then
|
||||
self:parseMotd(msg)
|
||||
elseif opcode == LoginServerUpdateNeeded then
|
||||
signalcall(self.onLoginError, self, tr("Client needs update."))
|
||||
elseif opcode == LoginServerTokenSuccess then
|
||||
local unknown = msg:getU8()
|
||||
elseif opcode == LoginServerTokenError then
|
||||
-- TODO: prompt for token here
|
||||
local unknown = msg:getU8()
|
||||
signalcall(self.onLoginError, self, tr("Invalid authentification token."))
|
||||
elseif opcode == LoginServerCharacterList then
|
||||
self:parseCharacterList(msg)
|
||||
elseif opcode == LoginServerExtendedCharacterList then
|
||||
@ -111,6 +169,8 @@ function ProtocolLogin:onRecv(msg)
|
||||
elseif opcode == LoginServerUpdate then
|
||||
local signature = msg:getString()
|
||||
signalcall(self.onUpdateNeeded, self, signature)
|
||||
elseif opcode == LoginServerSessionKey then
|
||||
self:parseSessionKey(msg)
|
||||
else
|
||||
self:parseOpcode(opcode, msg)
|
||||
end
|
||||
@ -128,6 +188,11 @@ function ProtocolLogin:parseMotd(msg)
|
||||
signalcall(self.onMotd, self, motd)
|
||||
end
|
||||
|
||||
function ProtocolLogin:parseSessionKey(msg)
|
||||
local sessionKey = msg:getString()
|
||||
signalcall(self.onSessionKey, self, sessionKey)
|
||||
end
|
||||
|
||||
function ProtocolLogin:parseCharacterList(msg)
|
||||
local characters = {}
|
||||
|
||||
@ -141,7 +206,7 @@ function ProtocolLogin:parseCharacterList(msg)
|
||||
world.worldName = msg:getString()
|
||||
world.worldIp = msg:getString()
|
||||
world.worldPort = msg:getU16()
|
||||
msg:getU8() -- unknow byte?
|
||||
world.previewState = msg:getU8()
|
||||
worlds[worldId] = world
|
||||
end
|
||||
|
||||
@ -153,6 +218,7 @@ function ProtocolLogin:parseCharacterList(msg)
|
||||
character.worldName = worlds[worldId].worldName
|
||||
character.worldIp = worlds[worldId].worldIp
|
||||
character.worldPort = worlds[worldId].worldPort
|
||||
character.previewState = worlds[worldId].previewState
|
||||
characters[i] = character
|
||||
end
|
||||
|
||||
@ -165,8 +231,8 @@ function ProtocolLogin:parseCharacterList(msg)
|
||||
character.worldIp = iptostring(msg:getU32())
|
||||
character.worldPort = msg:getU16()
|
||||
|
||||
if g_game.getClientVersion() >= 980 then
|
||||
character.unknown = msg:getU8()
|
||||
if g_game.getFeature(GamePreviewState) then
|
||||
character.previewState = msg:getU8()
|
||||
end
|
||||
|
||||
characters[i] = character
|
||||
|
30
modules/gamelib/textmessages.lua
Normal file
@ -0,0 +1,30 @@
|
||||
local messageModeCallbacks = {}
|
||||
|
||||
function g_game.onTextMessage(messageMode, message)
|
||||
local callbacks = messageModeCallbacks[messageMode]
|
||||
if not callbacks or #callbacks == 0 then
|
||||
perror(string.format('Unhandled onTextMessage message mode %i: %s', messageMode, message))
|
||||
return
|
||||
end
|
||||
|
||||
for _, callback in pairs(callbacks) do
|
||||
callback(messageMode, message)
|
||||
end
|
||||
end
|
||||
|
||||
function registerMessageMode(messageMode, callback)
|
||||
if not messageModeCallbacks[messageMode] then
|
||||
messageModeCallbacks[messageMode] = {}
|
||||
end
|
||||
|
||||
table.insert(messageModeCallbacks[messageMode], callback)
|
||||
return true
|
||||
end
|
||||
|
||||
function unregisterMessageMode(messageMode, callback)
|
||||
if not messageModeCallbacks[messageMode] then
|
||||
return false
|
||||
end
|
||||
|
||||
return table.removevalue(messageModeCallbacks[messageMode], callback)
|
||||
end
|
@ -85,6 +85,7 @@ end
|
||||
local function onFlagMouseRelease(widget, pos, button)
|
||||
if button == MouseRightButton then
|
||||
local menu = g_ui.createWidget('PopupMenu')
|
||||
menu:setGameMenu(true)
|
||||
menu:addOption(tr('Delete mark'), function() widget:destroy() end)
|
||||
menu:display(pos)
|
||||
return true
|
||||
@ -228,6 +229,7 @@ function UIMinimap:onMouseRelease(pos, button)
|
||||
return true
|
||||
elseif button == MouseRightButton then
|
||||
local menu = g_ui.createWidget('PopupMenu')
|
||||
menu:setGameMenu(true)
|
||||
menu:addOption(tr('Create mark'), function() self:createFlagWindow(mapPos) end)
|
||||
menu:display(pos)
|
||||
return true
|
||||
|
@ -24,6 +24,8 @@ set(client_SOURCES ${client_SOURCES}
|
||||
# core
|
||||
${CMAKE_CURRENT_LIST_DIR}/animatedtext.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/animatedtext.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/animator.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/animator.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/container.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/container.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/creature.cpp
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
199
src/client/animator.cpp
Normal file
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "declarations.h"
|
||||
#include "animator.h"
|
||||
|
||||
#include <framework/core/clock.h>
|
||||
#include <framework/core/filestream.h>
|
||||
|
||||
Animator::Animator()
|
||||
{
|
||||
m_animationPhases = 0;
|
||||
m_startPhase = 0;
|
||||
m_loopCount = 0;
|
||||
m_async = false;
|
||||
m_currentDuration = 0;
|
||||
m_currentDirection = AnimDirForward;
|
||||
m_currentLoop = 0;
|
||||
m_lastPhaseTicks = 0;
|
||||
m_isComplete = false;
|
||||
m_phase = 0;
|
||||
}
|
||||
|
||||
void Animator::unserialize(int animationPhases, const FileStreamPtr& fin)
|
||||
{
|
||||
m_animationPhases = animationPhases;
|
||||
m_async = fin->getU8() == 0;
|
||||
m_loopCount = fin->get32();
|
||||
m_startPhase = fin->get8();
|
||||
|
||||
for(int i = 0; i < m_animationPhases; ++i) {
|
||||
int minimum = fin->getU32();
|
||||
int maximum = fin->getU32();
|
||||
m_phaseDurations.push_back(std::make_tuple(minimum, maximum));
|
||||
}
|
||||
|
||||
m_phase = getStartPhase();
|
||||
|
||||
assert(m_animationPhases == (int)m_phaseDurations.size());
|
||||
assert(m_startPhase >= -1 && m_startPhase < m_animationPhases);
|
||||
}
|
||||
|
||||
void Animator::serialize(const FileStreamPtr& fin)
|
||||
{
|
||||
fin->addU8(m_async ? 0 : 1);
|
||||
fin->add32(m_loopCount);
|
||||
fin->add8(m_startPhase);
|
||||
|
||||
for(std::tuple<int, int> phase : m_phaseDurations) {
|
||||
fin->addU32(std::get<0>(phase));
|
||||
fin->addU32(std::get<1>(phase));
|
||||
}
|
||||
}
|
||||
|
||||
void Animator::setPhase(int phase)
|
||||
{
|
||||
if(m_phase == phase) return;
|
||||
|
||||
if(m_async) {
|
||||
if(phase == AnimPhaseAsync)
|
||||
m_phase = 0;
|
||||
else if(phase == AnimPhaseRandom)
|
||||
m_phase = (int)stdext::random_range(0, (long)m_animationPhases);
|
||||
else if(phase >= 0 && phase < m_animationPhases)
|
||||
m_phase = phase;
|
||||
else
|
||||
m_phase = getStartPhase();
|
||||
|
||||
m_isComplete = false;
|
||||
m_lastPhaseTicks = g_clock.millis();
|
||||
m_currentDuration = getPhaseDuration(phase);
|
||||
m_currentLoop = 0;
|
||||
} else
|
||||
calculateSynchronous();
|
||||
}
|
||||
|
||||
int Animator::getPhase()
|
||||
{
|
||||
ticks_t ticks = g_clock.millis();
|
||||
if(ticks != m_lastPhaseTicks && !m_isComplete) {
|
||||
int elapsedTicks = (int)(ticks - m_lastPhaseTicks);
|
||||
if(elapsedTicks >= m_currentDuration) {
|
||||
int phase = 0;
|
||||
if(m_loopCount < 0)
|
||||
phase = getPingPongPhase();
|
||||
else
|
||||
phase = getLoopPhase();
|
||||
|
||||
if(m_phase != phase) {
|
||||
int duration = getPhaseDuration(phase) - (elapsedTicks - m_currentDuration);
|
||||
if(duration < 0 && !m_async) {
|
||||
calculateSynchronous();
|
||||
} else {
|
||||
m_phase = phase;
|
||||
m_currentDuration = std::max<int>(0, duration);
|
||||
}
|
||||
} else
|
||||
m_isComplete = true;
|
||||
} else
|
||||
m_currentDuration -= elapsedTicks;
|
||||
|
||||
m_lastPhaseTicks = ticks;
|
||||
}
|
||||
return m_phase;
|
||||
}
|
||||
|
||||
int Animator::getStartPhase()
|
||||
{
|
||||
if(m_startPhase > -1)
|
||||
return m_startPhase;
|
||||
return (int)stdext::random_range(0, (long)m_animationPhases);
|
||||
}
|
||||
|
||||
void Animator::resetAnimation()
|
||||
{
|
||||
m_isComplete = false;
|
||||
m_currentDirection = AnimDirForward;
|
||||
m_currentLoop = 0;
|
||||
setPhase(AnimPhaseAutomatic);
|
||||
}
|
||||
|
||||
int Animator::getPingPongPhase()
|
||||
{
|
||||
int count = m_currentDirection == AnimDirForward ? 1 : -1;
|
||||
int nextPhase = m_phase + count;
|
||||
if(nextPhase < 0 || nextPhase >= m_animationPhases) {
|
||||
m_currentDirection = m_currentDirection == AnimDirForward ? AnimDirBackward : AnimDirForward;
|
||||
count *= -1;
|
||||
}
|
||||
return m_phase + count;
|
||||
}
|
||||
|
||||
int Animator::getLoopPhase()
|
||||
{
|
||||
int nextPhase = m_phase + 1;
|
||||
if(nextPhase < m_animationPhases)
|
||||
return nextPhase;
|
||||
|
||||
if(m_loopCount == 0)
|
||||
return 0;
|
||||
|
||||
if(m_currentLoop < (m_loopCount - 1)) {
|
||||
m_currentLoop++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return m_phase;
|
||||
}
|
||||
|
||||
int Animator::getPhaseDuration(int phase)
|
||||
{
|
||||
assert(phase < (int)m_phaseDurations.size());
|
||||
|
||||
std::tuple<int, int> data = m_phaseDurations.at(phase);
|
||||
int min = std::get<0>(data);
|
||||
int max = std::get<1>(data);
|
||||
if(min == max) return min;
|
||||
return (int)stdext::random_range((long)min, (long)max);
|
||||
}
|
||||
|
||||
void Animator::calculateSynchronous()
|
||||
{
|
||||
int totalDuration = 0;
|
||||
for(int i = 0; i < m_animationPhases; i++)
|
||||
totalDuration += getPhaseDuration(i);
|
||||
|
||||
ticks_t ticks = g_clock.millis();
|
||||
int elapsedTicks = (int)(ticks % totalDuration);
|
||||
int totalTime = 0;
|
||||
for(int i = 0; i < m_animationPhases; i++) {
|
||||
int duration = getPhaseDuration(i);
|
||||
if(elapsedTicks >= totalTime && elapsedTicks < totalTime + duration) {
|
||||
m_phase = i;
|
||||
m_currentDuration = duration - (elapsedTicks - totalTime);
|
||||
break;
|
||||
}
|
||||
totalTime += duration;
|
||||
}
|
||||
m_lastPhaseTicks = ticks;
|
||||
}
|
83
src/client/animator.h
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef ANIMATOR_H
|
||||
#define ANIMATOR_H
|
||||
|
||||
#include "declarations.h"
|
||||
|
||||
#include <framework/core/declarations.h>
|
||||
|
||||
enum AnimationPhase : int16
|
||||
{
|
||||
AnimPhaseAutomatic = -1,
|
||||
AnimPhaseRandom = 254,
|
||||
AnimPhaseAsync = 255,
|
||||
};
|
||||
|
||||
enum AnimationDirection : uint8
|
||||
{
|
||||
AnimDirForward = 0,
|
||||
AnimDirBackward = 1
|
||||
};
|
||||
|
||||
class Animator : public stdext::shared_object
|
||||
{
|
||||
public:
|
||||
Animator();
|
||||
|
||||
void unserialize(int animationPhases, const FileStreamPtr& fin);
|
||||
void serialize(const FileStreamPtr& fin);
|
||||
|
||||
void setPhase(int phase);
|
||||
int getPhase();
|
||||
|
||||
int getStartPhase();
|
||||
int getAnimationPhases() { return m_animationPhases; }
|
||||
bool isAsync() { return m_async; }
|
||||
bool isComplete() { return m_isComplete; }
|
||||
|
||||
void resetAnimation();
|
||||
|
||||
private:
|
||||
int getPingPongPhase();
|
||||
int getLoopPhase();
|
||||
int getPhaseDuration(int phase);
|
||||
void calculateSynchronous();
|
||||
|
||||
int m_animationPhases;
|
||||
int m_startPhase;
|
||||
int m_loopCount;
|
||||
bool m_async;
|
||||
std::vector< std::tuple<int, int> > m_phaseDurations;
|
||||
|
||||
int m_currentDuration;
|
||||
AnimationDirection m_currentDirection;
|
||||
int m_currentLoop;
|
||||
|
||||
ticks_t m_lastPhaseTicks;
|
||||
bool m_isComplete;
|
||||
|
||||
int m_phase;
|
||||
};
|
||||
|
||||
#endif
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@ -328,7 +328,9 @@ namespace Otc
|
||||
MessageRVRChannel = 46,
|
||||
MessageRVRAnswer = 47,
|
||||
MessageRVRContinue = 48,
|
||||
LastMessage = 49,
|
||||
MessageGameHighlight = 49,
|
||||
MessageNpcFromStartBlock = 50,
|
||||
LastMessage = 51,
|
||||
MessageInvalid = 255
|
||||
};
|
||||
|
||||
@ -390,6 +392,17 @@ namespace Otc
|
||||
GamePremiumExpiration = 57,
|
||||
GameBrowseField = 58,
|
||||
GameEnhancedAnimations = 59,
|
||||
GameOGLInformation = 60,
|
||||
GameMessageSizeCheck = 61,
|
||||
GamePreviewState = 62,
|
||||
GameLoginPacketEncryption = 63,
|
||||
GameClientVersion = 64,
|
||||
GameContentRevision = 65,
|
||||
GameExperienceBonus = 66,
|
||||
GameAuthenticator = 67,
|
||||
GameUnjustifiedPoints = 68,
|
||||
GameSessionKey = 69,
|
||||
GameDeathType = 70,
|
||||
|
||||
LastGameFeature = 101
|
||||
};
|
||||
@ -445,10 +458,19 @@ namespace Otc
|
||||
LastSpeedFormula
|
||||
};
|
||||
|
||||
enum AnimationPhase {
|
||||
PhaseAutomatic = 0,
|
||||
PhaseRandom = 254,
|
||||
PhaseAsync = 255
|
||||
enum Blessings {
|
||||
BlessingNone = 0,
|
||||
BlessingAdventurer = 1,
|
||||
BlessingSpiritualShielding = 1 << 1,
|
||||
BlessingEmbraceOfTibia = 1 << 2,
|
||||
BlessingFireOfSuns = 1 << 3,
|
||||
BlessingWisdomOfSolitude = 1 << 4,
|
||||
BlessingSparkOfPhoenix = 1 << 5
|
||||
};
|
||||
|
||||
enum DeathType {
|
||||
DeathRegular = 0,
|
||||
DeathBlessed = 1
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@ -696,6 +696,16 @@ void Creature::setSpeed(uint16 speed)
|
||||
callLuaField("onSpeedChange", m_speed, oldSpeed);
|
||||
}
|
||||
|
||||
void Creature::setBaseSpeed(double baseSpeed)
|
||||
{
|
||||
if(m_baseSpeed != baseSpeed) {
|
||||
double oldBaseSpeed = m_baseSpeed;
|
||||
m_baseSpeed = baseSpeed;
|
||||
|
||||
callLuaField("onBaseSpeedChange", baseSpeed, oldBaseSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
void Creature::setSkull(uint8 skull)
|
||||
{
|
||||
m_skull = skull;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@ -58,6 +58,7 @@ public:
|
||||
void setOutfitColor(const Color& color, int duration);
|
||||
void setLight(const Light& light) { m_light = light; }
|
||||
void setSpeed(uint16 speed);
|
||||
void setBaseSpeed(double baseSpeed);
|
||||
void setSkull(uint8 skull);
|
||||
void setShield(uint8 shield);
|
||||
void setEmblem(uint8 emblem);
|
||||
@ -82,6 +83,7 @@ public:
|
||||
Outfit getOutfit() { return m_outfit; }
|
||||
Light getLight() { return m_light; }
|
||||
uint16 getSpeed() { return m_speed; }
|
||||
double getBaseSpeed() { return m_baseSpeed; }
|
||||
uint8 getSkull() { return m_skull; }
|
||||
uint8 getShield() { return m_shield; }
|
||||
uint8 getEmblem() { return m_emblem; }
|
||||
@ -147,6 +149,7 @@ protected:
|
||||
Outfit m_outfit;
|
||||
Light m_light;
|
||||
int m_speed;
|
||||
double m_baseSpeed;
|
||||
uint8 m_skull;
|
||||
uint8 m_shield;
|
||||
uint8 m_emblem;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@ -45,6 +45,7 @@ class Effect;
|
||||
class Missile;
|
||||
class AnimatedText;
|
||||
class StaticText;
|
||||
class Animator;
|
||||
class ThingType;
|
||||
class ItemType;
|
||||
class House;
|
||||
@ -68,6 +69,7 @@ typedef stdext::shared_object_ptr<Effect> EffectPtr;
|
||||
typedef stdext::shared_object_ptr<Missile> MissilePtr;
|
||||
typedef stdext::shared_object_ptr<AnimatedText> AnimatedTextPtr;
|
||||
typedef stdext::shared_object_ptr<StaticText> StaticTextPtr;
|
||||
typedef stdext::shared_object_ptr<Animator> AnimatorPtr;
|
||||
typedef stdext::shared_object_ptr<ThingType> ThingTypePtr;
|
||||
typedef stdext::shared_object_ptr<ItemType> ItemTypePtr;
|
||||
typedef stdext::shared_object_ptr<House> HousePtr;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@ -84,6 +84,7 @@ void Game::resetGameStates()
|
||||
m_localPlayer = nullptr;
|
||||
m_pingSent = 0;
|
||||
m_pingReceived = 0;
|
||||
m_unjustifiedPoints = UnjustifiedPoints();
|
||||
|
||||
for(auto& it : m_containers) {
|
||||
const ContainerPtr& container = it.second;
|
||||
@ -155,6 +156,11 @@ void Game::processLoginWait(const std::string& message, int time)
|
||||
g_lua.callGlobalField("g_game", "onLoginWait", message, time);
|
||||
}
|
||||
|
||||
void Game::processLoginToken(bool unknown)
|
||||
{
|
||||
g_lua.callGlobalField("g_game", "onLoginToken", unknown);
|
||||
}
|
||||
|
||||
void Game::processLogin()
|
||||
{
|
||||
g_lua.callGlobalField("g_game", "onLogin");
|
||||
@ -222,12 +228,12 @@ void Game::processGameEnd()
|
||||
g_map.cleanDynamicThings();
|
||||
}
|
||||
|
||||
void Game::processDeath(int penality)
|
||||
void Game::processDeath(int deathType, int penality)
|
||||
{
|
||||
m_dead = true;
|
||||
m_localPlayer->stopWalk();
|
||||
|
||||
g_lua.callGlobalField("g_game", "onDeath", penality);
|
||||
g_lua.callGlobalField("g_game", "onDeath", deathType, penality);
|
||||
}
|
||||
|
||||
void Game::processGMActions(const std::vector<uint8>& actions)
|
||||
@ -308,9 +314,6 @@ void Game::processCloseContainer(int containerId)
|
||||
{
|
||||
ContainerPtr container = getContainer(containerId);
|
||||
if(!container) {
|
||||
/* happens if you close and restart client with container opened
|
||||
* g_logger.traceError("container not found");
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
@ -322,7 +325,6 @@ void Game::processContainerAddItem(int containerId, const ItemPtr& item, int slo
|
||||
{
|
||||
ContainerPtr container = getContainer(containerId);
|
||||
if(!container) {
|
||||
g_logger.traceError("container not found");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -333,7 +335,6 @@ void Game::processContainerUpdateItem(int containerId, int slot, const ItemPtr&
|
||||
{
|
||||
ContainerPtr container = getContainer(containerId);
|
||||
if(!container) {
|
||||
g_logger.traceError("container not found");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -344,7 +345,6 @@ void Game::processContainerRemoveItem(int containerId, int slot, const ItemPtr&
|
||||
{
|
||||
ContainerPtr container = getContainer(containerId);
|
||||
if(!container) {
|
||||
g_logger.traceError("container not found");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -528,7 +528,7 @@ void Game::processWalkCancel(Otc::Direction direction)
|
||||
m_localPlayer->cancelWalk(direction);
|
||||
}
|
||||
|
||||
void Game::loginWorld(const std::string& account, const std::string& password, const std::string& worldName, const std::string& worldHost, int worldPort, const std::string& characterName)
|
||||
void Game::loginWorld(const std::string& account, const std::string& password, const std::string& worldName, const std::string& worldHost, int worldPort, const std::string& characterName, const std::string& authenticatorToken, const std::string& sessionKey)
|
||||
{
|
||||
if(m_protocolGame || isOnline())
|
||||
stdext::throw_exception("Unable to login into a world while already online or logging.");
|
||||
@ -543,7 +543,7 @@ void Game::loginWorld(const std::string& account, const std::string& password, c
|
||||
m_localPlayer->setName(characterName);
|
||||
|
||||
m_protocolGame = ProtocolGamePtr(new ProtocolGame);
|
||||
m_protocolGame->login(account, password, worldHost, (uint16)worldPort, characterName);
|
||||
m_protocolGame->login(account, password, worldHost, (uint16)worldPort, characterName, authenticatorToken, sessionKey);
|
||||
m_characterName = characterName;
|
||||
m_worldName = worldName;
|
||||
}
|
||||
@ -854,7 +854,7 @@ void Game::useWith(const ItemPtr& item, const ThingPtr& toThing)
|
||||
|
||||
Position pos = item->getPosition();
|
||||
if(!pos.isValid()) // virtual item
|
||||
pos = Position(0xFFFF, 0, 0); // means that is a item in inventory
|
||||
pos = Position(0xFFFF, 0, 0); // means that is an item in inventory
|
||||
|
||||
m_protocolGame->sendUseItemWith(pos, item->getId(), item->getStackPos(), toThing->getPosition(), toThing->getId(), toThing->getStackPos());
|
||||
}
|
||||
@ -1204,6 +1204,31 @@ void Game::setPVPMode(Otc::PVPModes pvpMode)
|
||||
g_lua.callGlobalField("g_game", "onPVPModeChange", pvpMode);
|
||||
}
|
||||
|
||||
void Game::setUnjustifiedPoints(UnjustifiedPoints unjustifiedPoints)
|
||||
{
|
||||
if(!canPerformGameAction())
|
||||
return;
|
||||
if(!getFeature(Otc::GameUnjustifiedPoints))
|
||||
return;
|
||||
if(m_unjustifiedPoints == unjustifiedPoints)
|
||||
return;
|
||||
|
||||
m_unjustifiedPoints = unjustifiedPoints;
|
||||
g_lua.callGlobalField("g_game", "onUnjustifiedPointsChange", unjustifiedPoints);
|
||||
}
|
||||
|
||||
void Game::setOpenPvpSituations(int openPvpSituations)
|
||||
{
|
||||
if(!canPerformGameAction())
|
||||
return;
|
||||
if(m_openPvpSituations == openPvpSituations)
|
||||
return;
|
||||
|
||||
m_openPvpSituations = openPvpSituations;
|
||||
g_lua.callGlobalField("g_game", "onOpenPvpSituationsChange", openPvpSituations);
|
||||
}
|
||||
|
||||
|
||||
void Game::inspectNpcTrade(const ItemPtr& item)
|
||||
{
|
||||
if(!canPerformGameAction() || !item)
|
||||
@ -1425,7 +1450,7 @@ void Game::setProtocolVersion(int version)
|
||||
if(isOnline())
|
||||
stdext::throw_exception("Unable to change protocol version while online");
|
||||
|
||||
if(version != 0 && (version < 740 || version > 1051))
|
||||
if(version != 0 && (version < 740 || version > 1076))
|
||||
stdext::throw_exception(stdext::format("Protocol version %d not supported", version));
|
||||
|
||||
m_protocolVersion = version;
|
||||
@ -1443,7 +1468,7 @@ void Game::setClientVersion(int version)
|
||||
if(isOnline())
|
||||
stdext::throw_exception("Unable to change client version while online");
|
||||
|
||||
if(version != 0 && (version < 740 || version > 1051))
|
||||
if(version != 0 && (version < 740 || version > 1076))
|
||||
stdext::throw_exception(stdext::format("Client version %d not supported", version));
|
||||
|
||||
m_features.reset();
|
||||
@ -1452,6 +1477,7 @@ void Game::setClientVersion(int version)
|
||||
if(version >= 770) {
|
||||
enableFeature(Otc::GameLooktypeU16);
|
||||
enableFeature(Otc::GameMessageStatements);
|
||||
enableFeature(Otc::GameLoginPacketEncryption);
|
||||
}
|
||||
|
||||
if(version >= 780) {
|
||||
@ -1475,6 +1501,7 @@ void Game::setClientVersion(int version)
|
||||
|
||||
if(version >= 841) {
|
||||
enableFeature(Otc::GameChallengeOnLogin);
|
||||
enableFeature(Otc::GameMessageSizeCheck);
|
||||
}
|
||||
|
||||
if(version >= 854) {
|
||||
@ -1523,6 +1550,11 @@ void Game::setClientVersion(int version)
|
||||
enableFeature(Otc::GameAdditionalVipInfo);
|
||||
}
|
||||
|
||||
if(version >= 980) {
|
||||
enableFeature(Otc::GamePreviewState);
|
||||
enableFeature(Otc::GameClientVersion);
|
||||
}
|
||||
|
||||
if(version >= 981) {
|
||||
enableFeature(Otc::GameLoginPending);
|
||||
enableFeature(Otc::GameNewSpeedLaw);
|
||||
@ -1538,7 +1570,7 @@ void Game::setClientVersion(int version)
|
||||
enableFeature(Otc::GamePVPMode);
|
||||
}
|
||||
|
||||
if (version >= 1035) {
|
||||
if(version >= 1035) {
|
||||
enableFeature(Otc::GameDoubleSkills);
|
||||
enableFeature(Otc::GameBaseSkillU16);
|
||||
}
|
||||
@ -1556,6 +1588,34 @@ void Game::setClientVersion(int version)
|
||||
enableFeature(Otc::GameEnhancedAnimations);
|
||||
}
|
||||
|
||||
if(version >= 1053) {
|
||||
enableFeature(Otc::GameUnjustifiedPoints);
|
||||
}
|
||||
|
||||
if(version >= 1054) {
|
||||
enableFeature(Otc::GameExperienceBonus);
|
||||
}
|
||||
|
||||
if(version >= 1055) {
|
||||
enableFeature(Otc::GameDeathType);
|
||||
}
|
||||
|
||||
if(version >= 1061) {
|
||||
enableFeature(Otc::GameOGLInformation);
|
||||
}
|
||||
|
||||
if(version >= 1071) {
|
||||
enableFeature(Otc::GameContentRevision);
|
||||
}
|
||||
|
||||
if(version >= 1072) {
|
||||
enableFeature(Otc::GameAuthenticator);
|
||||
}
|
||||
|
||||
if(version >= 1074) {
|
||||
enableFeature(Otc::GameSessionKey);
|
||||
}
|
||||
|
||||
m_clientVersion = version;
|
||||
|
||||
g_lua.callGlobalField("g_game", "onClientVersionChange", version);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@ -36,6 +36,25 @@
|
||||
|
||||
#include <bitset>
|
||||
|
||||
struct UnjustifiedPoints {
|
||||
bool operator==(const UnjustifiedPoints& other) {
|
||||
return killsDay == other.killsDay &&
|
||||
killsDayRemaining == other.killsDayRemaining &&
|
||||
killsWeek == other.killsWeek &&
|
||||
killsWeekRemaining == other.killsWeekRemaining &&
|
||||
killsMonth == other.killsMonth &&
|
||||
killsMonthRemaining == other.killsMonthRemaining &&
|
||||
skullTime == other.skullTime;
|
||||
}
|
||||
uint8 killsDay;
|
||||
uint8 killsDayRemaining;
|
||||
uint8 killsWeek;
|
||||
uint8 killsWeekRemaining;
|
||||
uint8 killsMonth;
|
||||
uint8 killsMonthRemaining;
|
||||
uint8 skullTime;
|
||||
};
|
||||
|
||||
typedef std::tuple<std::string, uint, std::string, int, bool> Vip;
|
||||
|
||||
//@bindsingleton g_game
|
||||
@ -60,13 +79,14 @@ protected:
|
||||
void processLoginError(const std::string& error);
|
||||
void processLoginAdvice(const std::string& message);
|
||||
void processLoginWait(const std::string& message, int time);
|
||||
void processLoginToken(bool unknown);
|
||||
void processLogin();
|
||||
void processPendingGame();
|
||||
void processEnterGame();
|
||||
|
||||
void processGameStart();
|
||||
void processGameEnd();
|
||||
void processDeath(int penality);
|
||||
void processDeath(int deathType, int penality);
|
||||
|
||||
void processGMActions(const std::vector<uint8>& actions);
|
||||
void processInventoryChange(int slot, const ItemPtr& item);
|
||||
@ -139,7 +159,7 @@ protected:
|
||||
|
||||
public:
|
||||
// login related
|
||||
void loginWorld(const std::string& account, const std::string& password, const std::string& worldName, const std::string& worldHost, int worldPort, const std::string& characterName);
|
||||
void loginWorld(const std::string& account, const std::string& password, const std::string& worldName, const std::string& worldHost, int worldPort, const std::string& characterName, const std::string& authenticatorToken, const std::string& sessionKey);
|
||||
void cancelLogin();
|
||||
void forceLogout();
|
||||
void safeLogout();
|
||||
@ -218,6 +238,12 @@ public:
|
||||
bool isSafeFight() { return m_safeFight; }
|
||||
Otc::PVPModes getPVPMode() { return m_pvpMode; }
|
||||
|
||||
// pvp related
|
||||
void setUnjustifiedPoints(UnjustifiedPoints unjustifiedPoints);
|
||||
UnjustifiedPoints getUnjustifiedPoints() { return m_unjustifiedPoints; };
|
||||
void setOpenPvpSituations(int openPvpSitations);
|
||||
int getOpenPvpSituations() { return m_openPvpSituations; }
|
||||
|
||||
// npc trade related
|
||||
void inspectNpcTrade(const ItemPtr& item);
|
||||
void buyItem(const ItemPtr& item, int amount, bool ignoreCapacity, bool buyWithBackpack);
|
||||
@ -304,6 +330,8 @@ public:
|
||||
int getServerBeat() { return m_serverBeat; }
|
||||
void setCanReportBugs(bool enable) { m_canReportBugs = enable; }
|
||||
bool canReportBugs() { return m_canReportBugs; }
|
||||
void setExpertPvpMode(bool enable) { m_expertPvpMode = enable; }
|
||||
bool getExpertPvpMode() { return m_expertPvpMode; }
|
||||
LocalPlayerPtr getLocalPlayer() { return m_localPlayer; }
|
||||
ProtocolGamePtr getProtocolGame() { return m_protocolGame; }
|
||||
std::string getCharacterName() { return m_characterName; }
|
||||
@ -333,6 +361,7 @@ private:
|
||||
bool m_online;
|
||||
bool m_denyBotCall;
|
||||
bool m_dead;
|
||||
bool m_expertPvpMode;
|
||||
int m_serverBeat;
|
||||
ticks_t m_ping;
|
||||
uint m_pingSent;
|
||||
@ -345,6 +374,8 @@ private:
|
||||
Otc::ChaseModes m_chaseMode;
|
||||
Otc::PVPModes m_pvpMode;
|
||||
Otc::Direction m_lastWalkDir;
|
||||
UnjustifiedPoints m_unjustifiedPoints;
|
||||
int m_openPvpSituations;
|
||||
bool m_safeFight;
|
||||
bool m_canReportBugs;
|
||||
std::vector<uint8> m_gmActions;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@ -382,6 +382,9 @@ int Item::calculateAnimationPhase(bool animate)
|
||||
{
|
||||
if(getAnimationPhases() > 1) {
|
||||
if(animate) {
|
||||
if(getAnimator() != nullptr)
|
||||
return getAnimator()->getPhase();
|
||||
|
||||
if(m_async)
|
||||
return (g_clock.millis() % (Otc::ITEM_TICKS_PER_FRAME * getAnimationPhases())) / Otc::ITEM_TICKS_PER_FRAME;
|
||||
else {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014 OTClient <https://github.com/edubart/otclient>
|
||||
* Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|