This commit is contained in:
Tulioh 2015-04-19 12:22:16 -03:00
commit 2b96ae7f09
386 changed files with 2616 additions and 921 deletions

View File

@ -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

View File

@ -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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -4,6 +4,10 @@ locale = {
charset = "cp1252",
languageName = "Deutsch",
formatNumbers = true,
decimalSeperator = ',',
thousandsSeperator = ' ',
translation = {
["1a) Offensive Name"] = false,
["1b) Invalid Name Format"] = false,

View File

@ -3,6 +3,10 @@ locale = {
charset = "cp1252",
languageName = "English",
formatNumbers = true,
decimalSeperator = '.',
thousandsSeperator = ',',
-- translations are not needed because everything is already in english
translation = {}
}

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 })

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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 } )

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -30,5 +30,6 @@ Module
- game_spelllist
- game_cooldown
- game_modaldialog
- game_unjustifiedpoints
@onLoad: init()
@onUnload: terminate()

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -1,7 +1,7 @@
AmountWindow < MainWindow
id: amountWindow
!text: tr('Amount')
size: 270 80
size: 270 90
Item
id: item

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -8,6 +8,7 @@ TextMessageLabel < UILabel
Panel
anchors.fill: gameMapPanel
anchors.bottom: gameBottomPanel.top
focusable: false
Panel

View 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

View File

@ -0,0 +1,8 @@
Module
name: game_unjustifiedpoints
description: View unjustified points
author: Summ
sandboxed: true
scripts: [ unjustifiedpoints ]
@onLoad: init()
@onUnload: terminate()

View 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

View File

@ -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)

View File

@ -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
}
-- @}

View File

@ -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

View File

@ -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

View File

@ -19,6 +19,7 @@ Module
dofile 'creature'
dofile 'player'
dofile 'market'
dofile 'textmessages'
dofile 'thing'
dofile 'spells'

View File

@ -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)

View File

@ -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

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View 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
View 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

View File

@ -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

View File

@ -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

View File

@ -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
};
}

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

Some files were not shown because too many files have changed in this diff Show More