36 Commits

Author SHA1 Message Date
Eduardo Bart
b5911cf1de Use client version instead of entergame 2013-08-03 19:26:38 -03:00
BeniS
5fbb71157d Added enable/disable chat mode (allows for alphabetical key controls), battle window anchoring fixed (thank you River) & more:
* Added better walk key binding (for other uses, like the sample given WASD).
* Made string.ends part of the string meta table rather than parameter based.
2013-07-29 10:42:18 +12:00
Allan Ference
0ff36a1a0a Merge https://github.com/marksamman/otclient
* https://github.com/marksamman/otclient:
  Fix login with 9.83-10.10
2013-07-28 07:10:44 +02:00
Mark Samman
4a04a18835 Fix login with 9.83-10.10
This only adds protocol compatibility, the features aren't actually
implemented yet
2013-07-28 07:05:46 +02:00
Allan Ference
6fa9631d6a Merge https://github.com/marksamman/otclient
* https://github.com/marksamman/otclient:
  Fix compiling on OS X
2013-07-28 04:42:31 +02:00
Mark Samman
261642190b Fix compiling on OS X 2013-07-28 04:30:52 +02:00
BeniS
95abf2a1d2 Fix a bug with autowalking & add prompt param for tryLogout.
(We don't want to be prompted when pressing Ctrl + Q or  Ctrl + L).
2013-07-07 05:36:56 +12:00
Sam
c4adf2d817 Added whitelist
- Checkboxes to turn ignore/whitelist off
- Allow VIP messages
2013-07-04 18:03:21 +02:00
Sam
c7c259ef80 Game Menu / Attacking fixes
- No more "Attack" / "Follow" for creatures on different floor
- No rule violation for monsters, only players now
- Alt + Click to target works through roofs -> fixes #203
- Fixed battle list issue (check position when trying to attack)
2013-07-03 14:25:18 +02:00
Sam
987c6d6c91 Mount display issue
fixes #275
Not really needed but enables some cool stuff
2013-07-02 23:16:11 +02:00
Sam
c8185474de Implements battlelist sorting
Features:
Sort by age, name, health or distance
Sort ascending or descending
Button to toggle filters and sort options
http://i.imgur.com/cSCBwr4.png

closes 213
2013-07-02 22:43:52 +02:00
Sam
be071c7103 Fix deathwindow issue 2013-07-02 22:40:19 +02:00
Sam
2f9e2c3e33 Sorting VIP list by name
@ #209
What should sorting by type do?
2013-07-01 22:59:51 +02:00
Sam
b81590f297 Fixed Alt + F4 keycombo on Windows 2013-06-29 16:53:15 +02:00
BeniS
e062562888 Added sublime text 2 project file.
* Changed the order of the dofiles params.
2013-05-12 21:16:43 +12:00
BeniS
18d23653c4 Added some control params to dofiles lua method.
* File name contains string.
* Recursive file checking for deep searches.
2013-05-12 17:00:52 +12:00
Henrique Santiago
6c119627bb Merge pull request #330 from conde2/master
Improvement on styles
2013-04-30 13:26:56 -07:00
Joao Pasqualini Costa
d847a78a4d Improvement on styles
Added VerticalList
2013-04-30 17:21:04 -03:00
Eduardo Bart
0dccc870b5 Merge pull request #324 from TiagoTdA/master
Update/Refresh modules on unload
2013-04-13 18:11:47 -07:00
Tiago T. de Araujo
e4c7ca604b Fix update on unload module 2013-04-12 21:27:10 -03:00
Sam
d427560b98 Countwindow Fix [YAH]
Fixed typing amount in count window. Should be hidden again.
2013-04-12 19:15:28 +02:00
Sam
cc12db0d1f Reverted getTopMultiUseThing() change 2013-04-09 23:54:10 +02:00
Henrique Santiago
1ce6df99ac Merge pull request #318 from conde2/master
Fix #316
2013-04-09 14:21:15 -07:00
Joao Pasqualini Costa
57bb6ff974 Fix #315 by @pacmanis
Credits go to @pacmanis
2013-04-09 18:02:43 -03:00
Joao Pasqualini Costa
9bae1b9e25 Fix #316
It still have a bug when using string like this:

" teste {{}} teste"
2013-04-09 17:54:57 -03:00
Sam
1415de222c Merge pull request #313 from sn3ejk/sn3ejk
Fix extended opcodes for forgottenserver.
2013-04-05 18:50:02 -07:00
Kamil W
34ceb3c95e Fix extended opcodes for forgottenserver. 2013-04-05 22:03:17 +02:00
Eduardo Bart
b43a196eac Minor fixes and add auto resize for images 2013-03-15 21:59:22 -03:00
Eduardo Bart
a3a65d40ce Merge pull request #303 from conde2/master
Properly Fix #301
2013-03-15 08:35:04 -07:00
Joao Pasqualini Costa
6ef3508362 Properly Fix #301 2013-03-14 20:57:02 -03:00
Eduardo Bart
a71e07f063 Restore walk optimization 2013-03-14 00:16:57 -03:00
Eduardo Bart
4bdd1e79fd Fix compile error for win32 2013-03-13 21:18:17 -03:00
Eduardo Bart
e9e4dcd71b Improve walk when lagging 2013-03-13 20:55:20 -03:00
Eduardo Bart
0891e2b30a Add C++ traceback to errors 2013-03-12 01:36:36 -03:00
Eduardo Bart
24664714bd Fix rare but serious bug in Connection
* Implement output pooling for writing data in connection,
this should fix rare cases where sending would fail
2013-03-12 00:18:47 -03:00
Eduardo Bart
da51dd467e Minor fix 2013-03-11 15:00:31 -03:00
58 changed files with 1344 additions and 477 deletions

2
.gitignore vendored
View File

@@ -1,4 +1,3 @@
/modules/.project
build* build*
CMakeCache.txt CMakeCache.txt
CMakeFiles CMakeFiles
@@ -39,3 +38,4 @@ tags
Thumbs.db Thumbs.db
.directory .directory
src/framework/graphics/dx/ src/framework/graphics/dx/
modules/.project/modules.sublime-workspace

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 2.6) cmake_minimum_required(VERSION 2.6)
project(otclient) project(otclient)
set(VERSION "0.6.2") set(VERSION "0.6.3")
option(FRAMEWORK_SOUND "Use SOUND " ON) option(FRAMEWORK_SOUND "Use SOUND " ON)
option(FRAMEWORK_GRAPHICS "Use GRAPHICS " ON) option(FRAMEWORK_GRAPHICS "Use GRAPHICS " ON)

View File

@@ -11,3 +11,9 @@ HorizontalList < UIScrollArea
border-width: 1 border-width: 1
border-color: #1d222b border-color: #1d222b
background-color: #222833 background-color: #222833
VerticalList < UIScrollArea
layout: verticalBox
border-width: 1
border-color: #1d222b
background-color: #222833

View File

@@ -0,0 +1,10 @@
{
"folders":
[
{
"path": "..",
"folder_exclude_patterns": [".*", "*.*~"],
"file_exclude_patterns": [".*", "*.*~"]
}
]
}

View File

@@ -104,11 +104,6 @@ function terminate()
g_settings.set('window-size', g_window.getUnmaximizedSize()) g_settings.set('window-size', g_window.getUnmaximizedSize())
g_settings.set('window-pos', g_window.getUnmaximizedPos()) g_settings.set('window-pos', g_window.getUnmaximizedPos())
g_settings.set('window-maximized', g_window.isMaximized()) g_settings.set('window-maximized', g_window.isMaximized())
local protocolVersion = g_game.getProtocolVersion()
if protocolVersion ~= 0 then
g_settings.set('protocol-version', protocolVersion)
end
end end
function exit() function exit()

View File

@@ -19,4 +19,4 @@ Module
- client_terminal - client_terminal
- client_modulemanager - client_modulemanager
- client_serverlist - client_serverlist
//- client_stats - client_stats

View File

