Browse Source

Minimap, hotkeys and lot of other changes

* Begin working on a new layout system for UIMinimap and later UIMap,
this new layout system allows to add widgets to the minimap
* Add option to disable motd
* Rework hotkey binding
* Lots of fixes in hotkeys manager
* Add fullmap view using Ctrl+Shift+M
* Prevent some crashs in ThingType draw
* Add function to load minimap from PNG files
* Fixes in minimap saving
* Fixes in Tile::isClickable
* Add UIMapAnchorLayout, new layout for maps
* Fix freezes in win32 when pressing alt key
Eduardo Bart 8 years ago
parent
commit
9a54bfcc90
43 changed files with 831 additions and 488 deletions
  1. 1
    0
      .gitignore
  2. BIN
      data/images/game/minimap/cross.png
  3. 7
    27
      data/styles/30-minimap.otui
  4. 1
    1
      modules/client/client.lua
  5. 0
    1
      modules/client/client.otmod
  6. 7
    2
      modules/client_background/background.lua
  7. 1
    1
      modules/client_entergame/characterlist.lua
  8. 21
    15
      modules/client_entergame/entergame.lua
  9. 10
    1
      modules/client_stats/stats.lua
  10. 30
    55
      modules/corelib/keyboard.lua
  11. 6
    0
      modules/corelib/table.lua
  12. 3
    1
      modules/corelib/ui/uiscrollbar.lua
  13. 12
    1
      modules/corelib/util.lua
  14. 27
    17
      modules/game_hotkeys/hotkeys_manager.lua
  15. 26
    27
      modules/game_interface/gameinterface.lua
  16. 41
    3
      modules/game_minimap/minimap.lua
  17. 2
    1
      modules/game_npctrade/npctrade.lua
  18. 1
    0
      modules/game_npctrade/npctrade.otui
  19. 4
    0
      modules/game_textmessage/textmessage.lua
  20. 4
    2
      modules/game_viplist/viplist.lua
  21. 1
    1
      modules/gamelib/player.lua
  22. 98
    155
      modules/gamelib/ui/uiminimap.lua
  23. 2
    0
      src/client/CMakeLists.txt
  24. 4
    0
      src/client/declarations.h
  25. 14
    7
      src/client/luafunctions.cpp
  26. 2
    0
      src/client/map.cpp
  27. 115
    22
      src/client/minimap.cpp
  28. 10
    4
      src/client/minimap.h
  29. 12
    1
      src/client/protocolgameparse.cpp
  30. 9
    1
      src/client/thingtype.cpp
  31. 2
    12
      src/client/tile.cpp
  32. 92
    0
      src/client/uimapanchorlayout.cpp
  33. 55
    0
      src/client/uimapanchorlayout.h
  34. 71
    28
      src/client/uiminimap.cpp
  35. 15
    9
      src/client/uiminimap.h
  36. 6
    2
      src/framework/luaengine/luavaluecasts.cpp
  37. 4
    0
      src/framework/pch.h
  38. 6
    3
      src/framework/platform/win32window.cpp
  39. 5
    0
      src/framework/ui/declarations.h
  40. 92
    75
      src/framework/ui/uianchorlayout.cpp
  41. 10
    12
      src/framework/ui/uianchorlayout.h
  42. 1
    0
      src/framework/ui/uiwidget.cpp
  43. 1
    1
      src/framework/ui/uiwidgetbasestyle.cpp

+ 1
- 0
.gitignore View File

@@ -37,3 +37,4 @@ otclient.layout
37 37
 LOCALTODO
38 38
 tags
39 39
 Thumbs.db
40
+.directory

BIN
data/images/game/minimap/cross.png View File


+ 7
- 27
data/styles/30-minimap.otui View File

@@ -2,9 +2,13 @@ MinimapFlag < UIWidget
2 2
   size: 11 11
3 3
   anchors.left: parent.left
4 4
   anchors.top: parent.top
5
+  focusable: false
5 6
 
6
-MinimapFlags < UIWidget
7
-  anchors.fill: parent
7
+MinimapCross < UIWidget
8
+  focusable: false
9
+  phantom: true
10
+  image: /images/game/minimap/cross
11
+  size: 16 16
8 12
 
9 13
 MinimapFloorUpButton < Button
10 14
   size: 20 20
@@ -61,12 +65,7 @@ Minimap < UIMinimap
61 65
   draggable: true
62 66
   focusable: false
63 67
   cross: true
64
-  @onGeometryChange: self:updateFlags()
65
-
66
-  MinimapFlags
67
-    id: flags
68
-    phantom: true
69
-    focusable: false
68
+  color: black
70 69
 
71 70
   MinimapFloorUpButton
72 71
     id: floorUp
@@ -245,22 +244,3 @@ MinimapFlagWindow < MainWindow
245 244
     width: 64
246 245
     anchors.right: parent.right
247 246
     anchors.bottom: parent.bottom
248
-
249
-// Minimap Full Panel
250
-
251
-MinimapFullPanel < FlatPanel
252
-  phantom: false
253
-  anchors.fill: parent
254
-  anchors.top: topMenu.bottom
255
-
256
-  ImageView
257
-    id: image
258
-    anchors.fill: parent
259
-
260
-  Button
261
-    !text: tr('Close')
262
-    margin-right: 4
263
-    margin-top: 4
264
-    anchors.right: parent.right
265
-    anchors.top: parent.top
266
-    @onClick: self:getParent():destroy()

+ 1
- 1
modules/client/client.lua View File

@@ -55,7 +55,7 @@ end
55 55
 function init()
56 56
   connect(g_app, { onRun = startup, 
57 57
                    onExit = exit })
58
-                   
58
+
59 59
   g_window.setMinimumSize({ width = 600, height = 480 })
60 60
   g_sounds.preload(musicFilename)
61 61
 

+ 0
- 1
modules/client/client.otmod View File

@@ -12,7 +12,6 @@ Module
12 12
   load-later:
13 13
     - client_styles
14 14
     - client_locales
15
-    //- client_particles
16 15
     - client_topmenu
17 16
     - client_background
18 17
     - client_entergame

+ 7
- 2
modules/client_background/background.lua View File

@@ -1,18 +1,19 @@
1 1
 -- private variables
2 2
 local background
3
+local clientVersionLabel
3 4
 
4 5
 -- public functions
5 6
 function init()
6 7
   background = g_ui.displayUI('background')
7 8
   background:lower()
8 9
 
9
-  local clientVersionLabel = background:getChildById('clientVersionLabel')
10
+  clientVersionLabel = background:getChildById('clientVersionLabel')
10 11
   clientVersionLabel:setText(g_app.getName() .. ' ' .. g_app.getVersion() .. '\n' ..
11 12
                              'Rev  ' .. g_app.getBuildRevision() .. ' ('.. g_app.getBuildCommit() .. ')\n' ..
12 13
                              'Built on ' .. g_app.getBuildDate())
13 14
 
14 15
   if not g_game.isOnline() then
15
-    g_effects.fadeIn(clientVersionLabel, 1500)
16
+    addEvent(function() g_effects.fadeIn(clientVersionLabel, 1500) end)
16 17
   end
17 18
 
18 19
   connect(g_game, { onGameStart = hide })
@@ -40,3 +41,7 @@ end
40 41
 function hideVersionLabel()
41 42
   background:getChildById('clientVersionLabel'):hide()
42 43
 end
44
+
45
+function setVersionText(text)
46
+  clientVersionLabel:setText(text)
47
+end

+ 1
- 1
modules/client_entergame/characterlist.lua View File

@@ -111,7 +111,7 @@ end
111 111
 
112 112
 function onGameConnectionError(message, code)
113 113
   CharacterList.destroyLoadBox()
114
-  errorBox = displayErrorBox(tr("Login Error"), message)
114
+  errorBox = displayErrorBox(tr("Connection Error"), message)
115 115
   errorBox.onOk = function()
116 116
     errorBox = nil
117 117
     CharacterList.showAgain()

+ 21
- 15
modules/client_entergame/entergame.lua View File

@@ -8,6 +8,7 @@ local motdButton
8 8
 local enterGameButton
9 9
 local protocolBox
10 10
 local protocolLogin
11
+local motdEnabled = true
11 12
 
12 13
 -- private functions
13 14
 local function onError(protocol, message, errorCode)
@@ -27,7 +28,9 @@ end
27 28
 local function onMotd(protocol, motd)
28 29
   G.motdNumber = tonumber(motd:sub(0, motd:find("\n")))
