1battleWindow = nil
2battleButton = nil
3battlePanel = nil
4filterPanel = nil
5toggleFilterButton = nil
6
7mouseWidget = nil
8updateEvent = nil
9
10hoveredCreature = nil
11newHoveredCreature = nil
12prevCreature = nil
13
14battleButtons = {}
15local ageNumber = 1
16local ages = {}
17
18function init()
19 g_ui.importStyle('battlebutton')
20 battleButton = modules.client_topmenu.addRightGameToggleButton('battleButton', tr('Battle') .. ' (Ctrl+B)', '/images/topbuttons/battle', toggle, false, 2)
21 --battleButton:setOn(true)
22 battleWindow = g_ui.loadUI('battle', modules.game_interface.getRightPanel())
23 --g_keyboard.bindKeyDown('Ctrl+B', toggle)
24
25 -- this disables scrollbar auto hiding
26 local scrollbar = battleWindow:getChildById('miniwindowScrollBar')
27 scrollbar:mergeStyle({ ['$!on'] = { }})
28
29 battlePanel = battleWindow:recursiveGetChildById('battlePanel')
30
31 filterPanel = battleWindow:recursiveGetChildById('filterPanel')
32 toggleFilterButton = battleWindow:recursiveGetChildById('toggleFilterButton')
33
34 if isHidingFilters() then
35 hideFilterPanel()
36 end
37
38 local sortTypeBox = filterPanel.sortPanel.sortTypeBox
39 local sortOrderBox = filterPanel.sortPanel.sortOrderBox
40
41 mouseWidget = g_ui.createWidget('UIButton')
42 mouseWidget:setVisible(false)
43 mouseWidget:setFocusable(false)
44 mouseWidget.cancelNextRelease = false
45
46 battleWindow:setContentMinimumHeight(80)
47
48 sortTypeBox:addOption('Name', 'name')
49 sortTypeBox:addOption('Distance', 'distance')
50 sortTypeBox:addOption('Total age', 'age')
51 sortTypeBox:addOption('Screen age', 'screenage')
52 sortTypeBox:addOption('Health', 'health')
53 sortTypeBox:setCurrentOptionByData(getSortType())
54 sortTypeBox.onOptionChange = onChangeSortType
55
56 sortOrderBox:addOption('Asc.', 'asc')
57 sortOrderBox:addOption('Desc.', 'desc')
58 sortOrderBox:setCurrentOptionByData(getSortOrder())
59 sortOrderBox.onOptionChange = onChangeSortOrder
60
61 battleWindow:setup()
62
63 for i=1,30 do
64 local battleButton = g_ui.createWidget('BattleButton', battlePanel)
65 battleButton:setup()
66 battleButton:hide()
67 battleButton.onHoverChange = onBattleButtonHoverChange
68 battleButton.onMouseRelease = onBattleButtonMouseRelease
69 table.insert(battleButtons, battleButton)
70 end
71
72 updateBattleList()
73
74 connect(LocalPlayer, {
75 onPositionChange = onPlayerPositionChange
76 })
77 connect(Creature, {
78 onAppear = updateSquare,
79 onDisappear = updateSquare
80 })
81 connect(g_game, {
82 onAttackingCreatureChange = updateSquare,
83 onFollowingCreatureChange = updateSquare
84 })
85end
86
87function terminate()
88 if battleButton == nil then
89 return
90 end
91
92 battleButtons = {}
93
94 g_keyboard.unbindKeyDown('Ctrl+B')
95 battleButton:destroy()
96 battleWindow:destroy()
97 mouseWidget:destroy()
98
99 disconnect(LocalPlayer, {
100 onPositionChange = onPlayerPositionChange
101 })
102 disconnect(Creature, {
103 onAppear = onCreatureAppear,
104 onDisappear = onCreatureDisappear
105 })
106 disconnect(g_game, {
107 onAttackingCreatureChange = updateSquare,
108 onFollowingCreatureChange = updateSquare
109 })
110
111 removeEvent(updateEvent)
112end
113
114function Meclose()
115 battleWindow:close()
116end
117function toggle()
118 if battleButton:isOn() then
119 battleWindow:close()
120 battleButton:setOn(false)
121 else
122 battleWindow:open()
123 battleButton:setOn(true)
124 end
125end
126
127function onMiniWindowClose()
128 battleButton:setOn(false)
129end
130
131function getSortType()
132 local settings = g_settings.getNode('BattleList')
133 if not settings then
134 if g_app.isMobile() then
135 return 'distance'
136 else
137 return 'distance'
138 end
139 end
140 return settings['sortType']
141end
142
143function setSortType(state)
144 settings = {}
145 settings['sortType'] = state
146 g_settings.mergeNode('BattleList', settings)
147
148 checkCreatures()
149end
150
151function getSortOrder()
152 local settings = g_settings.getNode('BattleList')
153 if not settings then
154 return 'asc'
155 end
156 return settings['sortOrder']
157end
158
159function setSortOrder(state)
160 settings = {}
161 settings['sortOrder'] = state
162 g_settings.mergeNode('BattleList', settings)
163
164 checkCreatures()
165end
166
167function isSortAsc()
168 return getSortOrder() == 'asc'
169end
170
171function isSortDesc()
172 return getSortOrder() == 'desc'
173end
174
175function isHidingFilters()
176 local settings = g_settings.getNode('BattleList')
177 if not settings then
178 return false
179 end
180 return settings['hidingFilters']
181end
182
183function setHidingFilters(state)
184 settings = {}
185 settings['hidingFilters'] = state
186 g_settings.mergeNode('BattleList', settings)
187end
188
189function hideFilterPanel()
190 filterPanel.originalHeight = filterPanel:getHeight()
191 filterPanel:setHeight(0)
192 toggleFilterButton:getParent():setMarginTop(0)
193 toggleFilterButton:setImageClip(torect("0 0 21 12"))
194 setHidingFilters(true)
195 filterPanel:setVisible(false)
196end
197
198function showFilterPanel()
199 toggleFilterButton:getParent():setMarginTop(5)
200 filterPanel:setHeight(filterPanel.originalHeight)
201 toggleFilterButton:setImageClip(torect("21 0 21 12"))
202 setHidingFilters(false)
203 filterPanel:setVisible(true)
204end
205
206function toggleFilterPanel()
207 if filterPanel:isVisible() then
208 hideFilterPanel()
209 else
210 showFilterPanel()
211 end
212end
213
214function onChangeSortType(comboBox, option, value)
215 setSortType(value:lower())
216end
217
218function onChangeSortOrder(comboBox, option, value)
219 -- Replace dot in option name
220 setSortOrder(value:lower():gsub('[.]', ''))
221end
222
223-- functions
224function updateBattleList()
225 removeEvent(updateEvent)
226 updateEvent = scheduleEvent(updateBattleList, 100)
227 checkCreatures()
228end
229
230function checkCreatures()
231 if not battlePanel or not g_game.isOnline() then
232 return
233 end
234
235 local player = g_game.getLocalPlayer()
236 if not player then
237 return
238 end
239
240 local dimension = modules.game_interface.getMapPanel():getVisibleDimension()
241 local spectators = g_map.getSpectatorsInRangeEx(player:getPosition(), false, math.floor(dimension.width / 2), math.floor(dimension.width / 2), math.floor(dimension.height / 2), math.floor(dimension.height / 2))
242 local maxCreatures = battlePanel:getChildCount()
243
244 local creatures = {}
245 local now = g_clock.millis()
246 local resetAgePoint = now - 250
247 for _, creature in ipairs(spectators) do
248 if doCreatureFitFilters(creature) and #creatures < maxCreatures then
249 if not creature.lastSeen or creature.lastSeen < resetAgePoint then
250 creature.screenAge = now
251 end
252 creature.lastSeen = now
253 if not ages[creature:getId()] then
254 if ageNumber > 1000 then
255 ageNumber = 1
256 ages = {}
257 end
258 ages[creature:getId()] = ageNumber
259 ageNumber = ageNumber + 1
260 end
261 table.insert(creatures, creature)
262 end
263 end
264
265 updateSquare()
266 sortCreatures(creatures)
267 battlePanel:getLayout():disableUpdates()
268
269 -- sorting
270 local ascOrder = isSortAsc()
271 for i=1,#creatures do
272 local creature = creatures[i]
273 if ascOrder then
274 creature = creatures[#creatures - i + 1]
275 end
276 local battleButton = battleButtons[i]
277 battleButton:creatureSetup(creature)
278 battleButton:show()
279 battleButton:setOn(true)
280 end
281
282 if g_app.isMobile() and #creatures > 0 then
283 onBattleButtonHoverChange(battleButtons[1], true)
284 end
285
286 for i=#creatures + 1,maxCreatures do
287 if battleButtons[i]:isHidden() then break end
288 battleButtons[i]:hide()
289 battleButton:setOn(false)
290 end
291
292 battlePanel:getLayout():enableUpdates()
293 battlePanel:getLayout():update()
294end
295
296function doCreatureFitFilters(creature)
297 if creature:isLocalPlayer() then
298 return false
299 end
300 if creature:getHealthPercent() <= 0 then
301 return false
302 end
303
304 local pos = creature:getPosition()
305 if not pos then return false end
306
307 local localPlayer = g_game.getLocalPlayer()
308 if pos.z ~= localPlayer:getPosition().z or not creature:canBeSeen() then return false end
309
310 local hidePlayers = filterPanel.buttons.hidePlayers:isChecked()
311 local hideNPCs = filterPanel.buttons.hideNPCs:isChecked()
312 local hideMonsters = filterPanel.buttons.hideMonsters:isChecked()
313 local hideSkulls = filterPanel.buttons.hideSkulls:isChecked()
314 local hideParty = filterPanel.buttons.hideParty:isChecked()
315
316 if hidePlayers and creature:isPlayer() then
317 return false
318 elseif hideNPCs and creature:isNpc() then
319 return false
320 elseif hideMonsters and creature:isMonster() then
321 return false
322 elseif hideSkulls and creature:isPlayer() and creature:getSkull() == SkullNone then
323 return false
324 elseif hideParty and creature:getShield() > ShieldWhiteBlue then
325 return false
326 end
327
328 return true
329end
330
331local function getDistanceBetween(p1, p2)
332 return math.max(math.abs(p1.x - p2.x), math.abs(p1.y - p2.y))
333end
334
335function sortCreatures(creatures)
336 local player = g_game.getLocalPlayer()
337
338 if getSortType() == 'distance' then
339 local playerPos = player:getPosition()
340 table.sort(creatures, function(a, b)
341 if getDistanceBetween(playerPos, a:getPosition()) == getDistanceBetween(playerPos, b:getPosition()) then
342 return ages[a:getId()] > ages[b:getId()]
343 end
344 return getDistanceBetween(playerPos, a:getPosition()) > getDistanceBetween(playerPos, b:getPosition())
345 end)
346 elseif getSortType() == 'health' then
347 table.sort(creatures, function(a, b)
348 if a:getHealthPercent() == b:getHealthPercent() then
349 return ages[a:getId()] > ages[b:getId()]
350 end
351 return a:getHealthPercent() > b:getHealthPercent()
352 end)
353 elseif getSortType() == 'age' then
354 table.sort(creatures, function(a, b) return ages[a:getId()] > ages[b:getId()] end)
355 elseif getSortType() == 'screenage' then
356 table.sort(creatures, function(a, b) return a.screenAge > b.screenAge end)
357 else -- name
358 table.sort(creatures, function(a, b)
359 if a:getName():lower() == b:getName():lower() then
360 return ages[a:getId()] > ages[b:getId()]
361 end
362 return a:getName():lower() > b:getName():lower()
363 end)
364 end
365end
366
367-- other functions
368function onBattleButtonMouseRelease(self, mousePosition, mouseButton)
369 if mouseWidget.cancelNextRelease then
370 mouseWidget.cancelNextRelease = false
371 return false
372 end
373 if not self.creature then
374 return false
375 end
376 if ((g_mouse.isPressed(MouseLeftButton) and mouseButton == MouseRightButton)
377 or (g_mouse.isPressed(MouseRightButton) and mouseButton == MouseLeftButton)) then
378 mouseWidget.cancelNextRelease = true
379 g_game.look(self.creature, true)
380 return true
381 elseif mouseButton == MouseLeftButton and g_keyboard.isShiftPressed() then
382 g_game.look(self.creature, true)
383 return true
384 elseif mouseButton == MouseRightButton and not g_mouse.isPressed(MouseLeftButton) then
385 modules.game_interface.createThingMenu(mousePosition, nil, nil, self.creature)
386 return true
387 elseif mouseButton == MouseLeftButton and not g_mouse.isPressed(MouseRightButton) then
388 if self.isTarget then
389 g_game.cancelAttack()
390 else
391 g_game.attack(self.creature)
392 modules.game_walking.setPressKeyAction(05)
393 end
394 return true
395 end
396 return false
397end
398
399function onBattleButtonHoverChange(battleButton, hovered)
400 if not hovered then
401 newHoveredCreature = nil
402 else
403 newHoveredCreature = battleButton.creature
404 end
405 if battleButton.isHovered ~= hovered then
406 battleButton.isHovered = hovered
407 battleButton:update()
408 end
409 updateSquare()
410end
411
412function onPlayerPositionChange(creature, newPos, oldPos)
413 addEvent(checkCreatures)
414end
415
416local CreatureButtonColors = {
417 onIdle = {notHovered = '#888888', hovered = '#FFFFFF' },
418 onTargeted = {notHovered = '#FF0000', hovered = '#FF8888' },
419 onFollowed = {notHovered = '#00FF00', hovered = '#88FF88' }
420}
421
422function updateSquare()
423 local following = g_game.getFollowingCreature()
424 local attacking = g_game.getAttackingCreature()
425
426 if newHoveredCreature == nil then
427 if hoveredCreature ~= nil then
428 hoveredCreature:hideStaticSquare()
429 hoveredCreature = nil
430 end
431 else
432 if hoveredCreature ~= nil then
433 hoveredCreature:hideStaticSquare()
434 end
435 hoveredCreature = newHoveredCreature
436 hoveredCreature:showStaticSquare(CreatureButtonColors.onIdle.hovered)
437 end
438
439 local color = CreatureButtonColors.onIdle
440 local creature = nil
441 if attacking then
442 color = CreatureButtonColors.onTargeted
443 creature = attacking
444 elseif following then
445 color = CreatureButtonColors.onFollowed
446 creature = following
447 end
448
449 if prevCreature ~= creature then
450 if prevCreature ~= nil then
451 prevCreature:hideStaticSquare()
452 end
453 prevCreature = creature
454 end
455
456 if not creature then
457 return
458 end
459
460 color = creature == hoveredCreature and color.hovered or color.notHovered
461 creature:showStaticSquare(color)
462end