@@ -6,7 +6,7 @@ local enterGame
local motdWindow local motdWindow
local motdButton local motdButton
local enterGameButton local enterGameButton
local protocolBox local clientBox
local protocolLogin local protocolLogin
local motdEnabled = true local motdEnabled = true
@@ -73,11 +73,6 @@ local function onCharacterList(protocol, characters, account, otui)
end end
end end
local function onChangeProtocol(combobox, option)
local clients = g_game.getSupportedClients(option)
protocolBox:setTooltip("Supports Client" .. (#clients > 1 and "s" or "") .. ": " .. table.tostring(clients))
end
local function onUpdateNeeded(protocol, signature) local function onUpdateNeeded(protocol, signature)
loadBox:destroy() loadBox:destroy()
loadBox = nil loadBox = nil
@@ -109,7 +104,8 @@ function EnterGame.init()
local host = g_settings.get('host') local host = g_settings.get('host')
local port = g_settings.get('port') local port = g_settings.get('port')
local autologin = g_settings.getBoolean('autologin') local autologin = g_settings.getBoolean('autologin')
local protocolVersion = g_settings.getInteger('protocol-version') local clientVersion = g_settings.getInteger('client-version')
if clientVersion == 0 then clientVersion = 860 end
if port == nil or port == 0 then port = 7171 end if port == nil or port == 0 then port = 7171 end
@@ -120,11 +116,11 @@ function EnterGame.init()
enterGame:getChildById('serverPortTextEdit'):setText(port) enterGame:getChildById('serverPortTextEdit'):setText(port)
enterGame:getChildById('autoLoginBox'):setChecked(autologin) enterGame:getChildById('autoLoginBox'):setChecked(autologin)
protocolBox = enterGame:getChildById('protocolComboBox') clientBox = enterGame:getChildById('clientComboBox')
protocolBox.onOptionChange = onChangeProtocol for _, proto in pairs(g_game.getSupportedClients()) do
if protocolVersion then clientBox:addOption(proto)
protocolBox:setCurrentOption(protocolVersion)
end end
clientBox:setCurrentOption(clientVersion)
enterGame:hide() enterGame:hide()
@@ -154,7 +150,7 @@ function EnterGame.terminate()
enterGame = nil enterGame = nil
enterGameButton:destroy() enterGameButton:destroy()
enterGameButton = nil enterGameButton = nil
protocolBox = nil clientBox = nil
if motdWindow then if motdWindow then
motdWindow:destroy() motdWindow:destroy()
motdWindow = nil motdWindow = nil
@@ -218,8 +214,7 @@ function EnterGame.doLogin()
G.password = enterGame:getChildById('accountPasswordTextEdit'):getText() G.password = enterGame:getChildById('accountPasswordTextEdit'):getText()
G.host = enterGame:getChildById('serverHostTextEdit'):getText() G.host = enterGame:getChildById('serverHostTextEdit'):getText()
G.port = tonumber(enterGame:getChildById('serverPortTextEdit'):getText()) G.port = tonumber(enterGame:getChildById('serverPortTextEdit'):getText())
local protocolVersion = tonumber(protocolBox:getText()) local clientVersion = tonumber(clientBox:getText())
local clientVersions = g_game.getSupportedClients(protocolVersion)
EnterGame.hide() EnterGame.hide()
if g_game.isOnline() then if g_game.isOnline() then
@@ -230,6 +225,7 @@ function EnterGame.doLogin()
g_settings.set('host', G.host) g_settings.set('host', G.host)
g_settings.set('port', G.port) g_settings.set('port', G.port)
g_settings.set('client-version', clientVersion)
protocolLogin = ProtocolLogin.create() protocolLogin = ProtocolLogin.create()
protocolLogin.onLoginError = onError protocolLogin.onLoginError = onError
@@ -245,10 +241,8 @@ function EnterGame.doLogin()
end }) end })
g_game.chooseRsa(G.host) g_game.chooseRsa(G.host)
g_game.setProtocolVersion(protocolVersion) g_game.setClientVersion(clientVersion)
if #clientVersions > 0 then g_game.setProtocolVersion(g_game.getProtocolVersionForClient(clientVersion))
g_game.setClientVersion(clientVersions[#clientVersions])
end
if modules.game_things.isLoaded() then 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)
@@ -269,14 +263,14 @@ end
function EnterGame.setDefaultServer(host, port, protocol) function EnterGame.setDefaultServer(host, port, protocol)
local hostTextEdit = enterGame:getChildById('serverHostTextEdit') local hostTextEdit = enterGame:getChildById('serverHostTextEdit')
local portTextEdit = enterGame:getChildById('serverPortTextEdit') local portTextEdit = enterGame:getChildById('serverPortTextEdit')
local protocolLabel = enterGame:getChildById('protocolLabel') local clientLabel = enterGame:getChildById('clientLabel')
local accountTextEdit = enterGame:getChildById('accountNameTextEdit') local accountTextEdit = enterGame:getChildById('accountNameTextEdit')
local passwordTextEdit = enterGame:getChildById('accountPasswordTextEdit') local passwordTextEdit = enterGame:getChildById('accountPasswordTextEdit')
if hostTextEdit:getText() ~= host then if hostTextEdit:getText() ~= host then
hostTextEdit:setText(host) hostTextEdit:setText(host)
portTextEdit:setText(port) portTextEdit:setText(port)
protocolBox:setCurrentOption(protocol) clientBox:setCurrentOption(protocol)
accountTextEdit:setText('') accountTextEdit:setText('')
passwordTextEdit:setText('') passwordTextEdit:setText('')
end end
@@ -292,9 +286,9 @@ function EnterGame.setUniqueServer(host, port, protocol, windowWidth, windowHeig
portTextEdit:setVisible(false) portTextEdit:setVisible(false)
portTextEdit:setHeight(0) portTextEdit:setHeight(0)
protocolBox:setCurrentOption(protocol) clientBox:setCurrentOption(protocol)
protocolBox:setVisible(false) clientBox:setVisible(false)
protocolBox:setHeight(0) clientBox:setHeight(0)
local serverLabel = enterGame:getChildById('serverLabel') local serverLabel = enterGame:getChildById('serverLabel')
serverLabel:setVisible(false) serverLabel:setVisible(false)
@@ -302,9 +296,9 @@ function EnterGame.setUniqueServer(host, port, protocol, windowWidth, windowHeig
local portLabel = enterGame:getChildById('portLabel') local portLabel = enterGame:getChildById('portLabel')
portLabel:setVisible(false) portLabel:setVisible(false)
portLabel:setHeight(0) portLabel:setHeight(0)
local protocolLabel = enterGame:getChildById('protocolLabel') local clientLabel = enterGame:getChildById('clientLabel')
protocolLabel:setVisible(false) clientLabel:setVisible(false)
protocolLabel:setHeight(0) clientLabel:setHeight(0)
local serverListButton = enterGame:getChildById('serverListButton') local serverListButton = enterGame:getChildById('serverListButton')
serverListButton:setVisible(false) serverListButton:setVisible(false)

View File

@@ -68,7 +68,7 @@ EnterGameWindow
TextEdit TextEdit
id: serverHostTextEdit id: serverHostTextEdit
!tooltip: tr('Make sure that your client uses\nthe correct game protocol version') !tooltip: tr('Make sure that your client uses\nthe correct game client version')
anchors.left: parent.left anchors.left: parent.left
anchors.right: serverListButton.left anchors.right: serverListButton.left
anchors.top: serverLabel.bottom anchors.top: serverLabel.bottom
@@ -76,8 +76,8 @@ EnterGameWindow
margin-right: 4 margin-right: 4
MenuLabel MenuLabel
id: protocolLabel id: clientLabel
!text: tr('Protocol') !text: tr('Client Version')
anchors.left: parent.left anchors.left: parent.left
anchors.top: serverHostTextEdit.bottom anchors.top: serverHostTextEdit.bottom
text-auto-resize: true text-auto-resize: true
@@ -85,17 +85,13 @@ EnterGameWindow
margin-top: 8 margin-top: 8
ComboBox ComboBox
id: protocolComboBox id: clientComboBox
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.horizontalCenter anchors.right: parent.horizontalCenter
anchors.top: protocolLabel.bottom anchors.top: clientLabel.bottom
margin-top: 2 margin-top: 2
margin-right: 3 margin-right: 3
width: 90 width: 90
@onSetup: |
for _, proto in pairs(g_game.getSupportedProtocols()) do
self:addOption(proto)
end
MenuLabel MenuLabel
id: portLabel id: portLabel
@@ -110,7 +106,7 @@ EnterGameWindow
text: 7171 text: 7171
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.horizontalCenter anchors.left: parent.horizontalCenter
anchors.top: protocolComboBox.top anchors.top: clientComboBox.top
margin-left: 3 margin-left: 3
CheckBox CheckBox

View File

@@ -137,7 +137,7 @@ function unloadCurrentModule()
local module = g_modules.getModule(focusedChild:getText()) local module = g_modules.getModule(focusedChild:getText())
if module then if module then
module:unload() module:unload()
if ModuleManager == nil then return end if modules.client_modulemanager == nil then return end
updateModuleInfo(module:getName()) updateModuleInfo(module:getName())
refreshLoadedModules() refreshLoadedModules()
end end

View File

@@ -49,7 +49,7 @@ MainWindow
anchors.left: protocolLabel.left anchors.left: protocolLabel.left
anchors.right: port.right anchors.right: port.right
@onSetup: | @onSetup: |
for _, proto in pairs(g_game.getSupportedProtocols()) do for _, proto in pairs(g_game.getSupportedClients()) do
self:addOption(proto) self:addOption(proto)
end end

View File

@@ -257,10 +257,13 @@ function flushLines()
for _,line in pairs(cachedLines) do for _,line in pairs(cachedLines) do
-- delete old lines if needed -- delete old lines if needed
if numLines > MaxLogLines then if numLines > MaxLogLines then
local len = #terminalBuffer:getChildByIndex(1):getText() local firstChild = terminalBuffer:getChildByIndex(1)
terminalBuffer:getChildByIndex(1):destroy() if firstChild then
table.remove(allLines, 1) local len = #firstChild:getText()
fulltext = string.sub(fulltext, len) firstChild:destroy()
table.remove(allLines, 1)
fulltext = string.sub(fulltext, len)
end
end end
local label = g_ui.createWidget('TerminalLabel', terminalBuffer) local label = g_ui.createWidget('TerminalLabel', terminalBuffer)
@@ -285,6 +288,7 @@ function addLine(text, color)
flushEvent = scheduleEvent(flushLines, 10) flushEvent = scheduleEvent(flushLines, 10)
end end
text = string.gsub(text, '\t', ' ')
table.insert(cachedLines, {text=text, color=color}) table.insert(cachedLines, {text=text, color=color})
end end

View File

@@ -20,8 +20,8 @@ function string:starts(start)
return string.sub(self, 1, #start) == start return string.sub(self, 1, #start) == start
end end
function string.ends(s, test) function string:ends(test)
return test =='' or string.sub(s,-string.len(test)) == test return test =='' or string.sub(self,-string.len(test)) == test
end end
function string:trim() function string:trim()

View File

@@ -110,7 +110,9 @@ function UITabBar:selectTab(tab)
tab:setOn(false) tab:setOn(false)
local parent = tab:getParent() local parent = tab:getParent()
parent:focusChild(tab, MouseFocusReason) if parent then
parent:focusChild(tab, MouseFocusReason)
end
end end
function UITabBar:selectNextTab() function UITabBar:selectNextTab()

View File

@@ -1,11 +1,16 @@
battleWindow = nil battleWindow = nil
battleButton = nil battleButton = nil
battlePanel = nil battlePanel = nil
filterPanel = nil
toggleFilterButton = nil
lastBattleButtonSwitched = nil lastBattleButtonSwitched = nil
battleButtonsByCreaturesList = {} battleButtonsByCreaturesList = {}
creatureAgeList = {}
mouseWidget = nil mouseWidget = nil
sortTypeBox = nil
sortOrderBox = nil
hidePlayersButton = nil hidePlayersButton = nil
hideNPCsButton = nil hideNPCsButton = nil
hideMonstersButton = nil hideMonstersButton = nil
@@ -25,6 +30,15 @@ function init()
battlePanel = battleWindow:recursiveGetChildById('battlePanel') battlePanel = battleWindow:recursiveGetChildById('battlePanel')
filterPanel = battleWindow:recursiveGetChildById('filterPanel')
toggleFilterButton = battleWindow:recursiveGetChildById('toggleFilterButton')
if isHidingFilters() then
hideFilterPanel()
end
sortTypeBox = battleWindow:recursiveGetChildById('sortTypeBox')
sortOrderBox = battleWindow:recursiveGetChildById('sortOrderBox')
hidePlayersButton = battleWindow:recursiveGetChildById('hidePlayers') hidePlayersButton = battleWindow:recursiveGetChildById('hidePlayers')
hideNPCsButton = battleWindow:recursiveGetChildById('hideNPCs') hideNPCsButton = battleWindow:recursiveGetChildById('hideNPCs')
hideMonstersButton = battleWindow:recursiveGetChildById('hideMonsters') hideMonstersButton = battleWindow:recursiveGetChildById('hideMonsters')
@@ -38,6 +52,18 @@ function init()
battleWindow:setContentMinimumHeight(80) battleWindow:setContentMinimumHeight(80)
sortTypeBox:addOption('Name', 'name')
sortTypeBox:addOption('Distance', 'distance')
sortTypeBox:addOption('Age', 'age')
sortTypeBox:addOption('Health', 'health')
sortTypeBox:setCurrentOptionByData(getSortType())
sortTypeBox.onOptionChange = onChangeSortType
sortOrderBox:addOption('Asc.', 'asc')
sortOrderBox:addOption('Desc.', 'desc')
sortOrderBox:setCurrentOptionByData(getSortOrder())
sortOrderBox.onOptionChange = onChangeSortOrder
connect(Creature, { connect(Creature, {
onSkullChange = updateCreatureSkull, onSkullChange = updateCreatureSkull,
onEmblemChange = updateCreatureEmblem, onEmblemChange = updateCreatureEmblem,
@@ -47,6 +73,10 @@ function init()
onAppear = onCreatureAppear, onAppear = onCreatureAppear,
onDisappear = onCreatureDisappear onDisappear = onCreatureDisappear
}) })
connect(LocalPlayer, {
onPositionChange = onCreaturePositionChange
})
connect(g_game, { connect(g_game, {
onAttackingCreatureChange = onAttack, onAttackingCreatureChange = onAttack,
@@ -75,6 +105,10 @@ function terminate()
onDisappear = onCreatureDisappear onDisappear = onCreatureDisappear
}) })
disconnect(LocalPlayer, {
onPositionChange = onCreaturePositionChange
})
disconnect(g_game, { disconnect(g_game, {
onAttackingCreatureChange = onAttack, onAttackingCreatureChange = onAttack,
onFollowingCreatureChange = onFollow, onFollowingCreatureChange = onFollow,
@@ -96,6 +130,93 @@ function onMiniWindowClose()
battleButton:setOn(false) battleButton:setOn(false)
end end
function getSortType()
local settings = g_settings.getNode('BattleList')
if not settings then
return 'name'
end
return settings['sortType']
end
function setSortType(state)
settings = {}
settings['sortType'] = state
g_settings.mergeNode('BattleList', settings)
checkCreatures()
end
function getSortOrder()
local settings = g_settings.getNode('BattleList')
if not settings then
return 'asc'
end
return settings['sortOrder']
end
function setSortOrder(state)
settings = {}
settings['sortOrder'] = state
g_settings.mergeNode('BattleList', settings)
checkCreatures()
end
function isSortAsc()
return getSortOrder() == 'asc'
end
function isSortDesc()
return getSortOrder() == 'desc'
end
function isHidingFilters()
local settings = g_settings.getNode('BattleList')
if not settings then
return false
end
return settings['hidingFilters']
end
function setHidingFilters(state)
settings = {}
settings['hidingFilters'] = state
g_settings.mergeNode('BattleList', settings)
end
function hideFilterPanel()
filterPanel.originalHeight = filterPanel:getHeight()
filterPanel:setHeight(0)
toggleFilterButton:getParent():setMarginTop(0)
toggleFilterButton:setImageClip(torect("0 0 21 12"))
setHidingFilters(true)
filterPanel:setVisible(false)
end
function showFilterPanel()
toggleFilterButton:getParent():setMarginTop(5)
filterPanel:setHeight(filterPanel.originalHeight)
toggleFilterButton:setImageClip(torect("21 0 21 12"))
setHidingFilters(false)
filterPanel:setVisible(true)
end
function toggleFilterPanel()
if filterPanel:isVisible() then
hideFilterPanel()
else
showFilterPanel()
end
end
function onChangeSortType(comboBox, option)
setSortType(option:lower())
end
function onChangeSortOrder(comboBox, option)
setSortOrder(option:lower():gsub('[.]', '')) -- Replace dot in option name
end
function checkCreatures() function checkCreatures()
removeAllCreatures() removeAllCreatures()
@@ -151,15 +272,42 @@ end
function onCreatureHealthPercentChange(creature, health) function onCreatureHealthPercentChange(creature, health)
local battleButton = battleButtonsByCreaturesList[creature:getId()] local battleButton = battleButtonsByCreaturesList[creature:getId()]
if battleButton then if battleButton then
if getSortType() == 'health' then
removeCreature(creature)
addCreature(creature)
return
end
battleButton:setLifeBarPercent(creature:getHealthPercent()) battleButton:setLifeBarPercent(creature:getHealthPercent())
end end
end end
local function getDistanceBetween(p1, p2)
return math.max(math.abs(p1.x - p2.x), math.abs(p1.y - p2.y))
end
function onCreaturePositionChange(creature, newPos, oldPos) function onCreaturePositionChange(creature, newPos, oldPos)
if creature:isLocalPlayer() then if creature:isLocalPlayer() then
if oldPos and newPos and newPos.z ~= oldPos.z then if oldPos and newPos and newPos.z ~= oldPos.z then
checkCreatures() checkCreatures()
else else
-- Distance will change when moving, recalculate and move to correct index
if getSortType() == 'distance' then
local distanceList = {}
for id, creatureButton in pairs(battleButtonsByCreaturesList) do
table.insert(distanceList, {distance = getDistanceBetween(newPos, creatureButton.creature:getPosition()), widget = creatureButton})
end
if isSortAsc() then
table.sort(distanceList, function(a, b) return a.distance < b.distance end)
else
table.sort(distanceList, function(a, b) return a.distance > b.distance end)
end
for i = 1, #distanceList do
battlePanel:moveChildToIndex(distanceList[i].widget, i)
end
end
for id, creatureButton in pairs(battleButtonsByCreaturesList) do for id, creatureButton in pairs(battleButtonsByCreaturesList) do
addCreature(creatureButton.creature) addCreature(creatureButton.creature)
end end
@@ -170,6 +318,9 @@ function onCreaturePositionChange(creature, newPos, oldPos)
if has and not fit then if has and not fit then
removeCreature(creature) removeCreature(creature)
elseif fit then elseif fit then
if has and getSortType() == 'distance' then
removeCreature(creature)
end
addCreature(creature) addCreature(creature)
end end
end end
@@ -201,8 +352,13 @@ function addCreature(creature)
local creatureId = creature:getId() local creatureId = creature:getId()
local battleButton = battleButtonsByCreaturesList[creatureId] local battleButton = battleButtonsByCreaturesList[creatureId]
-- Register when creature is added to battlelist for the first time
if not creatureAgeList[creatureId] then
creatureAgeList[creatureId] = os.time()
end
if not battleButton then if not battleButton then
battleButton = g_ui.createWidget('BattleButton', battlePanel) battleButton = g_ui.createWidget('BattleButton')
battleButton:setup(creature) battleButton:setup(creature)
battleButton.onHoverChange = onBattleButtonHoverChange battleButton.onHoverChange = onBattleButtonHoverChange
@@ -217,6 +373,77 @@ function addCreature(creature)
if creature == g_game.getFollowingCreature() then if creature == g_game.getFollowingCreature() then
onFollow(creature) onFollow(creature)
end end
local inserted = false
local nameLower = creature:getName():lower()
local healthPercent = creature:getHealthPercent()
local playerPosition = g_game.getLocalPlayer():getPosition()
local distance = getDistanceBetween(playerPosition, creature:getPosition())
local age = creatureAgeList[creatureId]
local childCount = battlePanel:getChildCount()
for i = 1, childCount do
local child = battlePanel:getChildByIndex(i)
local childName = child:getCreature():getName():lower()
local equal = false
if getSortType() == 'age' then
local childAge = creatureAgeList[child:getCreature():getId()]
if (age < childAge and isSortAsc()) or (age > childAge and isSortDesc()) then
battlePanel:insertChild(i, battleButton)
inserted = true
break
elseif age == childAge then
equal = true
end
elseif getSortType() == 'distance' then
local childDistance = getDistanceBetween(child:getCreature():getPosition(), playerPosition)
if (distance < childDistance and isSortAsc()) or (distance > childDistance and isSortDesc()) then
battlePanel:insertChild(i, battleButton)
inserted = true
break
elseif childDistance == distance then
equal = true
end
elseif getSortType() == 'health' then
local childHealth = child:getCreature():getHealthPercent()
if (healthPercent < childHealth and isSortAsc()) or (healthPercent > childHealth and isSortDesc()) then
battlePanel:insertChild(i, battleButton)
inserted = true
break
elseif healthPercent == childHealth then
equal = true
end
end
-- If any other sort type is selected and values are equal, sort it by name also
if getSortType() == 'name' or equal then
local length = math.min(childName:len(), nameLower:len())
for j=1,length do
if (nameLower:byte(j) < childName:byte(j) and isSortAsc()) or (nameLower:byte(j) > childName:byte(j) and isSortDesc()) then
battlePanel:insertChild(i, battleButton)
inserted = true
break
elseif (nameLower:byte(j) > childName:byte(j) and isSortAsc()) or (nameLower:byte(j) < childName:byte(j) and isSortDesc()) then
break
elseif j == nameLower:len() and isSortAsc() then
battlePanel:insertChild(i, battleButton)
inserted = true
elseif j == childName:len() and isSortDesc() then
battlePanel:insertChild(i, battleButton)
inserted = true
end
end
end
if inserted then
break
end
end
-- Insert at the end if no other place is found
if not inserted then
battlePanel:insertChild(childCount + 1, battleButton)
end
else else
battleButton:setLifeBarPercent(creature:getHealthPercent()) battleButton:setLifeBarPercent(creature:getHealthPercent())
end end
@@ -226,6 +453,7 @@ function addCreature(creature)
end end
function removeAllCreatures() function removeAllCreatures()
creatureAgeList = {}
for i, v in pairs(battleButtonsByCreaturesList) do for i, v in pairs(battleButtonsByCreaturesList) do
removeCreature(v.creature) removeCreature(v.creature)
end end

View File

@@ -45,11 +45,12 @@ MiniWindow
&save: true &save: true
Panel Panel
id: filterPanel
margin-top: 26 margin-top: 26
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: miniwindowScrollBar.left anchors.right: miniwindowScrollBar.left
height: 20 height: 45
Panel Panel
anchors.top: parent.top anchors.top: parent.top
@@ -85,16 +86,56 @@ MiniWindow
!tooltip: tr('Hide party members') !tooltip: tr('Hide party members')
@onCheckChange: modules.game_battle.checkCreatures() @onCheckChange: modules.game_battle.checkCreatures()
HorizontalSeparator Panel
anchors.top: prev.bottom
anchors.horizontalCenter: parent.horizontalCenter
height: 20
width: 128
margin-top: 6
ComboBox
id: sortTypeBox
width: 74
anchors.top: parent.top
anchors.left: prev.right
anchors.horizontalCenter: parent.horizontalCenter
margin-left: -28
ComboBox
id: sortOrderBox
width: 54
anchors.top: parent.top
anchors.left: prev.right
margin-left: 4
Panel
height: 18
anchors.top: prev.bottom anchors.top: prev.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: miniwindowScrollBar.left anchors.right: miniwindowScrollBar.left
margin-right: 1 margin-top: 5
margin-top: 4
UIWidget
id: toggleFilterButton
anchors.top: prev.top
width: 21
anchors.horizontalCenter: parent.horizontalCenter
image-source: /images/ui/arrow_vertical
image-rect: 0 0 21 12
image-clip: 21 0 21 12
@onClick: modules.game_battle.toggleFilterPanel()
phantom: false
HorizontalSeparator
anchors.top: prev.top
anchors.left: parent.left
anchors.right: miniwindowScrollBar.left
margin-right: 1
margin-top: 11
MiniWindowContents MiniWindowContents
anchors.top: prev.bottom anchors.top: prev.bottom
margin-top: 0 margin-top: 6
Panel Panel
id: battlePanel id: battlePanel

View File

@@ -0,0 +1,206 @@
IgnoreListLabel < Label
font: verdana-11px-monochrome
background-color: alpha
text-offset: 2 0
focusable: true
phantom: false
$focus:
background-color: #ffffff22
color: #ffffff
WhiteListLabel < Label
font: verdana-11px-monochrome
background-color: alpha
text-offset: 2 0
focusable: true
phantom: false
$focus:
background-color: #ffffff22
color: #ffffff
MainWindow
id: communicationWindow
!text: tr('Ignore List')
size: 515 410
@onEscape: self:destroy()
CheckBox
id: checkboxUseIgnoreList
!text: tr('Activate ignorelist')
anchors.left: parent.left
anchors.top: parent.top
width: 180
Label
!text: tr('Ignored Players:')
anchors.left: parent.left
anchors.top: prev.bottom
margin-top: 10
TextList
id: ignoreList
vertical-scrollbar: ignoreListScrollBar
anchors.left: parent.left
anchors.top: prev.bottom
height: 150
width: 230
margin-bottom: 10
margin-top: 3
padding: 1
focusable: false
TextEdit
id: ignoreNameEdit
anchors.top: prev.bottom
anchors.left: parent.left
width: 110
margin-top: 5
Button
id: buttonIgnoreAdd
!text: tr('Add')
width: 48
height: 20
margin-left: 5
anchors.top: prev.top
anchors.left: prev.right
Button
id: buttonIgnoreRemove
!text: tr('Remove')
width: 64
height: 20
margin-left: 5
anchors.top: prev.top
anchors.left: prev.right
Label
!text: tr('Global ignore settings')
anchors.left: parent.left
anchors.top: prev.bottom
margin-top: 20
CheckBox
id: checkboxIgnorePrivateMessages
!text: tr('Ignore Private Messages')
anchors.left: parent.left
anchors.top: prev.bottom
width: 180
margin-top: 5
CheckBox
id: checkboxIgnoreYelling
!text: tr('Ignore Yelling')
anchors.left: parent.left
anchors.top: prev.bottom
width: 180
margin-top: 5
CheckBox
id: checkboxUseWhiteList
!text: tr('Activate whitelist')
anchors.top: parent.top
anchors.left: ignoreList.right
margin-left: 20
width: 180
Label
!text: tr('Allowed Players:')
anchors.top: prev.bottom
anchors.left: prev.left
margin-top: 10
TextList
id: whiteList
vertical-scrollbar: whiteListScrollBar
anchors.left: prev.left
anchors.top: prev.bottom
height: 150
width: 230
margin-bottom: 10
margin-top: 3
padding: 1
focusable: false
TextEdit
id: whitelistNameEdit
anchors.top: prev.bottom
anchors.left: prev.left
width: 110
margin-top: 5
Button
id: buttonWhitelistAdd
!text: tr('Add')
width: 48
height: 20
margin-left: 5
anchors.top: prev.top
anchors.left: prev.right
Button
id: buttonWhitelistRemove
!text: tr('Remove')
width: 64
height: 20
margin-left: 5
anchors.top: prev.top
anchors.left: prev.right
Label
!text: tr('Global whitelist settings')
anchors.left: whiteList.left
anchors.top: prev.bottom
margin-top: 20
CheckBox
id: checkboxAllowVIPs
!text: tr('Allow VIPs to message you')
anchors.left: prev.left
anchors.top: prev.bottom
width: 180
margin-top: 5
Panel
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: 30
Panel
size: 160 30
anchors.horizontalCenter: parent.horizontalCenter
Button
id: buttonSave
!text: tr('Save')
width: 75
anchors.top: parent.top
anchors.left: parent.left
Button
id: buttonCancel
!text: tr('Cancel')
width: 75
anchors.top: parent.top
anchors.left: prev.right
margin-left: 10
VerticalScrollBar
id: ignoreListScrollBar
anchors.top: ignoreList.top
anchors.bottom: ignoreList.bottom
anchors.right: ignoreList.right
step: 14
pixels-scroll: true
VerticalScrollBar
id: whiteListScrollBar
anchors.top: whiteList.top
anchors.bottom: whiteList.bottom
anchors.right: whiteList.right
step: 14
pixels-scroll: true

View File

@@ -61,7 +61,7 @@ consoleTabBar = nil
consoleTextEdit = nil consoleTextEdit = nil
channels = nil channels = nil
channelsWindow = nil channelsWindow = nil
ignoreWindow = nil communicationWindow = nil
ownPrivateName = nil ownPrivateName = nil
messageHistory = {} messageHistory = {}
currentMessageIndex = 0 currentMessageIndex = 0
@@ -74,10 +74,14 @@ violationReportTab = nil
ignoredChannels = {} ignoredChannels = {}
filters = {} filters = {}
local ignoreSettings = { local communicationSettings = {
useIgnoreList = true,
useWhiteList = true,
privateMessages = false, privateMessages = false,
yelling = false, yelling = false,
players = {} allowVIPs = false,
ignoredPlayers = {},
whitelistedPlayers = {}
} }
function init() function init()
@@ -139,10 +143,59 @@ function init()
g_keyboard.bindKeyDown('Ctrl+O', g_game.requestChannels) g_keyboard.bindKeyDown('Ctrl+O', g_game.requestChannels)
g_keyboard.bindKeyDown('Ctrl+E', removeCurrentTab) g_keyboard.bindKeyDown('Ctrl+E', removeCurrentTab)
g_keyboard.bindKeyDown('Ctrl+H', openHelp) g_keyboard.bindKeyDown('Ctrl+H', openHelp)
consoleToggleChat = consolePanel:getChildById('toggleChat')
load() load()
end end
function toggleChat()
if consoleToggleChat:isChecked() then
disableChat()
else
enableChat()
end
end
function enableChat()
local gameInterface = modules.game_interface
consoleTextEdit:setVisible(true)
consoleTextEdit:setText("")
g_keyboard.unbindKeyUp("Space")
g_keyboard.unbindKeyUp("Enter")
gameInterface.unbindWalkKey("W")
gameInterface.unbindWalkKey("D")
gameInterface.unbindWalkKey("S")
gameInterface.unbindWalkKey("A")
consoleToggleChat:setTooltip(tr("Disable chat mode, allow to walk using ASDW"))
end
function disableChat()
local gameInterface = modules.game_interface
consoleTextEdit:setVisible(false)
consoleTextEdit:setText("")
local quickFunc = function()
if consoleToggleChat:isChecked() then
consoleToggleChat:setChecked(false)
end
enableChat()
end
g_keyboard.bindKeyUp("Space", quickFunc)
g_keyboard.bindKeyUp("Enter", quickFunc)
gameInterface.bindWalkKey("W", North)
gameInterface.bindWalkKey("D", East)
gameInterface.bindWalkKey("S", South)
gameInterface.bindWalkKey("A", West)
consoleToggleChat:setTooltip(tr("Enable chat mode"))
end
function terminate() function terminate()
save() save()
disconnect(g_game, { disconnect(g_game, {
@@ -166,14 +219,14 @@ function terminate()
g_keyboard.unbindKeyDown('Ctrl+E') g_keyboard.unbindKeyDown('Ctrl+E')
g_keyboard.unbindKeyDown('Ctrl+H') g_keyboard.unbindKeyDown('Ctrl+H')
saveIgnoreSettings() saveCommunicationSettings()
if channelsWindow then if channelsWindow then
channelsWindow:destroy() channelsWindow:destroy()
end end
if ignoreWindow then if communicationWindow then
ignoreWindow:destroy() communicationWindow:destroy()
end end
if violationWindow then if violationWindow then
@@ -197,7 +250,7 @@ function load()
if settings then if settings then
messageHistory = settings.messageHistory or {} messageHistory = settings.messageHistory or {}
end end
loadIgnoreSettings() loadCommunicationSettings()
end end
function onTabChange(tabBar, tab) function onTabChange(tabBar, tab)
@@ -466,7 +519,7 @@ function addTabText(text, speaktype, tab, creatureName)
-- Remove the curly braces -- Remove the curly braces
for i = 1, #highlightData / 3 do for i = 1, #highlightData / 3 do
local dataBlock = { _start = highlightData[(i-1)*3+1], _end = highlightData[(i-1)*3+2], words = highlightData[(i-1)*3+3] } local dataBlock = { _start = highlightData[(i-1)*3+1], _end = highlightData[(i-1)*3+2], words = highlightData[(i-1)*3+3] }
text = text:gsub("{"..dataBlock.words.."}", dataBlock.words) text = text:gsub("%{(.-)%}", dataBlock.words, 1)
-- Recalculate positions as braces are removed -- Recalculate positions as braces are removed
highlightData[(i-1)*3+1] = dataBlock._start - ((i-1) * 2) highlightData[(i-1)*3+1] = dataBlock._start - ((i-1) * 2)
@@ -779,7 +832,11 @@ function onTalk(name, level, mode, message, channelId, creaturePos)
return return
end end
if name ~= g_game.getCharacterName() then local localPlayer = g_game.getLocalPlayer()
if name ~= g_game.getCharacterName()
and isUsingIgnoreList()
and not(isUsingWhiteList()) or (isUsingWhiteList() and not(isWhitelisted(name)) and not(isAllowingVIPs() and localPlayer:hasVip(name))) then
if mode == MessageModes.Yell and isIgnoringYelling() then if mode == MessageModes.Yell and isIgnoringYelling() then
return return
elseif speaktype.private and isIgnoringPrivate() and mode ~= MessageModes.NpcFrom then elseif speaktype.private and isIgnoringPrivate() and mode ~= MessageModes.NpcFrom then
@@ -957,107 +1014,221 @@ function onChannelList(channelList)
end end
end end
function loadIgnoreSettings() function loadCommunicationSettings()
communicationSettings.whitelistedPlayers = {}
communicationSettings.ignoredPlayers = {}
local ignoreNode = g_settings.getNode('IgnorePlayers') local ignoreNode = g_settings.getNode('IgnorePlayers')
if ignoreNode then if ignoreNode then
for i = 1, #ignoreNode do for i = 1, #ignoreNode do
table.insert(ignoreSettings.players, ignoreNode[i]) table.insert(communicationSettings.ignoredPlayers, ignoreNode[i])
end end
end end
ignoreSettings.privateMessages = g_settings.getBoolean('IgnorePrivateMessages')
ignoreSettings.yelling = g_settings.getBoolean('IgnoreYelling')
end
function saveIgnoreSettings() local whitelistNode = g_settings.getNode('WhitelistedPlayers')
local tmpSettings = {} if whitelistNode then
for i = 1, #ignoreSettings.players do for i = 1, #whitelistNode do
table.insert(tmpSettings, ignoreSettings.players[i]) table.insert(communicationSettings.whitelistedPlayers, whitelistNode[i])
end
end end
g_settings.set('IgnorePrivateMessages', ignoreSettings.privateMessages)
g_settings.set('IgnoreYelling', ignoreSettings.yelling) communicationSettings.useIgnoreList = g_settings.getBoolean('UseIgnoreList')
g_settings.setNode('IgnorePlayers', tmpSettings) communicationSettings.useWhiteList = g_settings.getBoolean('UseWhiteList')
communicationSettings.privateMessages = g_settings.getBoolean('IgnorePrivateMessages')
communicationSettings.yelling = g_settings.getBoolean('IgnoreYelling')
communicationSettings.allowVIPs = g_settings.getBoolean('AllowVIPs')
end end
function saveCommunicationSettings()
local tmpIgnoreList = {}
local ignoredPlayers = getIgnoredPlayers()
for i = 1, #ignoredPlayers do
table.insert(tmpIgnoreList, ignoredPlayers[i])
end
local tmpWhiteList = {}
local whitelistedPlayers = getWhitelistedPlayers()
for i = 1, #whitelistedPlayers do
table.insert(tmpWhiteList, whitelistedPlayers[i])
end
g_settings.set('UseIgnoreList', communicationSettings.useIgnoreList)
g_settings.set('UseWhiteList', communicationSettings.useWhiteList)
g_settings.set('IgnorePrivateMessages', communicationSettings.privateMessages)
g_settings.set('IgnoreYelling', communicationSettings.yelling)
g_settings.setNode('IgnorePlayers', tmpIgnoreList)
g_settings.setNode('WhitelistedPlayers', tmpWhiteList)
end
function getIgnoredPlayers()
return communicationSettings.ignoredPlayers
end
function getWhitelistedPlayers()
return communicationSettings.whitelistedPlayers
end
function isUsingIgnoreList()
return communicationSettings.useIgnoreList
end
function isUsingWhiteList()
return communicationSettings.useWhiteList
end
function isIgnored(name) function isIgnored(name)
return table.find(ignoreSettings.players, name, true) return table.find(communicationSettings.ignoredPlayers, name, true)
end end
function addIgnoredPlayer(name) function addIgnoredPlayer(name)
if not isIgnored(name) then if isIgnored(name) then return end
table.insert(ignoreSettings.players, name) table.insert(communicationSettings.ignoredPlayers, name)
end
end end
function removeIgnoredPlayer(name) function removeIgnoredPlayer(name)
table.removevalue(ignoreSettings.players, name) table.removevalue(communicationSettings.ignoredPlayers, name)
end
function isWhitelisted(name)
return table.find(communicationSettings.whitelistedPlayers, name, true)
end
function addWhitelistedPlayer(name)
if isWhitelisted(name) then return end
table.insert(communicationSettings.whitelistedPlayers, name)
end
function removeWhitelistedPlayer(name)
table.removevalue(communicationSettings.whitelistedPlayers, name)
end end
function isIgnoringPrivate() function isIgnoringPrivate()
return ignoreSettings.privateMessages return communicationSettings.privateMessages
end end
function isIgnoringYelling() function isIgnoringYelling()
return ignoreSettings.yelling return communicationSettings.yelling
end
function isAllowingVIPs()
return communicationSettings.allowVIPs
end end
function onClickIgnoreButton() function onClickIgnoreButton()
if ignoreWindow then return end if communicationWindow then return end
ignoreWindow = g_ui.displayUI('ignorewindow') communicationWindow = g_ui.displayUI('communicationwindow')
local ignoreListPanel = ignoreWindow:getChildById('ignoreList') local ignoreListPanel = communicationWindow:getChildById('ignoreList')
ignoreWindow.onDestroy = function() ignoreWindow = nil end local whiteListPanel = communicationWindow:getChildById('whiteList')
communicationWindow.onDestroy = function() communicationWindow = nil end
local removeButton = ignoreWindow:getChildById('buttonRemove') local useIgnoreListBox = communicationWindow:getChildById('checkboxUseIgnoreList')
removeButton:disable() useIgnoreListBox:setChecked(communicationSettings.useIgnoreList)
ignoreListPanel.onChildFocusChange = function() removeButton:enable() end local useWhiteListBox = communicationWindow:getChildById('checkboxUseWhiteList')
removeButton.onClick = function() useWhiteListBox:setChecked(communicationSettings.useWhiteList)
local removeIgnoreButton = communicationWindow:getChildById('buttonIgnoreRemove')
removeIgnoreButton:disable()
ignoreListPanel.onChildFocusChange = function() removeIgnoreButton:enable() end
removeIgnoreButton.onClick = function()
local selection = ignoreListPanel:getFocusedChild() local selection = ignoreListPanel:getFocusedChild()
if selection then if selection then
ignoreListPanel:removeChild(selection) ignoreListPanel:removeChild(selection)
selection:destroy() selection:destroy()
end end
if ignoreListPanel:getChildCount() == 0 then removeIgnoreButton:disable()
removeButton:disable() end
local removeWhitelistButton = communicationWindow:getChildById('buttonWhitelistRemove')
removeWhitelistButton:disable()
whiteListPanel.onChildFocusChange = function() removeWhitelistButton:enable() end
removeWhitelistButton.onClick = function()
local selection = whiteListPanel:getFocusedChild()
if selection then
whiteListPanel:removeChild(selection)
selection:destroy()
end end
removeWhitelistButton:disable()
end end
local newlyIgnoredPlayers = {} local newlyIgnoredPlayers = {}
local addName = ignoreWindow:getChildById('ignoreNameEdit') local addIgnoreName = communicationWindow:getChildById('ignoreNameEdit')
local addButton = ignoreWindow:getChildById('buttonAdd') local addIgnoreButton = communicationWindow:getChildById('buttonIgnoreAdd')
local addFunction = function() local addIgnoreFunction = function()
if addName:getText() == '' then return end local newEntry = addIgnoreName:getText()
if table.find(ignoreSettings.players, addName:getText()) then return end if newEntry == '' then return end
if table.find(newlyIgnoredPlayers, addName:getText()) then return end if table.find(getIgnoredPlayers(), newEntry) then return end
local label = g_ui.createWidget('IgnoreListLabel', ignoreListPanel) if table.find(newlyIgnoredPlayers, newEntry) then return end
label:setText(addName:getText())
table.insert(newlyIgnoredPlayers, addName:getText())
label:setPhantom(false)
addName:setText('')
end
addButton.onClick = addFunction
ignoreWindow.onEnter = addFunction
local ignorePrivateMessageBox = ignoreWindow:getChildById('checkboxIgnorePrivateMessages')
ignorePrivateMessageBox:setChecked(ignoreSettings.privateMessages)
local ignoreYellingBox = ignoreWindow:getChildById('checkboxIgnoreYelling')
ignoreYellingBox:setChecked(ignoreSettings.yelling)
local saveButton = ignoreWindow:getChildById('buttonSave')
saveButton.onClick = function()
ignoreSettings.players = {}
for i = 1, ignoreListPanel:getChildCount() do
addIgnoredPlayer(ignoreListPanel:getChildByIndex(i):getText())
--table.insert(ignoreSettings.players, ignoreListPanel:getChildByIndex(i):getText())
end
ignoreSettings.yelling = ignoreYellingBox:isChecked()
ignoreSettings.privateMessages = ignorePrivateMessageBox:isChecked()
ignoreWindow:destroy()
end
for _, name in pairs(ignoreSettings.players) do
local label = g_ui.createWidget('IgnoreListLabel', ignoreListPanel) local label = g_ui.createWidget('IgnoreListLabel', ignoreListPanel)
label:setText(name) label:setText(newEntry)
label:setPhantom(false) table.insert(newlyIgnoredPlayers, newEntry)
addIgnoreName:setText('')
end
addIgnoreButton.onClick = addIgnoreFunction
local newlyWhitelistedPlayers = {}
local addWhitelistName = communicationWindow:getChildById('whitelistNameEdit')
local addWhitelistButton = communicationWindow:getChildById('buttonWhitelistAdd')
local addWhitelistFunction = function()
local newEntry = addWhitelistName:getText()
if newEntry == '' then return end
if table.find(getWhitelistedPlayers(), newEntry) then return end
if table.find(newlyWhitelistedPlayers, newEntry) then return end
local label = g_ui.createWidget('WhiteListLabel', whiteListPanel)
label:setText(newEntry)
table.insert(newlyWhitelistedPlayers, newEntry)
addWhitelistName:setText('')
end
addWhitelistButton.onClick = addWhitelistFunction
communicationWindow.onEnter = function()
if addWhitelistName:isFocused() then
addWhitelistFunction()
elseif addIgnoreName:isFocused() then
addIgnoreFunction()
end
end
local ignorePrivateMessageBox = communicationWindow:getChildById('checkboxIgnorePrivateMessages')
ignorePrivateMessageBox:setChecked(communicationSettings.privateMessages)
local ignoreYellingBox = communicationWindow:getChildById('checkboxIgnoreYelling')
ignoreYellingBox:setChecked(communicationSettings.yelling)
local allowVIPsBox = communicationWindow:getChildById('checkboxAllowVIPs')
allowVIPsBox:setChecked(communicationSettings.allowVIPs)
local saveButton = communicationWindow:recursiveGetChildById('buttonSave')
saveButton.onClick = function()
communicationSettings.ignoredPlayers = {}
for i = 1, ignoreListPanel:getChildCount() do
addIgnoredPlayer(ignoreListPanel:getChildByIndex(i):getText())
end
communicationSettings.whitelistedPlayers = {}
for i = 1, whiteListPanel:getChildCount() do
addWhitelistedPlayer(whiteListPanel:getChildByIndex(i):getText())
end
communicationSettings.useIgnoreList = useIgnoreListBox:isChecked()
communicationSettings.useWhiteList = useWhiteListBox:isChecked()
communicationSettings.yelling = ignoreYellingBox:isChecked()
communicationSettings.privateMessages = ignorePrivateMessageBox:isChecked()
communicationSettings.allowVIPs = allowVIPsBox:isChecked()
communicationWindow:destroy()
end
local cancelButton = communicationWindow:recursiveGetChildById('buttonCancel')
cancelButton.onClick = function()
communicationWindow:destroy()
end
local ignoredPlayers = getIgnoredPlayers()
for i = 1, #ignoredPlayers do
local label = g_ui.createWidget('IgnoreListLabel', ignoreListPanel)
label:setText(ignoredPlayers[i])
end
local whitelistedPlayers = getWhitelistedPlayers()
for i = 1, #whitelistedPlayers do
local label = g_ui.createWidget('WhiteListLabel', whiteListPanel)
label:setText(whitelistedPlayers[i])
end end
end end
@@ -1089,4 +1260,4 @@ function offline()
g_keyboard.unbindKeyDown('Ctrl+R') g_keyboard.unbindKeyDown('Ctrl+R')
end end
clear() clear()
end end

View File

@@ -56,12 +56,21 @@ Panel
id: consolePanel id: consolePanel
anchors.fill: parent anchors.fill: parent
CheckBox
id: toggleChat
!tooltip: tr('Disable chat mode, allow to walk using ASDW')
anchors.left: parent.left
anchors.top: parent.top
margin-left: 13
margin-top: 8
@onCheckChange: toggleChat()
TabButton TabButton
id: prevChannelButton id: prevChannelButton
icon: /images/game/console/leftarrow icon: /images/game/console/leftarrow
anchors.left: parent.left anchors.left: toggleChat.right
anchors.top: parent.top anchors.top: parent.top
margin-left: 6 margin-left: 3
margin-top: 6 margin-top: 6
ConsoleTabBar ConsoleTabBar

View File

@@ -1,98 +0,0 @@
IgnoreListLabel < Label
font: verdana-11px-monochrome
background-color: alpha
text-offset: 2 0
focusable: true
$focus:
background-color: #ffffff22
color: #ffffff
MainWindow
id: ignoreWindow
!text: tr('Ignore List')
size: 500 240
@onEscape: self:destroy()
Label
!text: tr('Ignored players:')
anchors.left: parent.left
anchors.top: parent.top
TextList
id: ignoreList
vertical-scrollbar: ignoreListScrollBar
anchors.left: parent.left
anchors.top: prev.bottom
height: 150
width: 230
margin-bottom: 10
margin-top: 3
padding: 1
focusable: false
Button
id: buttonRemove
!text: tr('Remove')
width: 64
anchors.right: prev.right
anchors.bottom: parent.bottom
TextEdit
id: ignoreNameEdit
anchors.left: ignoreList.right
anchors.top: ignoreList.top
width: 180
margin-left: 8
margin-right: 3
Button
id: buttonAdd
!text: tr('Add')
width: 48
height: 20
anchors.right: parent.right
anchors.top: prev.top
CheckBox
id: checkboxIgnorePrivateMessages
!text: tr('Ignore Private Messages')
anchors.left: ignoreList.right
anchors.top: ignoreList.top
width: 180
margin-top: 25
margin-left: 8
CheckBox
id: checkboxIgnoreYelling
!text: tr('Ignore Yelling')
anchors.left: ignoreList.right
anchors.top: prev.top
width: 180
margin-top: 25
margin-left: 8
Button
id: buttonSave
!text: tr('Save')
width: 64
anchors.right: next.left
anchors.bottom: parent.bottom
margin-right: 10
@onClick: self:getParent():onEnter()
Button
id: buttonCancel
!text: tr('Cancel')
width: 64
anchors.right: parent.right
anchors.bottom: parent.bottom
@onClick: self:getParent():destroy()
VerticalScrollBar
id: ignoreListScrollBar
anchors.top: ignoreList.top
anchors.bottom: ignoreList.bottom
anchors.right: ignoreList.right
step: 14
pixels-scroll: true

View File

@@ -42,7 +42,8 @@ function init()
gameBottomPanel = gameRootPanel:getChildById('gameBottomPanel') gameBottomPanel = gameRootPanel:getChildById('gameBottomPanel')
connect(gameLeftPanel, { onVisibilityChange = onLeftPanelVisibilityChange }) connect(gameLeftPanel, { onVisibilityChange = onLeftPanelVisibilityChange })
logoutButton = modules.client_topmenu.addLeftButton('logoutButton', tr('Exit'), '/images/topbuttons/logout', tryLogout, true) logoutButton = modules.client_topmenu.addLeftButton('logoutButton', tr('Exit'),
'/images/topbuttons/logout', tryLogout, true)
setupViewMode(0) setupViewMode(0)
@@ -56,42 +57,19 @@ end
function bindKeys() function bindKeys()
gameRootPanel:setAutoRepeatDelay(250) gameRootPanel:setAutoRepeatDelay(250)
g_keyboard.bindKeyDown('Up', function() changeWalkDir(North) end, gameRootPanel, true)
g_keyboard.bindKeyDown('Right', function() changeWalkDir(East) end, gameRootPanel, true) bindWalkKey('Up', North)
g_keyboard.bindKeyDown('Down', function() changeWalkDir(South) end, gameRootPanel, true) bindWalkKey('Right', East)
g_keyboard.bindKeyDown('Left', function() changeWalkDir(West) end, gameRootPanel, true) bindWalkKey('Down', South)
g_keyboard.bindKeyDown('Numpad8', function() changeWalkDir(North) end, gameRootPanel, true) bindWalkKey('Left', West)
g_keyboard.bindKeyDown('Numpad9', function() changeWalkDir(NorthEast) end, gameRootPanel, true) bindWalkKey('Numpad8', North)
g_keyboard.bindKeyDown('Numpad6', function() changeWalkDir(East) end, gameRootPanel, true) bindWalkKey('Numpad9', NorthEast)
g_keyboard.bindKeyDown('Numpad3', function() changeWalkDir(SouthEast) end, gameRootPanel, true) bindWalkKey('Numpad6', East)
g_keyboard.bindKeyDown('Numpad2', function() changeWalkDir(South) end, gameRootPanel, true) bindWalkKey('Numpad3', SouthEast)
g_keyboard.bindKeyDown('Numpad1', function() changeWalkDir(SouthWest) end, gameRootPanel, true) bindWalkKey('Numpad2', South)
g_keyboard.bindKeyDown('Numpad4', function() changeWalkDir(West) end, gameRootPanel, true) bindWalkKey('Numpad1', SouthWest)
g_keyboard.bindKeyDown('Numpad7', function() changeWalkDir(NorthWest) end, gameRootPanel, true) bindWalkKey('Numpad4', West)
g_keyboard.bindKeyUp('Up', function() changeWalkDir(North, true) end, gameRootPanel, true) bindWalkKey('Numpad7', NorthWest)
g_keyboard.bindKeyUp('Right', function() changeWalkDir(East, true) end, gameRootPanel, true)
g_keyboard.bindKeyUp('Down', function() changeWalkDir(South, true) end, gameRootPanel, true)
g_keyboard.bindKeyUp('Left', function() changeWalkDir(West, true) end, gameRootPanel, true)
g_keyboard.bindKeyUp('Numpad8', function() changeWalkDir(North, true) end, gameRootPanel, true)
g_keyboard.bindKeyUp('Numpad9', function() changeWalkDir(NorthEast, true) end, gameRootPanel, true)
g_keyboard.bindKeyUp('Numpad6', function() changeWalkDir(East, true) end, gameRootPanel, true)
g_keyboard.bindKeyUp('Numpad3', function() changeWalkDir(SouthEast, true) end, gameRootPanel, true)
g_keyboard.bindKeyUp('Numpad2', function() changeWalkDir(South, true) end, gameRootPanel, true)
g_keyboard.bindKeyUp('Numpad1', function() changeWalkDir(SouthWest, true) end, gameRootPanel, true)
g_keyboard.bindKeyUp('Numpad4', function() changeWalkDir(West, true) end, gameRootPanel, true)
g_keyboard.bindKeyUp('Numpad7', function() changeWalkDir(NorthWest, true) end, gameRootPanel, true)
g_keyboard.bindKeyPress('Up', function() smartWalk(North) end, gameRootPanel)
g_keyboard.bindKeyPress('Right', function() smartWalk(East) end, gameRootPanel)
g_keyboard.bindKeyPress('Down', function() smartWalk(South) end, gameRootPanel)
g_keyboard.bindKeyPress('Left', function() smartWalk(West) end, gameRootPanel)
g_keyboard.bindKeyPress('Numpad8', function() smartWalk(North) end, gameRootPanel)
g_keyboard.bindKeyPress('Numpad9', function() smartWalk(NorthEast) end, gameRootPanel)
g_keyboard.bindKeyPress('Numpad6', function() smartWalk(East) end, gameRootPanel)
g_keyboard.bindKeyPress('Numpad3', function() smartWalk(SouthEast) end, gameRootPanel)
g_keyboard.bindKeyPress('Numpad2', function() smartWalk(South) end, gameRootPanel)
g_keyboard.bindKeyPress('Numpad1', function() smartWalk(SouthWest) end, gameRootPanel)
g_keyboard.bindKeyPress('Numpad4', function() smartWalk(West) end, gameRootPanel)
g_keyboard.bindKeyPress('Numpad7', function() smartWalk(NorthWest) end, gameRootPanel)
g_keyboard.bindKeyPress('Ctrl+Up', function() g_game.turn(North) changeWalkDir(North) end, gameRootPanel) g_keyboard.bindKeyPress('Ctrl+Up', function() g_game.turn(North) changeWalkDir(North) end, gameRootPanel)
g_keyboard.bindKeyPress('Ctrl+Right', function() g_game.turn(East) changeWalkDir(East) end, gameRootPanel) g_keyboard.bindKeyPress('Ctrl+Right', function() g_game.turn(East) changeWalkDir(East) end, gameRootPanel)
@@ -104,12 +82,24 @@ function bindKeys()
g_keyboard.bindKeyPress('Escape', function() g_game.cancelAttackAndFollow() end, gameRootPanel) g_keyboard.bindKeyPress('Escape', function() g_game.cancelAttackAndFollow() end, gameRootPanel)
g_keyboard.bindKeyPress('Ctrl+=', function() gameMapPanel:zoomIn() end, gameRootPanel) g_keyboard.bindKeyPress('Ctrl+=', function() gameMapPanel:zoomIn() end, gameRootPanel)
g_keyboard.bindKeyPress('Ctrl+-', function() gameMapPanel:zoomOut() end, gameRootPanel) g_keyboard.bindKeyPress('Ctrl+-', function() gameMapPanel:zoomOut() end, gameRootPanel)
g_keyboard.bindKeyDown('Ctrl+Q', logout, gameRootPanel) g_keyboard.bindKeyDown('Ctrl+Q', function() tryLogout(false) end, gameRootPanel)
g_keyboard.bindKeyDown('Ctrl+L', logout, gameRootPanel) g_keyboard.bindKeyDown('Ctrl+L', function() tryLogout(false) end, gameRootPanel)
g_keyboard.bindKeyDown('Ctrl+W', function() g_map.cleanTexts() modules.game_textmessage.clearMessages() end, gameRootPanel) g_keyboard.bindKeyDown('Ctrl+W', function() g_map.cleanTexts() modules.game_textmessage.clearMessages() end, gameRootPanel)
g_keyboard.bindKeyDown('Ctrl+.', nextViewMode, gameRootPanel) g_keyboard.bindKeyDown('Ctrl+.', nextViewMode, gameRootPanel)
end end
function bindWalkKey(key, dir)
g_keyboard.bindKeyDown(key, function() changeWalkDir(dir) end)
g_keyboard.bindKeyUp(key, function() changeWalkDir(dir, true) end)
g_keyboard.bindKeyPress(key, function() smartWalk(dir) end)
end
function unbindWalkKey(key)
g_keyboard.unbindKeyDown(key)
g_keyboard.unbindKeyUp(key)
g_keyboard.unbindKeyPress(key)
end
function terminate() function terminate()
save() save()
hide() hide()
@@ -215,8 +205,8 @@ function tryExit()
return true return true
end end
local exitFunc = function() logout() forceExit() end local exitFunc = function() g_game.safeLogout() forceExit() end
local logoutFunc = function() logout() exitWindow:destroy() exitWindow = nil end local logoutFunc = function() g_game.safeLogout() exitWindow:destroy() exitWindow = nil end
local cancelFunc = function() exitWindow:destroy() exitWindow = nil end local cancelFunc = function() exitWindow:destroy() exitWindow = nil end
exitWindow = displayGeneralBox(tr('Exit'), tr("If you shut down the program, your character might stay in the game.\nClick on 'Logout' to ensure that you character leaves the game properly.\nClick on 'Exit' if you want to exit the program without logging out your character."), exitWindow = displayGeneralBox(tr('Exit'), tr("If you shut down the program, your character might stay in the game.\nClick on 'Logout' to ensure that you character leaves the game properly.\nClick on 'Exit' if you want to exit the program without logging out your character."),
@@ -228,29 +218,55 @@ function tryExit()
return true return true
end end
function logout() function tryLogout(prompt)
if g_game.isOnline() then if type(prompt) ~= "boolean" then
g_game.safeLogout() prompt = true
return true
end end
end
function tryLogout()
if not g_game.isOnline() then if not g_game.isOnline() then
exit() exit()
return
end end
if logoutWindow then if logoutWindow then
return return
end end
local yesCallback = function() logout() logoutWindow:destroy() logoutWindow=nil end local msg, yesCallback
local noCallback = function() logoutWindow:destroy() logoutWindow=nil end if not g_game.isConnectionOk() then
msg = 'Your connection is failing, if you logout now your character will be still online, do you want to force logout?'
logoutWindow = displayGeneralBox(tr('Logout'), tr('Are you sure you want to logout?'), { yesCallback = function()
{ text=tr('Yes'), callback=yesCallback }, g_game.forceLogout()
{ text=tr('No'), callback=noCallback }, if logoutWindow then
anchor=AnchorHorizontalCenter}, yesCallback, noCallback) logoutWindow:destroy()
logoutWindow=nil
end
end
else
msg = 'Are you sure you want to logout?'
yesCallback = function()
g_game.safeLogout()
if logoutWindow then
logoutWindow:destroy()
logoutWindow=nil
end
end
end
local noCallback = function()
logoutWindow:destroy()
logoutWindow=nil
end
if prompt then
logoutWindow = displayGeneralBox(tr('Logout'), tr(msg), {
{ text=tr('Yes'), callback=yesCallback },
{ text=tr('No'), callback=noCallback },
anchor=AnchorHorizontalCenter}, yesCallback, noCallback)
else
yesCallback()
end
end end
function stopSmartWalk() function stopSmartWalk()
@@ -462,17 +478,20 @@ function createThingMenu(menuPosition, lookThing, useThing, creatureThing)
end end
else else
local localPosition = localPlayer:getPosition()
if not classic then shortcut = '(Alt)' else shortcut = nil end if not classic then shortcut = '(Alt)' else shortcut = nil end
if g_game.getAttackingCreature() ~= creatureThing then if creatureThing:getPosition().z == localPosition.z then
menu:addOption(tr('Attack'), function() g_game.attack(creatureThing) end, shortcut) if g_game.getAttackingCreature() ~= creatureThing then
else menu:addOption(tr('Attack'), function() g_game.attack(creatureThing) end, shortcut)
menu:addOption(tr('Stop Attack'), function() g_game.cancelAttack() end, shortcut) else
end menu:addOption(tr('Stop Attack'), function() g_game.cancelAttack() end, shortcut)
end
if g_game.getFollowingCreature() ~= creatureThing then
menu:addOption(tr('Follow'), function() g_game.follow(creatureThing) end) if g_game.getFollowingCreature() ~= creatureThing then
else menu:addOption(tr('Follow'), function() g_game.follow(creatureThing) end)
menu:addOption(tr('Stop Follow'), function() g_game.cancelFollow() end) else
menu:addOption(tr('Stop Follow'), function() g_game.cancelFollow() end)
end
end end
if creatureThing:isPlayer() then if creatureThing:isPlayer() then
@@ -518,7 +537,7 @@ function createThingMenu(menuPosition, lookThing, useThing, creatureThing)
end end
end end
if modules.game_ruleviolation.hasWindowAccess() then if modules.game_ruleviolation.hasWindowAccess() and creatureThing:isPlayer() then
menu:addSeparator() menu:addSeparator()
menu:addOption(tr('Rule Violation'), function() modules.game_ruleviolation.show(creatureThing:getName()) end) menu:addOption(tr('Rule Violation'), function() modules.game_ruleviolation.show(creatureThing:getName()) end)
end end
@@ -530,7 +549,7 @@ function createThingMenu(menuPosition, lookThing, useThing, creatureThing)
menu:display(menuPosition) menu:display(menuPosition)
end end
function processMouseAction(menuPosition, mouseButton, autoWalkPos, lookThing, useThing, creatureThing) function processMouseAction(menuPosition, mouseButton, autoWalkPos, lookThing, useThing, creatureThing, attackCreature)
local keyboardModifiers = g_keyboard.getModifiers() local keyboardModifiers = g_keyboard.getModifiers()
if not modules.client_options.getOption('classicControl') then if not modules.client_options.getOption('classicControl') then
@@ -556,7 +575,10 @@ function processMouseAction(menuPosition, mouseButton, autoWalkPos, lookThing, u
return true return true
end end
return true return true
elseif creatureThing and g_keyboard.isAltPressed() and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then elseif attackCreature and g_keyboard.isAltPressed() and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then
g_game.attack(attackCreature)
return true
elseif creatureThing and creatureThing:getPosition().z == autoWalkPos.z and g_keyboard.isAltPressed() and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then
g_game.attack(creatureThing) g_game.attack(creatureThing)
return true return true
end end
@@ -565,7 +587,10 @@ function processMouseAction(menuPosition, mouseButton, autoWalkPos, lookThing, u
else else
if useThing and keyboardModifiers == KeyboardNoModifier and mouseButton == MouseRightButton and not g_mouse.isPressed(MouseLeftButton) then if useThing and keyboardModifiers == KeyboardNoModifier and mouseButton == MouseRightButton and not g_mouse.isPressed(MouseLeftButton) then
local player = g_game.getLocalPlayer() local player = g_game.getLocalPlayer()
if creatureThing and creatureThing ~= player then if attackCreature and attackCreature ~= player then
g_game.attack(attackCreature)
return true
elseif creatureThing and creatureThing ~= player and creatureThing:getPosition().z == autoWalkPos.z then
g_game.attack(creatureThing) g_game.attack(creatureThing)
return true return true
elseif useThing:isContainer() then elseif useThing:isContainer() then
@@ -593,7 +618,10 @@ function processMouseAction(menuPosition, mouseButton, autoWalkPos, lookThing, u
elseif useThing and keyboardModifiers == KeyboardCtrlModifier and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then elseif useThing and keyboardModifiers == KeyboardCtrlModifier and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then
createThingMenu(menuPosition, lookThing, useThing, creatureThing) createThingMenu(menuPosition, lookThing, useThing, creatureThing)
return true return true
elseif creatureThing and g_keyboard.isAltPressed() and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then elseif attackCreature and g_keyboard.isAltPressed() and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then
g_game.attack(attackCreature)
return true
elseif creatureThing and creatureThing:getPosition().z == autoWalkPos.z and g_keyboard.isAltPressed() and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then
g_game.attack(creatureThing) g_game.attack(creatureThing)
return true return true
end end
@@ -654,6 +682,8 @@ function moveStackableItem(item, toPos)
end end
g_keyboard.bindKeyPress("Up", function() check() spinbox:up() end, spinbox) g_keyboard.bindKeyPress("Up", function() check() spinbox:up() end, spinbox)
g_keyboard.bindKeyPress("Down", function() check() spinbox:down() end, spinbox) g_keyboard.bindKeyPress("Down", function() check() spinbox:down() end, spinbox)
g_keyboard.bindKeyPress("Right", function() check() spinbox:up() end, spinbox)
g_keyboard.bindKeyPress("Left", function() check() spinbox:down() end, spinbox)
g_keyboard.bindKeyPress("PageUp", function() check() spinbox:setValue(spinbox:getValue()+10) end, spinbox) g_keyboard.bindKeyPress("PageUp", function() check() spinbox:setValue(spinbox:getValue()+10) end, spinbox)
g_keyboard.bindKeyPress("PageDown", function() check() spinbox:setValue(spinbox:getValue()-10) end, spinbox) g_keyboard.bindKeyPress("PageDown", function() check() spinbox:setValue(spinbox:getValue()-10) end, spinbox)

View File

@@ -6,11 +6,11 @@ CountWindow < MainWindow
SpinBox SpinBox
id: spinBox id: spinBox
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.bottom anchors.top: parent.top
width: 0 width: 1
height: 0 height: 1
phantom: true phantom: true
padding-bottom: -40 margin-top: 2
focusable: true focusable: true
Item Item

View File

@@ -78,15 +78,21 @@ function UIGameMap:onMouseRelease(mousePosition, mouseButton)
local useThing local useThing
local creatureThing local creatureThing
local multiUseThing local multiUseThing
local attackCreature
local tile = self:getTile(mousePosition) local tile = self:getTile(mousePosition)
if tile then if tile then
lookThing = tile:getTopLookThing() lookThing = tile:getTopLookThing()
useThing = tile:getTopUseThing() useThing = tile:getTopUseThing()
creatureThing = tile:getTopCreature() creatureThing = tile:getTopCreature()
end
local autoWalkTile = g_map.getTile(autoWalkPos)
if autoWalkTile then
attackCreature = autoWalkTile:getTopCreature()
end end
local ret = modules.game_interface.processMouseAction(mousePosition, mouseButton, autoWalkPos, lookThing, useThing, creatureThing) local ret = modules.game_interface.processMouseAction(mousePosition, mouseButton, autoWalkPos, lookThing, useThing, creatureThing, attackCreature)
if ret then if ret then
self.allowNextRelease = false self.allowNextRelease = false
end end

View File

@@ -86,7 +86,7 @@ function UIItem:onMouseRelease(mousePosition, mouseButton)
g_game.look(item) g_game.look(item)
self.cancelNextRelease = true self.cancelNextRelease = true
return true return true
elseif modules.game_interface.processMouseAction(mousePosition, mouseButton, nil, item, item, nil, item) then elseif modules.game_interface.processMouseAction(mousePosition, mouseButton, nil, item, item, nil, nil) then
return true return true
end end
return false return false

View File

@@ -151,8 +151,7 @@ function onTradeTypeChange(radioTabs, selected, deselected)
ignoreCapacity:setVisible(currentTradeType == BUY) ignoreCapacity:setVisible(currentTradeType == BUY)
ignoreEquipped:setVisible(currentTradeType == SELL) ignoreEquipped:setVisible(currentTradeType == SELL)
showAllItems:setVisible(currentTradeType == SELL) showAllItems:setVisible(currentTradeType == SELL)
sellAllButton:setVisible(false) sellAllButton:setVisible(currentTradeType == SELL)
--sellAllButton:setVisible(currentTradeType == SELL)
refreshTradeItems() refreshTradeItems()
refreshPlayerGoods() refreshPlayerGoods()

View File

@@ -252,7 +252,6 @@ MainWindow
margin-right: 10 margin-right: 10
visible: false visible: false
@onClick: modules.game_npctrade.sellAll() @onClick: modules.game_npctrade.sellAll()
visible: false
Button Button
id: tradeButton id: tradeButton

View File

@@ -47,7 +47,7 @@ function openWindow()
deathWindow = nil deathWindow = nil
end end
local cancelFunc = function() local cancelFunc = function()
modules.game_interface.logout() g_game.safeLogout()
cancelButton:getParent():destroy() cancelButton:getParent():destroy()
deathWindow = nil deathWindow = nil
end end

View File

@@ -18,7 +18,7 @@ function isLoaded()
end end
function load() function load()
local version = g_game.getProtocolVersion() local version = g_game.getClientVersion()
local datPath, sprPath local datPath, sprPath
if filename then if filename then

View File

@@ -88,6 +88,23 @@ function isHiddingOffline()
return settings['hideOffline'] return settings['hideOffline']
end end
function getSortedBy()
local settings = g_settings.getNode('VipList')
if not settings then
return 'status'
end
return settings['sortedBy']
end
function sortBy(state)
settings = {}
settings['sortedBy'] = state
g_settings.mergeNode('VipList', settings)
refresh()
end
function onAddVip(id, name, state) function onAddVip(id, name, state)
local vipList = vipWindow:getChildById('contentsPanel') local vipList = vipWindow:getChildById('contentsPanel')
@@ -118,13 +135,13 @@ function onAddVip(id, name, state)
for i=1,childrenCount do for i=1,childrenCount do
local child = vipList:getChildByIndex(i) local child = vipList:getChildByIndex(i)
if state == VipState.Online and child.vipState ~= VipState.Online then if state == VipState.Online and child.vipState ~= VipState.Online and getSortedBy() == 'status' then
vipList:insertChild(i, label) vipList:insertChild(i, label)
return return
end end
if (state ~= VipState.Online and child.vipState ~= VipState.Online) if ((state ~= VipState.Online and child.vipState ~= VipState.Online)
or (state == VipState.Online and child.vipState == VipState.Online) then or (state == VipState.Online and child.vipState == VipState.Online)) or getSortedBy() == 'name' then
local childText = child:getText():lower() local childText = child:getText():lower()
local length = math.min(childText:len(), nameLower:len()) local length = math.min(childText:len(), nameLower:len())
@@ -167,6 +184,14 @@ function onVipListMousePress(widget, mousePos, mouseButton)
else else
menu:addOption(tr('Show Offline'), function() hideOffline(false) end) menu:addOption(tr('Show Offline'), function() hideOffline(false) end)
end end
if not(getSortedBy() == 'name') then
menu:addOption(tr('Sort by name'), function() sortBy('name') end)
end
if not(getSortedBy() == 'status') then
menu:addOption(tr('Sort by status'), function() sortBy('status') end)
end
menu:display(mousePos) menu:display(mousePos)
@@ -196,6 +221,15 @@ function onVipListLabelMousePress(widget, mousePos, mouseButton)
else else
menu:addOption(tr('Show Offline'), function() hideOffline(false) end) menu:addOption(tr('Show Offline'), function() hideOffline(false) end)
end end
if not(getSortedBy() == 'name') then
menu:addOption(tr('Sort by name'), function() sortBy('name') end)
end
if not(getSortedBy() == 'status') then
menu:addOption(tr('Sort by status'), function() sortBy('status') end)
end
menu:display(mousePos) menu:display(mousePos)
return true return true

View File

@@ -6,7 +6,7 @@ end
function g_game.chooseRsa(host) function g_game.chooseRsa(host)
if currentRsa ~= CIPSOFT_RSA and currentRsa ~= OTSERV_RSA then return end if currentRsa ~= CIPSOFT_RSA and currentRsa ~= OTSERV_RSA then return end
if string.ends(host, '.tibia.com') or string.ends(host, '.cipsoft.com') then if host:ends('.tibia.com') or host:ends('.cipsoft.com') then
g_game.setRsa(CIPSOFT_RSA) g_game.setRsa(CIPSOFT_RSA)
if g_app.getOs() == 'windows' then if g_app.getOs() == 'windows' then
@@ -32,22 +32,29 @@ function g_game.isOfficialTibia()
return currentRsa == CIPSOFT_RSA return currentRsa == CIPSOFT_RSA
end end
function g_game.getSupportedProtocols() function g_game.getSupportedClients()
return { return {
810, 811, 840, 842, 850, 853, 854, 810, 811, 840, 842, 850, 853, 854,
860, 861, 862, 870, 910, 940, 944, 860, 861, 862, 870, 910, 940, 944,
953, 954, 960, 961, 963, 970, 971, 953, 954, 960, 961, 963, 970, 980,
973, 974 981, 982, 983, 984, 985, 986, 1001,
1002, 1010
} }
end end
function g_game.getSupportedClients(protocol) function g_game.getProtocolVersionForClient(client)
clients = { clients = {
[971] = {980}, [980] = 971,
[973] = {981}, [981] = 973,
[974] = {982} [982] = 974,
[983] = 975,
[984] = 976,
[985] = 977,
[986] = 978,
[1001] = 979,
[1002] = 980,
} }
return clients[protocol] or {protocol} return clients[client] or client
end end
g_game.setRsa(OTSERV_RSA) g_game.setRsa(OTSERV_RSA)

View File

@@ -353,6 +353,8 @@ namespace Otc
GameForceFirstAutoWalkStep = 37, GameForceFirstAutoWalkStep = 37,
GameMinimapRemove = 38, GameMinimapRemove = 38,
GameDoubleShopSellAmount = 39, GameDoubleShopSellAmount = 39,
GameContainerPagination = 40,
GameThingMarks = 41,
// 51-100 reserved to be defined in lua // 51-100 reserved to be defined in lua
LastGameFeature = 101 LastGameFeature = 101
}; };

View File

@@ -128,7 +128,7 @@ void Creature::internalDrawOutfit(Point dest, float scaleFactor, bool animateWal
dest -= datType->getDisplacement() * scaleFactor; dest -= datType->getDisplacement() * scaleFactor;
datType->draw(dest, scaleFactor, 0, xPattern, 0, 0, animationPhase, lightView); datType->draw(dest, scaleFactor, 0, xPattern, 0, 0, animationPhase, lightView);
dest += getDisplacement() * scaleFactor; dest += getDisplacement() * scaleFactor;
zPattern = 1; zPattern = std::min(1, getNumPatternZ() - 1);
} }
PointF jumpOffset = m_jumpOffset * scaleFactor; PointF jumpOffset = m_jumpOffset * scaleFactor;

View File

@@ -99,6 +99,11 @@ void Game::resetGameStates()
m_walkEvent = nullptr; m_walkEvent = nullptr;
} }
if(m_checkConnectionEvent) {
m_checkConnectionEvent->cancel();
m_checkConnectionEvent = nullptr;
}
m_containers.clear(); m_containers.clear();
m_vips.clear(); m_vips.clear();
m_gmActions.clear(); m_gmActions.clear();
@@ -183,6 +188,16 @@ void Game::processGameStart()
g_game.ping(); g_game.ping();
}, m_pingDelay); }, m_pingDelay);
} }
m_checkConnectionEvent = g_dispatcher.cycleEvent([this] {
if(!g_game.isConnectionOk() && !m_connectionFailWarned) {
g_lua.callGlobalField("g_game", "onConnectionFailing", true);
m_connectionFailWarned = true;
} else if(g_game.isConnectionOk() && m_connectionFailWarned) {
g_lua.callGlobalField("g_game", "onConnectionFailing", false);
m_connectionFailWarned = false;
}
}, 1000);
} }
void Game::processGameEnd() void Game::processGameEnd()
@@ -190,6 +205,11 @@ void Game::processGameEnd()
m_online = false; m_online = false;
g_lua.callGlobalField("g_game", "onGameEnd"); g_lua.callGlobalField("g_game", "onGameEnd");
if(m_connectionFailWarned) {
g_lua.callGlobalField("g_game", "onConnectionFailing", false);
m_connectionFailWarned = false;
}
// reset game state // reset game state
resetGameStates(); resetGameStates();
@@ -554,7 +574,7 @@ bool Game::walk(Otc::Direction direction)
if(m_lastWalkDir != direction) { if(m_lastWalkDir != direction) {
// must add a new walk event // must add a new walk event
float ticks = m_localPlayer->getStepTicksLeft(); float ticks = m_localPlayer->getStepTicksLeft();
if(ticks < 0) { ticks = 0; } if(ticks <= 0) { ticks = 1; }
if(m_walkEvent) { if(m_walkEvent) {
m_walkEvent->cancel(); m_walkEvent->cancel();
@@ -697,15 +717,16 @@ void Game::autoWalk(std::vector<Otc::Direction> dirs)
auto it = dirs.begin(); auto it = dirs.begin();
Otc::Direction direction = *it; Otc::Direction direction = *it;
if(m_localPlayer->canWalk(direction)) { if(!m_localPlayer->canWalk(direction))
TilePtr toTile = g_map.getTile(m_localPlayer->getPosition().translatedToDirection(direction)); return;
if(toTile && toTile->isWalkable() && !m_localPlayer->isServerWalking()) {
m_localPlayer->preWalk(direction);
if(getFeature(Otc::GameForceFirstAutoWalkStep)) { TilePtr toTile = g_map.getTile(m_localPlayer->getPosition().translatedToDirection(direction));
forceWalk(direction); if(toTile && toTile->isWalkable() && !m_localPlayer->isServerWalking()) {
dirs.erase(it); m_localPlayer->preWalk(direction);
}
if(getFeature(Otc::GameForceFirstAutoWalkStep)) {
forceWalk(direction);
dirs.erase(it);
} }
} }
@@ -1129,7 +1150,7 @@ void Game::removeVip(int playerId)
{ {
if(!canPerformGameAction()) if(!canPerformGameAction())
return; return;
auto it = m_vips.find(playerId); auto it = m_vips.find(playerId);
if(it == m_vips.end()) if(it == m_vips.end())
return; return;
@@ -1379,7 +1400,7 @@ void Game::setProtocolVersion(int version)
if(isOnline()) if(isOnline())
stdext::throw_exception("Unable to change protocol version while online"); stdext::throw_exception("Unable to change protocol version while online");
if(version != 0 && (version < 810 || version > 974)) if(version != 0 && (version < 810 || version > 1010))
stdext::throw_exception(stdext::format("Protocol version %d not supported", version)); stdext::throw_exception(stdext::format("Protocol version %d not supported", version));
m_features.reset(); m_features.reset();
@@ -1443,6 +1464,14 @@ void Game::setProtocolVersion(int version)
enableFeature(Otc::GameNewSpeedLaw); enableFeature(Otc::GameNewSpeedLaw);
} }
if(version >= 976) {
enableFeature(Otc::GameContainerPagination);
}
if(version >= 979) {
enableFeature(Otc::GameThingMarks);
}
m_protocolVersion = version; m_protocolVersion = version;
Proto::buildMessageModesMap(version); Proto::buildMessageModesMap(version);
@@ -1458,7 +1487,7 @@ void Game::setClientVersion(int version)
if(isOnline()) if(isOnline())
stdext::throw_exception("Unable to change client version while online"); stdext::throw_exception("Unable to change client version while online");
if(version != 0 && (version < 810 || version > 982)) if(version != 0 && (version < 810 || version > 1010))
stdext::throw_exception(stdext::format("Client version %d not supported", version)); stdext::throw_exception(stdext::format("Client version %d not supported", version));
m_clientVersion = version; m_clientVersion = version;

View File

@@ -231,7 +231,7 @@ public:
void openRuleViolation(const std::string& reporter); void openRuleViolation(const std::string& reporter);
void closeRuleViolation(const std::string& reporter); void closeRuleViolation(const std::string& reporter);
void cancelRuleViolation(); void cancelRuleViolation();
// reports // reports
void reportBug(const std::string& comment); void reportBug(const std::string& comment);
void reportRuleViolation(const std::string& target, int reason, int action, const std::string& comment, const std::string& statement, int statementId, bool ipBanishment); void reportRuleViolation(const std::string& target, int reason, int action, const std::string& comment, const std::string& statement, int statementId, bool ipBanishment);
@@ -281,6 +281,7 @@ public:
bool isDead() { return m_dead; } bool isDead() { return m_dead; }
bool isAttacking() { return !!m_attackingCreature; } bool isAttacking() { return !!m_attackingCreature; }
bool isFollowing() { return !!m_followingCreature; } bool isFollowing() { return !!m_followingCreature; }
bool isConnectionOk() { return m_protocolGame && m_protocolGame->getElapsedTicksSinceLastRead() < 5000; }
int getPing() { return m_ping >= 0 ? std::max(m_ping, m_pingTimer.elapsed_millis()) : -1; } int getPing() { return m_ping >= 0 ? std::max(m_ping, m_pingTimer.elapsed_millis()) : -1; }
ContainerPtr getContainer(int index) { return m_containers[index]; } ContainerPtr getContainer(int index) { return m_containers[index]; }
@@ -340,6 +341,8 @@ private:
std::bitset<Otc::LastGameFeature> m_features; std::bitset<Otc::LastGameFeature> m_features;
ScheduledEventPtr m_pingEvent; ScheduledEventPtr m_pingEvent;
ScheduledEventPtr m_walkEvent; ScheduledEventPtr m_walkEvent;
ScheduledEventPtr m_checkConnectionEvent;
bool m_connectionFailWarned;
int m_protocolVersion; int m_protocolVersion;
int m_clientVersion; int m_clientVersion;
std::string m_clientSignature; std::string m_clientSignature;

View File

@@ -72,7 +72,7 @@ bool LocalPlayer::canWalk(Otc::Direction direction)
return false; return false;
// last walk is not done yet // last walk is not done yet
if(m_walkTimer.ticksElapsed() < getStepDuration()) if((m_walkTimer.ticksElapsed() < getStepDuration()) && !isAutoWalking())
return false; return false;
// prewalk has a timeout, because for some reason that I don't know yet the server sometimes doesn't answer the prewalk // prewalk has a timeout, because for some reason that I don't know yet the server sometimes doesn't answer the prewalk
@@ -83,7 +83,7 @@ bool LocalPlayer::canWalk(Otc::Direction direction)
return false; return false;
// cannot walk while already walking // cannot walk while already walking
if(m_walking && !prewalkTimeouted) if((m_walking && !isAutoWalking()) && (!prewalkTimeouted || m_secondPreWalk))
return false; return false;
return true; return true;
@@ -95,6 +95,7 @@ void LocalPlayer::walk(const Position& oldPos, const Position& newPos)
if(m_preWalking) { if(m_preWalking) {
// switch to normal walking // switch to normal walking
m_preWalking = false; m_preWalking = false;
m_secondPreWalk = false;
m_lastPrewalkDone = true; m_lastPrewalkDone = true;
// if is to the last prewalk destination, updates the walk preserving the animation // if is to the last prewalk destination, updates the walk preserving the animation
if(newPos == m_lastPrewalkDestination) { if(newPos == m_lastPrewalkDestination) {
@@ -118,7 +119,8 @@ void LocalPlayer::preWalk(Otc::Direction direction)
Position newPos = m_position.translatedToDirection(direction); Position newPos = m_position.translatedToDirection(direction);
// avoid reanimating prewalks // avoid reanimating prewalks
if(m_preWalking && m_lastPrewalkDestination == newPos) { if(m_preWalking) {
m_secondPreWalk = true;
return; return;
} }
@@ -277,6 +279,7 @@ void LocalPlayer::terminateWalk()
{ {
Creature::terminateWalk(); Creature::terminateWalk();
m_preWalking = false; m_preWalking = false;
m_secondPreWalk = false;
m_idleTimer.restart(); m_idleTimer.restart();
auto self = asLocalPlayer(); auto self = asLocalPlayer();

View File

@@ -126,6 +126,7 @@ private:
ticks_t m_walkLockExpiration; ticks_t m_walkLockExpiration;
stdext::boolean<false> m_preWalking; stdext::boolean<false> m_preWalking;
stdext::boolean<true> m_lastPrewalkDone; stdext::boolean<true> m_lastPrewalkDone;
stdext::boolean<false> m_secondPreWalk;
stdext::boolean<false> m_serverWalking; stdext::boolean<false> m_serverWalking;
stdext::boolean<false> m_knownCompletePath; stdext::boolean<false> m_knownCompletePath;

View File

@@ -230,6 +230,7 @@ void Client::registerLuaFunctions()
g_lua.bindSingletonFunction("g_game", "isDead", &Game::isDead, &g_game); g_lua.bindSingletonFunction("g_game", "isDead", &Game::isDead, &g_game);
g_lua.bindSingletonFunction("g_game", "isAttacking", &Game::isAttacking, &g_game); g_lua.bindSingletonFunction("g_game", "isAttacking", &Game::isAttacking, &g_game);
g_lua.bindSingletonFunction("g_game", "isFollowing", &Game::isFollowing, &g_game); g_lua.bindSingletonFunction("g_game", "isFollowing", &Game::isFollowing, &g_game);
g_lua.bindSingletonFunction("g_game", "isConnectionOk", &Game::isConnectionOk, &g_game);
g_lua.bindSingletonFunction("g_game", "getPing", &Game::getPing, &g_game); g_lua.bindSingletonFunction("g_game", "getPing", &Game::getPing, &g_game);
g_lua.bindSingletonFunction("g_game", "getContainer", &Game::getContainer, &g_game); g_lua.bindSingletonFunction("g_game", "getContainer", &Game::getContainer, &g_game);
g_lua.bindSingletonFunction("g_game", "getContainers", &Game::getContainers, &g_game); g_lua.bindSingletonFunction("g_game", "getContainers", &Game::getContainers, &g_game);

View File

@@ -608,21 +608,21 @@ int MapView::calcFirstVisibleFloor()
Position pos = cameraPosition.translated(ix, iy); Position pos = cameraPosition.translated(ix, iy);
// process tiles that we can look through, e.g. windows, doors // process tiles that we can look through, e.g. windows, doors
if((ix == 0 && iy == 0) || (/*(std::abs(ix) != std::abs(iy)) && */g_map.isLookPossible(pos))) { if((ix == 0 && iy == 0) || ((std::abs(ix) != std::abs(iy)) && g_map.isLookPossible(pos))) {
Position upperPos = pos; Position upperPos = pos;
Position coveredPos = pos; Position coveredPos = pos;
while(coveredPos.coveredUp() && upperPos.up() && upperPos.z >= firstFloor) { while(coveredPos.coveredUp() && upperPos.up() && upperPos.z >= firstFloor) {
// check tiles physically above // check tiles physically above
TilePtr tile = g_map.getTile(upperPos); TilePtr tile = g_map.getTile(upperPos);
if(tile && tile->limitsFloorsView()) { if(tile && tile->limitsFloorsView(!g_map.isLookPossible(pos))) {
firstFloor = upperPos.z + 1; firstFloor = upperPos.z + 1;
break; break;
} }
// check tiles geometrically above // check tiles geometrically above
tile = g_map.getTile(coveredPos); tile = g_map.getTile(coveredPos);
if(tile && tile->limitsFloorsView()) { if(tile && tile->limitsFloorsView(g_map.isLookPossible(pos))) {
firstFloor = coveredPos.z + 1; firstFloor = coveredPos.z + 1;
break; break;
} }

View File

@@ -603,6 +603,14 @@ void ProtocolGame::parseOpenContainer(const InputMessagePtr& msg)
std::string name = msg->getString(); std::string name = msg->getString();
int capacity = msg->getU8(); int capacity = msg->getU8();
bool hasParent = (msg->getU8() != 0); bool hasParent = (msg->getU8() != 0);
if(g_game.getFeature(Otc::GameContainerPagination)) {
msg->getU8(); // drag and drop
msg->getU8(); // pagination
msg->getU16(); // container size
msg->getU16(); // first index
}
int itemCount = msg->getU8(); int itemCount = msg->getU8();
std::vector<ItemPtr> items(itemCount); std::vector<ItemPtr> items(itemCount);
@@ -622,13 +630,21 @@ void ProtocolGame::parseContainerAddItem(const InputMessagePtr& msg)
{ {
int containerId = msg->getU8(); int containerId = msg->getU8();
ItemPtr item = getItem(msg); ItemPtr item = getItem(msg);
if(g_game.getFeature(Otc::GameContainerPagination)) {
msg->getU16(); // slot
}
g_game.processContainerAddItem(containerId, item); g_game.processContainerAddItem(containerId, item);
} }
void ProtocolGame::parseContainerUpdateItem(const InputMessagePtr& msg) void ProtocolGame::parseContainerUpdateItem(const InputMessagePtr& msg)
{ {
int containerId = msg->getU8(); int containerId = msg->getU8();
int slot = msg->getU8(); int slot;
if(g_game.getFeature(Otc::GameContainerPagination)) {
slot = msg->getU16();
} else {
slot = msg->getU8();
}
ItemPtr item = getItem(msg); ItemPtr item = getItem(msg);
g_game.processContainerUpdateItem(containerId, slot, item); g_game.processContainerUpdateItem(containerId, slot, item);
} }
@@ -636,7 +652,13 @@ void ProtocolGame::parseContainerUpdateItem(const InputMessagePtr& msg)
void ProtocolGame::parseContainerRemoveItem(const InputMessagePtr& msg) void ProtocolGame::parseContainerRemoveItem(const InputMessagePtr& msg)
{ {
int containerId = msg->getU8(); int containerId = msg->getU8();
int slot = msg->getU8(); int slot;
if(g_game.getFeature(Otc::GameContainerPagination)) {
slot = msg->getU16();
getItem(msg);
} else {
slot = msg->getU8();
}
g_game.processContainerRemoveItem(containerId, slot); g_game.processContainerRemoveItem(containerId, slot);
} }
@@ -1808,6 +1830,12 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type)
if(g_game.getFeature(Otc::GameCreatureEmblems) && !known) if(g_game.getFeature(Otc::GameCreatureEmblems) && !known)
emblem = msg->getU8(); emblem = msg->getU8();
if(g_game.getFeature(Otc::GameThingMarks)) {
msg->getU8(); // creature type for summons
msg->getU8(); // mark
msg->getU16(); // helpers
}
if(g_game.getProtocolVersion() >= 854) if(g_game.getProtocolVersion() >= 854)
unpass = msg->getU8(); unpass = msg->getU8();
@@ -1860,6 +1888,10 @@ ItemPtr ProtocolGame::getItem(const InputMessagePtr& msg, int id)
if(item->getId() == 0) if(item->getId() == 0)
stdext::throw_exception(stdext::format("unable to create item with invalid id %d", id)); stdext::throw_exception(stdext::format("unable to create item with invalid id %d", id));
if(g_game.getFeature(Otc::GameThingMarks)) {
msg->getU8(); // mark
}
if(item->isStackable() || item->isFluidContainer() || item->isSplash() || item->isChargeable()) if(item->isStackable() || item->isFluidContainer() || item->isSplash() || item->isChargeable())
item->setCountOrSubType(msg->getU8()); item->setCountOrSubType(msg->getU8());

View File

@@ -67,6 +67,17 @@ void ThingType::unserialize(uint16 clientId, ThingCategory category, const FileS
attr -= 1; attr -= 1;
} }
if(g_game.getProtocolVersion() >= 1010) {
/* In 10.10 all attributes from 16 and up were
* incremented by 1 to make space for 16 as
* "No Movement Animation" flag.
*/
if(attr == 16)
attr = ThingAttrNoMoveAnimation;
else if(attr > 16)
attr -= 1;
}
switch(attr) { switch(attr) {
case ThingAttrDisplacement: { case ThingAttrDisplacement: {
m_displacement.x = fin->getU16(); m_displacement.x = fin->getU16();
@@ -126,8 +137,8 @@ void ThingType::unserialize(uint16 clientId, ThingCategory category, const FileS
int totalSprites = m_size.area() * m_layers * m_numPatternX * m_numPatternY * m_numPatternZ * m_animationPhases; int totalSprites = m_size.area() * m_layers * m_numPatternX * m_numPatternY * m_numPatternZ * m_animationPhases;
if(totalSprites == 0) // if(totalSprites == 0)
stdext::throw_exception("a thing type has no sprites"); // stdext::throw_exception("a thing type has no sprites");
if(totalSprites > 4096) if(totalSprites > 4096)
stdext::throw_exception("a thing type has more than 4096 sprites"); stdext::throw_exception("a thing type has more than 4096 sprites");

View File

@@ -81,6 +81,7 @@ enum ThingAttr : uint8 {
ThingAttrOpacity = 100, ThingAttrOpacity = 100,
ThingAttrNotPreWalkable = 101, ThingAttrNotPreWalkable = 101,
ThingAttrNoMoveAnimation = 253, // real value is 16, but we need to do this for backwards compatibility
ThingAttrChargeable = 254, // deprecated ThingAttrChargeable = 254, // deprecated
ThingLastAttr = 255 ThingLastAttr = 255
}; };

View File

@@ -457,7 +457,7 @@ ThingPtr Tile::getTopMultiUseThing()
if(thing->isForceUse()) if(thing->isForceUse())
return thing; return thing;
} }
for(uint i = 0; i < m_things.size(); ++i) { for(uint i = 0; i < m_things.size(); ++i) {
ThingPtr thing = m_things[i]; ThingPtr thing = m_things[i];
if(!thing->isGround() && !thing->isGroundBorder() && !thing->isOnBottom() && !thing->isOnTop()) { if(!thing->isGround() && !thing->isGroundBorder() && !thing->isOnBottom() && !thing->isOnTop()) {
@@ -590,15 +590,24 @@ bool Tile::hasCreature()
return false; return false;
} }
bool Tile::limitsFloorsView() bool Tile::limitsFloorsView(bool isFreeView)
{ {
// ground and walls limits the view // ground and walls limits the view
ThingPtr firstThing = getThing(0); ThingPtr firstThing = getThing(0);
if(firstThing && !firstThing->isDontHide() && (firstThing->isGround() || firstThing->isOnBottom()))
return true; if(isFreeView){
if(firstThing && !firstThing->isDontHide() && (firstThing->isGround() || firstThing->isOnBottom()))
return true;
}
else
{
if(firstThing && !firstThing->isDontHide() && (firstThing->isGround() || (firstThing->isOnBottom() && firstThing->blockProjectile())))
return true;
}
return false; return false;
} }
bool Tile::canErase() bool Tile::canErase()
{ {
return m_walkingCreatures.empty() && m_effects.empty() && m_things.empty() && m_flags == 0 && m_minimapColor == 0; return m_walkingCreatures.empty() && m_effects.empty() && m_things.empty() && m_flags == 0 && m_minimapColor == 0;

View File

@@ -48,7 +48,7 @@ enum tileflags_t
TILESTATE_TRASHHOLDER = 1 << 20, TILESTATE_TRASHHOLDER = 1 << 20,
TILESTATE_BED = 1 << 21, TILESTATE_BED = 1 << 21,
TILESTATE_DEPOT = 1 << 22, TILESTATE_DEPOT = 1 << 22,
TILESTATE_TRANSLUECENT_LIGHT = 1 << 23 TILESTATE_TRANSLUECENT_LIGHT = 1 << 23
}; };
class Tile : public LuaObject class Tile : public LuaObject
@@ -105,7 +105,7 @@ public:
bool mustHookSouth(); bool mustHookSouth();
bool mustHookEast(); bool mustHookEast();
bool hasCreature(); bool hasCreature();
bool limitsFloorsView(); bool limitsFloorsView(bool isFreeView = false);
bool canErase(); bool canErase();
bool hasElevation(int elevation = 1); bool hasElevation(int elevation = 1);
void overwriteMinimapColor(uint8 color) { m_minimapColor = color; } void overwriteMinimapColor(uint8 color) { m_minimapColor = color; }

View File

@@ -28,6 +28,7 @@
#ifdef FW_GRAPHICS #ifdef FW_GRAPHICS
#include <framework/platform/platformwindow.h> #include <framework/platform/platformwindow.h>
#include <framework/platform/platform.h>
#include <framework/luaengine/luainterface.h> #include <framework/luaengine/luainterface.h>
#endif #endif
@@ -104,16 +105,16 @@ void Logger::logFunc(Fw::LogLevel level, const std::string& message, std::string
prettyFunction = prettyFunction.substr(prettyFunction.find_last_of(' ') + 1); prettyFunction = prettyFunction.substr(prettyFunction.find_last_of(' ') + 1);
std::string out = message; std::stringstream ss;
ss << message;
if(!prettyFunction.empty()) { if(!prettyFunction.empty()) {
if(g_lua.isInCppCallback()) if(g_lua.isInCppCallback())
out = g_lua.traceback(out, 1); ss << g_lua.traceback("", 1);
else ss << g_platform.traceback(prettyFunction, 1, 8);
out += "\nat:\t[C++]: " + prettyFunction;
} }
log(level, out); log(level, ss.str());
} }
void Logger::fireOldMessages() void Logger::fireOldMessages()

View File

@@ -577,20 +577,19 @@ int LuaInterface::lua_dofile(lua_State* L)
int LuaInterface::lua_dofiles(lua_State* L) int LuaInterface::lua_dofiles(lua_State* L)
{ {
std::string directory = g_lua.popString(); std::string contains = "";
if(g_lua.getTop() > 2) {
for(const std::string& fileName : g_resources.listDirectoryFiles(directory)) { contains = g_lua.popString();
if(!g_resources.isFileType(fileName, "lua"))
continue;
try {
g_lua.loadScript(directory + "/" + fileName);
g_lua.call(0, 0);
} catch(stdext::exception& e) {
g_lua.pushString(e.what());
g_lua.error();
}
} }
bool recursive = false;
if(g_lua.getTop() > 1) {
recursive = g_lua.popBoolean();
}
std::string directory = g_lua.popString();
g_lua.loadFiles(directory, recursive, contains);
return 0; return 0;
} }
@@ -1247,3 +1246,29 @@ int LuaInterface::getTop()
{ {
return lua_gettop(L); return lua_gettop(L);
} }
void LuaInterface::loadFiles(std::string directory, bool recursive, std::string contains)
{
for(const std::string& fileName : g_resources.listDirectoryFiles(directory)) {
std::string fullPath = directory + "/" + fileName;
if(recursive && g_resources.directoryExists(fullPath)) {
loadFiles(fullPath, true, contains);
continue;
}
if(!g_resources.isFileType(fileName, "lua"))
continue;
if(!contains.empty() && fileName.find(contains) == std::string::npos)
continue;
try {
g_lua.loadScript(fullPath);
g_lua.call(0, 0);
} catch(stdext::exception& e) {
g_lua.pushString(e.what());
g_lua.error();
}
}
}

View File

@@ -321,6 +321,8 @@ public:
void clearStack() { pop(stackSize()); } void clearStack() { pop(stackSize()); }
bool hasIndex(int index) { return (stackSize() >= (index < 0 ? -index : index) && index != 0); } bool hasIndex(int index) { return (stackSize() >= (index < 0 ? -index : index) && index != 0); }
void loadFiles(std::string directory, bool recursive = false, std::string contains = "");
/// Pushes any type onto the stack /// Pushes any type onto the stack
template<typename T, typename... Args> template<typename T, typename... Args>
int polymorphicPush(const T& v, const Args&... args); int polymorphicPush(const T& v, const Args&... args);

View File

@@ -569,6 +569,7 @@ void Application::registerLuaFunctions()
g_lua.bindClassMemberFunction<UIWidget>("setImageFixedRatio", &UIWidget::setImageFixedRatio); g_lua.bindClassMemberFunction<UIWidget>("setImageFixedRatio", &UIWidget::setImageFixedRatio);
g_lua.bindClassMemberFunction<UIWidget>("setImageRepeated", &UIWidget::setImageRepeated); g_lua.bindClassMemberFunction<UIWidget>("setImageRepeated", &UIWidget::setImageRepeated);
g_lua.bindClassMemberFunction<UIWidget>("setImageSmooth", &UIWidget::setImageSmooth); g_lua.bindClassMemberFunction<UIWidget>("setImageSmooth", &UIWidget::setImageSmooth);
g_lua.bindClassMemberFunction<UIWidget>("setImageAutoResize", &UIWidget::setImageAutoResize);
g_lua.bindClassMemberFunction<UIWidget>("setImageBorderTop", &UIWidget::setImageBorderTop); g_lua.bindClassMemberFunction<UIWidget>("setImageBorderTop", &UIWidget::setImageBorderTop);
g_lua.bindClassMemberFunction<UIWidget>("setImageBorderRight", &UIWidget::setImageBorderRight); g_lua.bindClassMemberFunction<UIWidget>("setImageBorderRight", &UIWidget::setImageBorderRight);
g_lua.bindClassMemberFunction<UIWidget>("setImageBorderBottom", &UIWidget::setImageBorderBottom); g_lua.bindClassMemberFunction<UIWidget>("setImageBorderBottom", &UIWidget::setImageBorderBottom);
@@ -585,6 +586,7 @@ void Application::registerLuaFunctions()
g_lua.bindClassMemberFunction<UIWidget>("getImageColor", &UIWidget::getImageColor); g_lua.bindClassMemberFunction<UIWidget>("getImageColor", &UIWidget::getImageColor);
g_lua.bindClassMemberFunction<UIWidget>("isImageFixedRatio", &UIWidget::isImageFixedRatio); g_lua.bindClassMemberFunction<UIWidget>("isImageFixedRatio", &UIWidget::isImageFixedRatio);
g_lua.bindClassMemberFunction<UIWidget>("isImageSmooth", &UIWidget::isImageSmooth); g_lua.bindClassMemberFunction<UIWidget>("isImageSmooth", &UIWidget::isImageSmooth);
g_lua.bindClassMemberFunction<UIWidget>("isImageAutoResize", &UIWidget::isImageAutoResize);
g_lua.bindClassMemberFunction<UIWidget>("getImageBorderTop", &UIWidget::getImageBorderTop); g_lua.bindClassMemberFunction<UIWidget>("getImageBorderTop", &UIWidget::getImageBorderTop);
g_lua.bindClassMemberFunction<UIWidget>("getImageBorderRight", &UIWidget::getImageBorderRight); g_lua.bindClassMemberFunction<UIWidget>("getImageBorderRight", &UIWidget::getImageBorderRight);
g_lua.bindClassMemberFunction<UIWidget>("getImageBorderBottom", &UIWidget::getImageBorderBottom); g_lua.bindClassMemberFunction<UIWidget>("getImageBorderBottom", &UIWidget::getImageBorderBottom);

View File

@@ -27,16 +27,17 @@
#include <boost/asio.hpp> #include <boost/asio.hpp>
asio::io_service g_ioService; asio::io_service g_ioService;
std::list<std::shared_ptr<asio::streambuf>> Connection::m_outputStreams;
Connection::Connection() : Connection::Connection() :
m_readTimer(g_ioService), m_readTimer(g_ioService),
m_writeTimer(g_ioService), m_writeTimer(g_ioService),
m_delayedWriteTimer(g_ioService),
m_resolver(g_ioService), m_resolver(g_ioService),
m_socket(g_ioService) m_socket(g_ioService)
{ {
m_connected = false; m_connected = false;
m_connecting = false; m_connecting = false;
m_sendBufferSize = 0;
} }
Connection::~Connection() Connection::~Connection()
@@ -56,34 +57,7 @@ void Connection::poll()
void Connection::terminate() void Connection::terminate()
{ {
g_ioService.stop(); g_ioService.stop();
} m_outputStreams.clear();
void Connection::connect(const std::string& host, uint16 port, const std::function<void()>& connectCallback)
{
m_connected = false;
m_connecting = true;
m_error.clear();
m_connectCallback = connectCallback;
asio::ip::tcp::resolver::query query(host, stdext::unsafe_cast<std::string>(port));
auto self = asConnection();
m_resolver.async_resolve(query, [=](const boost::system::error_code& error, asio::ip::tcp::resolver::iterator endpointIterator) {
if(self.is_unique())
return;
m_readTimer.cancel();
if(!error) {
m_socket.async_connect(*endpointIterator, std::bind(&Connection::onConnect, asConnection(), std::placeholders::_1));
m_readTimer.expires_from_now(boost::posix_time::seconds(READ_TIMEOUT));
m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
} else
handleError(error);
});
m_readTimer.expires_from_now(boost::posix_time::seconds(READ_TIMEOUT));
m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
} }
void Connection::close() void Connection::close()
@@ -92,8 +66,8 @@ void Connection::close()
return; return;
// flush send data before disconnecting on clean connections // flush send data before disconnecting on clean connections
if(m_connected && !m_error && m_sendBufferSize > 0 && m_sendEvent) if(m_connected && !m_error && m_outputStream)
m_sendEvent->execute(); internal_write();
m_connecting = false; m_connecting = false;
m_connected = false; m_connected = false;
@@ -104,6 +78,7 @@ void Connection::close()
m_resolver.cancel(); m_resolver.cancel();
m_readTimer.cancel(); m_readTimer.cancel();
m_writeTimer.cancel(); m_writeTimer.cancel();
m_delayedWriteTimer.cancel();
if(m_socket.is_open()) { if(m_socket.is_open()) {
boost::system::error_code ec; boost::system::error_code ec;
@@ -112,94 +87,138 @@ void Connection::close()
} }
} }
void Connection::write(uint8* buffer, uint16 size) void Connection::connect(const std::string& host, uint16 port, const std::function<void()>& connectCallback)
{
m_connected = false;
m_connecting = true;
m_error.clear();
m_connectCallback = connectCallback;
asio::ip::tcp::resolver::query query(host, stdext::unsafe_cast<std::string>(port));
m_resolver.async_resolve(query, std::bind(&Connection::onResolve, asConnection(), std::placeholders::_1, std::placeholders::_2));
m_readTimer.cancel();
m_readTimer.expires_from_now(boost::posix_time::seconds(READ_TIMEOUT));
m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
}
void Connection::internal_connect(asio::ip::basic_resolver<asio::ip::tcp>::iterator endpointIterator)
{
m_socket.async_connect(*endpointIterator, std::bind(&Connection::onConnect, asConnection(), std::placeholders::_1));
m_readTimer.cancel();
m_readTimer.expires_from_now(boost::posix_time::seconds(READ_TIMEOUT));
m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
}
void Connection::write(uint8* buffer, size_t size)
{ {
if(!m_connected) if(!m_connected)
return; return;
// send old buffer if we can't add more data
if(m_sendBufferSize + size >= SEND_BUFFER_SIZE && m_sendEvent)
m_sendEvent->execute();
// we can't send the data right away, otherwise we could create tcp congestion // we can't send the data right away, otherwise we could create tcp congestion
memcpy(m_sendBuffer + m_sendBufferSize, buffer, size); if(!m_outputStream) {
m_sendBufferSize += size; if(!m_outputStreams.empty()) {
m_outputStream = m_outputStreams.front();
m_outputStreams.pop_front();
} else
m_outputStream = std::shared_ptr<asio::streambuf>(new asio::streambuf);
if(!m_sendEvent || m_sendEvent->isExecuted() || m_sendEvent->isCanceled()) { m_delayedWriteTimer.cancel();
auto self = asConnection(); m_delayedWriteTimer.expires_from_now(boost::posix_time::milliseconds(10));
m_delayedWriteTimer.async_wait(std::bind(&Connection::onCanWrite, asConnection(), std::placeholders::_1));
// wait 1 ms to do the real send
m_sendEvent = g_dispatcher.scheduleEvent([=] {
if(self.is_unique())
return;
//m_writeTimer.cancel();
asio::async_write(m_socket,
asio::buffer(m_sendBuffer, m_sendBufferSize),
std::bind(&Connection::onWrite, asConnection(), std::placeholders::_1, std::placeholders::_2));
m_writeTimer.expires_from_now(boost::posix_time::seconds(WRITE_TIMEOUT));
m_writeTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
m_sendBufferSize = 0;
}, SEND_INTERVAL);
} }
std::ostream os(m_outputStream.get());
os.write((const char*)buffer, size);
os.flush();
}
void Connection::internal_write()
{
if(!m_connected)
return;
std::shared_ptr<asio::streambuf> outputStream = m_outputStream;
m_outputStream = nullptr;
asio::async_write(m_socket,
*outputStream,
std::bind(&Connection::onWrite, asConnection(), std::placeholders::_1, std::placeholders::_2, outputStream));
m_writeTimer.cancel();
m_writeTimer.expires_from_now(boost::posix_time::seconds(WRITE_TIMEOUT));
m_writeTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
} }
void Connection::read(uint16 bytes, const RecvCallback& callback) void Connection::read(uint16 bytes, const RecvCallback& callback)
{ {
m_readTimer.cancel();
if(!m_connected) if(!m_connected)
return; return;
m_recvCallback = callback; m_recvCallback = callback;
asio::async_read(m_socket, asio::async_read(m_socket,
asio::buffer(m_streamBuffer.prepare(bytes)), asio::buffer(m_inputStream.prepare(bytes)),
std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, bytes)); std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2));
m_readTimer.cancel();
m_readTimer.expires_from_now(boost::posix_time::seconds(READ_TIMEOUT)); m_readTimer.expires_from_now(boost::posix_time::seconds(READ_TIMEOUT));
m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1)); m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
} }
void Connection::read_until(const std::string& what, const RecvCallback& callback) void Connection::read_until(const std::string& what, const RecvCallback& callback)
{ {
m_readTimer.cancel();
if(!m_connected) if(!m_connected)
return; return;
m_recvCallback = callback; m_recvCallback = callback;
asio::async_read_until(m_socket, asio::async_read_until(m_socket,
m_streamBuffer, m_inputStream,
what.c_str(), what.c_str(),
std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2)); std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2));
m_readTimer.cancel();
m_readTimer.expires_from_now(boost::posix_time::seconds(READ_TIMEOUT)); m_readTimer.expires_from_now(boost::posix_time::seconds(READ_TIMEOUT));
m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1)); m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
} }
void Connection::read_some(const RecvCallback& callback) void Connection::read_some(const RecvCallback& callback)
{ {
m_readTimer.cancel();
if(!m_connected) if(!m_connected)
return; return;
m_recvCallback = callback; m_recvCallback = callback;
m_socket.async_read_some(asio::buffer(m_streamBuffer.prepare(RECV_BUFFER_SIZE)), m_socket.async_read_some(asio::buffer(m_inputStream.prepare(RECV_BUFFER_SIZE)),
std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2)); std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2));
m_readTimer.cancel();
m_readTimer.expires_from_now(boost::posix_time::seconds(READ_TIMEOUT)); m_readTimer.expires_from_now(boost::posix_time::seconds(READ_TIMEOUT));
m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1)); m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
} }
void Connection::onResolve(const boost::system::error_code& error, asio::ip::basic_resolver<asio::ip::tcp>::iterator endpointIterator)
{
m_readTimer.cancel();
if(error == asio::error::operation_aborted)
return;
if(!error)
internal_connect(endpointIterator);
else
handleError(error);
}
void Connection::onConnect(const boost::system::error_code& error) void Connection::onConnect(const boost::system::error_code& error)
{ {
m_readTimer.cancel(); m_readTimer.cancel();
m_activityTimer.restart();
if(error == asio::error::operation_aborted)
return;
if(!error) { if(!error) {
m_connected = true; m_connected = true;
@@ -216,51 +235,72 @@ void Connection::onConnect(const boost::system::error_code& error)
m_connecting = false; m_connecting = false;
} }
void Connection::onWrite(const boost::system::error_code& error, size_t) void Connection::onCanWrite(const boost::system::error_code& error)
{
m_delayedWriteTimer.cancel();
if(error == asio::error::operation_aborted)
return;
if(m_connected)
internal_write();
}
void Connection::onWrite(const boost::system::error_code& error, size_t writeSize, std::shared_ptr<asio::streambuf> outputStream)
{ {
m_writeTimer.cancel(); m_writeTimer.cancel();
if(!m_connected) if(error == asio::error::operation_aborted)
return; return;
if(error) // free output stream and store for using it again later
outputStream->consume(outputStream->size());
m_outputStreams.push_back(outputStream);
if(m_connected && error)
handleError(error); handleError(error);
} }
void Connection::onRecv(const boost::system::error_code& error, size_t recvSize) void Connection::onRecv(const boost::system::error_code& error, size_t recvSize)
{ {
m_readTimer.cancel(); m_readTimer.cancel();
m_activityTimer.restart();
if(!m_connected) if(error == asio::error::operation_aborted)
return; return;
if(!error) { if(m_connected) {
if(m_recvCallback) { if(!error) {
const char* header = boost::asio::buffer_cast<const char*>(m_streamBuffer.data()); if(m_recvCallback) {
m_recvCallback((uint8*)header, recvSize); const char* header = boost::asio::buffer_cast<const char*>(m_inputStream.data());
} m_recvCallback((uint8*)header, recvSize);
}
} else
handleError(error);
} }
else
handleError(error);
m_streamBuffer.consume(recvSize); if(!error)
m_inputStream.consume(recvSize);
} }
void Connection::onTimeout(const boost::system::error_code& error) void Connection::onTimeout(const boost::system::error_code& error)
{ {
if(error != asio::error::operation_aborted) if(error == asio::error::operation_aborted)
handleError(asio::error::timed_out); return;
handleError(asio::error::timed_out);
} }
void Connection::handleError(const boost::system::error_code& error) void Connection::handleError(const boost::system::error_code& error)
{ {
if(error != asio::error::operation_aborted) { if(error == asio::error::operation_aborted)
m_error = error; return;
if(m_errorCallback)
m_errorCallback(error); m_error = error;
if(m_connected || m_connecting) if(m_errorCallback)
close(); m_errorCallback(error);
} if(m_connected || m_connecting)
close();
} }
int Connection::getIp() int Connection::getIp()

View File

@@ -36,7 +36,6 @@ class Connection : public LuaObject
enum { enum {
READ_TIMEOUT = 30, READ_TIMEOUT = 30,
WRITE_TIMEOUT = 30, WRITE_TIMEOUT = 30,
SEND_INTERVAL = 1,
SEND_BUFFER_SIZE = 65536, SEND_BUFFER_SIZE = 65536,
RECV_BUFFER_SIZE = 65536 RECV_BUFFER_SIZE = 65536
}; };
@@ -51,7 +50,7 @@ public:
void connect(const std::string& host, uint16 port, const std::function<void()>& connectCallback); void connect(const std::string& host, uint16 port, const std::function<void()>& connectCallback);
void close(); void close();
void write(uint8* buffer, uint16 size); void write(uint8* buffer, size_t size);
void read(uint16 bytes, const RecvCallback& callback); void read(uint16 bytes, const RecvCallback& callback);
void read_until(const std::string& what, const RecvCallback& callback); void read_until(const std::string& what, const RecvCallback& callback);
void read_some(const RecvCallback& callback); void read_some(const RecvCallback& callback);
@@ -62,11 +61,17 @@ public:
boost::system::error_code getError() { return m_error; } boost::system::error_code getError() { return m_error; }
bool isConnecting() { return m_connecting; } bool isConnecting() { return m_connecting; }
bool isConnected() { return m_connected; } bool isConnected() { return m_connected; }
ticks_t getElapsedTicksSinceLastRead() { return m_connected ? m_activityTimer.elapsed_millis() : -1; }
ConnectionPtr asConnection() { return static_self_cast<Connection>(); } ConnectionPtr asConnection() { return static_self_cast<Connection>(); }
protected: protected:
void internal_connect(asio::ip::basic_resolver<asio::ip::tcp>::iterator endpointIterator);
void internal_write();
void onResolve(const boost::system::error_code& error, asio::ip::tcp::resolver::iterator endpointIterator);
void onConnect(const boost::system::error_code& error); void onConnect(const boost::system::error_code& error);
void onWrite(const boost::system::error_code& error, size_t); void onCanWrite(const boost::system::error_code& error);
void onWrite(const boost::system::error_code& error, size_t writeSize, std::shared_ptr<asio::streambuf> outputStream);
void onRecv(const boost::system::error_code& error, size_t recvSize); void onRecv(const boost::system::error_code& error, size_t recvSize);
void onTimeout(const boost::system::error_code& error); void onTimeout(const boost::system::error_code& error);
void handleError(const boost::system::error_code& error); void handleError(const boost::system::error_code& error);
@@ -77,17 +82,17 @@ protected:
asio::deadline_timer m_readTimer; asio::deadline_timer m_readTimer;
asio::deadline_timer m_writeTimer; asio::deadline_timer m_writeTimer;
asio::deadline_timer m_delayedWriteTimer;
asio::ip::tcp::resolver m_resolver; asio::ip::tcp::resolver m_resolver;
asio::ip::tcp::socket m_socket; asio::ip::tcp::socket m_socket;
uint8 m_sendBuffer[SEND_BUFFER_SIZE]; static std::list<std::shared_ptr<asio::streambuf>> m_outputStreams;
asio::streambuf m_streamBuffer; std::shared_ptr<asio::streambuf> m_outputStream;
asio::streambuf m_inputStream;
bool m_connected; bool m_connected;
bool m_connecting; bool m_connecting;
boost::system::error_code m_error; boost::system::error_code m_error;
int m_sendBufferSize; stdext::timer m_activityTimer;
Timer m_sendTimer;
ScheduledEventPtr m_sendEvent;
friend class Server; friend class Server;
}; };

View File

@@ -42,6 +42,8 @@ public:
bool isConnected(); bool isConnected();
bool isConnecting(); bool isConnecting();
ticks_t getElapsedTicksSinceLastRead() { return m_connection ? m_connection->getElapsedTicksSinceLastRead() : -1; }
ConnectionPtr getConnection() { return m_connection; } ConnectionPtr getConnection() { return m_connection; }
void setConnection(const ConnectionPtr& connection) { m_connection = connection; } void setConnection(const ConnectionPtr& connection) { m_connection = connection; }

View File

@@ -45,6 +45,7 @@ public:
std::string getCPUName(); std::string getCPUName();
double getTotalSystemMemory(); double getTotalSystemMemory();
std::string getOSName(); std::string getOSName();
std::string traceback(const std::string& where, int level = 1, int maxDepth = 32);
}; };
extern Platform g_platform; extern Platform g_platform;

View File

@@ -29,6 +29,7 @@
#include <framework/stdext/stdext.h> #include <framework/stdext/stdext.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <execinfo.h>
void Platform::processArgs(std::vector<std::string>& args) void Platform::processArgs(std::vector<std::string>& args)
{ {
@@ -168,5 +169,35 @@ std::string Platform::getOSName()
return std::string(); return std::string();
} }
std::string Platform::traceback(const std::string& where, int level, int maxDepth)
{
std::stringstream ss;
ss << "\nC++ stack traceback:";
if(!where.empty())
ss << "\n\t[C++]: " << where;
void* buffer[maxDepth + level + 1];
int numLevels = backtrace(buffer, maxDepth + level + 1);
char **tracebackBuffer = backtrace_symbols(buffer, numLevels);
if(tracebackBuffer) {
for(int i = 1 + level; i < numLevels; i++) {
std::string line = tracebackBuffer[i];
if(line.find("__libc_start_main") != std::string::npos)
break;
std::size_t demanglePos = line.find("(_Z");
if(demanglePos != std::string::npos) {
demanglePos++;
int len = std::min(line.find_first_of("+", demanglePos), line.find_first_of(")", demanglePos)) - demanglePos;
std::string funcName = line.substr(demanglePos, len);
line.replace(demanglePos, len, stdext::demangle_name(funcName.c_str()));
}
ss << "\n\t" << line;
}
free(tracebackBuffer);
}
return ss.str();
}
#endif #endif

View File

@@ -414,4 +414,13 @@ std::string Platform::getOSName()
return ret; return ret;
} }
std::string Platform::traceback(const std::string& where, int level, int maxDepth)
{
std::stringstream ss;
ss << "\nat:";
ss << "\n\t[C++]: " << where;
return ss.str();
}
#endif #endif

View File

@@ -624,6 +624,9 @@ LRESULT WIN32Window::windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar
break; break;
} }
case WM_SYSKEYDOWN: { case WM_SYSKEYDOWN: {
if(wParam == VK_F4 && m_inputEvent.keyboardModifiers & Fw::KeyboardAltModifier)
return DefWindowProc(hWnd, uMsg, wParam, lParam);
processKeyDown(retranslateVirtualKey(wParam, lParam)); processKeyDown(retranslateVirtualKey(wParam, lParam));
break; break;
} }

