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/game_bot/functions/ui.lua +24-0 View file
@@ -0,0 +1,24 @@
1+local context = G.botContext
2+if type(context.UI) ~= "table" then
3+ context.UI = {}
4+end
5+local UI = context.UI
6+
7+UI.createWidget = function(name, parent)
8+ if parent == nil then
9+ parent = context.panel
10+ end
11+ local widget = g_ui.createWidget(name, parent)
12+ widget.botWidget = true
13+ return widget
14+end
15+
16+UI.createWindow = function(name)
17+ local widget = g_ui.createWidget(name, g_ui.getRootWidget())
18+ widget.botWidget = true
19+ widget:show()
20+ widget:raise()
21+ widget:focus()
22+ return widget
23+end
24+
A RD_Client/modules/game_bot/functions/ui_elements.lua +273-0 View file
@@ -0,0 +1,273 @@
1+local context = G.botContext
2+if type(context.UI) ~= "table" then
3+ context.UI = {}
4+end
5+local UI = context.UI
6+
7+UI.Button = function(text, callback, parent)
8+ local widget = UI.createWidget("BotButton", parent)
9+ widget:setText(text)
10+ widget.onClick = callback
11+ return widget
12+end
13+
14+
15+UI.Config = function(parent)
16+ return UI.createWidget("BotConfig", parent)
17+end
18+
19+-- call :setItems(table) to set items, call :getItems() to get them
20+-- unique if true, won't allow duplicates
21+-- callback (can be nil) gets table with new item list, eg: {{id=2160, count=1}, {id=268, count=100}, {id=269, count=20}}
22+UI.Container = function(callback, unique, parent, widget)
23+ if not widget then
24+ widget = UI.createWidget("BotContainer", parent)
25+ end
26+
27+ local oldItems = {}
28+
29+ local updateItems = function()
30+ local items = widget:getItems()
31+
32+ -- callback part
33+ local somethingNew = (#items ~= #oldItems)
34+ for i, item in ipairs(items) do
35+ if type(oldItems[i]) ~= "table" then
36+ somethingNew = true
37+ break
38+ end
39+ if oldItems[i].id ~= item.id or oldItems[i].count ~= item.count then
40+ somethingNew = true
41+ break
42+ end
43+ end
44+
45+ if somethingNew then
46+ oldItems = items
47+ callback(widget, items)
48+ end
49+
50+ widget:setItems(items)
51+ end
52+
53+ widget.setItems = function(self, items)
54+ if type(self) == 'table' then
55+ items = self
56+ end
57+ local itemsToShow = math.max(10, #items + 2)
58+ if itemsToShow % 5 ~= 0 then
59+ itemsToShow = itemsToShow + 5 - itemsToShow % 5
60+ end
61+ widget.items:destroyChildren()
62+ for i = 1, itemsToShow do
63+ local widget = g_ui.createWidget("BotItem", widget.items)
64+ if type(items[i]) == 'number' then
65+ items[i] = {id=items[i], count=1}
66+ end
67+ if type(items[i]) == 'table' then
68+ widget:setItem(Item.create(items[i].id, items[i].count))
69+ end
70+ end
71+ oldItems = items
72+ for i, child in ipairs(widget.items:getChildren()) do
73+ child.onItemChange = updateItems
74+ end
75+ end
76+
77+ widget.getItems = function()
78+ local items = {}
79+ local duplicates = {}
80+ for i, child in ipairs(widget.items:getChildren()) do
81+ if child:getItemId() >= 100 then
82+ if not duplicates[child:getItemId()] or not unique then
83+ table.insert(items, {id=child:getItemId(), count=child:getItemCountOrSubType()})
84+ duplicates[child:getItemId()] = true
85+ end
86+ end
87+ end
88+ return items
89+ end
90+
91+ widget:setItems({})
92+
93+ return widget
94+end
95+
96+UI.DualScrollPanel = function(params, callback, parent) -- callback = function(widget, newParams)
97+ --[[ params:
98+ on - bool,
99+ text - string,
100+ title - string,
101+ min - number,
102+ max - number,
103+ ]]
104+ params.title = params.title or "title"
105+ params.text = params.text or ""
106+ params.min = params.min or 20
107+ params.max = params.max or 80
108+
109+ local widget = UI.createWidget('DualScrollPanel', parent)
110+
111+ widget.title:setOn(params.on)
112+ widget.title.onClick = function()
113+ params.on = not params.on
114+ widget.title:setOn(params.on)
115+ if callback then
116+ callback(widget, params)
117+ end
118+ end
119+
120+ widget.text:setText(params.text or "")
121+ widget.text.onTextChange = function(widget, text)
122+ params.text = text
123+ if callback then
124+ callback(widget, params)
125+ end
126+ end
127+
128+ local update = function(dontSignal)
129+ widget.title:setText("" .. params.min .. "% <= " .. params.title .. " <= " .. params.max .. "%")
130+ if callback and not dontSignal then
131+ callback(widget, params)
132+ end
133+ end
134+
135+ widget.scroll1:setValue(params.min)
136+ widget.scroll2:setValue(params.max)
137+
138+ widget.scroll1.onValueChange = function(scroll, value)
139+ params.min = value
140+ update()
141+ end
142+ widget.scroll2.onValueChange = function(scroll, value)
143+ params.max = value
144+ update()
145+ end
146+ update(true)
147+end
148+
149+UI.DualScrollItemPanel = function(params, callback, parent) -- callback = function(widget, newParams)
150+ --[[ params:
151+ on - bool,
152+ item - number,
153+ subType - number,
154+ title - string,
155+ min - number,
156+ max - number,
157+ ]]
158+ params.title = params.title or "title"
159+ params.item = params.item or 0
160+ params.subType = params.subType or 0
161+ params.min = params.min or 20
162+ params.max = params.max or 80
163+
164+ local widget = UI.createWidget('DualScrollItemPanel', parent)
165+
166+ widget.title:setOn(params.on)
167+ widget.title.onClick = function()
168+ params.on = not params.on
169+ widget.title:setOn(params.on)
170+ if callback then
171+ callback(widget, params)
172+ end
173+ end
174+
175+ widget.item:setItem(Item.create(params.item, params.subType))
176+ widget.item.onItemChange = function()
177+ params.item = widget.item:getItemId()
178+ params.subType = widget.item:getItemSubType()
179+ if callback then
180+ callback(widget, params)
181+ end
182+ end
183+
184+ local update = function(dontSignal)
185+ widget.title:setText("" .. params.min .. "% <= " .. params.title .. " <= " .. params.max .. "%")
186+ if callback and not dontSignal then
187+ callback(widget, params)
188+ end
189+ end
190+
191+ widget.scroll1:setValue(params.min)
192+ widget.scroll2:setValue(params.max)
193+
194+ widget.scroll1.onValueChange = function(scroll, value)
195+ params.min = value
196+ update()
197+ end
198+ widget.scroll2.onValueChange = function(scroll, value)
199+ params.max = value
200+ update()
201+ end
202+ update(true)
203+end
204+
205+UI.Label = function(text, parent)
206+ local label = UI.createWidget('BotLabel', parent)
207+ label:setText(text)
208+ return label
209+end
210+
211+UI.Separator = function(parent)
212+ local separator = UI.createWidget('BotSeparator', parent)
213+ return separator
214+end
215+
216+UI.TextEdit = function(text, callback, parent)
217+ local widget = UI.createWidget('BotTextEdit', parent)
218+ widget.onTextChange = callback
219+ widget:setText(text)
220+ return widget
221+end
222+
223+UI.TwoItemsAndSlotPanel = function(params, callback, parent)
224+ --[[ params:
225+ on - bool,
226+ title - string,
227+ item1 - number,
228+ item2 - number,
229+ slot - number,
230+ ]]
231+ params.title = params.title or "title"
232+ params.item1 = params.item1 or 0
233+ params.item2 = params.item2 or 0
234+ params.slot = params.slot or 1
235+
236+ local widget = UI.createWidget("TwoItemsAndSlotPanel", parent)
237+
238+ widget.title:setText(params.title)
239+ widget.title:setOn(params.on)
240+ widget.title.onClick = function()
241+ params.on = not params.on
242+ widget.title:setOn(params.on)
243+ if callback then
244+ callback(widget, params)
245+ end
246+ end
247+
248+ widget.slot:setCurrentIndex(params.slot)
249+ widget.slot.onOptionChange = function()
250+ params.slot = widget.slot.currentIndex
251+ if callback then
252+ callback(widget, params)
253+ end
254+ end
255+
256+ widget.item1:setItemId(params.item1)
257+ widget.item1.onItemChange = function()
258+ params.item1 = widget.item1:getItemId()
259+ if callback then
260+ callback(widget, params)
261+ end
262+ end
263+
264+ widget.item2:setItemId(params.item2)
265+ widget.item2.onItemChange = function()
266+ params.item2 = widget.item2:getItemId()
267+ if callback then
268+ callback(widget, params)
269+ end
270+ end
271+
272+ return widget
273+end
A RD_Client/modules/game_bot/functions/ui_legacy.lua +135-0 View file
@@ -0,0 +1,135 @@
1+local context = G.botContext
2+
3+-- DO NOT USE THIS CODE.
4+-- IT'S ONLY HERE FOR BACKWARD COMPATIBILITY, MAY BE REMOVED IN THE FUTURE
5+
6+context.createWidget = function(name, parent)
7+ if parent == nil then
8+ parent = context.panel
9+ end
10+ g_ui.createWidget(name, parent)
11+end
12+
13+context.setupUI = function(otml, parent)
14+ if parent == nil then
15+ parent = context.panel
16+ end
17+ local widget = g_ui.loadUIFromString(otml, parent)
18+ widget.botWidget = true
19+ return widget
20+end
21+
22+context.importStyle = function(otml)
23+ if type(otml) ~= "string" then
24+ return error("Invalid parameter for importStyle, should be string")
25+ end
26+ if otml:find(".otui") and not otml:find("\n") then
27+ return g_ui.importStyle(context.configDir .. "/" .. otml)
28+ end
29+ return g_ui.importStyleFromString(otml)
30+end
31+
32+context.addTab = function(name)
33+ local tab = context.tabs:getTab(name)
34+ if tab then -- return existing tab
35+ return tab.tabPanel.content
36+ end
37+
38+ local smallTabs = #(context.tabs.tabs) >= 5
39+ local newTab = context.tabs:addTab(name, g_ui.createWidget('BotPanel')).tabPanel.content
40+ context.tabs:setOn(true)
41+ if smallTabs then
42+ for k,tab in pairs(context.tabs.tabs) do
43+ tab:setFont('small-9px')
44+ end
45+ end
46+
47+ return newTab
48+end
49+context.getTab = context.addTab
50+
51+context.setDefaultTab = function(name)
52+ local tab = context.addTab(name)
53+ context.panel = tab
54+end
55+
56+context.addSwitch = function(id, text, onClickCallback, parent)
57+ if not parent then
58+ parent = context.panel
59+ end
60+ local switch = g_ui.createWidget('BotSwitch', parent)
61+ switch:setId(id)
62+ switch:setText(text)
63+ switch.onClick = onClickCallback
64+ return switch
65+end
66+
67+context.addButton = function(id, text, onClickCallback, parent)
68+ if not parent then
69+ parent = context.panel
70+ end
71+ local button = g_ui.createWidget('BotButton', parent)
72+ button:setId(id)
73+ button:setText(text)
74+ button.onClick = onClickCallback
75+ return button
76+end
77+
78+context.addLabel = function(id, text, parent)
79+ if not parent then
80+ parent = context.panel
81+ end
82+ local label = g_ui.createWidget('BotLabel', parent)
83+ label:setId(id)
84+ label:setText(text)
85+ return label
86+end
87+
88+context.addTextEdit = function(id, text, onTextChangeCallback, parent)
89+ if not parent then
90+ parent = context.panel
91+ end
92+ local widget = g_ui.createWidget('BotTextEdit', parent)
93+ widget:setId(id)
94+ widget.onTextChange = onTextChangeCallback
95+ widget:setText(text)
96+ return widget
97+end
98+
99+context.addSeparator = function(id, parent)
100+ if not parent then
101+ parent = context.panel
102+ end
103+ local separator = g_ui.createWidget('BotSeparator', parent)
104+ separator:setId(id)
105+ return separator
106+end
107+
108+context._addMacroSwitch = function(name, keys, parent)
109+ if not parent then
110+ parent = context.panel
111+ end
112+ local text = name
113+ if keys:len() > 0 then
114+ text = name .. " [" .. keys .. "]"
115+ end
116+ local switch = context.addSwitch("macro_" .. #context._macros, text, function(widget)
117+ context.storage._macros[name] = not context.storage._macros[name]
118+ widget:setOn(context.storage._macros[name])
119+ end, parent)
120+ switch:setOn(context.storage._macros[name])
121+ return switch
122+end
123+
124+context._addHotkeySwitch = function(name, keys, parent)
125+ if not parent then
126+ parent = context.panel
127+ end
128+ local text = name
129+ if keys:len() > 0 then
130+ text = name .. " [" .. keys .. "]"
131+ end
132+ local switch = context.addSwitch("hotkey_" .. #context._hotkeys, text, nil, parent)
133+ switch:setOn(false)
134+ return switch
135+end
A RD_Client/modules/game_bot/functions/ui_windows.lua +49-0 View file
@@ -0,0 +1,49 @@
1+local context = G.botContext
2+if type(context.UI) ~= "table" then
3+ context.UI = {}
4+end
5+local UI = context.UI
6+
7+UI.EditorWindow = function(text, options, callback)
8+ --[[
9+ Available options:
10+ title = text
11+ description = text
12+ multiline = true / false
13+ width = number
14+ validation = text (regex)
15+ examples = {{name, text}, {name, text}}
16+ ]]--
17+ local window = modules.client_textedit.edit(text, options, callback)
18+ window.botWidget = true
19+ return window
20+end
21+
22+UI.SinglelineEditorWindow = function(text, options, callback)
23+ options = options or {}
24+ options.multiline = false
25+ return UI.EditorWindow(text, options, callback)
26+end
27+
28+UI.MultilineEditorWindow = function(text, options, callback)
29+ options = options or {}
30+ options.multiline = true
31+ return UI.EditorWindow(text, options, callback)
32+end
33+
34+UI.ConfirmationWindow = function(title, question, callback)
35+ local window = nil
36+ local onConfirm = function()
37+ window:destroy()
38+ callback()
39+ end
40+ local closeWindow = function()
41+ window:destroy()
42+ end
43+ window = context.displayGeneralBox(title, question, {
44+ { text=tr('Yes'), callback=onConfirm },
45+ { text=tr('No'), callback=closeWindow },
46+ anchor=AnchorHorizontalCenter}, onConfirm, closeWindow)
47+ window.botWidget = true
48+ return window
49+end
A RD_Client/modules/game_bot/panels/basic.lua +57-0 View file
@@ -0,0 +1,57 @@
1+local context = G.botContext
2+local Panels = context.Panels
3+
4+Panels.Turning = function(parent)
5+ context.macro(1000, "Turning / AntiIdle", nil, function()
6+ context.turn(math.random(1, 4))
7+ end, parent)
8+end
9+Panels.AntiIdle = Panels.Turning
10+
11+Panels.AttackSpell = function(parent)
12+ context.macro(500, "Auto attack spell", nil, function()
13+ local target = g_game.getAttackingCreature()
14+ if target and context.getCreatureById(target:getId()) and context.storage.autoAttackText:len() > 0 then
15+ if context.saySpell(context.storage.autoAttackText, 1000) then
16+ context.delay(1000)
17+ end
18+ end
19+ end, parent)
20+ context.addTextEdit("autoAttackText", context.storage.autoAttackText or "exori vis", function(widget, text)
21+ context.storage.autoAttackText = text
22+ end, parent)
23+end
24+
25+Panels.AttackItem = function(parent)
26+ if not parent then
27+ parent = context.panel
28+ end
29+
30+ local panelName = "attackItem"
31+ local ui = g_ui.createWidget("ItemAndButtonPanel", parent)
32+ ui:setId(panelName)
33+
34+ ui.title:setText("Auto attack item")
35+
36+ if not context.storage.attackItem then
37+ context.storage.attackItem = {}
38+ end
39+
40+ ui.title:setOn(context.storage.attackItem.enabled)
41+ ui.title.onClick = function(widget)
42+ context.storage.attackItem.enabled = not context.storage.attackItem.enabled
43+ widget:setOn(context.storage.attackItem.enabled)
44+ end
45+
46+ ui.item.onItemChange = function(widget)
47+ context.storage.attackItem.item = widget:getItemId()
48+ end
49+ ui.item:setItemId(context.storage.attackItem.item or 3155)
50+
51+ context.macro(500, function()
52+ local target = g_game.getAttackingCreature()
53+ if context.storage.attackItem.enabled and target and context.getCreatureById(target:getId()) and context.storage.attackItem.item and context.storage.attackItem.item >= 100 then
54+ context.useWith(context.storage.attackItem.item, target)
55+ end
56+ end)
57+end
A RD_Client/modules/game_bot/panels/DONT_USE_PANELS.txt +3-0 View file
@@ -0,0 +1,3 @@
1+DONT USE PANELS
2+THEY ONLY HERE FOR BACKWARD COMPATIBILITY
3+MAY BE REMOVED IN THE FUTURE
A RD_Client/modules/game_bot/panels/healing.lua +346-0 View file
@@ -0,0 +1,346 @@
1+local context = G.botContext
2+local Panels = context.Panels
3+
4+Panels.Haste = function(parent)
5+ context.macro(500, "Auto Haste", nil, function()
6+ if not context.hasHaste() and context.storage.autoHasteText:len() > 0 then
7+ if context.saySpell(context.storage.autoHasteText, 2500) then
8+ context.delay(5000)
9+ end
10+ end
11+ end, parent)
12+ context.addTextEdit("autoHasteText", context.storage.autoHasteText or "utani hur", function(widget, text)
13+ context.storage.autoHasteText = text
14+ end, parent)
15+end
16+
17+Panels.ManaShield = function(parent)
18+ local lastManaShield = 0
19+ context.macro(100, "Auto Mana Shield", nil, function()
20+ if not context.hasManaShield() or context.now > lastManaShield + 90000 then
21+ if context.saySpell("utamo vita", 200) then
22+ lastManaShield = context.now
23+ end
24+ end
25+ end, parent)
26+end
27+
28+Panels.AntiParalyze = function(parent)
29+ context.macro(100, "Anti Paralyze", nil, function()
30+ if context.isParalyzed() and context.storage.autoAntiParalyzeText:len() > 0 then
31+ context.saySpell(context.storage.autoAntiParalyzeText, 750)
32+ end
33+ end, parent)
34+ context.addTextEdit("autoAntiParalyzeText", context.storage.autoAntiParalyzeText or "utani hur", function(widget, text)
35+ context.storage.autoAntiParalyzeText = text
36+ end, parent)
37+end
38+
39+
40+Panels.Health = function(parent)
41+ if not parent then
42+ parent = context.panel
43+ end
44+
45+ local panelName = "autoHealthPanel"
46+ local panelId = 1
47+ while parent:getChildById(panelName .. panelId) do
48+ panelId = panelId + 1
49+ end
50+ panelName = panelName .. panelId
51+
52+ local ui = g_ui.createWidget("DualScrollPanel", parent)
53+ ui:setId(panelName)
54+
55+ if not context.storage[panelName] then
56+ context.storage[panelName] = {
57+ item = 266,
58+ min = 20,
59+ max = 80,
60+ text = "exura"
61+ }
62+ end
63+
64+ ui.title:setOn(context.storage[panelName].enabled)
65+ ui.title.onClick = function(widget)
66+ context.storage[panelName].enabled = not context.storage[panelName].enabled
67+ widget:setOn(context.storage[panelName].enabled)
68+ end
69+
70+ ui.text.onTextChange = function(widget, text)
71+ context.storage[panelName].text = text
72+ end
73+ ui.text:setText(context.storage[panelName].text or "exura")
74+
75+ local updateText = function()
76+ ui.title:setText("" .. context.storage[panelName].min .. "% <= hp <= " .. context.storage[panelName].max .. "%")
77+ end
78+
79+ ui.scroll1.onValueChange = function(scroll, value)
80+ context.storage[panelName].min = value
81+ updateText()
82+ end
83+ ui.scroll2.onValueChange = function(scroll, value)
84+ context.storage[panelName].max = value
85+ updateText()
86+ end
87+
88+ ui.scroll1:setValue(context.storage[panelName].min)
89+ ui.scroll2:setValue(context.storage[panelName].max)
90+
91+ context.macro(25, function()
92+ if context.storage[panelName].enabled and context.storage[panelName].text:len() > 0 and context.storage[panelName].min <= context.hppercent() and context.hppercent() <= context.storage[panelName].max then
93+ if context.saySpell(context.storage[panelName].text, 500) then
94+ context.delay(200)
95+ end
96+ end
97+ end)
98+end
99+
100+Panels.HealthItem = function(parent)
101+ if not parent then
102+ parent = context.panel
103+ end
104+
105+ local panelName = "autoHealthItemPanel"
106+ local panelId = 1
107+ while parent:getChildById(panelName .. panelId) do
108+ panelId = panelId + 1
109+ end
110+ panelName = panelName .. panelId
111+
112+ local ui = g_ui.createWidget("DualScrollItemPanel", parent)
113+ ui:setId(panelName)
114+
115+ if not context.storage[panelName] then
116+ context.storage[panelName] = {
117+ item = 266,
118+ min = 0,
119+ max = 60
120+ }
121+ end
122+
123+ ui.title:setOn(context.storage[panelName].enabled)
124+ ui.title.onClick = function(widget)
125+ context.storage[panelName].enabled = not context.storage[panelName].enabled
126+ widget:setOn(context.storage[panelName].enabled)
127+ end
128+
129+ ui.item.onItemChange = function(widget)
130+ context.storage[panelName].item = widget:getItemId()
131+ end
132+ ui.item:setItemId(context.storage[panelName].item)
133+
134+ local updateText = function()
135+ ui.title:setText("" .. (context.storage[panelName].min or "") .. "% <= hp <= " .. (context.storage[panelName].max or "") .. "%")
136+ end
137+
138+ ui.scroll1.onValueChange = function(scroll, value)
139+ context.storage[panelName].min = value
140+ updateText()
141+ end
142+ ui.scroll2.onValueChange = function(scroll, value)
143+ context.storage[panelName].max = value
144+ updateText()
145+ end
146+
147+ ui.scroll1:setValue(context.storage[panelName].min)
148+ ui.scroll2:setValue(context.storage[panelName].max)
149+
150+ context.macro(25, function()
151+ if context.storage[panelName].enabled and context.storage[panelName].item >= 100 and context.storage[panelName].min <= context.hppercent() and context.hppercent() <= context.storage[panelName].max then
152+ if context.useRune(context.storage[panelName].item, context.player, 500) then
153+ context.delay(300)
154+ end
155+ end
156+ end)
157+end
158+
159+Panels.Mana = function(parent)
160+ if not parent then
161+ parent = context.panel
162+ end
163+
164+ local panelName = "autoManaItemPanel"
165+ local panelId = 1
166+ while parent:getChildById(panelName .. panelId) do
167+ panelId = panelId + 1
168+ end
169+ panelName = panelName .. panelId
170+
171+ local ui = g_ui.createWidget("DualScrollItemPanel", parent)
172+ ui:setId(panelName)
173+
174+ if not context.storage[panelName] then
175+ context.storage[panelName] = {
176+ item = 268,
177+ min = 0,
178+ max = 60
179+ }
180+ end
181+
182+ ui.title:setOn(context.storage[panelName].enabled)
183+ ui.title.onClick = function(widget)
184+ context.storage[panelName].enabled = not context.storage[panelName].enabled
185+ widget:setOn(context.storage[panelName].enabled)
186+ end
187+
188+ ui.item.onItemChange = function(widget)
189+ context.storage[panelName].item = widget:getItemId()
190+ end
191+ ui.item:setItemId(context.storage[panelName].item)
192+
193+ local updateText = function()
194+ ui.title:setText("" .. (context.storage[panelName].min or "") .. "% <= mana <= " .. (context.storage[panelName].max or "") .. "%")
195+ end
196+
197+ ui.scroll1.onValueChange = function(scroll, value)
198+ context.storage[panelName].min = value
199+ updateText()
200+ end
201+ ui.scroll2.onValueChange = function(scroll, value)
202+ context.storage[panelName].max = value
203+ updateText()
204+ end
205+
206+ ui.scroll1:setValue(context.storage[panelName].min)
207+ ui.scroll2:setValue(context.storage[panelName].max)
208+
209+ context.macro(25, function()
210+ if context.storage[panelName].enabled and context.storage[panelName].item >= 100 and context.storage[panelName].min <= context.manapercent() and context.manapercent() <= context.storage[panelName].max then
211+ if context.useRune(context.storage[panelName].item, context.player, 500) then
212+ context.delay(300)
213+ end
214+ end
215+ end)
216+end
217+Panels.ManaItem = Panels.Mana
218+
219+Panels.Equip = function(parent)
220+ if not parent then
221+ parent = context.panel
222+ end
223+
224+ local panelName = "autoEquipItem"
225+ local panelId = 1
226+ while parent:getChildById(panelName .. panelId) do
227+ panelId = panelId + 1
228+ end
229+ panelName = panelName .. panelId
230+
231+ local ui = g_ui.createWidget("TwoItemsAndSlotPanel", parent)
232+ ui:setId(panelName)
233+
234+ if not context.storage[panelName] then
235+ context.storage[panelName] = {}
236+ if panelId == 1 then
237+ context.storage[panelName].item1 = 3052
238+ context.storage[panelName].item2 = 3089
239+ context.storage[panelName].slot = 9
240+ end
241+ end
242+
243+ ui.title:setText("Auto equip")
244+ ui.title:setOn(context.storage[panelName].enabled)
245+ ui.title.onClick = function(widget)
246+ context.storage[panelName].enabled = not context.storage[panelName].enabled
247+ widget:setOn(context.storage[panelName].enabled)
248+ end
249+
250+ ui.item1:setItemId(context.storage[panelName].item1 or 0)
251+ ui.item1.onItemChange = function(widget)
252+ context.storage[panelName].item1 = widget:getItemId()
253+ end
254+
255+ ui.item2:setItemId(context.storage[panelName].item2 or 0)
256+ ui.item2.onItemChange = function(widget)
257+ context.storage[panelName].item2 = widget:getItemId()
258+ end
259+
260+ if not context.storage[panelName].slot then
261+ context.storage[panelName].slot = 1
262+ end
263+ ui.slot:setCurrentIndex(context.storage[panelName].slot)
264+ ui.slot.onOptionChange = function(widget)
265+ context.storage[panelName].slot = widget.currentIndex
266+ end
267+
268+ context.macro(250, function()
269+ if context.storage[panelName].enabled and context.storage[panelName].slot > 0 then
270+ local item1 = context.storage[panelName].item1 or 0
271+ local item2 = context.storage[panelName].item2 or 0
272+ if item1 < 100 and item2 < 100 then
273+ return
274+ end
275+ local slotItem = context.getSlot(context.storage[panelName].slot)
276+ if slotItem and (slotItem:getId() == item1 or slotItem:getId() == item2) then
277+ return
278+ end
279+ local newItem = context.findItem(context.storage[panelName].item1)
280+ if not newItem then
281+ newItem = context.findItem(context.storage[panelName].item2)
282+ if not newItem then
283+ return
284+ end
285+ end
286+ g_game.move(newItem, {x=65535, y=context.storage[panelName].slot, z=0})
287+ context.delay(1000)
288+ end
289+ end)
290+end
291+Panels.AutoEquip = Panels.Equip
292+
293+Panels.Eating = function(parent)
294+ if not parent then
295+ parent = context.panel
296+ end
297+
298+ local panelName = "autoEatingPanel"
299+ local panelId = 1
300+ while parent:getChildById(panelName .. panelId) do
301+ panelId = panelId + 1
302+ end
303+ panelName = panelName .. panelId
304+
305+ local ui = g_ui.createWidget("ItemsPanel", parent)
306+ ui:setId(panelName)
307+
308+ if not context.storage[panelName] then
309+ context.storage[panelName] = {}
310+ end
311+
312+ ui.title:setText("Auto eating")
313+ ui.title:setOn(context.storage[panelName].enabled)
314+ ui.title.onClick = function(widget)
315+ context.storage[panelName].enabled = not context.storage[panelName].enabled
316+ widget:setOn(context.storage[panelName].enabled)
317+ end
318+
319+ if type(context.storage[panelName].items) ~= 'table' then
320+ context.storage[panelName].items = {3725, 0, 0, 0, 0}
321+ end
322+
323+ for i=1,5 do
324+ ui.items:getChildByIndex(i).onItemChange = function(widget)
325+ context.storage[panelName].items[i] = widget:getItemId()
326+ end
327+ ui.items:getChildByIndex(i):setItemId(context.storage[panelName].items[i])
328+ end
329+
330+ context.macro(15000, function()
331+ if not context.storage[panelName].enabled then
332+ return
333+ end
334+ local candidates = {}
335+ for i, item in pairs(context.storage[panelName].items) do
336+ if item >= 100 then
337+ table.insert(candidates, item)
338+ end
339+ end
340+ if #candidates == 0 then
341+ return
342+ end
343+ context.use(candidates[math.random(1, #candidates)])
344+ end)
345+end
346+
A RD_Client/modules/game_bot/panels/looting.lua +431-0 View file
@@ -0,0 +1,431 @@
1+local context = G.botContext
2+local Panels = context.Panels
3+
4+Panels.Looting = function(parent)
5+ local ui = context.setupUI([[
6+Panel
7+ id: looting
8+ height: 180
9+
10+ BotLabel
11+ anchors.top: parent.top
12+ anchors.left: parent.left
13+ anchors.right: parent.right
14+ text: Looting
15+
16+ ComboBox
17+ id: config
18+ anchors.top: prev.bottom
19+ anchors.left: parent.left
20+ margin-top: 5
21+ text-offset: 3 0
22+ width: 130
23+
24+ Button
25+ id: enableButton
26+ anchors.top: prev.top
27+ anchors.left: prev.right
28+ anchors.right: parent.right
29+ margin-left: 5
30+
31+ Button
32+ margin-top: 1
33+ id: add
34+ anchors.top: prev.bottom
35+ anchors.left: parent.left
36+ text: Add
37+ width: 60
38+ height: 17
39+
40+ Button
41+ id: edit
42+ anchors.top: prev.top
43+ anchors.horizontalCenter: parent.horizontalCenter
44+ text: Edit
45+ width: 60
46+ height: 17
47+
48+ Button
49+ id: remove
50+ anchors.top: prev.top
51+ anchors.right: parent.right
52+ text: Remove
53+ width: 60
54+ height: 17
55+
56+ ScrollablePanel
57+ id: items
58+ anchors.top: prev.bottom
59+ anchors.right: parent.right
60+ anchors.left: parent.left
61+ vertical-scrollbar: scrollBar
62+ margin-right: 5
63+ margin-top: 2
64+ height: 70
65+ layout:
66+ type: grid
67+ cell-size: 34 34
68+ flow: true
69+
70+ BotSmallScrollBar
71+ id: scrollBar
72+ anchors.top: prev.top
73+ anchors.bottom: prev.bottom
74+ anchors.right: parent.right
75+ step: 10
76+ pixels-scroll: true
77+
78+ BotLabel
79+ anchors.top: prev.bottom
80+ anchors.left: parent.left
81+ anchors.right: parent.right
82+ margin-top: 4
83+ text: Loot Containers
84+
85+ ItemsRow
86+ id: containers
87+ anchors.top: prev.bottom
88+ anchors.left: parent.left
89+ anchors.right: parent.right
90+ height: 33
91+ margin-top: 2
92+
93+]], parent)
94+
95+ local lootContainers = { ui.containers.item1, ui.containers.item2, ui.containers.item3, ui.containers.item4, ui.containers.item5 }
96+
97+ if type(context.storage.looting) ~= "table" then
98+ context.storage.looting = {}
99+ end
100+ if type(context.storage.looting.configs) ~= "table" then
101+ context.storage.looting.configs = {}
102+ end
103+
104+ local getConfigName = function(config)
105+ local matches = regexMatch(config, [[name:\s*([^\n]*)$]])
106+ if matches[1] and matches[1][2] then
107+ return matches[1][2]:trim()
108+ end
109+ return nil
110+ end
111+
112+ local items = {}
113+ local itemsByKey = {}
114+ local containers = {}
115+ local commands = {}
116+ local refreshConfig = nil -- declared later
117+
118+ local createNewConfig = function(focusedWidget)
119+ if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then
120+ return
121+ end
122+
123+ local tmpItems = {}
124+ local tmpContainers = {}
125+ local focusIndex = 0
126+
127+ local newConfig = ""
128+ for i, text in ipairs(commands) do
129+ newConfig = newConfig .. text .. "\n"
130+ end
131+ for i=1,ui.items:getChildCount() do
132+ local widget = ui.items:getChildByIndex(i)
133+ if widget and widget:getItemId() >= 100 then
134+ if tmpItems[widget:getItemId()] == nil then
135+ tmpItems[widget:getItemId()] = 1
136+ newConfig = newConfig .. "\n" .. widget:getItemId()
137+ end
138+ end
139+ if widget == focusedWidget then
140+ focusIndex = i
141+ end
142+ end
143+ for i, widget in ipairs(lootContainers) do
144+ if widget:getItemId() >= 100 then
145+ if tmpContainers[widget:getItemId()] == nil then
146+ tmpContainers[widget:getItemId()] = 1 -- remove duplicates
147+ newConfig = newConfig .. "\ncontainer:" .. widget:getItemId()
148+ end
149+ end
150+ end
151+
152+ context.storage.looting.configs[context.storage.looting.activeConfig] = newConfig
153+ refreshConfig(focusIndex)
154+ end
155+
156+ local parseConfig = function(config)
157+ items = {}
158+ itemsByKey = {}
159+ containers = {}
160+ commands = {}
161+ local matches = regexMatch(config, [[([^:^\n^\s]+)(:?)([^\n]*)]])
162+ for i=1,#matches do
163+ local command = matches[i][2]
164+ local validation = (matches[i][3] == ":")
165+ local text = matches[i][4]
166+ local commandAsNumber = tonumber(command)
167+ local textAsNumber = tonumber(text)
168+ if commandAsNumber and commandAsNumber >= 100 then
169+ table.insert(items, commandAsNumber)
170+ itemsByKey[commandAsNumber] = 1
171+ elseif command == "container" and validation and textAsNumber and textAsNumber >= 100 then
172+ containers[textAsNumber] = 1
173+ elseif validation then
174+ table.insert(commands, command .. ":" .. text)
175+ end
176+ end
177+
178+ local itemsToShow = #items + 2
179+ if itemsToShow % 5 ~= 0 then
180+ itemsToShow = itemsToShow + 5 - itemsToShow % 5
181+ end
182+ if itemsToShow < 10 then
183+ itemsToShow = 10
184+ end
185+
186+ for i=1,itemsToShow do
187+ local widget = g_ui.createWidget("BotItem", ui.items)
188+ local itemId = 0
189+ if i <= #items then
190+ itemId = items[i]
191+ end
192+ widget:setItemId(itemId)
193+ widget.onItemChange = createNewConfig
194+ end
195+
196+ for i, widget in ipairs(lootContainers) do
197+ widget:setItemId(0)
198+ end
199+ local containerIndex = 1
200+ for containerId, i in pairs(containers) do
201+ if lootContainers[containerIndex] then
202+ lootContainers[containerIndex]:setItemId(containerId)
203+ end
204+ containerIndex = containerIndex + 1
205+ end
206+ for i, widget in ipairs(lootContainers) do
207+ widget.onItemChange = createNewConfig
208+ end
209+ end
210+
211+ local ignoreOnOptionChange = true
212+ refreshConfig = function(focusIndex)
213+ ignoreOnOptionChange = true
214+ if context.storage.looting.enabled then
215+ ui.enableButton:setText("On")
216+ ui.enableButton:setColor('#00AA00FF')
217+ else
218+ ui.enableButton:setText("Off")
219+ ui.enableButton:setColor('#FF0000FF')
220+ end
221+
222+ ui.config:clear()
223+ for i, config in ipairs(context.storage.looting.configs) do
224+ local name = getConfigName(config)
225+ if not name then
226+ name = "Unnamed config"
227+ end
228+ ui.config:addOption(name)
229+ end
230+
231+ if (not context.storage.looting.activeConfig or context.storage.looting.activeConfig == 0) and #context.storage.looting.configs > 0 then
232+ context.storage.looting.activeConfig = 1
233+ end
234+
235+ ui.items:destroyChildren()
236+ for i, widget in ipairs(lootContainers) do
237+ widget.onItemChange = nil
238+ widget:setItemId(0)
239+ widget:setItemCount(0)
240+ end
241+
242+ if context.storage.looting.activeConfig and context.storage.looting.configs[context.storage.looting.activeConfig] then
243+ ui.config:setCurrentIndex(context.storage.looting.activeConfig)
244+ parseConfig(context.storage.looting.configs[context.storage.looting.activeConfig])
245+ end
246+
247+ context.saveConfig()
248+ if focusIndex and focusIndex > 0 and ui.items:getChildByIndex(focusIndex) then
249+ ui.items:focusChild(ui.items:getChildByIndex(focusIndex))
250+ end
251+
252+ ignoreOnOptionChange = false
253+ end
254+
255+ ui.config.onOptionChange = function(widget)
256+ if not ignoreOnOptionChange then
257+ context.storage.looting.activeConfig = widget.currentIndex
258+ refreshConfig()
259+ end
260+ end
261+ ui.enableButton.onClick = function()
262+ if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then
263+ return
264+ end
265+ context.storage.looting.enabled = not context.storage.looting.enabled
266+ refreshConfig()
267+ end
268+ ui.add.onClick = function()
269+ modules.client_textedit.multilineEditor("Looting editor", "name:Config name", function(newText)
270+ table.insert(context.storage.looting.configs, newText)
271+ context.storage.looting.activeConfig = #context.storage.looting.configs
272+ refreshConfig()
273+ end)
274+ end
275+ ui.edit.onClick = function()
276+ if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then
277+ return
278+ end
279+ modules.client_textedit.multilineEditor("Looting editor", context.storage.looting.configs[context.storage.looting.activeConfig], function(newText)
280+ context.storage.looting.configs[context.storage.looting.activeConfig] = newText
281+ refreshConfig()
282+ end)
283+ end
284+ ui.remove.onClick = function()
285+ if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then
286+ return
287+ end
288+ local questionWindow = nil
289+ local closeWindow = function()
290+ questionWindow:destroy()
291+ end
292+ local removeConfig = function()
293+ closeWindow()
294+ if not context.storage.looting.activeConfig or not context.storage.looting.configs[context.storage.looting.activeConfig] then
295+ return
296+ end
297+ context.storage.looting.enabled = false
298+ table.remove(context.storage.looting.configs, context.storage.looting.activeConfig)
299+ context.storage.looting.activeConfig = 0
300+ refreshConfig()
301+ end
302+ questionWindow = context.displayGeneralBox(tr('Remove config'), tr('Do you want to remove current looting config?'), {
303+ { text=tr('Yes'), callback=removeConfig },
304+ { text=tr('No'), callback=closeWindow },
305+ anchor=AnchorHorizontalCenter}, removeConfig, closeWindow)
306+ end
307+ refreshConfig()
308+
309+ context.onContainerOpen(function(container, prevContainer)
310+ if context.storage.attacking.enabled then
311+ return
312+ end
313+ if prevContainer then
314+ container.autoLooting = prevContainer.autoLooting
315+ else
316+ container.autoLooting = true
317+ end
318+ end)
319+
320+ context.macro(200, function()
321+ if not context.storage.looting.enabled then
322+ return
323+ end
324+ local candidates = {}
325+ local lootContainersCandidates = {}
326+ for containerId, container in pairs(g_game.getContainers()) do
327+ local containerItem = container:getContainerItem()
328+ if container.autoLooting and container:getItemsCount() > 0 and (not containerItem or containers[containerItem:getId()] == nil) then
329+ table.insert(candidates, container)
330+ elseif containerItem and containers[containerItem:getId()] ~= nil then
331+ table.insert(lootContainersCandidates, container)
332+ end
333+ end
334+ if #lootContainersCandidates == 0 then
335+ for slot = InventorySlotFirst, InventorySlotLast do
336+ local item = context.getInventoryItem(slot)
337+ if item and item:isContainer() and containers[item:getId()] ~= nil then
338+ table.insert(lootContainersCandidates, item)
339+ end
340+ end
341+ if #lootContainersCandidates > 0 then
342+ -- try to open inventory backpack
343+ local target = lootContainersCandidates[math.random(1,#lootContainersCandidates)]
344+ g_game.open(target, nil)
345+ context.delay(200)
346+ end
347+ return
348+ end
349+
350+ if #candidates == 0 then
351+ return
352+ end
353+
354+ local container = candidates[math.random(1,#candidates)]
355+ local nextContainers = {}
356+ local foundItem = nil
357+ for i, item in ipairs(container:getItems()) do
358+ if item:isContainer() then
359+ table.insert(nextContainers, item)
360+ elseif itemsByKey[item:getId()] ~= nil then
361+ foundItem = item
362+ break
363+ end
364+ end
365+
366+ -- found item to loot
367+ if foundItem then
368+ -- find backpack for it, first backpack with same items
369+ for i, container in ipairs(lootContainersCandidates) do
370+ if container:getItemsCount() < container:getCapacity() or foundItem:isStackable() then -- has space
371+ for j, item in ipairs(container:getItems()) do
372+ if item:getId() == foundItem:getId() then
373+ if foundItem:isStackable() then
374+ if item:getCount() ~= 100 then
375+ g_game.move(foundItem, container:getSlotPosition(j - 1), foundItem:getCount())
376+ return
377+ end
378+ else
379+ g_game.move(foundItem, container:getSlotPosition(container:getItemsCount()), foundItem:getCount())
380+ return
381+ end
382+ end
383+ end
384+ end
385+ end
386+ -- now any backpack with empty slot
387+ for i, container in ipairs(lootContainersCandidates) do
388+ if container:getItemsCount() < container:getCapacity() then -- has space
389+ g_game.move(foundItem, container:getSlotPosition(container:getItemsCount()), foundItem:getCount())
390+ return
391+ end
392+ end
393+
394+ -- can't find backpack, try to open new
395+ for i, container in ipairs(lootContainersCandidates) do
396+ local candidates = {}
397+ for j, item in ipairs(container:getItems()) do
398+ if item:isContainer() and containers[item:getId()] ~= nil then
399+ table.insert(candidates, item)
400+ end
401+ end
402+ if #candidates > 0 then
403+ g_game.open(candidates[math.random(1,#candidates)], container)
404+ return
405+ end
406+ -- full, close it
407+ g_game.close(container)
408+ return
409+ end
410+ return
411+ end
412+
413+ -- open remaining containers
414+ if #nextContainers == 0 then
415+ return
416+ end
417+ local delay = 1
418+ for i=2,#nextContainers do
419+ -- if more than 1 container, open them in new window
420+ context.schedule(delay, function()
421+ g_game.open(nextContainers[i], nil)
422+ end)
423+ delay = delay + 250
424+ end
425+ context.schedule(delay, function()
426+ g_game.open(nextContainers[1], container)
427+ end)
428+ context.delay(150 + delay)
429+ end)
430+end
431+
A RD_Client/modules/game_bot/panels/tools.lua +36-0 View file
@@ -0,0 +1,36 @@
1+local context = G.botContext
2+local Panels = context.Panels
3+
4+Panels.TradeMessage = function(parent)
5+ context.macro(60000, "Send message on trade", nil, function()
6+ local trade = context.getChannelId("advertising")
7+ if not trade then
8+ trade = context.getChannelId("trade")
9+ end
10+ if context.storage.autoTradeMessage:len() > 0 and trade then
11+ context.sayChannel(trade, context.storage.autoTradeMessage)
12+ end
13+ end, parent)
14+ context.addTextEdit("autoTradeMessage", context.storage.autoTradeMessage or "I'm using OTClientV8 - https://github.com/OTCv8/otclientv8", function(widget, text)
15+ context.storage.autoTradeMessage = text
16+ end, parent)
17+end
18+
19+Panels.AutoStackItems = function(parent)
20+ context.macro(500, "Auto stacking items", nil, function()
21+ local containers = context.getContainers()
22+ for i, container in pairs(containers) do
23+ local toStack = {}
24+ for j, item in ipairs(container:getItems()) do
25+ if item:isStackable() and item:getCount() ~= 100 then
26+ local otherItem = toStack[item:getId()]
27+ if otherItem then
28+ g_game.move(item, otherItem, item:getCount())
29+ return
30+ end
31+ toStack[item:getId()] = container:getSlotPosition(j - 1)
32+ end
33+ end
34+ end
35+ end, parent)
36+end
A RD_Client/modules/game_bot/panels/war.lua +127-0 View file
@@ -0,0 +1,127 @@
1+local context = G.botContext
2+local Panels = context.Panels
3+
4+Panels.AttackLeaderTarget = function(parent)
5+ local toAttack = nil
6+ context.onMissle(function(missle)
7+ if not context.storage.attackLeader or context.storage.attackLeader:len() == 0 then
8+ return
9+ end
10+ local src = missle:getSource()
11+ if src.z ~= context.posz() then
12+ return
13+ end
14+ local from = g_map.getTile(src)
15+ local to = g_map.getTile(missle:getDestination())
16+ if not from or not to then
17+ return
18+ end
19+ local fromCreatures = from:getCreatures()
20+ local toCreatures = to:getCreatures()
21+ if #fromCreatures ~= 1 or #toCreatures ~= 1 then
22+ return
23+ end
24+ local c1 = fromCreatures[1]
25+ if c1:getName():lower() == context.storage.attackLeader:lower() then
26+ toAttack = toCreatures[1]
27+ end
28+ end)
29+ context.macro(50, "Attack leader's target", nil, function()
30+ if toAttack and context.storage.attackLeader:len() > 0 and toAttack ~= g_game.getAttackingCreature() then
31+ g_game.attack(toAttack)
32+ toAttack = nil
33+ end
34+ end, parent)
35+ context.addTextEdit("attackLeader", context.storage.attackLeader or "player name", function(widget, text)
36+ context.storage.attackLeader = text
37+ end, parent)
38+end
39+
40+
41+Panels.LimitFloor = function(parent)
42+ context.onPlayerPositionChange(function(pos)
43+ if context.storage.limitFloor then
44+ local gameMapPanel = modules.game_interface.getMapPanel()
45+ if gameMapPanel then
46+ gameMapPanel:lockVisibleFloor(pos.z)
47+ end
48+ end
49+ end)
50+
51+ local switch = context.addSwitch("limitFloor", "Don't show higher floors", function(widget)
52+ widget:setOn(not widget:isOn())
53+ context.storage.limitFloor = widget:isOn()
54+ local gameMapPanel = modules.game_interface.getMapPanel()
55+ if gameMapPanel then
56+ if context.storage.limitFloor then
57+ gameMapPanel:lockVisibleFloor(context.posz())
58+ else
59+ gameMapPanel:unlockVisibleFloor()
60+ end
61+ end
62+ end, parent)
63+ switch:setOn(context.storage.limitFloor)
64+end
65+
66+Panels.AntiPush = function(parent)
67+ if not parent then
68+ parent = context.panel
69+ end
70+
71+ local panelName = "antiPushPanel"
72+ local ui = g_ui.createWidget("ItemsPanel", parent)
73+ ui:setId(panelName)
74+
75+ if not context.storage[panelName] then
76+ context.storage[panelName] = {}
77+ end
78+
79+ ui.title:setText("Anti push")
80+ ui.title:setOn(context.storage[panelName].enabled)
81+ ui.title.onClick = function(widget)
82+ context.storage[panelName].enabled = not context.storage[panelName].enabled
83+ widget:setOn(context.storage[panelName].enabled)
84+ end
85+
86+ if type(context.storage[panelName].items) ~= 'table' then
87+ context.storage[panelName].items = {3031, 3035, 0, 0, 0}
88+ end
89+
90+ for i=1,5 do
91+ ui.items:getChildByIndex(i).onItemChange = function(widget)
92+ context.storage[panelName].items[i] = widget:getItemId()
93+ end
94+ ui.items:getChildByIndex(i):setItemId(context.storage[panelName].items[i])
95+ end
96+
97+ context.macro(100, function()
98+ if not context.storage[panelName].enabled then
99+ return
100+ end
101+ local tile = g_map.getTile(context.player:getPosition())
102+ if not tile then
103+ return
104+ end
105+ local topItem = tile:getTopUseThing()
106+ if topItem and topItem:isStackable() then
107+ topItem = topItem:getId()
108+ else
109+ topItem = 0
110+ end
111+ local candidates = {}
112+ for i, item in pairs(context.storage[panelName].items) do
113+ if item >= 100 and item ~= topItem and context.findItem(item) then
114+ table.insert(candidates, item)
115+ end
116+ end
117+ if #candidates == 0 then
118+ return
119+ end
120+ if type(context.storage[panelName].lastItem) ~= 'number' or context.storage[panelName].lastItem > #candidates then
121+ context.storage[panelName].lastItem = 1
122+ end
123+ local item = context.findItem(candidates[context.storage[panelName].lastItem])
124+ g_game.move(item, context.player:getPosition(), 1)
125+ context.storage[panelName].lastItem = context.storage[panelName].lastItem + 1
126+ end)
127+end
Top