Client Upload Part 2

RD Core / Commit #5

Commit Client Upload Part 2

Alex Alex committed 19/10/2026 00:06 main Patch
1,244 files +271,215 -0
A RD_Client/modules/gamelib/ui/uichattabbar.lua +499-0 View file
@@ -0,0 +1,499 @@
1+-- @docclass
2+UIChatTabBar = extends(UIWidget, "UIChatTabBar")
3+
4+-- private functions
5+local function onTabClick(tab)
6+ tab.tabBar:selectTab(tab)
7+end
8+
9+local function updateMargins(tabBar)
10+ if #tabBar.tabs == 0 then return end
11+
12+ local currentMargin = 0
13+ for i = 1, #tabBar.tabs do
14+ tabBar.tabs[i]:setMarginLeft(currentMargin)
15+ currentMargin = currentMargin + tabBar.tabSpacing + tabBar.tabs[i]:getWidth()
16+ end
17+end
18+
19+local function updateNavigation(tabBar)
20+ if tabBar.prevNavigation then
21+ if #tabBar.preTabs > 0 or table.find(tabBar.tabs, tabBar.currentTab) ~= 1 then
22+ tabBar.prevNavigation:enable()
23+ else
24+ tabBar.prevNavigation:disable()
25+ end
26+ end
27+
28+ if tabBar.nextNavigation then
29+ if #tabBar.postTabs > 0 or table.find(tabBar.tabs, tabBar.currentTab) ~= #tabBar.tabs then
30+ tabBar.nextNavigation:enable()
31+ else
32+ tabBar.nextNavigation:disable()
33+ end
34+ end
35+end
36+
37+local function updateIndexes(tabBar, tab, xoff)
38+ local tabs = tabBar.tabs
39+ local currentMargin = 0
40+ local prevIndex = table.find(tabs, tab)
41+ local newIndex = prevIndex
42+ local xmid = xoff + tab:getWidth()/2
43+ for i = 1, #tabs do
44+ local nextTab = tabs[i]
45+ if xmid >= currentMargin + nextTab:getWidth()/2 then
46+ newIndex = table.find(tabs, nextTab)
47+ end
48+ currentMargin = currentMargin + tabBar.tabSpacing * (i - 1) + tabBar.tabs[i]:getWidth()
49+ end
50+ if newIndex ~= prevIndex then
51+ table.remove(tabs, table.find(tabs, tab))
52+ table.insert(tabs, newIndex, tab)
53+ end
54+ updateNavigation(tabBar)
55+end
56+
57+local function getMaxMargin(tabBar, tab)
58+ if #tabBar.tabs == 0 then return 0 end
59+
60+ local maxMargin = 0
61+ for i = 1, #tabBar.tabs do
62+ if tabBar.tabs[i] ~= tab then
63+ maxMargin = maxMargin + tabBar.tabs[i]:getWidth()
64+ end
65+ end
66+ return maxMargin + tabBar.tabSpacing * (#tabBar.tabs - 1)
67+end
68+
69+local function updateTabs(tabBar)
70+ if #tabBar.postTabs > 0 then
71+ local i = 1
72+ while i <= #tabBar.postTabs do
73+ local tab = tabBar.postTabs[i]
74+ if getMaxMargin(tabBar) + tab:getWidth() > tabBar:getWidth() then
75+ break
76+ end
77+
78+ table.remove(tabBar.postTabs, i)
79+ table.insert(tabBar.tabs, tab)
80+ tab:setVisible(true)
81+ end
82+ end
83+ if #tabBar.preTabs > 0 then
84+ for i = #tabBar.preTabs, 1, -1 do
85+ local tab = tabBar.preTabs[i]
86+ if getMaxMargin(tabBar) + tab:getWidth() > tabBar:getWidth() then
87+ break
88+ end
89+
90+ table.remove(tabBar.preTabs, i)
91+ table.insert(tabBar.tabs, 1, tab)
92+ tab:setVisible(true)
93+ end
94+ end
95+ updateNavigation(tabBar)
96+ updateMargins(tabBar)
97+
98+ if not tabBar.currentTab and #tabBar.tabs > 0 then
99+ tabBar:selectTab(tabBar.tabs[1])
100+ end
101+end
102+
103+local function hideTabs(tabBar, fromBack, toArray, width)
104+ while #tabBar.tabs > 0 and getMaxMargin(tabBar) + width > tabBar:getWidth() do
105+ local index = fromBack and #tabBar.tabs or 1
106+ local tab = tabBar.tabs[index]
107+ table.remove(tabBar.tabs, index)
108+ if fromBack then
109+ table.insert(toArray, 1, tab)
110+ else
111+ table.insert(toArray, tab)
112+ end
113+ if tabBar.currentTab == tab then
114+ if #tabBar.tabs > 0 then
115+ tabBar:selectTab(tabBar.tabs[#tabBar.tabs])
116+ else
117+ tabBar.currentTab:setChecked(false)
118+ tabBar.currentTab = nil
119+ end
120+ end
121+ tab:setVisible(false)
122+ end
123+end
124+
125+local function showPreTab(tabBar)
126+ if #tabBar.preTabs == 0 then
127+ return nil
128+ end
129+
130+ local tmpTab = tabBar.preTabs[#tabBar.preTabs]
131+ hideTabs(tabBar, true, tabBar.postTabs, tmpTab:getWidth())
132+
133+ table.remove(tabBar.preTabs, #tabBar.preTabs)
134+ table.insert(tabBar.tabs, 1, tmpTab)
135+ tmpTab:setVisible(true)
136+ return tmpTab
137+end
138+
139+local function showPostTab(tabBar)
140+ if #tabBar.postTabs == 0 then
141+ return nil
142+ end
143+
144+ local tmpTab = tabBar.postTabs[1]
145+ hideTabs(tabBar, false, tabBar.preTabs, tmpTab:getWidth())
146+
147+ table.remove(tabBar.postTabs, 1)
148+ table.insert(tabBar.tabs, tmpTab)
149+ tmpTab:setVisible(true)
150+ return tmpTab
151+end
152+
153+local function onTabMousePress(tab, mousePos, mouseButton)
154+ if mouseButton == MouseRightButton then
155+ if tab.menuCallback then tab.menuCallback(tab, mousePos, mouseButton) end
156+ return true
157+ end
158+end
159+
160+local function onTabDragEnter(tab, mousePos)
161+ tab:raise()
162+ tab.hotSpot = mousePos.x - tab:getMarginLeft()
163+ tab.tabBar.selected = tab
164+ return true
165+end
166+
167+local function onTabDragLeave(tab)
168+ updateMargins(tab.tabBar)
169+ tab.tabBar.selected = nil
170+ return true
171+end
172+
173+local function onTabDragMove(tab, mousePos, mouseMoved)
174+ if tab == tab.tabBar.selected then
175+ local xoff = mousePos.x - tab.hotSpot
176+
177+ -- update indexes
178+ updateIndexes(tab.tabBar, tab, xoff)
179+ updateIndexes(tab.tabBar, tab, xoff)
180+
181+ -- update margins
182+ updateMargins(tab.tabBar)
183+ xoff = math.max(xoff, 0)
184+ xoff = math.min(xoff, getMaxMargin(tab.tabBar, tab))
185+ tab:setMarginLeft(xoff)
186+ end
187+end
188+
189+local function tabBlink(tab, steps, maxSteps, interval)
190+ local steps = steps or 0
191+ local maxSteps = maxSteps or 4
192+ local interval = interval or 500
193+ tab:setOn(not tab:isOn())
194+
195+ removeEvent(tab.blinkEvent)
196+ if steps < maxSteps then
197+ tab.blinkEvent = scheduleEvent(function() tabBlink(tab, steps + 1, maxSteps, interval) end, interval)
198+ else
199+ tab:setOn(true)
200+ tab.blinkEvent = nil
201+ end
202+end
203+
204+-- public functions
205+function UIChatTabBar.create()
206+ local tabbar = UIChatTabBar.internalCreate()
207+ tabbar:setFocusable(false)
208+ tabbar.tabs = {}
209+ tabbar.selected = nil -- dragged tab
210+ tabbar.tabSpacing = 0
211+ tabbar.tabsMoveable = false
212+ tabbar.preTabs = {}
213+ tabbar.postTabs = {}
214+ tabbar.prevNavigation = nil
215+ tabbar.nextNavigation = nil
216+ tabbar.onGeometryChange = function()
217+ hideTabs(tabbar, true, tabbar.postTabs, 0)
218+ updateTabs(tabbar)
219+ end
220+ return tabbar
221+end
222+
223+function UIChatTabBar:onDestroy()
224+ if self.prevNavigation then
225+ self.prevNavigation:disable()
226+ end
227+
228+ if self.nextNavigation then
229+ self.nextNavigation:disable()
230+ end
231+
232+ self.nextNavigation = nil
233+ self.prevNavigation = nil
234+end
235+
236+function UIChatTabBar:setContentWidget(widget)
237+ self.contentWidget = widget
238+ if #self.tabs > 0 then
239+ self.contentWidget:addChild(self.tabs[1].tabPanel)
240+ end
241+end
242+
243+function UIChatTabBar:setTabSpacing(tabSpacing)
244+ self.tabSpacing = tabSpacing
245+ updateMargins(self)
246+end
247+
248+function UIChatTabBar:addTab(text, panel, menuCallback)
249+ if panel == nil then
250+ panel = g_ui.createWidget(self:getStyleName() .. 'Panel')
251+ panel:setId('tabPanel')
252+ end
253+
254+ local tab = g_ui.createWidget(self:getStyleName() .. 'Button', self)
255+ panel.isTab = true
256+ tab.tabPanel = panel
257+ tab.tabBar = self
258+ tab:setId('tab')
259+ tab:setDraggable(self.tabsMoveable)
260+ tab:setText(text)
261+ tab:setWidth(tab:getTextSize().width + tab:getPaddingLeft() + tab:getPaddingRight())
262+ tab.menuCallback = menuCallback or nil
263+ tab.onClick = onTabClick
264+ tab.onMousePress = onTabMousePress
265+ tab.onDragEnter = onTabDragEnter
266+ tab.onDragLeave = onTabDragLeave
267+ tab.onDragMove = onTabDragMove
268+ tab.onDestroy = function() tab.tabPanel:destroy() end
269+
270+ if #self.tabs == 0 then
271+ self:selectTab(tab)
272+ tab:setMarginLeft(0)
273+ table.insert(self.tabs, tab)
274+ else
275+ local newMargin = self.tabSpacing * #self.tabs
276+ for i = 1, #self.tabs do
277+ newMargin = newMargin + self.tabs[i]:getWidth()
278+ end
279+ tab:setMarginLeft(newMargin)
280+
281+ hideTabs(self, true, self.postTabs, tab:getWidth())
282+ table.insert(self.tabs, tab)
283+ if #self.tabs == 1 then
284+ self:selectTab(tab)
285+ end
286+ updateMargins(self)
287+ end
288+
289+ updateNavigation(self)
290+ return tab
291+end
292+
293+-- Additional function to move the tab by lua
294+function UIChatTabBar:moveTab(tab, units)
295+ local index = table.find(self.tabs, tab)
296+ if index == nil then return end
297+
298+ local focus = false
299+ if self.currentTab == tab then
300+ self:selectPrevTab()
301+ focus = true
302+ end
303+
304+ table.remove(self.tabs, index)
305+
306+ local newIndex = math.min(#self.tabs+1, math.max(index + units, 1))
307+ table.insert(self.tabs, newIndex, tab)
308+ if focus then self:selectTab(tab) end
309+ updateMargins(self)
310+ return newIndex
311+end
312+
313+function UIChatTabBar:onStyleApply(styleName, styleNode)
314+ if styleNode['movable'] then
315+ self.tabsMoveable = styleNode['movable']
316+ end
317+ if styleNode['tab-spacing'] then
318+ self:setTabSpacing(styleNode['tab-spacing'])
319+ end
320+end
321+
322+function UIChatTabBar:removeTab(tab)
323+ local tabTables = {self.tabs, self.preTabs, self.postTabs}
324+ local index = nil
325+ local tabTable = nil
326+ for i = 1, #tabTables do
327+ index = table.find(tabTables[i], tab)
328+ if index ~= nil then
329+ tabTable = tabTables[i]
330+ break
331+ end
332+ end
333+
334+ if tabTable == nil then
335+ return
336+ end
337+
338+ if self.currentTab == tab then
339+ self:selectPrevTab()
340+ if #self.tabs == 1 then
341+ self.currentTab = nil
342+ end
343+ end
344+ table.remove(tabTable, index)
345+ if tab.blinkEvent then
346+ removeEvent(tab.blinkEvent)
347+ end
348+ tab:destroy()
349+ updateTabs(self)
350+end
351+
352+function UIChatTabBar:getTab(text)
353+ for k,tab in pairs(self.tabs) do
354+ if tab:getText():lower() == text:lower() then
355+ return tab
356+ end
357+ end
358+ for k,tab in pairs(self.preTabs) do
359+ if tab:getText():lower() == text:lower() then
360+ return tab
361+ end
362+ end
363+ for k,tab in pairs(self.postTabs) do
364+ if tab:getText():lower() == text:lower() then
365+ return tab
366+ end
367+ end
368+end
369+
370+function UIChatTabBar:selectTab(tab)
371+ if self.currentTab == tab then return end
372+ if self.contentWidget then
373+ local selectedWidget = self.contentWidget:getLastChild()
374+ if selectedWidget and selectedWidget.isTab then
375+ self.contentWidget:removeChild(selectedWidget)
376+ end
377+ self.contentWidget:addChild(tab.tabPanel)
378+ tab.tabPanel:fill('parent')
379+ end
380+
381+ if self.currentTab then
382+ self.currentTab:setChecked(false)
383+ end
384+ signalcall(self.onTabChange, self, tab)
385+ self.currentTab = tab
386+ tab:setChecked(true)
387+ tab:setOn(false)
388+ tab.blinking = false
389+
390+ local parent = tab:getParent()
391+ parent:focusChild(tab, MouseFocusReason)
392+ updateNavigation(self)
393+end
394+
395+function UIChatTabBar:selectNextTab()
396+ if self.currentTab == nil then
397+ return
398+ end
399+
400+ local index = table.find(self.tabs, self.currentTab)
401+ if index == nil then
402+ return
403+ end
404+
405+ local newIndex = index + 1
406+ if newIndex > #self.tabs then
407+ if #self.postTabs > 0 then
408+ local widget = showPostTab(self)
409+ self:selectTab(widget)
410+ else
411+ if #self.preTabs > 0 then
412+ for i = 1, #self.preTabs do
413+ showPreTab(self)
414+ end
415+ end
416+
417+ self:selectTab(self.tabs[1])
418+ end
419+ updateTabs(self)
420+ return
421+ end
422+
423+ local nextTab = self.tabs[newIndex]
424+ if not nextTab then
425+ return
426+ end
427+
428+ self:selectTab(nextTab)
429+end
430+
431+function UIChatTabBar:selectPrevTab()
432+ if self.currentTab == nil then
433+ return
434+ end
435+
436+ local index = table.find(self.tabs, self.currentTab)
437+ if index == nil then
438+ return
439+ end
440+
441+ local newIndex = index - 1
442+ if newIndex <= 0 then
443+ if #self.preTabs > 0 then
444+ local widget = showPreTab(self)
445+ self:selectTab(widget)
446+ else
447+ if #self.postTabs > 0 then
448+ for i = 1, #self.postTabs do
449+ showPostTab(self)
450+ end
451+ end
452+
453+ self:selectTab(self.tabs[#self.tabs])
454+ end
455+ updateTabs(self)
456+ return
457+ end
458+
459+ local prevTab = self.tabs[newIndex]
460+ if not prevTab then
461+ return
462+ end
463+
464+ self:selectTab(prevTab)
465+end
466+
467+function UIChatTabBar:blinkTab(tab)
468+ if tab:isChecked() then return end
469+ if tab.blinking then return end
470+ tab.blinking = true
471+ tabBlink(tab, 0, 2, 800)
472+end
473+
474+function UIChatTabBar:getTabPanel(tab)
475+ return tab.tabPanel
476+end
477+
478+function UIChatTabBar:getCurrentTabPanel()
479+ if self.currentTab then
480+ return self.currentTab.tabPanel
481+ end
482+end
483+
484+function UIChatTabBar:getCurrentTab()
485+ return self.currentTab
486+end
487+
488+function UIChatTabBar:setNavigation(prevButton, nextButton)
489+ self.prevNavigation = prevButton
490+ self.nextNavigation = nextButton
491+
492+ if self.prevNavigation then
493+ self.prevNavigation.onClick = function() self:selectPrevTab() end
494+ end
495+ if self.nextNavigation then
496+ self.nextNavigation.onClick = function() self:selectNextTab() end
497+ end
498+ updateNavigation(self)
499+end
A RD_Client/modules/gamelib/ui/uicolorwheel.lua +62-0 View file
@@ -0,0 +1,62 @@
1+-- @docclass
2+UIColorWheel = extends(UIButton, "UIColorWheel")
3+
4+local epsilon = 1E-12
5+
6+function UIColorWheel.create()
7+ local btn = UIColorWheel.internalCreate()
8+ return btn
9+end
10+
11+function UIColorWheel:onStyleApply(styleName, styleNode)
12+ for name,value in pairs(styleNode) do
13+ if name == 'num-slices' then
14+ self.numSlices = tonumber(value)
15+ elseif name == 'num-lines' then
16+ self.numRows = tonumber(value)
17+ end
18+ end
19+end
20+
21+function UIColorWheel.setNumSlices(self, value)
22+ self.numSlices = tonumber(value)
23+end
24+
25+function UIColorWheel.setNumRows(self, value)
26+ self.numRows = tonumber(value)
27+end
28+
29+function UIColorWheel.getColorByMousePos(self)
30+ if not self.numSlices or not self.numRows or self.numSlices <= 0 or self.numRows <= 0 then
31+ return
32+ end
33+
34+ -- define variables
35+ local anglePerSlice = 360 / self.numSlices
36+
37+ local mousePos = g_window.getMousePosition()
38+ local center = { x = self:getWidth() / 2, y = self:getHeight() / 2}
39+ local right = { x = 1, y = 0 }
40+
41+ local localMousePos = {x = mousePos.x - self:getPosition().x - center.x, y = mousePos.y - self:getPosition().y - center.y}
42+ localMousePos.x = localMousePos.x / center.x
43+ localMousePos.y = localMousePos.y / center.y
44+
45+ if getMagnitude(localMousePos) > 1 then
46+ return self.colorIndex
47+ end
48+
49+ -- find angle to find slice
50+ local angle = getAngle(localMousePos, right)
51+ local sliceIndex = math.floor(angle / anglePerSlice)
52+
53+ local rowIndex = math.floor(getMagnitude(localMousePos) * self.numRows)
54+
55+ self.colorIndex = rowIndex * self.numSlices + sliceIndex
56+
57+ return self.colorIndex
58+end
59+
60+function UIColorWheel.getColor(self)
61+ return self.colorIndex
62+end
A RD_Client/modules/gamelib/ui/uicreaturebutton.lua +314-0 View file
@@ -0,0 +1,314 @@
1+-- @docclass
2+UICreatureButton = extends(UIWidget, "UICreatureButton")
3+
4+local CreatureButtonColors = {
5+ onIdle = {notHovered = '#888888', hovered = '#FFFFFF' },
6+ onTargeted = {notHovered = '#FF0000', hovered = '#FF8888' },
7+ onFollowed = {notHovered = '#4CBBFF', hovered = '#93D6FF' }
8+}
9+
10+local FightStanceColors = {
11+ [FightOffensive] = {notHovered = '#FF0000', hovered = '#FF8888' },
12+ [FightBalanced] = {notHovered = '#FFFF00', hovered = '#FFFF8B' },
13+ [FightDefensive] = {notHovered = '#00FF00', hovered = '#88FF88' },
14+}
15+
16+local LifeBarColors = {} -- Must be sorted by percentAbove
17+table.insert(LifeBarColors, {percentAbove = 92, color = '#00BC00' } )
18+table.insert(LifeBarColors, {percentAbove = 60, color = '#50A150' } )
19+table.insert(LifeBarColors, {percentAbove = 30, color = '#A1A100' } )
20+table.insert(LifeBarColors, {percentAbove = 8, color = '#BF0A0A' } )
21+table.insert(LifeBarColors, {percentAbove = 3, color = '#910F0F' } )
22+table.insert(LifeBarColors, {percentAbove = -1, color = '#850C0C' } )
23+
24+local ThreatMeterColors = {
25+ { percentAbove = 67, color = '#ed1c24' },
26+ { percentAbove = 33, color = '#ff7f27' },
27+ { percentAbove = -1, color = '#22b14c' },
28+}
29+
30+function UICreatureButton.create()
31+ local button = UICreatureButton.internalCreate()
32+ button:setFocusable(false)
33+ button.creature = nil
34+ button.creatureName = ''
35+ button.isHovered = false
36+ button.healthPercent = 0
37+ button.manaPercent = 0
38+ button.threatPercent = 0
39+
40+ button.manaBar = false
41+ button.threatBar = false
42+ return button
43+end
44+
45+function UICreatureButton:setCreature(creature)
46+ self.creature = creature
47+end
48+
49+function UICreatureButton:getCreature()
50+ return self.creature
51+end
52+
53+function UICreatureButton:getCreatureId()
54+ return self.creature:getId()
55+end
56+
57+function UICreatureButton:setup(creature, id)
58+ self.lifeBarWidget = self:getChildById('lifeBar')
59+ self.manaBarWidget = self:getChildById('manaBar')
60+ self.threatMeterWidget = self:getChildById('threatMeter')
61+ self.creatureWidget = self:getChildById('creature')
62+ self.labelWidget = self:getChildById('label')
63+ self.skullWidget = self:getChildById('skull')
64+ self.emblemWidget = self:getChildById('emblem')
65+ self.summonWidget = self:getChildById('summon')
66+ self.shieldWidget = self:getChildById('shield')
67+end
68+
69+function UICreatureButton:update()
70+ local color = CreatureButtonColors.onIdle
71+ if self.creature == g_game.getAttackingCreature() then
72+ if self.creature:isNpc() then
73+ color = CreatureButtonColors.onFollowed
74+ else
75+ color = CreatureButtonColors.onTargeted
76+ end
77+ elseif self.creature == g_game.getFollowingCreature() then
78+ color = CreatureButtonColors.onFollowed
79+ end
80+
81+ color = self.isHovered and color.hovered or color.notHovered
82+
83+ if self.color == color then
84+ return
85+ end
86+
87+ self.color = color
88+
89+ if color ~= CreatureButtonColors.onIdle.notHovered then
90+ self.creatureWidget:setBorderWidth(1)
91+ self.creatureWidget:setBorderColor(color)
92+ self.labelWidget:setColor(color)
93+ else
94+ self.creatureWidget:setBorderWidth(0)
95+ self.labelWidget:setColor(color)
96+ end
97+
98+ -- getHealingCreature not implemented in C++ bindings, skip yellow tint
99+ self.creatureWidget:setImageColor('white')
100+end
101+
102+function UICreatureButton:creatureSetup(creature)
103+ if self.creature ~= creature then
104+ self.creature = creature
105+ self.creatureWidget:setCreature(creature)
106+
107+ if self.creatureName ~= creature:getName() then
108+ self.creatureName = creature:getName()
109+ self.labelWidget:setText(creature:getName())
110+ end
111+
112+ if self.manaBar then
113+ self.manaBar = false
114+ self.manaPercent = 0
115+ self.manaBarWidget:setPercent(0)
116+ self.manaBarWidget:setVisible(false)
117+ end
118+
119+ if self.threatBar then
120+ self.threatBar = false
121+ self.threatPercent = 0
122+ self.threatMeterWidget:setPercent(0)
123+ self.threatMeterWidget:setVisible(false)
124+ end
125+
126+ if self.creature:isPlayer() and self.manaBarWidget then
127+ self:setHeight(25)
128+ self.manaBar = true
129+ self.manaPercent = 0
130+ self.manaBarWidget:setPercent(0)
131+ self.manaBarWidget:setVisible(true)
132+ elseif false then -- hasThreatMeter() not implemented in C++ bindings
133+ if self.threatMeterWidget then
134+ self:setHeight(25)
135+ self.threatBar = true
136+ self.threatPercent = 0
137+ self.threatMeterWidget:setPercent(0)
138+ self.threatMeterWidget:setVisible(true)
139+ end
140+ else
141+ self:setHeight(20)
142+ end
143+ end
144+
145+ self:updateLifeBarPercent()
146+ self:updateManaBarPercent()
147+ self:updateThreatMeterPercent()
148+ self:updateSkull()
149+ self:updateEmblem()
150+ self:updateSummon()
151+ self:updateShield()
152+ self:update()
153+end
154+
155+function UICreatureButton:updateSkull()
156+ if not self.creature then
157+ return
158+ end
159+
160+ local skullId = self.creature:getSkull()
161+ if skullId == self.skullId then
162+ return
163+ end
164+
165+ self.skullId = skullId
166+
167+ if skullId ~= SkullNone and self.skullWidget then
168+ self.skullWidget:setWidth(self.skullWidget:getHeight())
169+ local imagePath = getSkullImagePath(skullId)
170+ self.skullWidget:setImageSource(imagePath)
171+ self.skullWidget:setMarginLeft(5)
172+ elseif self.skullWidget then
173+ self.skullWidget:setWidth(0)
174+ end
175+end
176+
177+function UICreatureButton:updateEmblem()
178+ if not self.creature then
179+ return
180+ end
181+
182+ local emblemId = self.creature:getEmblem()
183+ if emblemId == self.emblemId then
184+ return
185+ end
186+
187+ self.emblemId = emblemId
188+
189+ if emblemId ~= EmblemNone and self.emblemWidget then
190+ self.emblemWidget:setWidth(self.emblemWidget:getHeight())
191+ local imagePath = getEmblemImagePath(emblemId)
192+ self.emblemWidget:setImageSource(imagePath)
193+ self.emblemWidget:setMarginLeft(5)
194+ elseif self.emblemWidget then
195+ self.emblemWidget:setWidth(0)
196+ self.emblemWidget:setMarginLeft(0)
197+ end
198+end
199+
200+function UICreatureButton:updateSummon(iconId)
201+ if not self.creature then
202+ return
203+ end
204+
205+ local iconId = self.creature:getType()
206+ if iconId == self.iconId then
207+ return
208+ end
209+
210+ self.iconId = iconId
211+
212+ if iconId >= CreatureTypeSummonOwn and self.summonWidget then
213+ self.summonWidget:setWidth(self.summonWidget:getHeight())
214+ local imagePath = getTypeImagePath(iconId)
215+ self.summonWidget:setImageSource(imagePath)
216+ self.summonWidget:setMarginLeft(7)
217+ elseif self.summonWidget then
218+ self.summonWidget:setWidth(0)
219+ self.summonWidget:setMarginLeft(0)
220+ end
221+end
222+
223+function UICreatureButton:updateShield(shieldId)
224+ if not self.creature then
225+ return
226+ end
227+
228+ local shieldId = self.creature:getShield()
229+ if shieldId == self.shieldId then
230+ return
231+ end
232+
233+ self.shieldId = shieldId
234+
235+ if shieldId ~= ShieldNone and self.shieldWidget then
236+ self.shieldWidget:setWidth(self.shieldWidget:getHeight())
237+ local imagePath = getShieldImagePathAndBlink(shieldId)
238+ self.shieldWidget:setImageSource(imagePath)
239+ self.shieldWidget:setMarginLeft(5)
240+ elseif self.shieldWidget then
241+ self.shieldWidget:setWidth(0)
242+ self.shieldWidget:setMarginLeft(0)
243+ end
244+end
245+
246+function UICreatureButton:updateLifeBarPercent()
247+ if not self.creature then
248+ return
249+ end
250+
251+ local percent = self.creature:getHealthPercent()
252+ if self.healthPercent == percent then
253+ return
254+ end
255+
256+ self.healthPercent = percent
257+ if self.lifeBarWidget then
258+ self.lifeBarWidget:setPercent(percent)
259+
260+ local color
261+ for i, v in pairs(LifeBarColors) do
262+ if percent > v.percentAbove then
263+ color = v.color
264+ break
265+ end
266+ end
267+
268+ self.lifeBarWidget:setBackgroundColor(color)
269+ end
270+end
271+
272+function UICreatureButton:updateManaBarPercent()
273+ if not self.creature or not self.manaBar then
274+ return
275+ end
276+
277+ local percent = self.creature:getManaPercent()
278+ if self.manaPercent == percent then
279+ return
280+ end
281+
282+ self.manaPercent = percent
283+ if self.manaBarWidget then
284+ self.manaBarWidget:setPercent(percent)
285+ self.manaBarWidget:setBackgroundColor('#ba55d3')
286+ end
287+end
288+
289+function UICreatureButton:updateThreatMeterPercent()
290+ if not self.creature or not self.threatBar then
291+ return
292+ end
293+
294+ -- getThreatMeterPercent() not implemented in C++ bindings
295+ local percent = 0
296+ if self.threatPercent == percent then
297+ return
298+ end
299+
300+ self.threatPercent = percent
301+ if self.threatMeterWidget then
302+ self.threatMeterWidget:setPercent(percent)
303+
304+ local color
305+ for i, v in pairs(ThreatMeterColors) do
306+ if percent > v.percentAbove then
307+ color = v.color
308+ break
309+ end
310+ end
311+
312+ self.threatMeterWidget:setBackgroundColor(color)
313+ end
314+end
A RD_Client/modules/gamelib/ui/uihealthbar.lua +60-0 View file
@@ -0,0 +1,60 @@
1+-- @docclass
2+UIHealthBar = extends(UIWidget, "UIHealthBar")
3+
4+function UIHealthBar.create()
5+ local bar = UIHealthBar.internalCreate()
6+ bar:setPhantom(false)
7+ bar:setAutoFocusPolicy(AutoFocusFirst)
8+ bar.hasBorder = true
9+ return bar
10+end
11+
12+function UIHealthBar.setHealthBorder(self, value)
13+ self.hasBorder = value
14+end
15+
16+-- used for healthbars, manabars and resourcebars (check game_healthinfo module for examples)
17+function UIHealthBar.setPercentage(self, value, maxValue)
18+ -- the extra resize code is implemented to work in junction with uiwidget.lua:resizeElements
19+ local percentage = math.min(100, math.floor((value/maxValue)*100))
20+ local widget = self:recursiveGetChildById('bar')
21+ local icon = self:recursiveGetChildById('icon')
22+
23+ widget:setImageWidth(math.ceil((widget:getWidth() - icon:getWidth() + 13) * percentage / 100))
24+
25+ widget:setVisible(widget:getImageWidth() > 0)
26+
27+ local text = self:recursiveGetChildById('text')
28+ if text then
29+ --text:setText(getFormattedShortNumber(value) .. '/' .. getFormattedShortNumber(maxValue))
30+ text:setText(value .. '/' .. maxValue)
31+ end
32+
33+ local resourceText = self:recursiveGetChildById('resourceText')
34+ if resourceText then
35+ resourceText:setText(value)
36+ end
37+
38+--[[
39+ local indicator = self:recursiveGetChildById('indicator')
40+ if indicator then
41+ indicator:setMarginRight(-2*resizeFactor + (size.width - size.newWidth))
42+ end
43+
44+
45+ local anim = self:recursiveGetChildById('orb')
46+ local backGlow = self:recursiveGetChildById('background-glow')
47+ if anim and backGlow then
48+ if percentage == 100 then
49+ -- anim:play()
50+ backGlow:setOpacity(0)
51+ backGlow:show()
52+ g_effects.startFadeInOut(backGlow, 0, 50, 0.05)
53+ else
54+ -- anim:stop()
55+ g_effects.stopFadeInOut(backGlow)
56+ backGlow:hide()
57+ end
58+ end
59+]]
60+end
A RD_Client/modules/gamelib/ui/uiitem.lua +137-0 View file
@@ -0,0 +1,137 @@
1+function UIItem:onDragEnter(mousePos)
2+ if self:isVirtual() then return false end
3+
4+ local item = self:getItem()
5+ if not item then return false end
6+
7+ self:setBorderWidth(1)
8+ self.currentDragThing = item
9+ g_mouse.pushCursor('target')
10+ return true
11+end
12+
13+function UIItem:onDragLeave(droppedWidget, mousePos)
14+ if self:isVirtual() then return false end
15+ self.currentDragThing = nil
16+ g_mouse.popCursor('target')
17+ self:setBorderWidth(0)
18+ self.hoveredWho = nil
19+ return true
20+end
21+
22+function UIItem:onDrop(widget, mousePos, forced)
23+ if not self:canAcceptDrop(widget, mousePos) and not forced then return false end
24+
25+ local item = widget.currentDragThing
26+ if not item or not item:isItem() then return false end
27+
28+ if self.selectable then
29+ if item:isPickupable() then
30+ self:setItem(Item.create(item:getId(), item:getCountOrSubType()))
31+ return true
32+ end
33+ return false
34+ end
35+
36+ local toPos = self.position
37+
38+ local itemPos = item:getPosition()
39+ if itemPos.x == toPos.x and itemPos.y == toPos.y and itemPos.z == toPos.z then return false end
40+
41+ if item:getCount() > 1 then
42+ modules.game_interface.moveStackableItem(item, toPos)
43+ else
44+ g_game.move(item, toPos, 1)
45+ end
46+
47+ self:setBorderWidth(0)
48+ return true
49+end
50+
51+function UIItem:onDestroy()
52+ if self == g_ui.getDraggingWidget() and self.hoveredWho then
53+ self.hoveredWho:setBorderWidth(0)
54+ end
55+
56+ if self.hoveredWho then
57+ self.hoveredWho = nil
58+ end
59+end
60+
61+function UIItem:onHoverChange(hovered)
62+ UIWidget.onHoverChange(self, hovered)
63+
64+ if self:isVirtual() or not self:isDraggable() then return end
65+
66+ local draggingWidget = g_ui.getDraggingWidget()
67+ if draggingWidget and self ~= draggingWidget then
68+ local gotMap = draggingWidget:getClassName() == 'UIGameMap'
69+ local gotItem = draggingWidget:getClassName() == 'UIItem' and not draggingWidget:isVirtual()
70+ if hovered and (gotItem or gotMap) then
71+ self:setBorderWidth(1)
72+ draggingWidget.hoveredWho = self
73+ else
74+ self:setBorderWidth(0)
75+ draggingWidget.hoveredWho = nil
76+ end
77+ end
78+end
79+
80+function UIItem:onMouseRelease(mousePosition, mouseButton)
81+ if self.cancelNextRelease then
82+ self.cancelNextRelease = false
83+ return true
84+ end
85+
86+ if self:isVirtual() then return false end
87+
88+ local item = self:getItem()
89+ if not item or not self:containsPoint(mousePosition) then return false end
90+
91+ if modules.client_options.getOption('classicControl') and not g_app.isMobile() and
92+ ((g_mouse.isPressed(MouseLeftButton) and mouseButton == MouseRightButton) or
93+ (g_mouse.isPressed(MouseRightButton) and mouseButton == MouseLeftButton)) then
94+ g_game.look(item)
95+ self.cancelNextRelease = true
96+ return true
97+ elseif modules.game_interface.processMouseAction(mousePosition, mouseButton, nil, item, item, nil, nil) then
98+ return true
99+ end
100+ return false
101+end
102+
103+function UIItem:canAcceptDrop(widget, mousePos)
104+ if not self.selectable and (self:isVirtual() or not self:isDraggable()) then return false end
105+ if not widget or not widget.currentDragThing then return false end
106+
107+ local children = rootWidget:recursiveGetChildrenByPos(mousePos)
108+ for i=1,#children do
109+ local child = children[i]
110+ if child == self then
111+ return true
112+ elseif not child:isPhantom() then
113+ return false
114+ end
115+ end
116+
117+ error('Widget ' .. self:getId() .. ' not in drop list.')
118+ return false
119+end
120+
121+function UIItem:onClick(mousePos)
122+ if not self.selectable or not self.editable then
123+ return
124+ end
125+
126+ if modules.game_itemselector then
127+ modules.game_itemselector.show(self)
128+ end
129+end
130+
131+function UIItem:onItemChange()
132+ local tooltip = nil
133+ if self:getItem() and self:getItem():getTooltip():len() > 0 then
134+ tooltip = self:getItem():getTooltip()
135+ end
136+ self:setTooltip(tooltip)
137+end
A RD_Client/modules/gamelib/ui/uimenupanel.lua +35-0 View file
@@ -0,0 +1,35 @@
1+-- @docclass
2+UIMenuPanel = extends(UIWidget, "UIMenuPanel")
3+
4+function UIMenuPanel.create()
5+ local panel = UIMenuPanel.internalCreate()
6+
7+--[[
8+ connect(rootWidget, {
9+ onUIResize = function() panel:onUIResize(styleName, styleNode) end
10+ })
11+]]
12+ return panel
13+end
14+
15+function UIMenuPanel:onUIResize(styleName, styleNode)
16+ if self.autoResize == false then
17+ return
18+ end
19+
20+ if not self.originalWidth then
21+ self.originalWidth = self:getWidth()
22+ self.widthRatio = self.originalWidth/(1920-152)
23+ end
24+
25+ local width = g_window:getWidth() - 152
26+ self:setWidth(self.widthRatio * width)
27+end
28+
29+function UIMenuPanel:onStyleApply(styleName, styleNode)
30+ for name,value in pairs(styleNode) do
31+ if name == 'auto-resize' then
32+ self.autoResize = value
33+ end
34+ end
35+end
A RD_Client/modules/gamelib/ui/uiminimap.lua +356-0 View file
@@ -0,0 +1,356 @@
1+-- Left-click teleport for gamemasters. The server tells us on login whether
2+-- this character may use it (server: data/scripts/customs/admin_minimap_teleport.lua).
3+local ADMIN_MINIMAP_TELEPORT_OPCODE = 215
4+local adminMinimapTeleportEnabled = false
5+local adminMinimapTeleportRegistered = false
6+
7+local function sendAdminMinimapTeleport(pos)
8+ local protocolGame = g_game.getProtocolGame()
9+ if not protocolGame or not pos then
10+ return false
11+ end
12+
13+ protocolGame:sendExtendedOpcode(ADMIN_MINIMAP_TELEPORT_OPCODE, json.encode({
14+ action = 'teleport',
15+ position = { x = pos.x, y = pos.y, z = pos.z }
16+ }))
17+ return true
18+end
19+
20+local function onAdminMinimapTeleportOpcode(protocol, opcode, buffer)
21+ local ok, status = pcall(json.decode, buffer)
22+ if not ok or type(status) ~= 'table' or status.action ~= 'access' then
23+ return
24+ end
25+
26+ adminMinimapTeleportEnabled = status.enabled == true
27+end
28+
29+function UIMinimap:onCreate()
30+ self.autowalk = true
31+end
32+
33+function UIMinimap:onSetup()
34+ if not adminMinimapTeleportRegistered then
35+ -- pcall: after a gamelib reload the opcode is still registered
36+ adminMinimapTeleportRegistered = pcall(ProtocolGame.registerExtendedOpcode,
37+ ADMIN_MINIMAP_TELEPORT_OPCODE, onAdminMinimapTeleportOpcode)
38+ end
39+
40+ self.flagWindow = nil
41+ self.flags = {}
42+ self.alternatives = {}
43+ self.onAddAutomapFlag = function(pos, icon, description) self:addFlag(pos, icon, description) end
44+ self.onRemoveAutomapFlag = function(pos, icon, description) self:removeFlag(pos, icon, description) end
45+ connect(g_game, {
46+ onAddAutomapFlag = self.onAddAutomapFlag,
47+ onRemoveAutomapFlag = self.onRemoveAutomapFlag,
48+ })
49+end
50+
51+function UIMinimap:onDestroy()
52+ for _,widget in pairs(self.alternatives) do
53+ widget:destroy()
54+ end
55+ self.alternatives = {}
56+ disconnect(g_game, {
57+ onAddAutomapFlag = self.onAddAutomapFlag,
58+ onRemoveAutomapFlag = self.onRemoveAutomapFlag,
59+ })
60+ self:destroyFlagWindow()
61+ self.flags = {}
62+end
63+
64+function UIMinimap:onVisibilityChange()
65+ if not self:isVisible() then
66+ self:destroyFlagWindow()
67+ end
68+end
69+
70+function UIMinimap:onCameraPositionChange(cameraPos)
71+ if self.cross then
72+ self:setCrossPosition(self.cross.pos)
73+ end
74+end
75+
76+function UIMinimap:hideFloor()
77+ self.floorUpWidget:hide()
78+ self.floorDownWidget:hide()
79+end
80+
81+function UIMinimap:hideZoom()
82+ self.zoomInWidget:hide()
83+ self.zoomOutWidget:hide()
84+end
85+
86+function UIMinimap:disableAutoWalk()
87+ self.autowalk = false
88+end
89+
90+function UIMinimap:load()
91+ local settings = g_settings.getNode('Minimap')
92+ if settings then
93+ if settings.flags then
94+ for _,flag in pairs(settings.flags) do
95+ self:addFlag(flag.position, flag.icon, flag.description)
96+ end
97+ end
98+ self:setZoom(settings.zoom)
99+ end
100+end
101+
102+function UIMinimap:save()
103+ local settings = { flags={} }
104+ for _,flag in pairs(self.flags) do
105+ if not flag.temporary then
106+ table.insert(settings.flags, {
107+ position = flag.pos,
108+ icon = flag.icon,
109+ description = flag.description,
110+ })
111+ end
112+ end
113+ settings.zoom = self:getZoom()
114+ g_settings.setNode('Minimap', settings)
115+end
116+
117+local function onFlagMouseRelease(widget, pos, button)
118+ if button == MouseRightButton then
119+ local menu = g_ui.createWidget('PopupMenu')
120+ menu:setGameMenu(true)
121+ menu:addOption(tr('Delete mark'), function() widget:destroy() end)
122+ menu:display(pos)
123+ return true
124+ end
125+ return false
126+end
127+
128+function UIMinimap:setCrossPosition(pos)
129+ local cross = self.cross
130+ if not self.cross then
131+ cross = g_ui.createWidget('MinimapCross', self)
132+ cross:setIcon('/images/ui/windows/minimap/cross')
133+ self.cross = cross
134+ end
135+
136+ pos.z = self:getCameraPosition().z
137+ cross.pos = pos
138+ if pos then
139+ self:centerInPosition(cross, pos)
140+ else
141+ cross:breakAnchors()
142+ end
143+end
144+
145+function UIMinimap:addFlag(pos, icon, description, temporary)
146+ if not pos or not icon then return end
147+ local flag = self:getFlag(pos, icon, description)
148+ if flag or not icon then
149+ return
150+ end
151+ temporary = temporary or false
152+
153+ flag = g_ui.createWidget('MinimapFlag')
154+ self:insertChild(1, flag)
155+ flag.pos = pos
156+ flag.description = description
157+ flag.icon = icon
158+ flag.temporary = temporary
159+ if type(tonumber(icon)) == 'number' then
160+ flag:setIcon('/images/ui/windows/minimap/flag' .. icon)
161+ else
162+ flag:setIcon(resolvepath(icon, 1))
163+ end
164+ flag:setTooltip(description)
165+ flag.onMouseRelease = onFlagMouseRelease
166+ flag.onDestroy = function() table.removevalue(self.flags, flag) end
167+ table.insert(self.flags, flag)
168+ self:centerInPosition(flag, pos)
169+end
170+
171+function UIMinimap:addAlternativeWidget(widget, pos, maxZoom)
172+ widget.pos = pos
173+ widget.maxZoom = maxZoom or 0
174+ widget.minZoom = minZoom
175+ table.insert(self.alternatives, widget)
176+end
177+
178+function UIMinimap:setAlternativeWidgetsVisible(show)
179+ local layout = self:getLayout()
180+ layout:disableUpdates()
181+ for _,widget in pairs(self.alternatives) do
182+ if show then
183+ self:insertChild(1, widget)
184+ self:centerInPosition(widget, widget.pos)
185+ else
186+ self:removeChild(widget)
187+ widget:destroy()
188+ end
189+ end
190+ layout:enableUpdates()
191+ layout:update()
192+end
193+
194+function UIMinimap:onZoomChange(zoom)
195+ for _,widget in pairs(self.alternatives) do
196+ if (not widget.minZoom or widget.minZoom >= zoom) and widget.maxZoom <= zoom then
197+ widget:show()
198+ else
199+ widget:hide()
200+ end
201+ end
202+end
203+
204+function UIMinimap:getFlag(pos)
205+ for _,flag in pairs(self.flags) do
206+ if flag.pos.x == pos.x and flag.pos.y == pos.y and flag.pos.z == pos.z then
207+ return flag
208+ end
209+ end
210+ return nil
211+end
212+
213+function UIMinimap:removeFlag(pos, icon, description)
214+ local flag = self:getFlag(pos)
215+ if flag then
216+ flag:destroy()
217+ end
218+end
219+
220+function UIMinimap:reset()
221+ self:setZoom(0)
222+ if self.cross then
223+ self:setCameraPosition(self.cross.pos)
224+ end
225+end
226+
227+function UIMinimap:move(x, y)
228+ local cameraPos = self:getCameraPosition()
229+ local scale = self:getScale()
230+ if scale > 1 then scale = 1 end
231+ local dx = x/scale
232+ local dy = y/scale
233+ local pos = {x = cameraPos.x - dx, y = cameraPos.y - dy, z = cameraPos.z}
234+ self:setCameraPosition(pos)
235+end
236+
237+function UIMinimap:onMouseWheel(mousePos, direction)
238+ local keyboardModifiers = g_keyboard.getModifiers()
239+ if direction == MouseWheelUp and keyboardModifiers == KeyboardNoModifier then
240+ self:zoomIn()
241+ elseif direction == MouseWheelDown and keyboardModifiers == KeyboardNoModifier then
242+ self:zoomOut()
243+ elseif direction == MouseWheelDown and keyboardModifiers == KeyboardCtrlModifier then
244+ self:floorUp(1)
245+ elseif direction == MouseWheelUp and keyboardModifiers == KeyboardCtrlModifier then
246+ self:floorDown(1)
247+ end
248+end
249+
250+function UIMinimap:onMousePress(pos, button)
251+ if not self:isDragging() then
252+ self.allowNextRelease = true
253+ end
254+end
255+
256+function UIMinimap:onMouseRelease(pos, button)
257+ if not self.allowNextRelease then return true end
258+ self.allowNextRelease = false
259+
260+ local mapPos = self:getTilePosition(pos)
261+ if not mapPos then return end
262+
263+ if button == MouseLeftButton then
264+ local player = g_game.getLocalPlayer()
265+ if adminMinimapTeleportEnabled and sendAdminMinimapTeleport(mapPos) then
266+ return true
267+ end
268+ if self.autowalk then
269+ player:autoWalk(mapPos)
270+ end
271+ return true
272+ elseif button == MouseRightButton then
273+ local menu = g_ui.createWidget('PopupMenu')
274+ menu:setGameMenu(true)
275+ menu:addOption(tr('Create mark'), function() self:createFlagWindow(mapPos) end)
276+ menu:display(pos)
277+ return true
278+ end
279+ return false
280+end
281+
282+function UIMinimap:onDragEnter(pos)
283+ self.dragReference = pos
284+ self.dragCameraReference = self:getCameraPosition()
285+ return true
286+end
287+
288+function UIMinimap:onDragMove(pos, moved)
289+ local scale = self:getScale()
290+ local dx = (self.dragReference.x - pos.x)/scale
291+ local dy = (self.dragReference.y - pos.y)/scale
292+ local pos = {x = self.dragCameraReference.x + dx, y = self.dragCameraReference.y + dy, z = self.dragCameraReference.z}
293+ self:setCameraPosition(pos)
294+ return true
295+end
296+
297+function UIMinimap:onDragLeave(widget, pos)
298+ return true
299+end
300+
301+function UIMinimap:onStyleApply(styleName, styleNode)
302+ for name,value in pairs(styleNode) do
303+ if name == 'autowalk' then
304+ self.autowalk = value
305+ end
306+ end
307+end
308+
309+function UIMinimap:createFlagWindow(pos)
310+ if self.flagWindow then return end
311+ if not pos then return end
312+
313+ self.flagWindow = g_ui.createWidget('MinimapFlagWindow', rootWidget)
314+ self.flagWindow:setWidth(210) -- For some reason changing this in otui got ignored
315+ self.flagWindow:setHeight(215)
316+
317+ local positionLabel = self.flagWindow:getChildById('position')
318+ local description = self.flagWindow:getChildById('description')
319+ local okButton = self.flagWindow:getChildById('okButton')
320+ local cancelButton = self.flagWindow:getChildById('cancelButton')
321+
322+ positionLabel:setText(string.format('%i, %i, %i', pos.x, pos.y, pos.z))
323+
324+ local flagRadioGroup = UIRadioGroup.create()
325+ for i=0,19 do
326+ local checkbox = self.flagWindow:getChildById('flag' .. i)
327+ checkbox.icon = i
328+ flagRadioGroup:addWidget(checkbox)
329+ end
330+
331+ flagRadioGroup:selectWidget(flagRadioGroup:getFirstWidget())
332+
333+ local successFunc = function()
334+ self:addFlag(pos, flagRadioGroup:getSelectedWidget().icon, description:getText())
335+ self:destroyFlagWindow()
336+ end
337+
338+ local cancelFunc = function()
339+ self:destroyFlagWindow()
340+ end
341+
342+ okButton.onClick = successFunc
343+ cancelButton.onClick = cancelFunc
344+
345+ self.flagWindow.onEnter = successFunc
346+ self.flagWindow.onEscape = cancelFunc
347+
348+ self.flagWindow.onDestroy = function() flagRadioGroup:destroy() end
349+end
350+
351+function UIMinimap:destroyFlagWindow()
352+ if self.flagWindow then
353+ self.flagWindow:destroy()
354+ self.flagWindow = nil
355+ end
356+end
A RD_Client/modules/gamelib/ui/uiradialprogress.lua +83-0 View file
@@ -0,0 +1,83 @@
1+-- @docclass
2+UIRadialProgress = extends(UIWidget, "UIRadialProgress")
3+
4+function UIRadialProgress.create()
5+ local progressbar = UIRadialProgress.internalCreate()
6+ progressbar.progress = 100
7+ progressbar.radial = false
8+ return progressbar
9+end
10+
11+function UIRadialProgress:onStyleApply(name, node)
12+ for name,value in pairs(node) do
13+ if name == 'radial' then
14+ self:setRadial(value)
15+ end
16+ end
17+end
18+
19+function UIRadialProgress:setProgress(value)
20+ self.progress = tonumber(value) or 100
21+ self:updateProgressbar()
22+end
23+
24+function UIRadialProgress:getProgress()
25+ return self.progress
26+end
27+
28+function UIRadialProgress:setRadial(value)
29+ self.radial = not not value
30+end
31+
32+function UIRadialProgress:isRadial()
33+ return self.radial
34+end
35+
36+function UIRadialProgress:updateProgressbar()
37+ local progress1 = self:getChildById('progress1')
38+ local progress2 = self:getChildById('progress2')
39+
40+ if not (progress1 and progress2) then
41+ return
42+ end
43+
44+ local radius = progress1:getImageTextureWidth()
45+ local height = radius * 2
46+ local progressRad = self.progress / 100 * math.pi * 2
47+
48+ -- calculate progressbar heights
49+ local height1 = radius * 2
50+ local height2 = 0
51+
52+ if self.radial then
53+ if progressRad < math.pi then
54+ height1 = radius - math.cos(progressRad) * radius
55+ else
56+ progressRad = progressRad - math.pi
57+ height2 = radius - math.cos(progressRad) * radius
58+ end
59+ else
60+ if self.progress < 50 then
61+ height1 = self.progress / 50 * height
62+ else
63+ height2 = (self.progress-50) / 50 * height
64+ end
65+ end
66+
67+ -- set image rects
68+ if height1 == 0 then
69+ progress1:hide()
70+ else
71+ progress1:show()
72+ progress1:setImageRect({x=0, y=0, width=radius, height=height1})
73+ progress1:setImageClip({x=0, y=0, width=radius, height=height1})
74+ end
75+
76+ if height2 == 0 then
77+ progress2:hide()
78+ else
79+ progress2:show()
80+ progress2:setImageRect({x=0, y=height-height2, width=radius, height=height2})
81+ progress2:setImageClip({x=0, y=height-height2, width=radius, height=height2})
82+ end
83+end
A RD_Client/modules/gamelib/ui/uirating.lua +67-0 View file
@@ -0,0 +1,67 @@
1+-- @docclass
2+UIRating = extends(UIWidget, "UIRating")
3+
4+function UIRating.create()
5+ local panel = UIRating.internalCreate()
6+ panel.rating = 5
7+ return panel
8+end
9+
10+function UIRating:onStyleApply(styleName, styleNode)
11+ for name,value in pairs(styleNode) do
12+ if name == 'rating' then
13+ self.rating = value -- delayed execution
14+ elseif name == 'custom-size' then
15+ self.customSize = value
16+ end
17+ end
18+end
19+
20+function UIRating:setRating(value)
21+ self._rating = self.rating
22+
23+ for index, child in pairs(self:getChildren()) do
24+ if index <= value then
25+ child:show()
26+ else
27+ child:hide()
28+ end
29+ end
30+
31+ self:updateSize()
32+end
33+
34+function UIRating:updateSize()
35+ local children = 0
36+ for index, child in pairs(self:getChildren()) do
37+ if child:isVisible() then
38+ children = children + 1
39+ end
40+ end
41+ self:setWidth(self:getHeight() * children + self:getLayout():getSpacing() * (children-1))
42+end
43+
44+function UIRating:onGeometryChange()
45+ if self.customSize then
46+ self:setCustomSize(self.customSize)
47+ self.customSize = nil
48+ end
49+ if self.rating and self.rating ~= self._rating then
50+ self:setRating(self.rating)
51+ end
52+end
53+
54+function UIRating:getRating()
55+ return self.rating
56+end
57+
58+function UIRating:setCustomSize(size)
59+ self:setHeight(size)
60+
61+ for index, child in pairs(self:getChildren()) do
62+ child:setWidth(size)
63+ child:setHeight(size)
64+ end
65+
66+ self:updateSize()
67+end
A RD_Client/modules/gamelib/ui/uispellprogressrect.lua +89-0 View file
@@ -0,0 +1,89 @@
1+-- @docclass
2+UISpellProgressRect = extends(UIProgressRect, "UISpellProgressRect")
3+
4+function UISpellProgressRect.create()
5+ local progressbar = UISpellProgressRect.internalCreate()
6+ progressbar.cooldown = 1000
7+ progressbar.event = nil
8+ progressbar.currentCooldown = 0
9+ progressbar.showText = false
10+ progressbar.globalCooldown = false
11+ progressbar.cooldownRemainder = 0
12+ return progressbar
13+end
14+
15+function UISpellProgressRect:setCooldown(num)
16+ self.cooldown = num
17+end
18+
19+function UISpellProgressRect:getCooldown()
20+ return self.cooldown
21+end
22+
23+function UISpellProgressRect:reset()
24+ removeEvent(self.event)
25+ self.currentCooldown = 0
26+ self.cooldownRemainder = 0
27+ self:removeSpellCooldown()
28+end
29+
30+function UISpellProgressRect:onStyleApply(name, node)
31+ for name,value in pairs(node) do
32+ if name == 'cooldown' then
33+ self.cooldown = tonumber(value)
34+ elseif name == 'show-text' then
35+ self.showText = true
36+ end
37+ end
38+end
39+
40+function UISpellProgressRect:onSpellCooldown(cooldown, isGCD)
41+ if self.event then
42+ removeEvent(self.event)
43+ if not self.globalCooldown and self.currentCooldown > 0 then
44+ self.cooldownRemainder = self.currentCooldown
45+ end
46+ end
47+
48+ self.cooldown = cooldown
49+ self.currentCooldown = self.cooldown
50+ self.globalCooldown = isGCD
51+ self:setPercent(100)
52+
53+ local text = self:recursiveGetChildById('text')
54+ if text then
55+ text:setText(math.max(0, math.ceil(cooldown/1000)))
56+ text:setVisible(self.showText)
57+ end
58+
59+ local widget = self
60+ self.event = scheduleEvent(function() widget:updateSpellCooldown() end, 10)
61+ self:show()
62+end
63+
64+function UISpellProgressRect:updateSpellCooldown()
65+ if self.currentCooldown <= 0 then
66+ if not self.globalCooldown then self.cooldownRemainder = 0 end
67+ self:removeSpellCooldown()
68+ return
69+ end
70+
71+ if self.globalCooldown and self.cooldownRemainder > 0 then self.cooldownRemainder = self.cooldownRemainder - 10 end
72+ self.currentCooldown = self.currentCooldown - 10
73+ self:setPercent(100 - (self.currentCooldown/self.cooldown * 100))
74+ self.text:setText(math.max(0, math.ceil(self.currentCooldown/1000)))
75+
76+ local widget = self
77+ self.event = scheduleEvent(function() widget:updateSpellCooldown(false) end, 10)
78+ self:show()
79+end
80+
81+function UISpellProgressRect:removeSpellCooldown()
82+ self.globalCooldown = false
83+ self:hide()
84+ removeEvent(self.event)
85+ self.event = nil
86+ if self.cooldownRemainder > 0 then
87+ self:onSpellCooldown(self.cooldownRemainder, false)
88+ end
89+end
A RD_Client/modules/gamelib/ui/uisprite.lua +134-0 View file
@@ -0,0 +1,134 @@
1+-- @docclass
2+UISprite = extends(UIImageView, "UISprite")
3+
4+function UISprite.create()
5+ local widget = UISprite.internalCreate()
6+ widget.anim = {
7+ path = '',
8+ count = 0,
9+ speed = 0,
10+ currentIndex = 0,
11+ startIndex = 1,
12+ defaultIndex = 1,
13+ prefix = '',
14+ }
15+ widget.event = nil
16+ widget.playing = false
17+ return widget
18+end
19+
20+function UISprite:onStyleApply(styleName, styleNode)
21+ local updateAnim = false
22+
23+ for name,value in pairs(styleNode) do
24+ if name == 'path' then
25+ if not value:ends('/') then
26+ value = value .. '/'
27+ end
28+ self.anim.path = value
29+ updateAnim = true
30+ elseif name == 'prefix' then
31+ self.anim.prefix = value
32+ updateAnim = true
33+ elseif name == 'count' then
34+ self.anim.count = tonumber(value)
35+ updateAnim = true
36+ elseif name == 'speed' then
37+ self.anim.speed = tonumber(value)
38+ updateAnim = true
39+ elseif name == 'start-index' then
40+ self.anim.startIndex = tonumber(value)
41+ updateAnim = true
42+ elseif name == 'default-index' then
43+ self.anim.defaultIndex = tonumber(value)
44+ updateAnim = true
45+ end
46+ end
47+
48+ if updateAnim then
49+ self:updateAnim()
50+ end
51+end
52+
53+function UISprite:onReset()
54+ local anim = self.anim
55+ anim.currentIndex = anim.defaultIndex
56+
57+ local filename = anim.path .. anim.prefix .. anim.currentIndex
58+ self:setImageSource(filename)
59+
60+ -- sprite.event = scheduleEvent(self.onAnim, sprite.speed)
61+end
62+
63+function UISprite:onStep()
64+ local anim = self.anim
65+ anim.currentIndex = anim.currentIndex + 1
66+ if anim.currentIndex > anim.count then
67+ -- todo implement loop count
68+ -- anim.currentIndex = anim.startIndex
69+ self:stopAnim()
70+ return
71+ end
72+
73+ local filename = anim.path .. anim.prefix .. anim.currentIndex
74+ self:setImageSource(filename)
75+
76+ -- sprite.event = scheduleEvent(self.onAnim, sprite.speed)
77+end
78+
79+function UISprite:updateAnim()
80+ -- if not self.sprite then return end
81+
82+ if self.event then
83+ removeEvent(self.event)
84+ self.event = nil
85+ end
86+
87+ if self.playing == false then
88+ return
89+ end
90+
91+ if self.anim then
92+ local speed = self.anim.speed
93+ if speed > 0 then
94+ self.event = cycleEvent(function() self:onStep() end, speed)
95+ end
96+ end
97+end
98+
99+function UISprite:stopAnim()
100+ removeEvent(self.event)
101+ self.event = nil
102+
103+ removeEvent(self.delayEvent)
104+ self.delayEvent = nil
105+
106+ self.playing = false
107+
108+ self:onReset()
109+end
110+
111+function UISprite:startAnim(delay)
112+ if delay and delay < 0 then delay = 0 end
113+ local spr = self
114+
115+ if delay and delay > 0 then
116+ self.delayEvent = scheduleEvent(function()
117+ spr.playing = true
118+ spr:updateAnim()
119+ end, delay)
120+ else
121+ spr.playing = true
122+ spr:updateAnim()
123+ end
124+
125+end
126+
127+function UISprite:onDestroy()
128+ removeEvent(self.event)
129+ self.event = nil
130+end
131+
132+function UISprite:isPlaying()
133+ return self.event ~= nil
134+end
A RD_Client/modules/gamelib/ui/uitradepacklistitem.lua +50-0 View file
@@ -0,0 +1,50 @@
1+-- @docclass
2+UITradepackListItem = extends(UIWidget, "UITradepackListItem")
3+
4+function UITradepackListItem.create()
5+ local btn = UITradepackListItem.internalCreate()
6+ return btn
7+end
8+
9+function UITradepackListItem:onStyleApply(styleName, styleNode)
10+ for name,value in pairs(styleNode) do
11+ if name == 'even-background-color' then
12+ self.evenBackgroundColor = value
13+ elseif name == 'odd-background-color' then
14+ self.oddBackgroundColor = value
15+ end
16+ end
17+end
18+
19+function UITradepackListItem:updateBackgroundColor()
20+ self.backgroundColor = nil
21+
22+ local children = self:getParent():getChildren()
23+ local isEven = self.isEven
24+
25+ if isEven and self.evenBackgroundColor then
26+ self.backgroundColor = self.evenBackgroundColor
27+ elseif not isEven and self.oddBackgroundColor then
28+ self.backgroundColor = self.oddBackgroundColor
29+ end
30+
31+ if self.backgroundColor then
32+ self:mergeStyle({ ['background-color'] = self.backgroundColor })
33+ end
34+end
35+
36+function UITradepackListItem:setRowBackgroundColor(order)
37+ self.backgroundColor = nil
38+
39+ local children = self:getParent():getChildren()
40+
41+ if order == 'even' and self.evenBackgroundColor then
42+ self.backgroundColor = self.evenBackgroundColor
43+ elseif order == 'odd' and self.oddBackgroundColor then
44+ self.backgroundColor = self.oddBackgroundColor
45+ end
46+
47+ if self.backgroundColor then
48+ self:mergeStyle({ ['background-color'] = self.backgroundColor })
49+ end
50+end
A RD_Client/modules/gamelib/util.lua +11-0 View file
@@ -0,0 +1,11 @@
1+function postostring(pos)
2+ return pos.x .. " " .. pos.y .. " " .. pos.z
3+end
4+
5+function dirtostring(dir)
6+ for k, v in pairs(Directions) do
7+ if v == dir then
8+ return k
9+ end
10+ end
11+end
A RD_Client/modules/game_actionbar/actionbar.lua +455-0 View file
@@ -0,0 +1,455 @@
1+actionPanel1 = nil
2+actionPanel2 = nil
3+
4+local actionConfig
5+local hotkeyAssignWindow
6+local actionButtonsInPanel = 50
7+
8+ActionTypes = {
9+ USE = 0,
10+ USE_SELF = 1,
11+ USE_TARGET = 2,
12+ USE_WITH = 3,
13+ EQUIP = 4
14+}
15+
16+ActionColors = {
17+ empty = '#00000033',
18+ text = '#00000033',
19+ itemUse = '#8888FF88',
20+ itemUseSelf = '#00FF0088',
21+ itemUseTarget = '#FF000088',
22+ itemUseWith = '#F5B32588',
23+ itemEquip = '#FFFFFF88'
24+}
25+
26+function init()
27+ local bottomPanel = modules.game_interface.getActionPanel()
28+ actionPanel1 = g_ui.loadUI('actionbar', bottomPanel)
29+ bottomPanel:moveChildToIndex(actionPanel1, 1)
30+ actionPanel2 = g_ui.loadUI('actionbar', bottomPanel)
31+ bottomPanel:moveChildToIndex(actionPanel2, 1)
32+
33+ actionConfig = g_configs.create("/actionbar.otml")
34+
35+ connect(g_game, {
36+ onGameStart = online,
37+ onGameEnd = offline,
38+ onSpellGroupCooldown = onSpellGroupCooldown,
39+ onSpellCooldown = onSpellCooldown
40+ })
41+
42+ if g_game.isOnline() then
43+ online()
44+ end
45+end
46+
47+function terminate()
48+ disconnect(g_game, {
49+ onGameStart = online,
50+ onGameEnd = offline,
51+ onSpellGroupCooldown = onSpellGroupCooldown,
52+ onSpellCooldown = onSpellCooldown
53+ })
54+
55+ -- remove hotkeys, also saves config
56+ if actionPanel1.tabBar:getChildCount() > 0 and actionPanel2.tabBar:getChildCount() > 0 then
57+ offline()
58+ end
59+
60+ actionPanel1:destroy()
61+ actionPanel2:destroy()
62+end
63+
64+function show()
65+ if not g_game.isOnline() then return end
66+ actionPanel1:setOn(g_settings.getBoolean("actionBar1", false))
67+ actionPanel2:setOn(g_settings.getBoolean("actionBar2", false))
68+end
69+
70+function hide()
71+ actionPanel1:setOn(false)
72+ actionPanel2:setOn(false)
73+end
74+
75+function switchMode(newMode)
76+ if newMode then
77+ actionPanel1:setImageColor('#ffffff88')
78+ actionPanel2:setImageColor('#ffffff88')
79+ else
80+ actionPanel1:setImageColor('white')
81+ actionPanel2:setImageColor('white')
82+ end
83+end
84+
85+function online()
86+ setupActionPanel(1, actionPanel1)
87+ setupActionPanel(2, actionPanel2)
88+ show()
89+end
90+
91+function offline()
92+ hide()
93+ if hotkeyAssignWindow then
94+ hotkeyAssignWindow:destroy()
95+ hotkeyAssignWindow = nil
96+ end
97+
98+ local gameRootPanel = modules.game_interface.getRootPanel()
99+ for index, panel in ipairs({actionPanel1, actionPanel2}) do
100+ local config = {}
101+ for i, child in ipairs(panel.tabBar:getChildren()) do
102+ if child.config then
103+ table.insert(config, child.config)
104+ if type(child.config.hotkey) == 'string' and child.config.hotkey:len() > 0 then
105+ g_keyboard.unbindKeyPress(child.config.hotkey, child.callback, gameRootPanel)
106+ end
107+ else
108+ table.insert(config, {})
109+ end
110+ if child.cooldownEvent then
111+ removeEvent(child.cooldownEvent)
112+ end
113+ end
114+ actionConfig:setNode('actions_' .. index, config)
115+ panel.tabBar:destroyChildren()
116+ end
117+ actionConfig:save()
118+end
119+
120+function setupActionPanel(index, panel)
121+ local rawConfig = actionConfig:getNode('actions_' .. index) or {}
122+ local config = {}
123+ for i, buttonConfig in pairs(rawConfig) do -- sorting, because key in rawConfig is string
124+ config[tonumber(i)] = buttonConfig
125+ end
126+
127+ for i=1,actionButtonsInPanel do
128+ local action = g_ui.createWidget('ActionButton', panel.tabBar)
129+ action.config = config[i] or {}
130+ setupAction(action)
131+ end
132+
133+ panel.nextButton.onClick = function()
134+ panel.tabBar:moveChildToIndex(panel.tabBar:getFirstChild(), panel.tabBar:getChildCount())
135+ end
136+ panel.prevButton.onClick = function()
137+ panel.tabBar:moveChildToIndex(panel.tabBar:getLastChild(), 1)
138+ end
139+end
140+
141+function setupAction(action)
142+ local config = action.config
143+ action.item:setShowCount(false)
144+ action.onMouseRelease = actionOnMouseRelease
145+ action.onTouchRelease = actionOnMouseRelease
146+ action.callback = function(k, c, ticks) executeAction(action, ticks) end
147+ action.item.onItemChange = nil -- disable callbacks for setup
148+
149+ if config then
150+ if type(config.text) == 'number' then
151+ config.text = tostring(config.text)
152+ end
153+ if type(config.hotkey) == 'number' then
154+ config.hotkey = tostring(config.hotkey)
155+ end
156+ if type(config.hotkey) == 'string' and config.hotkey:len() > 0 then
157+ local gameRootPanel = modules.game_interface.getRootPanel()
158+ g_keyboard.bindKeyPress(config.hotkey, action.callback, gameRootPanel)
159+ action.hotkeyLabel:setText(config.hotkey)
160+ else
161+ action.hotkeyLabel:setText("")
162+ end
163+
164+ action.text:setImageSource("")
165+ action.cooldownTill = 0
166+ action.cooldownStart = 0
167+ if type(config.text) == 'string' and config.text:len() > 0 then
168+ action.text:setText(config.text)
169+ action.item:setBorderColor(ActionColors.text)
170+ action.item:setOn(true) -- removes background
171+ action.item:setItemId(0)
172+ if Spells then
173+ local spell, profile = Spells.getSpellByWords(config.text:lower())
174+ action.spell = spell
175+ if action.spell and action.spell.icon and profile then
176+ action.text:setImageSource(SpelllistSettings[profile].iconFile)
177+ action.text:setImageClip(Spells.getImageClip(SpellIcons[action.spell.icon][1], profile))
178+ action.text:setText("")
179+ end
180+ end
181+ else
182+ action.text:setText("")
183+ action.spell = nil
184+ if type(config.item) == 'number' and config.item > 100 then
185+ action.item:setOn(true)
186+ action.item:setItemId(config.item)
187+ action.item:setItemCount(config.count or 1)
188+ setupActionType(action, config.actionType)
189+ else
190+ action.item:setItemId(0)
191+ action.item:setOn(false)
192+ action.item:setBorderColor(ActionColors.empty)
193+ end
194+ end
195+ end
196+
197+ action.item.onItemChange = actionOnItemChange
198+end
199+
200+function setupActionType(action, actionType)
201+ local item = action.item:getItem()
202+ if action.item:getItem():isMultiUse() then
203+ if not actionType or actionType <= ActionTypes.USE then
204+ actionType = ActionTypes.USE_WITH
205+ end
206+ elseif g_game.getClientVersion() >= 910 then
207+ if actionType ~= ActionTypes.USE and actionType ~= ActionTypes.EQUIP then
208+ actionType = ActionTypes.USE
209+ end
210+ else
211+ actionType = ActionTypes.USE
212+ end
213+
214+ action.config.actionType = actionType
215+ if action.config.actionType == ActionTypes.USE then
216+ action.item:setBorderColor(ActionColors.itemUse)
217+ elseif action.config.actionType == ActionTypes.USE_SELF then
218+ action.item:setBorderColor(ActionColors.itemUseSelf)
219+ elseif action.config.actionType == ActionTypes.USE_TARGET then
220+ action.item:setBorderColor(ActionColors.itemUseTarget)
221+ elseif action.config.actionType == ActionTypes.USE_WITH then
222+ action.item:setBorderColor(ActionColors.itemUseWith)
223+ elseif action.config.actionType == ActionTypes.EQUIP then
224+ action.item:setBorderColor(ActionColors.itemEquip)
225+ end
226+end
227+
228+function updateAction(action, newConfig)
229+ local config = action.config
230+ if type(config.hotkey) == 'string' and config.hotkey:len() > 0 then
231+ local gameRootPanel = modules.game_interface.getRootPanel()
232+ g_keyboard.unbindKeyPress(config.hotkey, action.callback, gameRootPanel)
233+ end
234+ for key, val in pairs(newConfig) do
235+ action.config[key] = val
236+ end
237+ setupAction(action)
238+end
239+
240+function actionOnMouseRelease(action, mousePosition, mouseButton)
241+ if mouseButton == MouseTouch then return end
242+ if mouseButton == MouseRightButton or not action.item:isOn() then
243+ local menu = g_ui.createWidget('PopupMenu')
244+ menu:setGameMenu(true)
245+ if action.item:getItemId() > 0 then
246+ if action.item:getItem():isMultiUse() then
247+ menu:addOption(tr('Use on yourself'), function() return setupActionType(action, ActionTypes.USE_SELF) end)
248+ menu:addOption(tr('Use on target'), function() return setupActionType(action, ActionTypes.USE_TARGET) end)
249+ menu:addOption(tr('With crosshair'), function() return setupActionType(action, ActionTypes.USE_WITH) end)
250+ end
251+ if g_game.getClientVersion() >= 910 then
252+ if not action.item:getItem():isMultiUse() then
253+ menu:addOption(tr('Use'), function() return setupActionType(action, ActionTypes.USE) end)
254+ end
255+ menu:addOption(tr('Equip'), function() return setupActionType(action, ActionTypes.EQUIP) end)
256+ end
257+ else
258+ menu:addOption(tr('Select item'), function() return modules.game_itemselector.show(action.item) end)
259+ end
260+ menu:addSeparator()
261+ menu:addOption(tr('Set text'), function()
262+ modules.client_textedit.singlelineEditor(action.config.text or "", function(newText)
263+ updateAction(action, {text=newText, item=0})
264+ end)
265+ end)
266+ menu:addOption(tr('Set hotkey'), function()
267+ if hotkeyAssignWindow then
268+ hotkeyAssignWindow:destroy()
269+ end
270+ local assignWindow = g_ui.createWidget('ActionAssignWindow', rootWidget)
271+ assignWindow:grabKeyboard()
272+ assignWindow.comboPreview.keyCombo = ''
273+ assignWindow.onKeyDown = function(assignWindow, keyCode, keyboardModifiers)
274+ local keyCombo = determineKeyComboDesc(keyCode, keyboardModifiers)
275+ assignWindow.comboPreview:setText(tr('Current action hotkey: %s', keyCombo))
276+ assignWindow.comboPreview.keyCombo = keyCombo
277+ assignWindow.comboPreview:resizeToText()
278+ return true
279+ end
280+ assignWindow.onDestroy = function(widget)
281+ if widget == hotkeyAssignWindow then
282+ hotkeyAssignWindow = nil
283+ end
284+ end
285+ assignWindow.addButton.onClick = function()
286+ updateAction(action, {hotkey=tostring(assignWindow.comboPreview.keyCombo)})
287+ assignWindow:destroy()
288+ end
289+ hotkeyAssignWindow = assignWindow
290+ end)
291+ menu:addSeparator()
292+ menu:addOption(tr('Clear'), function()
293+ updateAction(action, {hotkey="", text="", item=0, count=1})
294+ end)
295+ menu:display(mousePosition)
296+ return true
297+ elseif mouseButton == MouseLeftButton or mouseButton == MouseTouch2 or mouseButton == MouseTouch3 then
298+ action.callback()
299+ return true
300+ end
301+ return false
302+end
303+
304+function actionOnItemChange(widget)
305+ updateAction(widget:getParent(), {text="", item=widget:getItemId(), count=widget:getItemCountOrSubType()})
306+end
307+
308+function onSpellCooldown(iconId, duration)
309+ for index, panel in ipairs({actionPanel1, actionPanel2}) do
310+ for i, child in ipairs(panel.tabBar:getChildren()) do
311+ if child.spell and child.spell.id == iconId then
312+ startCooldown(child, duration)
313+ end
314+ end
315+ end
316+end
317+
318+function onSpellGroupCooldown(groupId, duration)
319+ for index, panel in ipairs({actionPanel1, actionPanel2}) do
320+ for i, child in ipairs(panel.tabBar:getChildren()) do
321+ if child.spell and child.spell.group then
322+ for group, duration in pairs(child.spell.group) do
323+ if groupId == group then
324+ startCooldown(child, duration)
325+ end
326+ end
327+ end
328+ end
329+ end
330+end
331+
332+function startCooldown(action, duration)
333+ if type(action.cooldownTill) == 'number' and action.cooldownTill > g_clock.millis() + duration then
334+ return -- already has cooldown with greater duration
335+ end
336+ action.cooldownStart = g_clock.millis()
337+ action.cooldownTill = g_clock.millis() + duration
338+ updateCooldown(action)
339+end
340+
341+function updateCooldown(action)
342+ if not action or not action.cooldownTill then return end
343+ local timeleft = action.cooldownTill - g_clock.millis()
344+ if timeleft <= 30 then
345+ action.cooldown:setPercent(100)
346+ action.cooldownEvent = nil
347+ return
348+ end
349+ local duration = action.cooldownTill - action.cooldownStart
350+ action.cooldown:setPercent(100 - math.floor(100 * timeleft / duration))
351+ action.cooldownEvent = scheduleEvent(function() updateCooldown(action) end, 30)
352+end
353+
354+function executeAction(action, ticks)
355+ if not action.config then return end
356+ if type(ticks) ~= 'number' then ticks = 0 end
357+
358+ local actionDelay = 100
359+ if ticks == 0 then
360+ actionDelay = 200 -- for first use
361+ elseif action.actionDelayTo ~= nil and g_clock.millis() < action.actionDelayTo then
362+ return
363+ end
364+
365+ local actionType = action.config.actionType
366+
367+ if type(action.config.text) == 'string' and action.config.text:len() > 0 then
368+ if g_app.isMobile() then -- turn to direction of targer
369+ local target = g_game.getAttackingCreature()
370+ if target then
371+ local pos = g_game.getLocalPlayer():getPosition()
372+ local tpos = target:getPosition()
373+ if pos and tpos then
374+ local offx = tpos.x - pos.x
375+ local offy = tpos.y - pos.y
376+ if offy < 0 and offx <= 0 and math.abs(offx) < math.abs(offy) then
377+ g_game.turn(Directions.North)
378+ elseif offy > 0 and offx >= 0 and math.abs(offx) < math.abs(offy) then
379+ g_game.turn(Directions.South)
380+ elseif offx < 0 and offy <= 0 and math.abs(offx) > math.abs(offy) then
381+ g_game.turn(Directions.West)
382+ elseif offx > 0 and offy >= 0 and math.abs(offx) > math.abs(offy) then
383+ g_game.turn(Directions.East)
384+ end
385+ end
386+ end
387+ end
388+ if modules.game_interface.isChatVisible() then
389+ modules.game_console.sendMessage(action.config.text)
390+ else
391+ g_game.talk(action.config.text)
392+ end
393+ action.actionDelayTo = g_clock.millis() + actionDelay
394+ elseif action.item:getItemId() > 0 then
395+ if actionType == ActionTypes.USE then
396+ if g_game.getClientVersion() < 780 then
397+ local item = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1)
398+ if item then
399+ g_game.use(item)
400+ end
401+ else
402+ g_game.useInventoryItem(action.item:getItemId())
403+ end
404+ action.actionDelayTo = g_clock.millis() + actionDelay
405+ elseif actionType == ActionTypes.USE_SELF then
406+ if g_game.getClientVersion() < 780 then
407+ local item = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1)
408+ if item then
409+ g_game.useWith(item, g_game.getLocalPlayer())
410+ end
411+ else
412+ g_game.useInventoryItemWith(action.item:getItemId(), g_game.getLocalPlayer(), action.item:getItemSubType() or -1)
413+ end
414+ action.actionDelayTo = g_clock.millis() + actionDelay
415+ elseif actionType == ActionTypes.USE_TARGET then
416+ local attackingCreature = g_game.getAttackingCreature()
417+ if not attackingCreature then
418+ local item = Item.create(action.item:getItemId())
419+ if g_game.getClientVersion() < 780 then
420+ local tmpItem = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1)
421+ if not tmpItem then return end
422+ item = tmpItem
423+ end
424+
425+ modules.game_interface.startUseWith(item, action.item:getItemSubType() or - 1)
426+ return
427+ end
428+
429+ if not attackingCreature:getTile() then return end
430+ if g_game.getClientVersion() < 780 then
431+ local item = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1)
432+ if item then
433+ g_game.useWith(item, attackingCreature, action.item:getItemSubType() or -1)
434+ end
435+ else
436+ g_game.useInventoryItemWith(action.item:getItemId(), attackingCreature, action.item:getItemSubType() or -1)
437+ end
438+ action.actionDelayTo = g_clock.millis() + actionDelay
439+ elseif actionType == ActionTypes.USE_WITH then
440+ local item = Item.create(action.item:getItemId())
441+ if g_game.getClientVersion() < 780 then
442+ local tmpItem = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1)
443+ if not tmpItem then return true end
444+ item = tmpItem
445+ end
446+ modules.game_interface.startUseWith(item, action.item:getItemSubType() or - 1)
447+ elseif actionType == ActionTypes.EQUIP then
448+ if g_game.getClientVersion() >= 910 then
449+ local item = Item.create(action.item:getItemId())
450+ g_game.equipItem(item)
451+ action.actionDelayTo = g_clock.millis() + actionDelay
452+ end
453+ end
454+ end
455+end
A RD_Client/modules/game_actionbar/actionbar.otmod +9-0 View file
@@ -0,0 +1,9 @@
1+Module
2+ name: game_actionbar
3+ description: Action bar
4+ author: otclient@otclient.ovh
5+ website: otclient.ovh
6+ sandboxed: true
7+ scripts: [ actionbar ]
8+ @onLoad: init()
9+ @onUnload: terminate()
A RD_Client/modules/game_actionbar/actionbar.otui +145-0 View file
@@ -0,0 +1,145 @@
1+ActionButton < Panel
2+ font: cipsoftFont
3+ anchors.top: parent.top
4+ anchors.bottom: parent.bottom
5+ width: 40
6+ padding: 1 1 1 1
7+ margin-left: 1
8+
9+ $first:
10+ anchors.left: parent.left
11+
12+ $!first:
13+ anchors.left: prev.right
14+
15+ Item
16+ id: item
17+ anchors.fill: parent
18+ &selectable: true
19+ &editable: false
20+ virtual: true
21+ border-width: 1
22+
23+ border-color: #00000000
24+
25+ $!on:
26+ image-source: /images/game/actionbarslot
27+
28+ Label
29+ id: text
30+ anchors.fill: parent
31+ text-auto-resize: true
32+ text-wrap: true
33+ phantom: true
34+ text-align: center
35+ font: verdana-9px
36+
37+ Label
38+ id: hotkeyLabel
39+ anchors.top: parent.top
40+ anchors.left: parent.left
41+ margin: 2 3 3 3
42+ text-auto-resize: true
43+ text-wrap: false
44+ phantom: true
45+ font: small-9px
46+ color: yellow
47+
48+ UIProgressRect
49+ id: cooldown
50+ background: #585858AA
51+ percent: 100
52+ focusable: false
53+ phantom: true
54+ anchors.fill: parent
55+ margin: 1 1 1 1
56+
57+Panel
58+ id: actionBar
59+ focusable: false
60+ image-source: /images/ui/panel_side
61+ image-border: 4
62+ margin-top: -1
63+
64+ $on:
65+ height: 40
66+ visible: true
67+
68+ $!on:
69+ height: 0
70+ visible: false
71+
72+ TabButton
73+ id: prevButton
74+ icon: /images/game/console/leftarrow
75+ anchors.left: parent.left
76+ anchors.top: parent.top
77+ anchors.bottom: parent.bottom
78+ margin-left: 1
79+ margin-top: 1
80+ margin-bottom: 2
81+
82+ Panel
83+ id: tabBar
84+ anchors.top: parent.top
85+ anchors.bottom: parent.bottom
86+ anchors.left: prev.right
87+ anchors.right: next.left
88+ margin-right: 3
89+ clipping: true
90+
91+ TabButton
92+ id: nextButton
93+ icon: /images/game/console/rightarrow
94+ anchors.right: parent.right
95+ anchors.top: parent.top
96+ anchors.bottom: parent.bottom
97+ margin-right: 1
98+ margin-top: 1
99+ margin-bottom: 2
100+
101+
102+ActionAssignWindow < MainWindow
103+ id: assignWindow
104+ !text: tr('Button Assign')
105+ size: 360 150
106+ @onEscape: self:destroy()
107+
108+ Label
109+ !text: tr('Please, press the key you wish to use for action')
110+ anchors.left: parent.left
111+ anchors.right: parent.right
112+ anchors.top: parent.top
113+ text-auto-resize: true
114+ text-align: left
115+
116+ Label
117+ id: comboPreview
118+ !text: tr('Current action hotkey: %s', 'none')
119+ anchors.horizontalCenter: parent.horizontalCenter
120+ anchors.top: prev.bottom
121+ margin-top: 10
122+ text-auto-resize: true
123+
124+ HorizontalSeparator
125+ id: separator
126+ anchors.left: parent.left
127+ anchors.right: parent.right
128+ anchors.bottom: next.top
129+ margin-bottom: 10
130+
131+ Button
132+ id: addButton
133+ !text: tr('Add')
134+ width: 64
135+ anchors.right: next.left
136+ anchors.bottom: parent.bottom
137+ margin-right: 10
138+
139+ Button
140+ id: cancelButton
141+ !text: tr('Cancel')
142+ width: 64
143+ anchors.right: parent.right
144+ anchors.bottom: parent.bottom
145+ @onClick: self:getParent():destroy()
Top