View File

@@ -26,7 +26,7 @@
#include <boost/thread/future.hpp> #include <boost/thread/future.hpp>
// hack to enable std::thread on mingw32 4.6 // hack to enable std::thread on mingw32 4.6
#if !defined(_GLIBCXX_HAS_GTHREADS) && defined(__GNUG__) #if !defined(_GLIBCXX_HAS_GTHREADS) && defined(__GNUG__) && !defined(__clang__)
#include <boost/thread/thread.hpp> #include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp> #include <boost/thread/mutex.hpp>

View File

@@ -98,9 +98,9 @@ void UIWidget::drawSelf(Fw::DrawPane drawPane)
} }
drawImage(m_rect); drawImage(m_rect);
drawBorder(m_rect);
drawIcon(m_rect); drawIcon(m_rect);
drawText(m_rect); drawText(m_rect);
drawBorder(m_rect);
} }
void UIWidget::drawChildren(const Rect& visibleRect, Fw::DrawPane drawPane) void UIWidget::drawChildren(const Rect& visibleRect, Fw::DrawPane drawPane)

View File

@@ -419,6 +419,7 @@ protected:
stdext::boolean<false> m_imageFixedRatio; stdext::boolean<false> m_imageFixedRatio;
stdext::boolean<false> m_imageRepeated; stdext::boolean<false> m_imageRepeated;
stdext::boolean<false> m_imageSmooth; stdext::boolean<false> m_imageSmooth;
stdext::boolean<false> m_imageAutoResize;
EdgeGroup<int> m_imageBorder; EdgeGroup<int> m_imageBorder;
public: public:
@@ -435,6 +436,7 @@ public:
void setImageFixedRatio(bool fixedRatio) { m_imageFixedRatio = fixedRatio; updateImageCache(); } void setImageFixedRatio(bool fixedRatio) { m_imageFixedRatio = fixedRatio; updateImageCache(); }
void setImageRepeated(bool repeated) { m_imageRepeated = repeated; updateImageCache(); } void setImageRepeated(bool repeated) { m_imageRepeated = repeated; updateImageCache(); }
void setImageSmooth(bool smooth) { m_imageSmooth = smooth; } void setImageSmooth(bool smooth) { m_imageSmooth = smooth; }
void setImageAutoResize(bool autoResize) { m_imageAutoResize = autoResize; }
void setImageBorderTop(int border) { m_imageBorder.top = border; configureBorderImage(); } void setImageBorderTop(int border) { m_imageBorder.top = border; configureBorderImage(); }
void setImageBorderRight(int border) { m_imageBorder.right = border; configureBorderImage(); } void setImageBorderRight(int border) { m_imageBorder.right = border; configureBorderImage(); }
void setImageBorderBottom(int border) { m_imageBorder.bottom = border; configureBorderImage(); } void setImageBorderBottom(int border) { m_imageBorder.bottom = border; configureBorderImage(); }
@@ -452,6 +454,7 @@ public:
Color getImageColor() { return m_imageColor; } Color getImageColor() { return m_imageColor; }
bool isImageFixedRatio() { return m_imageFixedRatio; } bool isImageFixedRatio() { return m_imageFixedRatio; }
bool isImageSmooth() { return m_imageSmooth; } bool isImageSmooth() { return m_imageSmooth; }
bool isImageAutoResize() { return m_imageAutoResize; }
int getImageBorderTop() { return m_imageBorder.top; } int getImageBorderTop() { return m_imageBorder.top; }
int getImageBorderRight() { return m_imageBorder.right; } int getImageBorderRight() { return m_imageBorder.right; }
int getImageBorderBottom() { return m_imageBorder.bottom; } int getImageBorderBottom() { return m_imageBorder.bottom; }