29 30
   G.motdMessage = motd:sub(motd:find("\n") + 1, #motd)
30
-  motdButton:show()
31
+  if motdEnabled then
32
+    motdButton:show()
33
+  end
31 34
 end
32 35
 
33 36
 local function onCharacterList(protocol, characters, account, otui)
@@ -45,12 +48,14 @@ local function onCharacterList(protocol, characters, account, otui)
45 48
   CharacterList.create(characters, account, otui)
46 49
   CharacterList.show()
47 50
 
48
-  local lastMotdNumber = g_settings.getNumber("motd")
49
-  if G.motdNumber and G.motdNumber ~= lastMotdNumber then
50
-    g_settings.set("motd", motdNumber)
51
-    motdWindow = displayInfoBox(tr('Message of the day'), G.motdMessage)
52
-    connect(motdWindow, { onOk = function() CharacterList.show() motdWindow = nil end })
53
-    CharacterList.hide()
51
+  if motdEnabled then
52
+    local lastMotdNumber = g_settings.getNumber("motd")
53
+    if G.motdNumber and G.motdNumber ~= lastMotdNumber then
54
+      g_settings.set("motd", motdNumber)
55
+      motdWindow = displayInfoBox(tr('Message of the day'), G.motdMessage)
56
+      connect(motdWindow, { onOk = function() CharacterList.show() motdWindow = nil end })
57
+      CharacterList.hide()
58
+    end
54 59
   end
55 60
 end
56 61
 
@@ -81,7 +86,7 @@ function EnterGame.init()
81 86
   motdButton:hide()
82 87
   g_keyboard.bindKeyDown('Ctrl+G', EnterGame.openWindow)
83 88
 
84
-  if G.motdNumber then
89
+  if motdEnabled and G.motdNumber then
85 90
     motdButton:show()
86 91
   end
87 92
 
@@ -127,13 +132,15 @@ function EnterGame.firstShow()
127 132
   local host = g_settings.get('host')
128 133
   local autologin = g_settings.getBoolean('autologin')
129 134
   if #host > 0 and #password > 0 and #account > 0 and autologin then
130
-    autoLoginEvent = addEvent(EnterGame.doLogin)
135
+    connect(g_app, { onRun = function()
136
+      if not g_settings.getBoolean('autologin') then return end
137
+      EnterGame.doLogin()
138
+    end})
131 139
   end
132 140
 end
133 141
 
134 142
 function EnterGame.terminate()
135 143
   g_keyboard.unbindKeyDown('Ctrl+G')
136
-  removeEvent(autoLoginEvent)
137 144
   enterGame:destroy()
138 145
   enterGame = nil
139 146
   enterGameButton:destroy()
@@ -186,7 +193,6 @@ function EnterGame.clearAccountFields()
186 193
 end
187 194
 
188 195
 function EnterGame.doLogin()
189
-  autoLoginEvent = nil
190 196
   G.account = enterGame:getChildById('accountNameTextEdit'):getText()
191 197
   G.password = enterGame:getChildById('accountPasswordTextEdit'):getText()
192 198
   G.host = enterGame:getChildById('serverHostTextEdit'):getText()
@@ -252,10 +258,6 @@ function EnterGame.setDefaultServer(host, port, protocol)
252 258
     protocolBox:setCurrentOption(protocol)
253 259
     accountTextEdit:setText('')
254 260
     passwordTextEdit:setText('')
255
-
256
-    if autoLoginEvent then
257
-      autoLoginEvent:cancel()
258
-    end
259 261
   end
260 262
 end
261 263
 
@@ -297,3 +299,7 @@ function EnterGame.setServerInfo(message)
297 299
   label:setText(message)
298 300
 end
299 301
 
302
+function EnterGame.disableMotd()
303
+  motdEnabled = false
304
+  motdButton:hide()
305
+end

+ 10
- 1
modules/client_stats/stats.lua View File

@@ -25,6 +25,8 @@ end
25 25
 function terminate()
26 26
   disconnect(g_game, { onGameStart = onGameStart,
27 27
                        onGameEnd = onGameEnd })
28
+  removeEvent(firstReportEvent)
29
+  removeEvent(sendReportEvent)
28 30
 end
29 31
 
30 32
 function configure(host, port, delay)
@@ -45,13 +47,15 @@ end
45 47
 
46 48
 function onGameStart()
47 49
   if not HOST then return end
50
+  removeEvent(firstReportEvent)
51
+  removeEvent(sendReportEvent)
48 52
   firstReportEvent = addEvent(sendReport, FIRST_REPORT_DELAY*1000)
49 53
   sendReportEvent = cycleEvent(sendReport, REPORT_DELAY*1000)
50 54
 end
51 55
 
52 56
 function onGameEnd()
53
-  removeEvent(sendReportEvent)
54 57
   removeEvent(firstReportEvent)
58
+  removeEvent(sendReportEvent)
55 59
 end
56 60
 
57 61
 function onConnect(protocol)
@@ -84,6 +88,7 @@ function onConnect(protocol)
84 88
   post = post .. '&cpu='               .. g_platform.getCPUName()
85 89
   post = post .. '&mem='               .. g_platform.getTotalSystemMemory()
86 90
   post = post .. '&os_name='           .. g_platform.getOSName()
91
+  post = post .. getAdditionalData()
87 92
 
88 93
   local message = ''
89 94
   message = message .. "POST /report HTTP/1.1\r\n"
@@ -98,6 +103,10 @@ function onConnect(protocol)
98 103
   protocol:recv()
99 104
 end
100 105
 
106
+function getAdditionalData()
107
+  return ''
108
+end
109
+
101 110
 function onRecv(protocol, message)
102 111
   if string.find(message, 'HTTP/1.1 200 OK') then
103 112
     --pinfo('Stats sent to server successfully!')

+ 30
- 55
modules/corelib/keyboard.lua View File

@@ -4,7 +4,6 @@ g_keyboard = {}
4 4
 -- private functions
5 5
 function translateKeyCombo(keyCombo)
6 6
   if not keyCombo or #keyCombo == 0 then return nil end
7
-  table.sort(keyCombo)
8 7
   local keyComboDesc = ''
9 8
   for k,v in pairs(keyCombo) do
10 9
     local keyDesc = KeyCodeDescs[v]
@@ -65,47 +64,29 @@ function determineKeyComboDesc(keyCode, keyboardModifiers)
65 64
     end
66 65
     table.insert(keyCombo, keyCode)
67 66
   end
68
-  table.sort(keyCombo)
69 67
   return translateKeyCombo(keyCombo)
70 68
 end
71 69
 
72 70
 local function onWidgetKeyDown(widget, keyCode, keyboardModifiers)
73 71
   if keyCode == KeyUnknown then return false end
74 72
   local callback = widget.boundAloneKeyDownCombos[determineKeyComboDesc(keyCode, KeyboardNoModifier)]
75
-  if callback then
76
-    callback(widget, keyCode)
77
-  end
73
+  signalcall(callback, widget, keyCode)
78 74
   callback = widget.boundKeyDownCombos[determineKeyComboDesc(keyCode, keyboardModifiers)]
79
-  if callback then
80
-    callback(widget, keyCode)
81
-    return true
82
-  end
83
-  return false
75
+  return signalcall(callback, widget, keyCode)
84 76
 end
85 77
 
86 78
 local function onWidgetKeyUp(widget, keyCode, keyboardModifiers)
87 79
   if keyCode == KeyUnknown then return false end
88 80
   local callback = widget.boundAloneKeyUpCombos[determineKeyComboDesc(keyCode, KeyboardNoModifier)]
89
-  if callback then
90
-    callback(widget, keyCode)
91
-  end
81
+  signalcall(callback, widget, keyCode)
92 82
   callback = widget.boundKeyUpCombos[determineKeyComboDesc(keyCode, keyboardModifiers)]
93
-  if callback then
94
-    callback(widget, keyCode)
95
-    return true
96
-  end
97
-  return false
83
+  return signalcall(callback, widget, keyCode)
98 84
 end
99 85
 
100 86
 local function onWidgetKeyPress(widget, keyCode, keyboardModifiers, autoRepeatTicks)
101 87
   if keyCode == KeyUnknown then return false end
102
-  local keyComboDesc = determineKeyComboDesc(keyCode, keyboardModifiers)
103
-  local comboConf = widget.boundKeyPressCombos[keyComboDesc]
104
-  if comboConf and (autoRepeatTicks >= comboConf.autoRepeatDelay or autoRepeatTicks == 0) and comboConf.callback then
105
-    comboConf.callback(widget, keyCode)
106
-    return true
107
-  end
108
-  return false
88
+  local callback = widget.boundKeyPressCombos[determineKeyComboDesc(keyCode, keyboardModifiers)]
89
+  return signalcall(callback, widget, keyCode, autoRepeatTicks)
109 90
 end
110 91
 
111 92
 local function connectKeyDownEvent(widget)
@@ -133,13 +114,10 @@ function g_keyboard.bindKeyDown(keyComboDesc, callback, widget, alone)
133 114
   widget = widget or rootWidget
134 115
   connectKeyDownEvent(widget)
135 116
   local keyComboDesc = retranslateKeyComboDesc(keyComboDesc)
136
-  if widget.boundKeyDownCombos[keyComboDesc] then
137
-    pwarning('KeyDown event \'' .. keyComboDesc .. '\' redefined on widget ' .. widget:getId())
138
-  end
139 117
   if alone then
140
-    widget.boundAloneKeyDownCombos[keyComboDesc] = callback
118
+    connect(widget.boundAloneKeyDownCombos, keyComboDesc, callback)
141 119
   else
142
-    widget.boundKeyDownCombos[keyComboDesc] = callback
120
+    connect(widget.boundKeyDownCombos, keyComboDesc, callback)
143 121
   end
144 122
 end
145 123
 
@@ -147,53 +125,50 @@ function g_keyboard.bindKeyUp(keyComboDesc, callback, widget, alone)
147 125
   widget = widget or rootWidget
148 126
   connectKeyUpEvent(widget)
149 127
   local keyComboDesc = retranslateKeyComboDesc(keyComboDesc)
150
-  if widget.boundKeyUpCombos[keyComboDesc] then
151
-    pwarning('KeyUp event \'' .. keyComboDesc .. '\' redefined on widget ' .. widget:getId())
152
-  end
153 128
   if alone then
154
-    widget.boundAloneKeyUpCombos[keyComboDesc] = callback
129
+    connect(widget.boundAloneKeyUpCombos, keyComboDesc, callback)
155 130
   else
156
-    widget.boundKeyUpCombos[keyComboDesc] = callback
131
+    connect(widget.boundKeyUpCombos, keyComboDesc, callback)
157 132
   end
158 133
 end
159 134
 
160
-function g_keyboard.bindKeyPress(keyComboDesc, callback, widget, autoRepeatDelay)
161
-  autoRepeatDelay = autoRepeatDelay or 500
135
+function g_keyboard.bindKeyPress(keyComboDesc, callback, widget)
162 136
   widget = widget or rootWidget
163 137
   connectKeyPressEvent(widget)
164 138
   local keyComboDesc = retranslateKeyComboDesc(keyComboDesc)
165
-  if widget.boundKeyPressCombos[keyComboDesc] then
166
-    pwarning('KeyPress event \'' .. keyComboDesc .. '\' redefined on widget ' .. widget:getId())
167
-  end
168
-  widget.boundKeyPressCombos[keyComboDesc] = { callback = callback, autoRepeatDelay = autoRepeatDelay }
169
-  widget:setAutoRepeatDelay(math.min(autoRepeatDelay, widget:getAutoRepeatDelay()))
139
+  connect(widget.boundKeyPressCombos, keyComboDesc, callback)
170 140
 end
171 141
 
172
-function g_keyboard.unbindKeyDown(keyComboDesc, widget)
142
+local function getUnbindArgs(arg1, arg2)
143
+  local callback
144
+  local widget
145
+  if type(arg1) == 'function' then callback = arg1
146
+  elseif type(arg2) == 'function' then callback = arg2 end
147
+  if type(arg1) == 'userdata' then widget = arg1
148
+  elseif type(arg2) == 'userdata' then widget = arg2 end
173 149
   widget = widget or rootWidget
150
+  return callback, widget
151
+end
152
+
153
+function g_keyboard.unbindKeyDown(keyComboDesc, arg1, arg2)
154
+  local callback, widget = getUnbindArgs(arg1, arg2)
174 155
   if widget.boundKeyDownCombos == nil then return end
175 156
   local keyComboDesc = retranslateKeyComboDesc(keyComboDesc)
176
-  if keyComboDesc then
177
-    widget.boundKeyDownCombos[keyComboDesc] = nil
178
-  end
157
+  disconnect(widget.boundKeyDownCombos, keyComboDesc, callback)
179 158
 end
180 159
 
181 160
 function g_keyboard.unbindKeyUp(keyComboDesc, widget)
182
-  widget = widget or rootWidget
161
+  local callback, widget = getUnbindArgs(arg1, arg2)
183 162
   if widget.boundKeyUpCombos == nil then return end
184 163
   local keyComboDesc = retranslateKeyComboDesc(keyComboDesc)
185
-  if keyComboDesc then
186
-    widget.boundKeyUpCombos[keyComboDesc] = nil
187
-  end
164
+  disconnect(widget.boundKeyUpCombos, keyComboDesc, callback)
188 165
 end
189 166
 
190
-function g_keyboard.unbindKeyPress(keyComboDesc, widget)
191
-  widget = widget or rootWidget
167
+function g_keyboard.unbindKeyPress(keyComboDesc, widget, callback)
168
+  local callback, widget = getUnbindArgs(arg1, arg2)
192 169
   if widget.boundKeyPressCombos == nil then return end
193 170
   local keyComboDesc = retranslateKeyComboDesc(keyComboDesc)
194
-  if keyComboDesc then
195
-    widget.boundKeyPressCombos[keyComboDesc] = nil
196
-  end
171
+  disconnect(widget.boundKeyPressCombos, keyComboDesc, callback)
197 172
 end
198 173
 
199 174
 function g_keyboard.getModifiers()

+ 6
- 0
modules/corelib/table.lua View File

@@ -13,6 +13,12 @@ function table.dump(t, depth)
13 13
   end
14 14
 end
15 15
 
16
+function table.clear(t)
17
+  for k,v in pairs(t) do
18
+    t[k] = nil
19
+  end
20
+end
21
+
16 22
 function table.copy(t)
17 23
   local res = {}
18 24
   for k,v in pairs(t) do

+ 3
- 1
modules/corelib/ui/uiscrollbar.lua View File

@@ -224,7 +224,9 @@ end
224 224
 
225 225
 function UIScrollBar:setText(text)
226 226
   local valueLabel = self:getChildById('valueLabel')
227
-  valueLabel:setText(text)
227
+  if valueLabel then
228
+    valueLabel:setText(text)
229
+  end
228 230
 end
229 231
 
230 232
 function UIScrollBar:onGeometryChange()

+ 12
- 1
modules/corelib/util.lua View File

@@ -67,6 +67,11 @@ function connect(object, arg1, arg2, arg3)
67 67
     elseif type(object[signal]) == 'function' then
68 68
       object[signal] = { object[signal] }
69 69
     end
70
+
71
+    if type(slot) ~= 'function' then
72
+      perror(debug.traceback('unable to connect a non function value'))
73
+    end
74
+
70 75
     if type(object[signal]) == 'table' then
71 76
       if pushFront then
72 77
         table.insert(object[signal], 1, slot)
@@ -80,9 +85,15 @@ end
80 85
 function disconnect(object, arg1, arg2)
81 86
   local signalsAndSlots
82 87
   if type(arg1) == 'string' then
88
+    if arg2 == nil then
89
+      object[arg1] = nil
90
+      return
91
+    end
83 92
     signalsAndSlots = { [arg1] = arg2 }
84
-  else
93
+  elseif type(arg1) == 'table' then
85 94
     signalsAndSlots = arg1
95
+  else
96
+    perror(debug.traceback('unable to disconnect'))
86 97
   end
87 98
 
88 99
   for signal,slot in pairs(signalsAndSlots) do

+ 27
- 17
modules/game_hotkeys/hotkeys_manager.lua View File

@@ -34,6 +34,7 @@ perCharacter = true
34 34
 mouseGrabberWidget = nil
35 35
 useRadioGroup = nil
36 36
 currentHotkeys = nil
37
+boundCombosCallback = {}
37 38
 hotkeysList = {}
38 39
 
39 40
 -- public functions
@@ -149,6 +150,7 @@ function load(forceDefaults)
149 150
   if not forceDefaults then
150 151
     if not table.empty(hotkeys) then
151 152
       for keyCombo, setting in pairs(hotkeys) do
153
+        keyCombo = tostring(keyCombo)
152 154
         addKeyCombo(keyCombo, setting)
153 155
         hotkeyList[keyCombo] = setting
154 156
       end
@@ -163,12 +165,13 @@ function load(forceDefaults)
163 165
 end
164 166
 
165 167
 function unload()
166
-  for _,child in pairs(currentHotkeys:getChildren()) do
167
-    g_keyboard.unbindKeyPress(child.keyCombo)
168
+  for keyCombo,callback in pairs(boundCombosCallback) do
169
+    g_keyboard.unbindKeyPress(keyCombo, callback)
168 170
   end
171
+  boundCombosCallback = {}
169 172
   currentHotkeys:destroyChildren()
170 173
   currentHotkeyLabel = nil
171
-  updateHotkeyForm()
174
+  updateHotkeyForm(true)
172 175
   hotkeyList = {}
173 176
 end
174 177
 
@@ -196,6 +199,8 @@ function save()
196 199
     hotkeys = hotkeys[g_game.getCharacterName()]
197 200
   end
198 201
 
202
+  table.clear(hotkeys)
203
+
199 204
   for _,child in pairs(currentHotkeys:getChildren()) do
200 205
     hotkeys[child.keyCombo] = {
201 206
       autoSend = child.autoSend,
@@ -207,7 +212,7 @@ function save()
207 212
 
208 213
   hotkeyList = hotkeys
209 214
   g_settings.setNode('game_hotkeys', hotkeySettings)
210
-  --g_settings.save()
215
+  g_settings.save()
211 216
 end
212 217
 
213 218
 function loadDefautComboKeys()
@@ -258,7 +263,7 @@ function onChooseItemMouseRelease(self, mousePosition, mouseButton)
258 263
     currentHotkeyLabel.value = nil
259 264
     currentHotkeyLabel.autoSend = false
260 265
     updateHotkeyLabel(currentHotkeyLabel)
261
-    updateHotkeyForm()
266
+    updateHotkeyForm(true)
262 267
   end
263 268
 
264 269
   show()
@@ -281,7 +286,7 @@ function clearObject()
281 286
   currentHotkeyLabel.autoSend = nil
282 287
   currentHotkeyLabel.value = nil
283 288
   updateHotkeyLabel(currentHotkeyLabel)
284
-  updateHotkeyForm()
289
+  updateHotkeyForm(true)
285 290
 end
286 291
 
287 292
 function addHotkey()
@@ -294,6 +299,7 @@ function addHotkey()
294 299
 end
295 300
 
296 301
 function addKeyCombo(keyCombo, keySettings, focus)
302
+  if keyCombo == nil or #keyCombo == 0 then return end
297 303
   if not keyCombo then return end
298 304
   local hotkeyLabel = currentHotkeys:getChildById(keyCombo)
299 305
   if not hotkeyLabel then
@@ -321,27 +327,28 @@ function addKeyCombo(keyCombo, keySettings, focus)
321 327
     if keySettings then
322 328
       currentHotkeyLabel = hotkeyLabel
323 329
       hotkeyLabel.keyCombo = keyCombo
324
-      hotkeyLabel.autoSend = keySettings.autoSend
325
-      hotkeyLabel.itemId = keySettings.itemId
330
+      hotkeyLabel.autoSend = toboolean(keySettings.autoSend)
331
+      hotkeyLabel.itemId = tonumber(keySettings.itemId)
326 332
       hotkeyLabel.useType = tonumber(keySettings.useType)
327
-      hotkeyLabel.value = keySettings.value
333
+      if keySettings.value then hotkeyLabel.value = tostring(keySettings.value) end
328 334
     else
329 335
       hotkeyLabel.keyCombo = keyCombo
330
-      hotkeyLabel.autoSend = nil
336
+      hotkeyLabel.autoSend = false
331 337
       hotkeyLabel.itemId = nil
332 338
       hotkeyLabel.useType = nil
333
-      hotkeyLabel.value = nil
339
+      hotkeyLabel.value = ''
334 340
     end
335 341
 
336 342
     updateHotkeyLabel(hotkeyLabel)
337 343
 
338
-    g_keyboard.bindKeyPress(keyCombo, function() doKeyCombo(keyCombo) end, nil, 350)
344
+    boundCombosCallback[keyCombo] = function() doKeyCombo(keyCombo) end
345
+    g_keyboard.bindKeyPress(keyCombo, boundCombosCallback[keyCombo])
339 346
   end
340 347
 
341 348
   if focus then
342 349
     currentHotkeys:focusChild(hotkeyLabel)
343 350
     currentHotkeys:ensureChildVisible(hotkeyLabel)
344
-    updateHotkeyForm()
351
+    updateHotkeyForm(true)
345 352
   end
346 353
 end
347 354
 
@@ -398,7 +405,7 @@ function updateHotkeyLabel(hotkeyLabel)
398 405
   end
399 406
 end
400 407
 
401
-function updateHotkeyForm()
408
+function updateHotkeyForm(reset)
402 409
   if currentHotkeyLabel then
403 410
     removeHotkeyButton:enable()
404 411
     if currentHotkeyLabel.itemId ~= nil then
@@ -435,8 +442,10 @@ function updateHotkeyForm()
435 442
       hotkeyText:enable()
436 443
       hotkeyText:focus()
437 444
       hotKeyTextLabel:enable()
445
+      if reset then
446
+        hotkeyText:setCursorPos(-1)
447
+      end
438 448
       hotkeyText:setText(currentHotkeyLabel.value)
439
-      hotkeyText:setCursorPos(-1)
440 449
       sendAutomatically:setChecked(currentHotkeyLabel.autoSend)
441 450
       sendAutomatically:setEnabled(currentHotkeyLabel.value and #currentHotkeyLabel.value > 0)
442 451
       selectObjectButton:enable()
@@ -461,7 +470,8 @@ end
461 470
 
462 471
 function removeHotkey()
463 472
   if currentHotkeyLabel == nil then return end
464
-  g_keyboard.unbindKeyPress(currentHotkeyLabel.keyCombo)
473
+  g_keyboard.unbindKeyPress(currentHotkeyLabel.keyCombo, boundCombosCallback[currentHotkeyLabel.keyCombo])
474
+  boundCombosCallback[currentHotkeyLabel.keyCombo] = nil
465 475
   currentHotkeyLabel:destroy()
466 476
   currentHotkeyLabel = nil
467 477
 end
@@ -504,7 +514,7 @@ end
504 514
 
505 515
 function onSelectHotkeyLabel(hotkeyLabel)
506 516
   currentHotkeyLabel = hotkeyLabel
507
-  updateHotkeyForm()
517
+  updateHotkeyForm(true)
508 518
 end
509 519
 
510 520
 function hotkeyCapture(assignWindow, keyCode, keyboardModifiers)

+ 26
- 27
modules/game_interface/gameinterface.lua View File

@@ -1,4 +1,3 @@
1
-WALK_AUTO_REPEAT_DELAY = 90
2 1
 WALK_STEPS_RETRY = 10
3 2
 
4 3
 gameRootPanel = nil
@@ -25,7 +24,7 @@ function init()
25 24
     onGameStart = onGameStart,
26 25
     onGMActions = onGMActions,
27 26
     onGameEnd = onGameEnd,
28
-    onLoginAdvice = onLoginAdvice
27
+    onLoginAdvice = onLoginAdvice,
29 28
   }, true)
30 29
 
31 30
   gameRootPanel = g_ui.displayUI('gameinterface')
@@ -57,6 +56,7 @@ function init()
57 56
 end
58 57
 
59 58
 function bindKeys()
59
+  gameRootPanel:setAutoRepeatDelay(250)
60 60
   g_keyboard.bindKeyDown('Up', function() changeWalkDir(North) end, gameRootPanel, true)
61 61
   g_keyboard.bindKeyDown('Right', function() changeWalkDir(East) end, gameRootPanel, true)
62 62
   g_keyboard.bindKeyDown('Down', function() changeWalkDir(South) end, gameRootPanel, true)
@@ -81,30 +81,30 @@ function bindKeys()
81 81
   g_keyboard.bindKeyUp('Numpad1', function() changeWalkDir(SouthWest, true) end, gameRootPanel, true)
82 82
   g_keyboard.bindKeyUp('Numpad4', function() changeWalkDir(West, true) end, gameRootPanel, true)
83 83
   g_keyboard.bindKeyUp('Numpad7', function() changeWalkDir(NorthWest, true) end, gameRootPanel, true)
84
-  g_keyboard.bindKeyPress('Up', function() smartWalk(North) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
85
-  g_keyboard.bindKeyPress('Right', function() smartWalk(East) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
86
-  g_keyboard.bindKeyPress('Down', function() smartWalk(South) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
87
-  g_keyboard.bindKeyPress('Left', function() smartWalk(West) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
88
-  g_keyboard.bindKeyPress('Numpad8', function() smartWalk(North) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
89
-  g_keyboard.bindKeyPress('Numpad9', function() smartWalk(NorthEast) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
90
-  g_keyboard.bindKeyPress('Numpad6', function() smartWalk(East) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
91
-  g_keyboard.bindKeyPress('Numpad3', function() smartWalk(SouthEast) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
92
-  g_keyboard.bindKeyPress('Numpad2', function() smartWalk(South) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
93
-  g_keyboard.bindKeyPress('Numpad1', function() smartWalk(SouthWest) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
94
-  g_keyboard.bindKeyPress('Numpad4', function() smartWalk(West) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
95
-  g_keyboard.bindKeyPress('Numpad7', function() smartWalk(NorthWest) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
96
-
97
-  g_keyboard.bindKeyPress('Ctrl+Up', function() g_game.turn(North) changeWalkDir(North) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
98
-  g_keyboard.bindKeyPress('Ctrl+Right', function() g_game.turn(East) changeWalkDir(East) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
99
-  g_keyboard.bindKeyPress('Ctrl+Down', function() g_game.turn(South) changeWalkDir(South) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
100
-  g_keyboard.bindKeyPress('Ctrl+Left', function() g_game.turn(West) changeWalkDir(West) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
101
-  g_keyboard.bindKeyPress('Ctrl+Numpad8', function() g_game.turn(North) changeWalkDir(North) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
102
-  g_keyboard.bindKeyPress('Ctrl+Numpad6', function() g_game.turn(East) changeWalkDir(East) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
103
-  g_keyboard.bindKeyPress('Ctrl+Numpad2', function() g_game.turn(South) changeWalkDir(South) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
104
-  g_keyboard.bindKeyPress('Ctrl+Numpad4', function() g_game.turn(West) changeWalkDir(West) end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
105
-  g_keyboard.bindKeyPress('Escape', function() g_game.cancelAttackAndFollow() end, gameRootPanel, WALK_AUTO_REPEAT_DELAY)
106
-  g_keyboard.bindKeyPress('Ctrl+=', function() gameMapPanel:zoomIn() end, gameRootPanel, 250)
107
-  g_keyboard.bindKeyPress('Ctrl+-', function() gameMapPanel:zoomOut() end, gameRootPanel, 250)
84
+  g_keyboard.bindKeyPress('Up', function() smartWalk(North) end, gameRootPanel)
85
+  g_keyboard.bindKeyPress('Right', function() smartWalk(East) end, gameRootPanel)
86
+  g_keyboard.bindKeyPress('Down', function() smartWalk(South) end, gameRootPanel)
87
+  g_keyboard.bindKeyPress('Left', function() smartWalk(West) end, gameRootPanel)
88
+  g_keyboard.bindKeyPress('Numpad8', function() smartWalk(North) end, gameRootPanel)
89
+  g_keyboard.bindKeyPress('Numpad9', function() smartWalk(NorthEast) end, gameRootPanel)
90
+  g_keyboard.bindKeyPress('Numpad6', function() smartWalk(East) end, gameRootPanel)
91
+  g_keyboard.bindKeyPress('Numpad3', function() smartWalk(SouthEast) end, gameRootPanel)
92
+  g_keyboard.bindKeyPress('Numpad2', function() smartWalk(South) end, gameRootPanel)
93
+  g_keyboard.bindKeyPress('Numpad1', function() smartWalk(SouthWest) end, gameRootPanel)
94
+  g_keyboard.bindKeyPress('Numpad4', function() smartWalk(West) end, gameRootPanel)
95
+  g_keyboard.bindKeyPress('Numpad7', function() smartWalk(NorthWest) end, gameRootPanel)
96
+
97
+  g_keyboard.bindKeyPress('Ctrl+Up', function() g_game.turn(North) changeWalkDir(North) end, gameRootPanel)
98
+  g_keyboard.bindKeyPress('Ctrl+Right', function() g_game.turn(East) changeWalkDir(East) end, gameRootPanel)
99
+  g_keyboard.bindKeyPress('Ctrl+Down', function() g_game.turn(South) changeWalkDir(South) end, gameRootPanel)
100
+  g_keyboard.bindKeyPress('Ctrl+Left', function() g_game.turn(West) changeWalkDir(West) end, gameRootPanel)
101
+  g_keyboard.bindKeyPress('Ctrl+Numpad8', function() g_game.turn(North) changeWalkDir(North) end, gameRootPanel)
102
+  g_keyboard.bindKeyPress('Ctrl+Numpad6', function() g_game.turn(East) changeWalkDir(East) end, gameRootPanel)
103
+  g_keyboard.bindKeyPress('Ctrl+Numpad2', function() g_game.turn(South) changeWalkDir(South) end, gameRootPanel)
104
+  g_keyboard.bindKeyPress('Ctrl+Numpad4', function() g_game.turn(West) changeWalkDir(West) end, gameRootPanel)
105
+  g_keyboard.bindKeyPress('Escape', function() g_game.cancelAttackAndFollow() end, gameRootPanel)
106
+  g_keyboard.bindKeyPress('Ctrl+=', function() gameMapPanel:zoomIn() end, gameRootPanel)
107
+  g_keyboard.bindKeyPress('Ctrl+-', function() gameMapPanel:zoomOut() end, gameRootPanel)
108 108
   g_keyboard.bindKeyDown('Ctrl+Q', logout, gameRootPanel)
109 109
   g_keyboard.bindKeyDown('Ctrl+L', logout, gameRootPanel)
110 110
   g_keyboard.bindKeyDown('Ctrl+W', function() g_map.cleanTexts() modules.game_textmessage.clearMessages() end, gameRootPanel)
@@ -591,7 +591,6 @@ function processMouseAction(menuPosition, mouseButton, autoWalkPos, lookThing, u
591 591
   player:stopAutoWalk()
592 592
 
593 593
   if autoWalkPos and keyboardModifiers == KeyboardNoModifier and mouseButton == MouseLeftButton then
594
-    player.onAutoWalkFail = function() modules.game_textmessage.displayFailureMessage(tr('There is no way.')) end
595 594
     player:autoWalk(autoWalkPos)
596 595
     return true
597 596
   end

+ 41
- 3
modules/game_minimap/minimap.lua View File

@@ -3,6 +3,9 @@ minimapButton = nil
3 3
 minimapWindow = nil
4 4
 otmm = true
5 5
 preloaded = false
6
+fullmapView = false
7
+oldZoom = nil
8
+oldPos = nil
6 9
 
7 10
 function init()
8 11
   minimapButton = modules.client_topmenu.addRightGameToggleButton('minimapButton', tr('Minimap') .. ' (Ctrl+M)', '/images/topbuttons/minimap', toggle)
@@ -19,6 +22,7 @@ function init()
19 22
   g_keyboard.bindKeyPress('Alt+Up', function() minimapWidget:move(0,1) end, gameRootPanel)
20 23
   g_keyboard.bindKeyPress('Alt+Down', function() minimapWidget:move(0,-1) end, gameRootPanel)
21 24
   g_keyboard.bindKeyDown('Ctrl+M', toggle)
25
+  g_keyboard.bindKeyDown('Ctrl+Shift+M', toggleFullMap)
22 26
 
23 27
   minimapWindow:setup()
24 28
 
@@ -27,6 +31,10 @@ function init()
27 31
     onGameEnd = offline,
28 32
   })
29 33
 
34
+  connect(LocalPlayer, {
35
+    onPositionChange = updateCameraPosition
36
+  })
37
+
30 38
   if g_game.isOnline() then
31 39
     online()
32 40
   end
@@ -48,6 +56,7 @@ function terminate()
48 56
   g_keyboard.unbindKeyPress('Alt+Up', gameRootPanel)
49 57
   g_keyboard.unbindKeyPress('Alt+Down', gameRootPanel)
50 58
   g_keyboard.unbindKeyDown('Ctrl+M')
59
+  g_keyboard.unbindKeyDown('Ctrl+Shift+M')
51 60
 
52 61
   minimapWindow:destroy()
53 62
   minimapButton:destroy()
@@ -74,7 +83,6 @@ end
74 83
 
75 84
 function online()
76 85
   loadMap(not preloaded)
77
-  minimapWidget:followLocalPlayer()
78 86
 end
79 87
 
80 88
 function offline()
@@ -114,6 +122,36 @@ function saveMap()
114 122
   minimapWidget:save()
115 123
 end
116 124
 
117
-function getMinimap()
118
-  return minimapWidget
125
+function updateCameraPosition()
126
+  local player = g_game.getLocalPlayer()
127
+  if not minimapWidget:isDragging() then
128
+    if not fullmapView then
129
+      minimapWidget:setCameraPosition(player:getPosition())
130
+    end
131
+    minimapWidget:setCrossPosition(player:getPosition())
132
+  end
133
+end
134
+
135
+function toggleFullMap()
136
+  if not fullmapView then
137
+    fullmapView = true
138
+    minimapWindow:hide()
139
+    minimapWidget:setParent(modules.game_interface.getRootPanel())
140
+    minimapWidget:fill('parent')
141
+    minimapWidget:setAlternativeWidgetsVisible(true)
142
+  else
143
+    fullmapView = false
144
+    minimapWidget:setParent(minimapWindow:getChildById('contentsPanel'))
145
+    minimapWidget:fill('parent')
146
+    minimapWindow:show()
147
+    minimapWidget:setAlternativeWidgetsVisible(false)
148
+  end
149
+
150
+  local zoom = oldZoom or 0
151
+  local pos = oldPos or minimapWidget:getCameraPosition()
152
+  pos.z = 7
153
+  oldZoom = minimapWidget:getZoom()
154
+  oldPos = minimapWidget:getCameraPosition()
155
+  minimapWidget:setZoom(zoom)
156
+  minimapWidget:setCameraPosition(pos)
119 157
 end

+ 2
- 1
modules/game_npctrade/npctrade.lua View File

@@ -151,7 +151,8 @@ function onTradeTypeChange(radioTabs, selected, deselected)
151 151
   ignoreCapacity:setVisible(currentTradeType == BUY)
152 152
   ignoreEquipped:setVisible(currentTradeType == SELL)
153 153
   showAllItems:setVisible(currentTradeType == SELL)
154
-  sellAllButton:setVisible(currentTradeType == SELL)
154
+  sellAllButton:setVisible(false)
155
+  --sellAllButton:setVisible(currentTradeType == SELL)
155 156
 
156 157
   refreshTradeItems()
157 158
   refreshPlayerGoods()

+ 1
- 0
modules/game_npctrade/npctrade.otui View File

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

+ 4
- 0
modules/game_textmessage/textmessage.lua View File

@@ -123,3 +123,7 @@ function clearMessages()
123 123
     end
124 124
   end
125 125
 end
126
+
127
+function LocalPlayer:onAutoWalkFail(player)
128
+  modules.game_textmessage.displayFailureMessage(tr('There is no way.'))
129
+end

+ 4
- 2
modules/game_viplist/viplist.lua View File

@@ -3,7 +3,8 @@ vipButton = nil
3 3
 addVipWindow = nil
4 4
 
5 5
 function init()
6
-  connect(g_game, { onGameEnd = clear,
6
+  connect(g_game, { onGameStart = refresh,
7
+                    onGameEnd = clear,
7 8
                     onAddVip = onAddVip,
8 9
                     onVipStateChange = onVipStateChange })
9 10
 
@@ -20,7 +21,8 @@ end
20 21
 
21 22
 function terminate()
22 23
   g_keyboard.unbindKeyDown('Ctrl+P')
23
-  disconnect(g_game, { onGameEnd = clear,
24
+  disconnect(g_game, { onGameStart = refresh,
25
+                       onGameEnd = clear,
24 26
                        onAddVip = onAddVip,
25 27
                        onVipStateChange = onVipStateChange })
26 28
 

+ 1
- 1
modules/gamelib/player.lua View File

@@ -161,4 +161,4 @@ function Player:hasState(_state, states)
161 161
     end
162 162
   end
163 163
   return false
164
-end
164
+end

+ 98
- 155
modules/gamelib/ui/uiminimap.lua View File

@@ -1,45 +1,39 @@
1 1
 function UIMinimap:onSetup()
2 2
   self.flagWindow = nil
3
-  self.flagsWidget = self:getChildById('flags')
4 3
   self.floorUpWidget = self:getChildById('floorUp')
5 4
   self.floorDownWidget = self:getChildById('floorDown')
6 5
   self.zoomInWidget = self:getChildById('zoomIn')
7 6
   self.zoomOutWidget = self:getChildById('zoomOut')
8
-  self.dx = 0
9
-  self.dy = 0
7
+  self.flags = {}
8
+  self.alternatives = {}
10 9
   self.autowalk = true
11
-  self.allowFollowLocalPlayer = true
12
-  self.onPositionChange = function() self:followLocalPlayer() end
13 10
   self.onAddAutomapFlag = function(pos, icon, description) self:addFlag(pos, icon, description) end
14 11
   self.onRemoveAutomapFlag = function(pos, icon, description) self:removeFlag(pos, icon, description) end
15 12
   connect(g_game, {
16 13
     onAddAutomapFlag = self.onAddAutomapFlag,
17 14
     onRemoveAutomapFlag = self.onRemoveAutomapFlag,
18 15
   })
19
-  connect(LocalPlayer, { onPositionChange = self.onPositionChange })
20 16
 end
21 17
 
22 18
 function UIMinimap:onDestroy()
23
-  disconnect(LocalPlayer, { onPositionChange = self.onPositionChange })
19
+  for _,widget in pairs(self.alternatives) do
20
+    widget:destroy()
21
+  end
22
+  self.alternatives = {}
24 23
   disconnect(g_game, {
25 24
     onAddAutomapFlag = self.onAddAutomapFlag,
26 25
     onRemoveAutomapFlag = self.onRemoveAutomapFlag,
27 26
   })
28 27
   self:destroyFlagWindow()
29
-  self:destroyFullPanel()
28
+  self.flags = {}
30 29
 end
31 30
 
32 31
 function UIMinimap:onVisibilityChange()
33 32
   if not self:isVisible() then
34 33
     self:destroyFlagWindow()
35
-    self:destroyFullPanel()
36 34
   end
37 35
 end
38 36
 
39
-function UIMinimap:hideFlags()
40
-  self.flagsWidget:hide()
41
-end
42
-
43 37
 function UIMinimap:hideFloor()
44 38
   self.floorUpWidget:hide()
45 39
   self.floorDownWidget:hide()
@@ -54,33 +48,21 @@ function UIMinimap:disableAutoWalk()
54 48
   self.autowalk = false
55 49
 end
56 50
 
57
-function UIMinimap:disableFollowPlayer()
58
-  self.allowFollowLocalPlayer = false
59
-end
60
-
61
-function UIMinimap:enableFullPanel(image)
62
-  self.fullImage = image
63
-end
64
-
65 51
 function UIMinimap:load()
66 52
   local settings = g_settings.getNode('Minimap')
67 53
   if settings then
68 54
     if settings.flags then
69
-      for i=1,#settings.flags do
70
-        local flag = settings.flags[i]
55
+      for _,flag in pairs(settings.flags) do
71 56
         self:addFlag(flag.position, flag.icon, flag.description)
72 57
       end
73 58
     end
74 59
     self:setZoom(settings.zoom)
75 60
   end
76
-  self:updateFlags()
77 61
 end
78 62
 
79 63
 function UIMinimap:save()
80 64
   local settings = { flags={} }
81
-  local children = self.flagsWidget:getChildren()
82
-  for i=1,#children do
83
-    local flag = children[i]
65
+  for _,flag in pairs(self.flags) do
84 66
     table.insert(settings.flags, {
85 67
       position = flag.pos,
86 68
       icon = flag.icon,
@@ -91,134 +73,114 @@ function UIMinimap:save()
91 73
   g_settings.setNode('Minimap', settings)
92 74
 end
93 75
 
76
+local function onFlagMouseRelease(widget, pos, button)
77
+  if button == MouseRightButton then
78
+    local menu = g_ui.createWidget('PopupMenu')
79
+    menu:addOption(tr('Delete mark'), function() widget:destroy() end)
80
+    menu:display(pos)
81
+    return true
82
+  end
83
+  return false
84
+end
85
+
86
+function UIMinimap:setCrossPosition(pos)
87
+  local cross = self.cross
88
+  if not self.cross then
89
+    cross = g_ui.createWidget('MinimapCross', self)
90
+    cross:setIcon('/images/game/minimap/cross')
91
+    self.cross = cross
92
+  end
93
+
94
+  cross.pos = pos
95
+  if pos then
96
+    self:centerInPosition(cross, pos)
97
+  else
98
+    cross:breakAnchors()
99
+  end
100
+end
101
+
94 102
 function UIMinimap:addFlag(pos, icon, description)
103
+  if not pos or not icon then return end
95 104
   local flag = self:getFlag(pos, icon, description)
96
-  if flag then
105
+  if flag or not icon then
97 106
     return
98 107
   end
99
-  
100
-  flag = g_ui.createWidget('MinimapFlag', self.flagsWidget)
108
+
109
+  flag = g_ui.createWidget('MinimapFlag', self)
101 110
   flag.pos = pos
102
-  flag.icon = icon
103 111
   flag.description = description
112
+  flag.icon = icon
104 113
   flag:setIcon('/images/game/minimap/flag' .. icon)
105 114
   flag:setTooltip(description)
106
-  flag.onMouseRelease = function(widget, pos, button)
107
-    if button == MouseRightButton then
108
-      local menu = g_ui.createWidget('PopupMenu')
109
-      menu:addOption(tr('Delete mark'), function() widget:destroy() end)
110
-      menu:display(pos)
111
-      return true
115
+  flag.onMouseRelease = onFlagMouseRelease
116
+  table.insert(self.flags, flag)
117
+  self:centerInPosition(flag, pos)
118
+end
119
+
120
+function UIMinimap:addAlternativeWidget(widget, pos, maxZoom)
121
+  widget.pos = pos
122
+  widget.maxZoom = maxZoom or 0
123
+  widget.minZoom = minZoom
124
+  table.insert(self.alternatives, widget)
125
+end
126
+
127
+function UIMinimap:setAlternativeWidgetsVisible(show)
128
+  local layout = self:getLayout()
129
+  layout:disableUpdates()
130
+  for _,widget in pairs(self.alternatives) do
131
+    if show then
132
+      self:addChild(widget)
133
+      self:centerInPosition(widget, widget.pos)
134
+    else
135
+      self:removeChild(widget)
112 136
     end
113
-    return false
114 137
   end
138
+  layout:enableUpdates()
139
+  layout:update()
140
+end
115 141
 
116
-  self:updateFlag(flag)
142
+function UIMinimap:onZoomChange(zoom)
143
+  for _,widget in pairs(self.alternatives) do
144
+    if (not widget.minZoom or widget.minZoom >= zoom) and widget.maxZoom <= zoom then
145
+      widget:show()
146
+    else
147
+      widget:hide()
148
+    end
149
+  end
117 150
 end
118 151
 
119
-function UIMinimap:getFlag(pos, icon, description)
120
-  local children = self.flagsWidget:getChildren()
121
-  for i=1,#children do
122
-    local flag = children[i]
152
+function UIMinimap:getFlag(pos)
153
+  for _,flag in pairs(self.flags) do
123 154
     if flag.pos.x == pos.x and flag.pos.y == pos.y and flag.pos.z == pos.z then
124 155
       return flag
125 156
     end
126 157
   end
158
+  return nil
127 159
 end
128 160
 
129 161
 function UIMinimap:removeFlag(pos, icon, description)
130
-  local flag = self:getFlag(pos, icon, description)
162
+  local flag = self:getFlag(pos)
131 163
   if flag then
132 164
     flag:destroy()
133
-  end
134
-end
135
-
136
-function UIMinimap:updateFlag(flag)
137
-  local point = self:getPoint(flag.pos)
138
-  if self:containsPoint(point) and self:getZoom() >= 0 and flag.pos.z == self:getCameraPosition().z then
139
-    flag:setVisible(true)
140
-    flag:setMarginLeft(point.x - self:getX() - flag:getWidth()/2)
141
-    flag:setMarginTop(point.y - self:getY() - flag:getHeight()/2)
142
-  else
143
-    flag:setVisible(false)   
144
-  end
145
-end
146
-
147
-function UIMinimap:updateFlags()
148
-  local children = self.flagsWidget:getChildren()
149
-  for i=1,#children do
150
-    self:updateFlag(children[i])
151
-  end
152
-end
153
-
154
-UIMinimap.realSetCameraPosition = UIMinimap.realSetCameraPosition or UIMinimap.setCameraPosition
155
-function UIMinimap:setCameraPosition(pos)
156
-  self:realSetCameraPosition(pos)
157
-  self:updateFlags()
158
-end
159
-
160
-UIMinimap.realZoomIn = UIMinimap.realZoomIn or UIMinimap.zoomIn
161
-function UIMinimap:zoomIn()
162
-  self:realZoomIn()
163
-  self:updateFlags()
164
-end
165
-
166
-UIMinimap.realZoomOut = UIMinimap.realZoomOut or UIMinimap.zoomOut
167
-function UIMinimap:zoomOut()
168
-  self:realZoomOut()
169
-  self:updateFlags()
170
-end
171
-
172
-UIMinimap.realSetZoom = UIMinimap.realSetZoom or UIMinimap.setZoom
173
-function UIMinimap:setZoom(zoom)
174
-  self:realSetZoom(zoom)
175
-  self:updateFlags()
176
-end
177
-
178
-function UIMinimap:floorUp(floors)
179
-  local pos = self:getCameraPosition()
180
-  pos.z = pos.z - floors
181
-  if pos.z >= FloorHigher then
182
-    self:setCameraPosition(pos)
183
-  end
184
-  self:updateFlags()
185
-end
186
-
187
-function UIMinimap:floorDown(floors)
188
-  local pos = self:getCameraPosition()
189
-  pos.z = pos.z + floors
190
-  if pos.z <= FloorLower then
191
-    self:setCameraPosition(pos)
192
-  end
193
-  self:updateFlags()
194
-end
195
-
196
-function UIMinimap:followLocalPlayer()
197
-  if not self:isDragging() and self.allowFollowLocalPlayer then
198
-    local player = g_game.getLocalPlayer()
199
-    self:followCreature(player)
200
-    self:updateFlags()
165
+    table.removevalue(self.flags, flag)
201 166
   end
202 167
 end
203 168
 
204 169
 function UIMinimap:reset()
205
-  self:followLocalPlayer()
206 170
   self:setZoom(0)
171
+  if self.cross then
172
+    self:setCameraPosition(self.cross.pos)
173
+  end
207 174
 end
208 175
 
209 176
 function UIMinimap:move(x, y)
210
-  local topLeft, bottomRight = self:getArea()
211
-  self.dx = self.dx + ((bottomRight.x - topLeft.x) / self:getWidth() ) * x
212
-  self.dy = self.dy + ((bottomRight.y - topLeft.y) / self:getHeight()) * y
213
-  local dx = math.floor(self.dx)
214
-  local dy = math.floor(self.dy)
215
-  self.dx = self.dx - dx
216
-  self.dy = self.dy - dy
217
-
218 177
   local cameraPos = self:getCameraPosition()
178
+  local scale = self:getScale()
179
+  if scale > 1 then scale = 1 end
180
+  local dx = x/scale
181
+  local dy = y/scale
219 182
   local pos = {x = cameraPos.x - dx, y = cameraPos.y - dy, z = cameraPos.z}
220 183
   self:setCameraPosition(pos)
221
-  self:updateFlags()
222 184
 end
223 185
 
224 186
 function UIMinimap:onMouseWheel(mousePos, direction)
@@ -232,7 +194,6 @@ function UIMinimap:onMouseWheel(mousePos, direction)
232 194
   elseif direction == MouseWheelUp and keyboardModifiers == KeyboardCtrlModifier then
233 195
     self:floorDown(1)
234 196
   end
235
-  self:updateFlags()
236 197
 end
237 198
 
238 199
 function UIMinimap:onMousePress(pos, button)
@@ -245,20 +206,18 @@ function UIMinimap:onMouseRelease(pos, button)
245 206
   if not self.allowNextRelease then return true end
246 207
   self.allowNextRelease = false
247 208
 
248
-  local mapPos = self:getPosition(pos)
209
+  local mapPos = self:getTilePosition(pos)
249 210
   if not mapPos then return end
250 211
 
251 212
   if button == MouseLeftButton then
252 213
     local player = g_game.getLocalPlayer()
253 214
     if self.autowalk then
254
-      player.onAutoWalkFail = function() modules.game_textmessage.displayFailureMessage(tr('There is no way.')) end
255 215
       player:autoWalk(mapPos)
256 216
     end
257 217
     return true
258 218
   elseif button == MouseRightButton then
259 219
     local menu = g_ui.createWidget('PopupMenu')
260 220
     menu:addOption(tr('Create mark'), function() self:createFlagWindow(mapPos) end)
261
-    if self.fullImage then menu:addOption(tr('Full map'), function() self:createFullPanel() end) end
262 221
     menu:display(pos)
263 222
     return true
264 223
   end
@@ -266,11 +225,17 @@ function UIMinimap:onMouseRelease(pos, button)
266 225
 end
267 226
 
268 227
 function UIMinimap:onDragEnter(pos)
228
+  self.dragReference = pos
229
+  self.dragCameraReference = self:getCameraPosition()
269 230
   return true
270 231
 end
271 232
 
272 233
 function UIMinimap:onDragMove(pos, moved)
273
-  self:move(moved.x, moved.y)
234
+  local scale = self:getScale()
235
+  local dx = (self.dragReference.x - pos.x)/scale
236
+  local dy = (self.dragReference.y - pos.y)/scale
237
+  local pos = {x = self.dragCameraReference.x + dx, y = self.dragCameraReference.y + dy, z = self.dragCameraReference.z}
238
+  self:setCameraPosition(pos)
274 239
   return true
275 240
 end
276 241
 
@@ -278,20 +243,6 @@ function UIMinimap:onDragLeave(widget, pos)
278 243
   return true
279 244
 end
280 245
 
281
-function UIMinimap:createFullPanel()
282
-  self.fullPanel = g_ui.createWidget('MinimapFullPanel', rootWidget)
283
-  self.fullPanel.onDestroy = function() self.fullPanel = nil end
284
-  local image = self.fullPanel:getChildById('image')
285
-  image:setImage(self.fullImage)
286
-end
287
-
288
-function UIMinimap:destroyFullPanel()
289
-  if self.fullPanel then
290
-    self.fullPanel:destroy()
291
-    self.fullPanel = nil
292
-  end
293
-end
294
-
295 246
 function UIMinimap:createFlagWindow(pos)
296 247
   if self.flagWindow then return end
297 248
   if not pos then return end
@@ -311,16 +262,14 @@ function UIMinimap:createFlagWindow(pos)
311 262
     checkbox.icon = i
312 263
     flagRadioGroup:addWidget(checkbox)
313 264
   end
314
-  
265
+
315 266
   flagRadioGroup:selectWidget(flagRadioGroup:getFirstWidget())
316
-  
267
+
317 268
   okButton.onClick = function() 
318
-      self:addFlag(pos, flagRadioGroup:getSelectedWidget().icon, description:getText())
319
-      self:destroyFlagWindow()
320
-    end
321
-  cancelButton.onClick = function()
322
-      self:destroyFlagWindow()
323
-    end
269
+    self:addFlag(pos, flagRadioGroup:getSelectedWidget().icon, description:getText())
270
+    self:destroyFlagWindow()
271
+  end
272
+  cancelButton.onClick = function() self:destroyFlagWindow() end
324 273
 
325 274
   self.flagWindow.onDestroy = function() flagRadioGroup:destroy() end
326 275
 end
@@ -331,9 +280,3 @@ function UIMinimap:destroyFlagWindow()
331 280
     self.flagWindow = nil
332 281
   end
333 282
 end
334
-
335
-function UIMinimap:getArea()
336
-  local topLeft = self:getPosition({ x = self:getX() + 1, y = self:getY() + 1 })
337
-  local bottomRight = self:getPosition({ x = self:getX() + self:getWidth() - 2, y = self:getY() + self:getHeight() - 2 })
338
-  return topLeft, bottomRight
339
-end

+ 2
- 0
src/client/CMakeLists.txt View File

@@ -98,6 +98,8 @@ set(client_SOURCES ${client_SOURCES}
98 98
     ${CMAKE_CURRENT_LIST_DIR}/uiminimap.h
99 99
     ${CMAKE_CURRENT_LIST_DIR}/uiprogressrect.cpp
100 100
     ${CMAKE_CURRENT_LIST_DIR}/uiprogressrect.h
101
+    ${CMAKE_CURRENT_LIST_DIR}/uimapanchorlayout.cpp
102
+    ${CMAKE_CURRENT_LIST_DIR}/uimapanchorlayout.h
101 103
 
102 104
     # util
103 105
     ${CMAKE_CURRENT_LIST_DIR}/position.h

+ 4
- 0
src/client/declarations.h View File

@@ -94,11 +94,15 @@ class UICreature;
94 94
 class UIMap;
95 95
 class UIMinimap;
96 96
 class UIProgressRect;
97
+class UIMapAnchorLayout;
98
+class UIPositionAnchor;
97 99
 
98 100
 typedef stdext::shared_object_ptr<UIItem> UIItemPtr;
99 101
 typedef stdext::shared_object_ptr<UICreature> UICreaturePtr;
100 102
 typedef stdext::shared_object_ptr<UIMap> UIMapPtr;
101 103
 typedef stdext::shared_object_ptr<UIMinimap> UIMinimapPtr;
102 104
 typedef stdext::shared_object_ptr<UIProgressRect> UIProgressRectPtr;
105
+typedef stdext::shared_object_ptr<UIMapAnchorLayout> UIMapAnchorLayoutPtr;
106
+typedef stdext::shared_object_ptr<UIPositionAnchor> UIPositionAnchorPtr;
103 107
 
104 108
 #endif

+ 14
- 7
src/client/luafunctions.cpp View File

@@ -45,6 +45,7 @@
45 45
 #include "uicreature.h"
46 46
 #include "uimap.h"
47 47
 #include "uiminimap.h"
48
+#include "uimapanchorlayout.h"
48 49
 #include "uiprogressrect.h"
49 50
 #include "outfit.h"
50 51
 
@@ -121,6 +122,8 @@ void Client::registerLuaFunctions()
121 122
 
122 123
     g_lua.registerSingletonClass("g_minimap");
123 124
     g_lua.bindSingletonFunction("g_minimap", "clean", &Minimap::clean, &g_minimap);
125
+    g_lua.bindSingletonFunction("g_minimap", "loadImage", &Minimap::loadImage, &g_minimap);
126
+    g_lua.bindSingletonFunction("g_minimap", "saveImage", &Minimap::saveImage, &g_minimap);
124 127
     g_lua.bindSingletonFunction("g_minimap", "loadOtmm", &Minimap::loadOtmm, &g_minimap);
125 128
     g_lua.bindSingletonFunction("g_minimap", "saveOtmm", &Minimap::saveOtmm, &g_minimap);
126 129
 
@@ -597,20 +600,24 @@ void Client::registerLuaFunctions()
597 600
     g_lua.bindClassMemberFunction<UIMinimap>("setMixZoom", &UIMinimap::setMinZoom);
598 601
     g_lua.bindClassMemberFunction<UIMinimap>("setMaxZoom", &UIMinimap::setMaxZoom);
599 602
     g_lua.bindClassMemberFunction<UIMinimap>("setCameraPosition", &UIMinimap::setCameraPosition);
600
-    g_lua.bindClassMemberFunction<UIMinimap>("setCross", &UIMinimap::setCross);
601
-    g_lua.bindClassMemberFunction<UIMinimap>("followCreature", &UIMinimap::followCreature);
602
-    g_lua.bindClassMemberFunction<UIMinimap>("getPoint", &UIMinimap::getPoint);
603
-    g_lua.bindClassMemberFunction<UIMinimap>("getPosition", &UIMinimap::getPosition);
603
+    g_lua.bindClassMemberFunction<UIMinimap>("floorUp", &UIMinimap::floorUp);
604
+    g_lua.bindClassMemberFunction<UIMinimap>("floorDown", &UIMinimap::floorDown);
605
+    g_lua.bindClassMemberFunction<UIMinimap>("getTilePoint", &UIMinimap::getTilePoint);
606
+    g_lua.bindClassMemberFunction<UIMinimap>("getTilePosition", &UIMinimap::getTilePosition);
607
+    g_lua.bindClassMemberFunction<UIMinimap>("getTileRect", &UIMinimap::getTileRect);
604 608
     g_lua.bindClassMemberFunction<UIMinimap>("getCameraPosition", &UIMinimap::getCameraPosition);
605
-    g_lua.bindClassMemberFunction<UIMinimap>("getFollowingCreature", &UIMinimap::getFollowingCreature);
606 609
     g_lua.bindClassMemberFunction<UIMinimap>("getMinZoom", &UIMinimap::getMinZoom);
607 610
     g_lua.bindClassMemberFunction<UIMinimap>("getMaxZoom", &UIMinimap::getMaxZoom);
608 611
     g_lua.bindClassMemberFunction<UIMinimap>("getZoom", &UIMinimap::getZoom);
609
-    g_lua.bindClassMemberFunction<UIMinimap>("getCross", &UIMinimap::getCross);
610
-    g_lua.bindClassMemberFunction<UIMinimap>("getScale", &UIMinimap::getScale);
612
+    g_lua.bindClassMemberFunction<UIMinimap>("getScale", &UIMinimap::getScale); 
613
+    g_lua.bindClassMemberFunction<UIMinimap>("anchorPosition", &UIMinimap::anchorPosition);
614
+    g_lua.bindClassMemberFunction<UIMinimap>("fillPosition", &UIMinimap::fillPosition);
615
+    g_lua.bindClassMemberFunction<UIMinimap>("centerInPosition", &UIMinimap::centerInPosition);
611 616
 
612 617
     g_lua.registerClass<UIProgressRect, UIWidget>();
613 618
     g_lua.bindClassStaticFunction<UIProgressRect>("create", []{ return UIProgressRectPtr(new UIProgressRect); } );
614 619
     g_lua.bindClassMemberFunction<UIProgressRect>("setPercent", &UIProgressRect::setPercent);
615 620
     g_lua.bindClassMemberFunction<UIProgressRect>("getPercent", &UIProgressRect::getPercent);
621
+
622
+    g_lua.registerClass<UIMapAnchorLayout, UIAnchorLayout>();
616 623
 }

+ 2
- 0
src/client/map.cpp View File

@@ -621,6 +621,8 @@ std::tuple<std::vector<Otc::Direction>, Otc::PathFindResult> Map::findPath(const
621 621
                     wasSeen = mtile.hasFlag(MinimapTileWasSeen);
622 622
                     isNotWalkable = mtile.hasFlag(MinimapTileNotWalkable);
623 623
                     isNotPathable = mtile.hasFlag(MinimapTileNotPathable);
624
+                    if(isNotWalkable || isNotPathable)
625
+                        wasSeen = true;
624 626
                     speed = mtile.getSpeed();
625 627
                 }
626 628
 

+ 115
- 22
src/client/minimap.cpp View File

@@ -23,13 +23,14 @@
23 23
 
24 24
 #include "minimap.h"
25 25
 #include "tile.h"
26
+
26 27
 #include <framework/graphics/image.h>
27 28
 #include <framework/graphics/texture.h>
28 29
 #include <framework/graphics/painter.h>
30
+#include <framework/graphics/image.h>
29 31
 #include <framework/graphics/framebuffermanager.h>
30 32
 #include <framework/core/resourcemanager.h>
31 33
 #include <framework/core/filestream.h>
32
-#include <boost/concept_check.hpp>
33 34
 #include <zlib.h>
34 35
 
35 36
 Minimap g_minimap;
@@ -51,10 +52,14 @@ void MinimapBlock::update()
51 52
     bool shouldDraw = false;
52 53
     for(int x=0;x<MMBLOCK_SIZE;++x) {
53 54
         for(int y=0;y<MMBLOCK_SIZE;++y) {
54
-            uint32 col = Color::from8bit(getTile(x, y).color).rgba();
55
-            image->setPixel(x, y, col);
56
-            if(col != 0)
55
+            uint8 c = getTile(x, y).color;
56
+            uint32 col;
57
+            if(c != 255) {
58
+                col = Color::from8bit(c).rgba();
57 59
                 shouldDraw = true;
60
+            } else
61
+                col = Color::alpha.rgba();
62
+            image->setPixel(x, y, col);
58 63
         }
59 64
     }
60 65
 
@@ -93,14 +98,14 @@ void Minimap::clean()
93 98
         m_tileBlocks[i].clear();
94 99
 }
95 100
 
96
-void Minimap::draw(const Rect& screenRect, const Position& mapCenter, float scale)
101
+void Minimap::draw(const Rect& screenRect, const Position& mapCenter, float scale, const Color& color)
97 102
 {
98 103
     if(screenRect.isEmpty())
99 104
         return;
100 105
 
101 106
     Rect mapRect = calcMapRect(screenRect, mapCenter, scale);
102 107
     g_painter->saveState();
103
-    g_painter->setColor(Color::black);
108
+    g_painter->setColor(color);
104 109
     g_painter->drawFilledRect(screenRect);
105 110
     g_painter->resetColor();
106 111
     g_painter->setClipRect(screenRect);
@@ -137,15 +142,16 @@ void Minimap::draw(const Rect& screenRect, const Position& mapCenter, float scal
137 142
                 tex->setSmooth(scale < 1.0f);
138 143
                 g_painter->drawTexturedRect(dest, tex, src);
139 144
             }
145
+            //g_painter->drawBoundingRect(Rect(xs,ys, MMBLOCK_SIZE * scale, MMBLOCK_SIZE * scale));
140 146
         }
141 147
     }
142 148
 
143 149
     g_painter->restoreSavedState();
144 150
 }
145 151
 
146
-Point Minimap::getPoint(const Position& pos, const Rect& screenRect, const Position& mapCenter, float scale)
152
+Point Minimap::getTilePoint(const Position& pos, const Rect& screenRect, const Position& mapCenter, float scale)
147 153
 {
148
-    if(screenRect.isEmpty() || MMBLOCK_SIZE*scale <= 1 || !mapCenter.isMapPosition())
154
+    if(screenRect.isEmpty() || pos.z != mapCenter.z)
149 155
         return Point(-1,-1);
150 156
 
151 157
     Rect mapRect = calcMapRect(screenRect, mapCenter, scale);
@@ -154,9 +160,9 @@ Point Minimap::getPoint(const Position& pos, const Rect& screenRect, const Posit
154 160
     return posoff + screenRect.topLeft() - off + (Point(1,1)*scale)/2;
155 161
 }
156 162
 
157
-Position Minimap::getPosition(const Point& point, const Rect& screenRect, const Position& mapCenter, float scale)
163
+Position Minimap::getTilePosition(const Point& point, const Rect& screenRect, const Position& mapCenter, float scale)
158 164
 {
159
-    if(screenRect.isEmpty() || MMBLOCK_SIZE*scale <= 1 || !mapCenter.isMapPosition())
165
+    if(screenRect.isEmpty())
160 166
         return Position();
161 167
 
162 168
     Rect mapRect = calcMapRect(screenRect, mapCenter, scale);
@@ -165,24 +171,25 @@ Position Minimap::getPosition(const Point& point, const Rect& screenRect, const
165 171
     return Position(pos2d.x, pos2d.y, mapCenter.z);
166 172
 }
167 173
 
174
+Rect Minimap::getTileRect(const Position& pos, const Rect& screenRect, const Position& mapCenter, float scale)
175
+{
176
+    if(screenRect.isEmpty() || pos.z != mapCenter.z)
177
+        return Rect();
178
+
179
+    int tileSize = 32 * scale;
180
+    Rect tileRect(0,0,tileSize, tileSize);
181
+    tileRect.moveCenter(getTilePoint(pos, screenRect, mapCenter, scale));
182
+    return tileRect;
183
+}
184
+
168 185
 Rect Minimap::calcMapRect(const Rect& screenRect, const Position& mapCenter, float scale)
169 186
 {
170
-    int w, h;
171
-    do {
172
-        w = std::ceil(screenRect.width() / scale);
173
-        h = std::ceil(screenRect.height() / scale);
174
-        if(w % 2 == 0)
175
-            w++;
176
-        if(h % 2 == 0)
177
-            h++;
178
-        scale *= 2;
179
-    } while(w > 8192 || h > 8192);
187
+    int w = screenRect.width() / scale, h = std::ceil(screenRect.height() / scale);
180 188
     Rect mapRect(0,0,w,h);
181 189
     mapRect.moveCenter(Point(mapCenter.x, mapCenter.y));
182 190
     return mapRect;
183 191
 }
184 192
 
185
-
186 193
 void Minimap::updateTile(const Position& pos, const TilePtr& tile)
187 194
 {
188 195
     MinimapTile minimapTile;
@@ -200,6 +207,7 @@ void Minimap::updateTile(const Position& pos, const TilePtr& tile)
200 207
         MinimapBlock& block = getBlock(pos);
201 208
         Point offsetPos = getBlockOffset(Point(pos.x, pos.y));
202 209
         block.updateTile(pos.x - offsetPos.x, pos.y - offsetPos.y, minimapTile);
210
+        block.justSaw();
203 211
     }
204 212
 }
205 213
 
@@ -214,6 +222,87 @@ const MinimapTile& Minimap::getTile(const Position& pos)
214 222
     return nulltile;
215 223
 }
216 224
 
225
+bool Minimap::loadImage(const std::string& fileName, const Position& topLeft, float colorFactor)
226
+{
227
+    if(colorFactor <= 0.01f)
228
+        colorFactor = 1.0f;
229
+
230
+    try {
231
+        ImagePtr image = Image::load(fileName);
232
+
233
+        uint8 waterc = Color::to8bit(std::string("#3300cc"));
234
+
235
+        // non pathable colors
236
+        Color nonPathableColors[] = {
237
+            std::string("#ffff00"), // yellow
238
+        };
239
+
240
+        // non walkable colors
241
+        Color nonWalkableColors[] = {
242
+            std::string("#000000"), // oil, black
243
+            std::string("#006600"), // trees, dark green
244
+            std::string("#ff3300"), // walls, red
245
+            std::string("#666666"), // mountain, grey
246
+            std::string("#ff6600"), // lava, orange
247
+            std::string("#00ff00"), // positon
248
+            std::string("#ccffff"), // ice, very light blue
249
+        };
250
+
251
+        for(int y=0;y<image->getHeight();++y) {
252
+            for(int x=0;x<image->getWidth();++x) {
253
+                Color color = *(uint32*)image->getPixel(x,y);
254
+                uint8 c = Color::to8bit(color * colorFactor);
255
+                int flags = 0;
256
+
257
+                if(c == waterc || color.a() == 0) {
258
+                    flags |= MinimapTileNotWalkable;
259
+                    c = 255; // alpha
260
+                }
261
+
262
+                if(flags != 0) {
263
+                    for(Color &col : nonWalkableColors) {
264
+                        if(col == color) {
265
+                            flags |= MinimapTileNotWalkable;
266
+                            break;
267
+                        }
268
+                    }
269
+                }
270
+
271
+                if(flags != 0) {
272
+                    for(Color &col : nonPathableColors) {
273
+                        if(col == color) {
274
+                            flags |= MinimapTileNotPathable;
275
+                            break;
276
+                        }
277
+                    }
278
+                }
279
+
280
+                if(c == 255)
281
+                    continue;
282
+
283
+                Position pos(topLeft.x + x, topLeft.y + y, topLeft.z);
284
+                MinimapBlock& block = getBlock(pos);
285
+                Point offsetPos = getBlockOffset(Point(pos.x, pos.y));
286
+                MinimapTile& tile = block.getTile(pos.x - offsetPos.x, pos.y - offsetPos.y);
287
+                if(!(tile.flags & MinimapTileWasSeen)) {
288
+                    tile.color = c;
289
+                    tile.flags = flags;
290
+                    block.mustUpdate();
291
+                }
292
+            }
293
+        }
294
+        return true;
295
+    } catch(stdext::exception& e) {
296
+        g_logger.error(stdext::format("failed to load OTMM minimap: %s", e.what()));
297
+        return false;
298
+    }
299
+}
300
+
301
+void Minimap::saveImage(const std::string& fileName, const Rect& mapRect)
302
+{
303
+    //TODO
304
+}
305
+
217 306
 bool Minimap::loadOtmm(const std::string& fileName)
218 307
 {
219 308
     try {
@@ -263,6 +352,7 @@ bool Minimap::loadOtmm(const std::string& fileName)
263 352
             assert(ret == Z_OK);
264 353
             assert(destLen == blockSize);
265 354
             block.mustUpdate();
355
+            block.justSaw();
266 356
         }
267 357
 
268 358
         fin->close();
@@ -307,6 +397,9 @@ void Minimap::saveOtmm(const std::string& fileName)
307 397
             for(auto& it : m_tileBlocks[z]) {
308 398
                 int index = it.first;
309 399
                 MinimapBlock& block = it.second;
400
+                if(!block.wasSeen())
401
+                    continue;
402
+
310 403
                 Position pos = getIndexPosition(index, z);
311 404
                 fin->addU16(pos.x);
312 405
                 fin->addU16(pos.y);
@@ -319,7 +412,7 @@ void Minimap::saveOtmm(const std::string& fileName)
319 412
                 fin->write(compressBuffer.data(), len);
320 413
             }
321 414
         }
322
-
415
+    
323 416
         // end of file
324 417
         Position invalidPos;
325 418
         fin->addU16(invalidPos.x);

+ 10
- 4
src/client/minimap.h View File

@@ -42,7 +42,7 @@ enum MinimapTileFlags {
42 42
 #pragma pack(push,1) // disable memory alignment
43 43
 struct MinimapTile
44 44
 {
45
-    MinimapTile() : flags(0), color(0), speed(10) { }
45
+    MinimapTile() : flags(0), color(255), speed(10) { }
46 46
     uint8 flags;
47 47
     uint8 color;
48 48
     uint8 speed;
@@ -64,10 +64,13 @@ public:
64 64
     const TexturePtr& getTexture() { return m_texture; }
65 65
     std::array<MinimapTile, MMBLOCK_SIZE *MMBLOCK_SIZE>& getTiles() { return m_tiles; }
66 66
     void mustUpdate() { m_mustUpdate = true; }
67
+    void justSaw() { m_wasSeen = true; }
68
+    bool wasSeen() { return m_wasSeen; }
67 69
 private:
68 70
     TexturePtr m_texture;
69 71
     std::array<MinimapTile, MMBLOCK_SIZE *MMBLOCK_SIZE> m_tiles;
70 72
     stdext::boolean<true> m_mustUpdate;
73
+    stdext::boolean<false> m_wasSeen;
71 74
 };
72 75
 
73 76
 #pragma pack(pop)
@@ -81,13 +84,16 @@ public:
81 84
 
82 85
     void clean();
83 86
 
84
-    void draw(const Rect& screenRect, const Position& mapCenter, float scale);
85
-    Point getPoint(const Position& pos, const Rect& screenRect, const Position& mapCenter, float scale);
86
-    Position getPosition(const Point& point, const Rect& screenRect, const Position& mapCenter, float scale);
87
+    void draw(const Rect& screenRect, const Position& mapCenter, float scale, const Color& color);
88
+    Point getTilePoint(const Position& pos, const Rect& screenRect, const Position& mapCenter, float scale);
89
+    Position getTilePosition(const Point& point, const Rect& screenRect, const Position& mapCenter, float scale);
90
+    Rect getTileRect(const Position& pos, const Rect& screenRect, const Position& mapCenter, float scale);
87 91
 
88 92
     void updateTile(const Position& pos, const TilePtr& tile);
89 93
     const MinimapTile& getTile(const Position& pos);
90 94
 
95
+    bool loadImage(const std::string& fileName, const Position& topLeft, float colorFactor);
96
+    void saveImage(const std::string& fileName, const Rect& mapRect);
91 97
     bool loadOtmm(const std::string& fileName);
92 98
     void saveOtmm(const std::string& fileName);
93 99
 

+ 12
- 1
src/client/protocolgameparse.cpp View File

@@ -763,6 +763,9 @@ void ProtocolGame::parseMagicEffect(const InputMessagePtr& msg)
763 763
     else
764 764
         effectId = msg->getU8();
765 765
 
766
+    if(!g_things.isValidDatId(effectId, ThingCategoryEffect))
767
+        g_logger.traceError("invalid effect id");
768
+
766 769
     EffectPtr effect = EffectPtr(new Effect());
767 770
     effect->setId(effectId);
768 771
     g_map.addThing(effect, pos);
@@ -786,6 +789,9 @@ void ProtocolGame::parseDistanceMissile(const InputMessagePtr& msg)
786 789
     Position toPos = getPosition(msg);
787 790
     int shotId = msg->getU8();
788 791
 
792
+    if(!g_things.isValidDatId(shotId, ThingCategoryMissile))
793
+        g_logger.traceError("invalid effect id");
794
+
789 795
     MissilePtr missile = MissilePtr(new Missile());
790 796
     missile->setId(shotId);
791 797
     missile->setPath(fromPos, toPos);
@@ -1642,6 +1648,9 @@ Outfit ProtocolGame::getOutfit(const InputMessagePtr& msg)
1642 1648
         int feet = msg->getU8();
1643 1649
         int addons = msg->getU8();
1644 1650
 
1651
+        if(!g_things.isValidDatId(lookType, ThingCategoryCreature))
1652
+            lookType = 0;
1653
+
1645 1654
         outfit.setId(lookType);
1646 1655
         outfit.setHead(head);
1647 1656
         outfit.setBody(body);
@@ -1653,9 +1662,11 @@ Outfit ProtocolGame::getOutfit(const InputMessagePtr& msg)
1653 1662
         int lookTypeEx = msg->getU16();
1654 1663
         if(lookTypeEx == 0) {
1655 1664
             outfit.setCategory(ThingCategoryEffect);
1656
-            outfit.setAuxId(13);
1665
+            outfit.setAuxId(13); // invisible effect id
1657 1666
         }
1658 1667
         else {
1668
+            if(!g_things.isValidDatId(lookTypeEx, ThingCategoryItem))
1669
+                lookTypeEx = 0;
1659 1670
             outfit.setCategory(ThingCategoryItem);
1660 1671
             outfit.setAuxId(lookTypeEx);
1661 1672
         }

+ 9
- 1
src/client/thingtype.cpp View File

@@ -164,9 +164,17 @@ void ThingType::draw(const Point& dest, float scaleFactor, int layer, int xPatte
164 164
     if(m_null)
165 165
         return;
166 166
 
167
+    if(animationPhase >= m_animationPhases)
168
+        return;
169
+
167 170
     const TexturePtr& texture = getTexture(animationPhase); // texture might not exists, neither its rects.
171
+    if(!texture)
172
+        return;
173
+
174
+    uint frameIndex = getTextureIndex(layer, xPattern, yPattern, zPattern);
175
+    if(frameIndex >= m_texturesFramesRects[animationPhase].size())
176
+        return;
168 177
 
169
-    int frameIndex = getTextureIndex(layer, xPattern, yPattern, zPattern);
170 178
     Point textureOffset;
171 179
     Rect textureRect;
172 180
 

+ 2
- 12
src/client/tile.cpp View File

@@ -338,7 +338,7 @@ int Tile::getGroundSpeed()
338 338
 
339 339
 uint8 Tile::getMinimapColorByte()
340 340
 {
341
-    uint8 color = 0;
341
+    uint8 color = 255; // alpha
342 342
     if(m_minimapColor != 0) {
343 343
         return m_minimapColor;
344 344
     }
@@ -542,18 +542,8 @@ bool Tile::isLookPossible()
542 542
 
543 543
 bool Tile::isClickable()
544 544
 {
545
-    bool hasGround = false;
546
-    bool hasOnBottom = false;
547
-    bool hasIgnoreLook = false;
548 545
     for(const ThingPtr& thing : m_things) {
549
-        if(thing->isGround())
550
-            hasGround = true;
551
-        if(thing->isOnBottom())
552
-            hasOnBottom = true;
553
-        if(thing->isIgnoreLook())
554
-            hasIgnoreLook = true;
555
-
556
-        if((hasGround || hasOnBottom) && !hasIgnoreLook)
546
+        if(!thing->isOnTop() && !thing->isIgnoreLook())
557 547
             return true;
558 548
     }
559 549
     return false;

+ 92
- 0
src/client/uimapanchorlayout.cpp View File

@@ -0,0 +1,92 @@
1
+/*
2
+ * Copyright (c) 2010-2013 OTClient <https://github.com/edubart/otclient>
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ * THE SOFTWARE.
21
+ */
22
+
23
+#include "declarations.h"
24
+#include "uimapanchorlayout.h"
25
+#include "uiminimap.h"
26
+#include <framework/ui/uiwidget.h>
27
+
28
+int UIPositionAnchor::getHookedPoint(const UIWidgetPtr& hookedWidget, const UIWidgetPtr& parentWidget)
29
+{
30
+    UIMinimapPtr minimap = hookedWidget->static_self_cast<UIMinimap>();
31
+    Rect hookedRect = minimap->getTileRect(m_hookedPosition);
32
+    int point = 0;
33
+        if(hookedRect.isValid()) {
34
+        switch(m_hookedEdge) {
35
+            case Fw::AnchorLeft:
36
+                point = hookedRect.left();
37
+                break;
38
+            case Fw::AnchorRight:
39
+                point = hookedRect.right();
40
+                break;
41
+            case Fw::AnchorTop:
42
+                point = hookedRect.top();
43
+                break;
44
+            case Fw::AnchorBottom:
45
+                point = hookedRect.bottom();
46
+                break;
47
+            case Fw::AnchorHorizontalCenter:
48
+                point = hookedRect.horizontalCenter();
49
+                break;
50
+            case Fw::AnchorVerticalCenter:
51
+                point = hookedRect.verticalCenter();
52
+                break;
53
+            default:
54
+                // must never happens
55
+                assert(false);
56
+                break;
57
+        }
58
+    }
59
+    return point;
60
+}
61
+
62
+void UIMapAnchorLayout::addPositionAnchor(const UIWidgetPtr& anchoredWidget, Fw::AnchorEdge anchoredEdge, const Position& hookedPosition, Fw::AnchorEdge hookedEdge)
63
+{
64
+    if(!anchoredWidget)
65
+        return;
66
+
67
+    assert(anchoredWidget != getParentWidget());
68
+
69
+    UIPositionAnchorPtr anchor(new UIPositionAnchor(anchoredEdge, hookedPosition, hookedEdge));
70
+    UIAnchorGroupPtr& anchorGroup = m_anchorsGroups[anchoredWidget];
71
+    if(!anchorGroup)
72
+        anchorGroup = UIAnchorGroupPtr(new UIAnchorGroup);
73
+
74
+    anchorGroup->addAnchor(anchor);
75
+
76
+    // layout must be updated because a new anchor got in
77
+    update();
78
+}
79
+
80
+void UIMapAnchorLayout::centerInPosition(const UIWidgetPtr& anchoredWidget, const Position& hookedPosition)
81
+{
82
+    addPositionAnchor(anchoredWidget, Fw::AnchorHorizontalCenter, hookedPosition, Fw::AnchorHorizontalCenter);
83
+    addPositionAnchor(anchoredWidget, Fw::AnchorVerticalCenter, hookedPosition, Fw::AnchorVerticalCenter);
84
+}
85
+
86
+void UIMapAnchorLayout::fillPosition(const UIWidgetPtr& anchoredWidget, const Position& hookedPosition)
87
+{
88
+    addPositionAnchor(anchoredWidget, Fw::AnchorLeft, hookedPosition, Fw::AnchorLeft);
89
+    addPositionAnchor(anchoredWidget, Fw::AnchorRight, hookedPosition, Fw::AnchorRight);
90
+    addPositionAnchor(anchoredWidget, Fw::AnchorTop, hookedPosition, Fw::AnchorTop);
91
+    addPositionAnchor(anchoredWidget, Fw::AnchorBottom, hookedPosition, Fw::AnchorBottom);
92
+}

+ 55
- 0
src/client/uimapanchorlayout.h View File

@@ -0,0 +1,55 @@
1
+/*
2
+ * Copyright (c) 2010-2013 OTClient <https://github.com/edubart/otclient>
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ * THE SOFTWARE.
21
+ */
22
+
23
+#ifndef UIMAPANCHORLAYOUT_H
24
+#define UIMAPANCHORLAYOUT_H
25
+
26
+#include "declarations.h"
27
+#include <framework/ui/uianchorlayout.h>
28
+
29
+class UIPositionAnchor : public UIAnchor
30
+{
31
+public:
32
+    UIPositionAnchor(Fw::AnchorEdge anchoredEdge, const Position& hookedPosition, Fw::AnchorEdge hookedEdge) :
33
+        UIAnchor(anchoredEdge, std::string(), hookedEdge), m_hookedPosition(hookedPosition) { }
34
+
35
+    UIWidgetPtr getHookedWidget(const UIWidgetPtr& widget, const UIWidgetPtr& parentWidget) { return parentWidget; }
36
+    int getHookedPoint(const UIWidgetPtr& hookedWidget, const UIWidgetPtr& parentWidget);
37
+
38
+private:
39
+    Position m_hookedPosition;
40
+};
41
+
42
+class UIMapAnchorLayout : public UIAnchorLayout
43
+{
44
+public:
45
+    UIMapAnchorLayout(UIWidgetPtr parentWidget) : UIAnchorLayout(parentWidget) { }
46
+
47
+    void addPositionAnchor(const UIWidgetPtr& anchoredWidget, Fw::AnchorEdge anchoredEdge,
48
+                           const Position& hookedPosition, Fw::AnchorEdge hookedEdge);
49
+    void centerInPosition(const UIWidgetPtr& anchoredWidget, const Position& hookedPosition);
50
+    void fillPosition(const UIWidgetPtr& anchoredWidget, const Position& hookedPosition);
51
+
52
+protected:
53
+};
54
+
55
+#endif

+ 71
- 28
src/client/uiminimap.cpp View File

@@ -23,16 +23,19 @@
23 23
 #include "uiminimap.h"
24 24
 #include "minimap.h"
25 25
 #include "game.h"
26
+#include "uimapanchorlayout.h"
27
+#include "luavaluecasts.h"
26 28
 
27 29
 #include <framework/graphics/painter.h>
30
+#include "uimapanchorlayout.h"
28 31
 
29 32
 UIMinimap::UIMinimap()
30 33
 {
31
-    m_crossEnabled = true;
32 34
     m_zoom = 0;
33 35
     m_scale = 1.0f;
34 36
     m_minZoom = -5;
35 37
     m_maxZoom = 5;
38
+    m_layout = UIMapAnchorLayoutPtr(new UIMapAnchorLayout(static_self_cast<UIWidget>()));
36 39
 }
37 40
 
38 41
 void UIMinimap::drawSelf(Fw::DrawPane drawPane)
@@ -42,22 +45,18 @@ void UIMinimap::drawSelf(Fw::DrawPane drawPane)
42 45
     if((drawPane & Fw::ForegroundPane) == 0)
43 46
         return;
44 47
 
45
-    g_minimap.draw(getPaddingRect(), getCameraPosition(), m_scale);
46
-
47
-    // draw a cross in the center
48
-    Rect vRect(0, 0, 2, 10);
49
-    Rect hRect(0, 0, 10, 2);
50
-    vRect.moveCenter(m_rect.center());
51
-    hRect.moveCenter(m_rect.center());
52
-    g_painter->setColor(Color::white);
53
-    g_painter->drawFilledRect(vRect);
54
-    g_painter->drawFilledRect(hRect);
48
+    g_minimap.draw(getPaddingRect(), getCameraPosition(), m_scale, m_color);
55 49
 }
56 50
 
57 51
 bool UIMinimap::setZoom(int zoom)
58 52
 {
53
+    if(zoom == m_zoom)
54
+        return true;
55
+
59 56
     if(zoom < m_minZoom || zoom > m_maxZoom)
60 57
         return false;
58
+
59
+    int oldZoom = m_zoom;
61 60
     m_zoom = zoom;
62 61
     if(m_zoom < 0)
63 62
         m_scale = 1.0f / (1 << std::abs(zoom));
@@ -65,37 +64,83 @@ bool UIMinimap::setZoom(int zoom)
65 64
         m_scale = 1.0f * (1 << std::abs(zoom));
66 65
     else
67 66
         m_scale = 1;
67
+    m_layout->update();
68
+    
69
+    onZoomChange(zoom, oldZoom);
68 70
     return true;
69 71
 }
70 72
 
71
-void UIMinimap::followCreature(const CreaturePtr& creature)
73
+void UIMinimap::setCameraPosition(const Position& pos)
72 74
 {
73
-    m_followingCreature = creature;
74
-    m_cameraPosition = Position();
75
+    Position oldPos = m_cameraPosition;
76
+    m_cameraPosition = pos;
77
+    m_layout->update();
78
+
79
+    onCameraPositionChange(pos, oldPos);
75 80
 }
76 81
 
77
-void UIMinimap::setCameraPosition(const Position& pos)
82
+bool UIMinimap::floorUp()
78 83
 {
79
-    m_followingCreature = nullptr;
80
-    m_cameraPosition = pos;
84
+    Position pos = getCameraPosition();
85
+    if(!pos.up())
86
+        return false;
87
+    setCameraPosition(pos);
88
+    return true;
89
+}
90
+
91
+bool UIMinimap::floorDown()
92
+{
93
+    Position pos = getCameraPosition();
94
+    if(!pos.down())
95
+        return false;
96
+    setCameraPosition(pos);
97
+    return true;
81 98
 }
82 99
 
83
-Point UIMinimap::getPoint(const Position& pos)
100
+Point UIMinimap::getTilePoint(const Position& pos)
84 101
 {
85
-    return g_minimap.getPoint(pos, getPaddingRect(), getCameraPosition(), m_scale);
102
+    return g_minimap.getTilePoint(pos, getPaddingRect(), getCameraPosition(), m_scale);
86 103
 }
87 104
 
88
-Position UIMinimap::getPosition(const Point& mousePos)
105
+Rect UIMinimap::getTileRect(const Position& pos)
89 106
 {
90
-    return g_minimap.getPosition(mousePos, getPaddingRect(), getCameraPosition(), m_scale);
107
+    return g_minimap.getTileRect(pos, getPaddingRect(), getCameraPosition(), m_scale);
91 108
 }
92 109
 
93
-Position UIMinimap::getCameraPosition()
110
+Position UIMinimap::getTilePosition(const Point& mousePos)
94 111
 {
95
-    if(m_followingCreature)
96
-        return m_followingCreature->getPosition();
97
-    else
98
-        return m_cameraPosition;
112
+    return g_minimap.getTilePosition(mousePos, getPaddingRect(), getCameraPosition(), m_scale);
113
+}
114
+
115
+void UIMinimap::anchorPosition(const UIWidgetPtr& anchoredWidget, Fw::AnchorEdge anchoredEdge, const Position& hookedPosition, Fw::AnchorEdge hookedEdge)
116
+{
117
+    UIMapAnchorLayoutPtr layout = m_layout->static_self_cast<UIMapAnchorLayout>();
118
+    assert(layout);
119
+    layout->addPositionAnchor(anchoredWidget, anchoredEdge, hookedPosition, hookedEdge);
120
+}
121
+
122
+void UIMinimap::fillPosition(const UIWidgetPtr& anchoredWidget, const Position& hookedPosition)
123
+{
124
+    UIMapAnchorLayoutPtr layout = m_layout->static_self_cast<UIMapAnchorLayout>();
125
+    assert(layout);
126
+    layout->fillPosition(anchoredWidget, hookedPosition);
127
+}
128
+
129
+void UIMinimap::centerInPosition(const UIWidgetPtr& anchoredWidget, const Position& hookedPosition)
130
+{
131
+    UIMapAnchorLayoutPtr layout = m_layout->static_self_cast<UIMapAnchorLayout>();
132
+    assert(layout);
133
+    layout->centerInPosition(anchoredWidget, hookedPosition);
134
+}
135
+
136
+void UIMinimap::onZoomChange(int zoom, int oldZoom)
137
+{
138
+    callLuaField("onZoomChange", zoom, oldZoom);
139
+}
140
+
141
+void UIMinimap::onCameraPositionChange(const Position& position, const Position& oldPosition)
142
+{
143
+    callLuaField("onCameraPositionChange", position, oldPosition);
99 144
 }
100 145
 
101 146
 void UIMinimap::onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode)
@@ -108,7 +153,5 @@ void UIMinimap::onStyleApply(const std::string& styleName, const OTMLNodePtr& st
108 153
             setMaxZoom(node->value<int>());
109 154
         else if(node->tag() == "min-zoom")
110 155
             setMinZoom(node->value<int>());
111
-        else if(node->tag() == "cross")
112
-            setCross(node->value<bool>());
113 156
     }
114 157
 }

+ 15
- 9
src/client/uiminimap.h View File

@@ -40,26 +40,32 @@ public:
40 40
     void setMinZoom(int minZoom) { m_minZoom = minZoom; }
41 41
     void setMaxZoom(int maxZoom) { m_maxZoom = maxZoom; }
42 42
     void setCameraPosition(const Position& pos);
43
-    void setCross(bool enable) { m_crossEnabled = enable; }
44
-    void followCreature(const CreaturePtr& creature);
43
+    bool floorUp();
44
+    bool floorDown();
45 45
 
46
-    Point getPoint(const Position& pos);
47
-    Position getPosition(const Point& mousePos);
48
-    Position getCameraPosition();
49
-    CreaturePtr getFollowingCreature() { return m_followingCreature; }
46
+    Point getTilePoint(const Position& pos);
47
+    Rect getTileRect(const Position& pos);
48
+    Position getTilePosition(const Point& mousePos);
49
+
50
+    Position getCameraPosition() { return m_cameraPosition; }
50 51
     int getMinZoom() { return m_minZoom; }
51 52
     int getMaxZoom() { return m_maxZoom; }
52 53
     int getZoom() { return m_zoom; }
53
-    bool getCross() { return m_crossEnabled; }
54 54
     float getScale() { return m_scale; }
55 55
 
56
+    void anchorPosition(const UIWidgetPtr& anchoredWidget, Fw::AnchorEdge anchoredEdge, const Position& hookedPosition, Fw::AnchorEdge hookedEdge);
57
+    void fillPosition(const UIWidgetPtr& anchoredWidget, const Position& hookedPosition);
58
+    void centerInPosition(const UIWidgetPtr& anchoredWidget, const Position& hookedPosition);
59
+
56 60
 protected:
61
+    virtual void onZoomChange(int zoom, int oldZoom);
62
+    virtual void onCameraPositionChange(const Position& position, const Position& oldPosition);
57 63
     virtual void onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode);
58 64
 
59 65
 private:
66
+    void update();
67
+
60 68
     Rect m_mapArea;
61
-    bool m_crossEnabled;
62
-    CreaturePtr m_followingCreature;
63 69
     Position m_cameraPosition;
64 70
     float m_scale;
65 71
     int m_zoom;

+ 6
- 2
src/framework/luaengine/luavaluecasts.cpp View File

@@ -289,8 +289,12 @@ bool luavalue_cast(int index, OTMLNodePtr& node)
289 289
         g_lua.pushNil();
290 290
         while(g_lua.next(index < 0 ? index-1 : index)) {
291 291
             std::string cnodeName;
292
-            if(!g_lua.isNumber(-2))
293
-                cnodeName = g_lua.toString(-2);
292
+            if(g_lua.isString(-2)) {
293
+                g_lua.pushValue(-2);
294
+                cnodeName = g_lua.toString();
295
+                g_lua.pop();
296
+            } else
297
+                assert(g_lua.isNumber());
294 298
             if(g_lua.isTable()) {
295 299
                 OTMLNodePtr cnode;
296 300
                 if(luavalue_cast(-1, cnode)) {

+ 4
- 0
src/framework/pch.h View File

@@ -26,6 +26,7 @@
26 26
 // common C headers
27 27
 #include <cstdio>
28 28
 #include <cstdlib>
29
+#include <cstddef>
29 30
 #include <cstring>
30 31
 #include <cassert>
31 32
 #include <cmath>
@@ -42,5 +43,8 @@
42 43
 #include <functional>
43 44
 #include <array>
44 45
 #include <unordered_map>
46
+#include <tuple>
47
+#include <iomanip>
48
+#include <typeinfo>
45 49
 
46 50
 #endif

+ 6
- 3
src/framework/platform/win32window.cpp View File

@@ -618,9 +618,12 @@ LRESULT WIN32Window::windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar
618 618
         case WM_SYSKEYUP:
619 619
         case WM_SYSKEYDOWN: {
620 620
             // F10 is the shortcut key to enter a windows menu, this is a workaround to get F10 working
621
-            if(wParam != VK_F10)
622
-                return DefWindowProc(hWnd, uMsg, wParam, lParam);
623
-            else {
621
+            if(wParam != VK_F10) {
622
+                if(wParam != VK_MENU && wParam != VK_LMENU  && wParam != VK_RMENU)
623
+                    return DefWindowProc(hWnd, uMsg, wParam, lParam);
624
+                else
625
+                    return 0;
626
+            } else {
624 627
                 if(uMsg == WM_SYSKEYUP)
625 628
                     processKeyUp(retranslateVirtualKey(wParam, lParam));
626 629
                 else

+ 5
- 0
src/framework/ui/declarations.h View File

@@ -33,6 +33,8 @@ class UIBoxLayout;
33 33
 class UIHorizontalLayout;
34 34
 class UIVerticalLayout;
35 35
 class UIGridLayout;
36
+class UIAnchor;
37
+class UIAnchorGroup;
36 38
 class UIAnchorLayout;
37 39
 class UIParticles;
38 40
 
@@ -44,8 +46,11 @@ typedef stdext::shared_object_ptr<UIBoxLayout> UIBoxLayoutPtr;
44 46
 typedef stdext::shared_object_ptr<UIHorizontalLayout> UIHorizontalLayoutPtr;
45 47
 typedef stdext::shared_object_ptr<UIVerticalLayout> UIVerticalLayoutPtr;
46 48
 typedef stdext::shared_object_ptr<UIGridLayout> UIGridLayoutPtr;
49
+typedef stdext::shared_object_ptr<UIAnchor> UIAnchorPtr;
50
+typedef stdext::shared_object_ptr<UIAnchorGroup> UIAnchorGroupPtr;
47 51
 typedef stdext::shared_object_ptr<UIAnchorLayout> UIAnchorLayoutPtr;
48 52
 
49 53
 typedef std::deque<UIWidgetPtr> UIWidgetList;
54
+typedef std::vector<UIAnchorPtr> UIAnchorList;
50 55
 
51 56
 #endif

+ 92
- 75
src/framework/ui/uianchorlayout.cpp View File

@@ -23,11 +23,81 @@
23 23
 #include "uianchorlayout.h"
24 24
 #include "uiwidget.h"
25 25
 
26
-void UIAnchorGroup::addAnchor(const UIAnchor& anchor)
26
+UIWidgetPtr UIAnchor::getHookedWidget(const UIWidgetPtr& widget, const UIWidgetPtr& parentWidget)
27
+{
28
+    // determine hooked widget
29
+    UIWidgetPtr hookedWidget;
30
+    if(parentWidget) {
31
+        if(m_hookedWidgetId == "parent")
32
+            hookedWidget = parentWidget;
33
+        else if(m_hookedWidgetId == "next")
34
+            hookedWidget = parentWidget->getChildAfter(widget);
35
+        else if(m_hookedWidgetId == "prev")
36
+            hookedWidget = parentWidget->getChildBefore(widget);
37
+        else
38
+            hookedWidget = parentWidget->getChildById(m_hookedWidgetId);
39
+    }
40
+    return hookedWidget;
41
+}
42
+
43
+int UIAnchor::getHookedPoint(const UIWidgetPtr& hookedWidget, const UIWidgetPtr& parentWidget)
44
+{
45
+    // determine hooked widget edge point
46
+    Rect hookedWidgetRect = hookedWidget->getRect();
47
+    if(hookedWidget == parentWidget)
48
+        hookedWidgetRect = parentWidget->getPaddingRect();
49
+
50
+    int point = 0;
51
+    switch(m_hookedEdge) {
52
+        case Fw::AnchorLeft:
53
+            point = hookedWidgetRect.left();
54
+            break;
55
+        case Fw::AnchorRight:
56
+            point = hookedWidgetRect.right();
57
+            break;
58
+        case Fw::AnchorTop:
59
+            point = hookedWidgetRect.top();
60
+            break;
61
+        case Fw::AnchorBottom:
62
+            point = hookedWidgetRect.bottom();
63
+            break;
64
+        case Fw::AnchorHorizontalCenter:
65
+            point = hookedWidgetRect.horizontalCenter();
66
+            break;
67
+        case Fw::AnchorVerticalCenter:
68
+            point = hookedWidgetRect.verticalCenter();
69
+            break;
70
+        default:
71
+            // must never happens
72
+            assert(false);
73
+            break;
74
+    }
75
+
76
+    if(hookedWidget == parentWidget) {
77
+        switch(m_hookedEdge) {
78
+            case Fw::AnchorLeft:
79
+            case Fw::AnchorRight:
80
+            case Fw::AnchorHorizontalCenter:
81
+                point -= parentWidget->getVirtualOffset().x;
82
+                break;
83
+            case Fw::AnchorBottom:
84
+            case Fw::AnchorTop:
85
+            case Fw::AnchorVerticalCenter:
86
+                point -= parentWidget->getVirtualOffset().y;
87
+                break;
88
+            default:
89
+                break;
90
+        }
91
+    }
92
+
93
+    return point;
94
+}
95
+
96
+void UIAnchorGroup::addAnchor(const UIAnchorPtr& anchor)
27 97
 {
28 98
     // duplicated anchors must be replaced
29
-    for(UIAnchor& other : m_anchors) {
30
-        if(other.getAnchoredEdge() == anchor.getAnchoredEdge()) {
99
+    for(UIAnchorPtr& other : m_anchors) {
100
+        if(other->getAnchoredEdge() == anchor->getAnchoredEdge()) {
31 101
             other = anchor;
32 102
             return;
33 103
         }
@@ -43,9 +113,12 @@ void UIAnchorLayout::addAnchor(const UIWidgetPtr& anchoredWidget, Fw::AnchorEdge
43 113
 
44 114
     assert(anchoredWidget != getParentWidget());
45 115
 
46
-    UIAnchor anchor(anchoredEdge, hookedWidgetId, hookedEdge);
47
-    UIAnchorGroup& anchorGroup = m_anchorsGroups[anchoredWidget];
48
-    anchorGroup.addAnchor(anchor);
116
+    UIAnchorPtr anchor(new UIAnchor(anchoredEdge, hookedWidgetId, hookedEdge));
117
+    UIAnchorGroupPtr& anchorGroup = m_anchorsGroups[anchoredWidget];
118
+    if(!anchorGroup)
119
+        anchorGroup = UIAnchorGroupPtr(new UIAnchorGroup);
120
+
121
+    anchorGroup->addAnchor(anchor);
49 122
 
50 123
     // layout must be updated because a new anchor got in
51 124
     update();
@@ -86,7 +159,7 @@ void UIAnchorLayout::removeWidget(const UIWidgetPtr& widget)
86 159
     removeAnchors(widget);
87 160
 }
88 161
 
89
-bool UIAnchorLayout::updateWidget(const UIWidgetPtr& widget, UIAnchorGroup& anchorGroup, UIWidgetPtr first)
162
+bool UIAnchorLayout::updateWidget(const UIWidgetPtr& widget, const UIAnchorGroupPtr& anchorGroup, UIWidgetPtr first)
90 163
 {
91 164
     UIWidgetPtr parentWidget = getParentWidget();
92 165
     if(!parentWidget)
@@ -105,23 +178,13 @@ bool UIAnchorLayout::updateWidget(const UIWidgetPtr& widget, UIAnchorGroup& anch
105 178
     bool horizontalMoved = false;
106 179
 
107 180
     // calculates new rect based on anchors
108
-    for(const UIAnchor& anchor : anchorGroup.getAnchors()) {
181
+    for(const UIAnchorPtr& anchor : anchorGroup->getAnchors()) {
109 182
         // skip invalid anchors
110
-        if(anchor.getHookedEdge() == Fw::AnchorNone)
183
+        if(anchor->getHookedEdge() == Fw::AnchorNone)
111 184
             continue;
112 185
 
113 186
         // determine hooked widget
114
-        UIWidgetPtr hookedWidget;
115
-        if(parentWidget) {
116
-            if(anchor.getHookedWidgetId() == "parent")
117
-                hookedWidget = parentWidget;
118
-            else if(anchor.getHookedWidgetId() == "next")
119
-                hookedWidget = parentWidget->getChildAfter(widget);
120
-            else if(anchor.getHookedWidgetId() == "prev")
121
-                hookedWidget = parentWidget->getChildBefore(widget);
122
-            else
123
-                hookedWidget = parentWidget->getChildById(anchor.getHookedWidgetId());
124
-        }
187
+        UIWidgetPtr hookedWidget = anchor->getHookedWidget(widget, parentWidget);
125 188
 
126 189
         // skip invalid anchors
127 190
         if(!hookedWidget)
@@ -131,61 +194,15 @@ bool UIAnchorLayout::updateWidget(const UIWidgetPtr& widget, UIAnchorGroup& anch
131 194
             // update this hooked widget anchors
132 195
             auto it = m_anchorsGroups.find(hookedWidget);
133 196
             if(it != m_anchorsGroups.end()) {
134
-                UIAnchorGroup& hookedAnchorGroup = it->second;
135
-                if(!hookedAnchorGroup.isUpdated())
197
+                const UIAnchorGroupPtr& hookedAnchorGroup = it->second;
198
+                if(!hookedAnchorGroup->isUpdated())
136 199
                     updateWidget(hookedWidget, hookedAnchorGroup, first);
137 200
             }
138 201
         }
139 202
 
140
-        // determine hooked widget edge point
141
-        Rect hookedWidgetRect = hookedWidget->getRect();
142
-        if(hookedWidget == parentWidget)
143
-            hookedWidgetRect = parentWidget->getPaddingRect();
144
-
145
-        int point = 0;
146
-        switch(anchor.getHookedEdge()) {
147
-            case Fw::AnchorLeft:
148
-                point = hookedWidgetRect.left();
149
-                break;
150
-            case Fw::AnchorRight:
151
-                point = hookedWidgetRect.right();
152
-                break;
153
-            case Fw::AnchorTop:
154
-                point = hookedWidgetRect.top();
155
-                break;
156
-            case Fw::AnchorBottom:
157
-                point = hookedWidgetRect.bottom();
158
-                break;
159
-            case Fw::AnchorHorizontalCenter:
160
-                point = hookedWidgetRect.horizontalCenter();
161
-                break;
162
-            case Fw::AnchorVerticalCenter:
163
-                point = hookedWidgetRect.verticalCenter();
164
-                break;
165
-            default:
166
-                // must never happens
167
-                assert(false);
168
-                break;
169
-        }
170
-
171
-        if(hookedWidget == parentWidget) {
172
-            switch(anchor.getHookedEdge()) {
173
-                case Fw::AnchorLeft:
174
-                case Fw::AnchorRight:
175
-                case Fw::AnchorHorizontalCenter:
176
-                    point -= parentWidget->getVirtualOffset().x;
177
-                    break;
178
-                case Fw::AnchorBottom:
179
-                case Fw::AnchorTop:
180
-                case Fw::AnchorVerticalCenter:
181
-                    point -= parentWidget->getVirtualOffset().y;
182
-                    break;
183
-                default:
184
-                    break;
185
-            }
186
-        }
203
+        int point = anchor->getHookedPoint(hookedWidget, parentWidget);
187 204
 
188
-        switch(anchor.getAnchoredEdge()) {
205
+        switch(anchor->getAnchoredEdge()) {
189 206
             case Fw::AnchorHorizontalCenter:
190 207
                 newRect.moveHorizontalCenter(point + widget->getMarginLeft() - widget->getMarginRight());
191 208
                 horizontalMoved = true;
@@ -230,7 +247,7 @@ bool UIAnchorLayout::updateWidget(const UIWidgetPtr& widget, UIAnchorGroup& anch
230 247
     bool changed = false;
231 248
     if(widget->setRect(newRect))
232 249
         changed = true;
233
-    anchorGroup.setUpdated(true);
250
+    anchorGroup->setUpdated(true);
234 251
     return changed;
235 252
 }
236 253
 
@@ -240,15 +257,15 @@ bool UIAnchorLayout::internalUpdate()
240 257
 
241 258
     // reset all anchors groups update state
242 259
     for(auto& it : m_anchorsGroups) {
243
-        UIAnchorGroup& anchorGroup = it.second;
244
-        anchorGroup.setUpdated(false);
260
+        const UIAnchorGroupPtr& anchorGroup = it.second;
261
+        anchorGroup->setUpdated(false);
245 262
     }
246 263
 
247 264
     // update all anchors
248 265
     for(auto& it : m_anchorsGroups) {
249 266
         const UIWidgetPtr& widget = it.first;
250
-        UIAnchorGroup& anchorGroup = it.second;
251
-        if(!anchorGroup.isUpdated()) {
267
+        const UIAnchorGroupPtr& anchorGroup = it.second;
268
+        if(!anchorGroup->isUpdated()) {
252 269
             if(updateWidget(widget, anchorGroup))
253 270
                 changed = true;
254 271
         }

+ 10
- 12
src/framework/ui/uianchorlayout.h View File

@@ -25,30 +25,30 @@
25 25
 
26 26
 #include "uilayout.h"
27 27
 
28
-class UIAnchor
28
+class UIAnchor : public stdext::shared_object
29 29
 {
30 30
 public:
31 31
     UIAnchor(Fw::AnchorEdge anchoredEdge, const std::string& hookedWidgetId, Fw::AnchorEdge hookedEdge) :
32 32
         m_anchoredEdge(anchoredEdge), m_hookedEdge(hookedEdge), m_hookedWidgetId(hookedWidgetId) { }
33 33
 
34 34
     Fw::AnchorEdge getAnchoredEdge() const { return m_anchoredEdge; }
35
-    std::string getHookedWidgetId() const { return m_hookedWidgetId; }
36 35
     Fw::AnchorEdge getHookedEdge() const { return m_hookedEdge; }
37 36
 
38
-private:
37
+    virtual UIWidgetPtr getHookedWidget(const UIWidgetPtr& widget, const UIWidgetPtr& parentWidget);
38
+    virtual int getHookedPoint(const UIWidgetPtr& hookedWidget, const UIWidgetPtr& parentWidget);
39
+
40
+protected:
39 41
     Fw::AnchorEdge m_anchoredEdge;
40 42
     Fw::AnchorEdge m_hookedEdge;
41 43
     std::string m_hookedWidgetId;
42 44
 };
43 45
 
44
-typedef std::vector<UIAnchor> UIAnchorList;
45
-
46
-class UIAnchorGroup
46
+class UIAnchorGroup : public stdext::shared_object
47 47
 {
48 48
 public:
49 49
     UIAnchorGroup() : m_updated(true) { }
50 50
 
51
-    void addAnchor(const UIAnchor& anchor);
51
+    void addAnchor(const UIAnchorPtr& anchor);
52 52
     const UIAnchorList& getAnchors() { return m_anchors; }
53 53
     bool isUpdated() { return m_updated; }
54 54
     void setUpdated(bool updated) { m_updated = updated; }
@@ -77,11 +77,9 @@ public:
77 77
     bool isUIAnchorLayout() { return true; }
78 78
 
79 79
 protected:
80
-    bool internalUpdate();
81
-
82
-private:
83
-    bool updateWidget(const UIWidgetPtr& widget, UIAnchorGroup&