View File

@@ -68,9 +68,10 @@ void UIWidget::parseImageStyle(const OTMLNodePtr& styleNode)
setImageBorderBottom(node->value<int>()); setImageBorderBottom(node->value<int>());
else if(node->tag() == "image-border-left") else if(node->tag() == "image-border-left")
setImageBorderLeft(node->value<int>()); setImageBorderLeft(node->value<int>());
else if(node->tag() == "image-border") { else if(node->tag() == "image-border")
setImageBorder(node->value<int>()); setImageBorder(node->value<int>());
} else if(node->tag() == "image-auto-resize")
setImageAutoResize(node->value<bool>());
} }
} }
@@ -180,5 +181,16 @@ void UIWidget::setImageSource(const std::string& source)
m_imageTexture = nullptr; m_imageTexture = nullptr;
else else
m_imageTexture = g_textures.getTexture(source); m_imageTexture = g_textures.getTexture(source);
if(m_imageTexture && (!m_rect.isValid() || m_imageAutoResize)) {
Size size = getSize();
Size imageSize = m_imageTexture->getSize();
if(size.width() <= 0 || m_imageAutoResize)
size.setWidth(imageSize.width());
if(size.height() <= 0 || m_imageAutoResize)
size.setHeight(imageSize.height());
setSize(size);
}
m_imageMustRecache = true; m_imageMustRecache = true;
} }

View File

@@ -365,7 +365,7 @@ index b980be0..7a84f61 100644
+ std::string buffer = msg.getString(); + std::string buffer = msg.getString();
+ +
+ // process additional opcodes via lua script event + // process additional opcodes via lua script event
+ addGameTask(&Game::parsePlayerExtendedOpcode, player->getId(), opcode, buffer); + addGameTask(&Game::parsePlayerExtendedOpcode, player->getID(), opcode, buffer);
+} +}
+ +
+void ProtocolGame::sendExtendedOpcode(uint8_t opcode, const std::string& buffer) +void ProtocolGame::sendExtendedOpcode(uint8_t opcode, const std::string& buffer)