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/default_configs/cavebot_1.2/targetbot/creature_priority.lua +40-0 View file
@@ -0,0 +1,40 @@
1+TargetBot.Creature.calculatePriority = function(creature, config, path)
2+ -- config is based on creature_editor
3+ local priority = 0
4+
5+ -- extra priority if it's current target
6+ if g_game.getAttackingCreature() == creature then
7+ priority = priority + 1
8+ end
9+
10+ -- check if distance is fine, if not then attack only if already attacked
11+ if #path > config.maxDistance then
12+ return priority
13+ end
14+
15+ -- add config priority
16+ priority = priority + config.priority
17+
18+ -- extra priority for close distance
19+ local path_length = #path
20+ if path_length == 1 then
21+ priority = priority + 3
22+ elseif path_length <= 3 then
23+ priority = priority + 1
24+ end
25+
26+ -- extra priority for low health
27+ if config.chase and creature:getHealthPercent() < 30 then
28+ priority = priority + 5
29+ elseif creature:getHealthPercent() < 20 then
30+ priority = priority + 2.5
31+ elseif creature:getHealthPercent() < 40 then
32+ priority = priority + 1.5
33+ elseif creature:getHealthPercent() < 60 then
34+ priority = priority + 0.5
35+ elseif creature:getHealthPercent() < 80 then
36+ priority = priority + 0.2
37+ end
38+
39+ return priority
40+end
A RD_Client/modules/game_bot/default_configs/cavebot_1.2/targetbot/looting.lua +295-0 View file
@@ -0,0 +1,295 @@
1+TargetBot.Looting = {}
2+TargetBot.Looting.list = {} -- list of containers to loot
3+
4+local ui
5+local items = {}
6+local containers = {}
7+local itemsById = {}
8+local containersById = {}
9+local dontSave = false
10+
11+TargetBot.Looting.setup = function()
12+ ui = UI.createWidget("TargetBotLootingPanel")
13+ UI.Container(TargetBot.Looting.onItemsUpdate, true, nil, ui.items)
14+ UI.Container(TargetBot.Looting.onContainersUpdate, true, nil, ui.containers)
15+ ui.everyItem.onClick = function()
16+ ui.everyItem:setOn(not ui.everyItem:isOn())
17+ TargetBot.save()
18+ end
19+ ui.maxDangerPanel.value.onTextChange = function()
20+ local value = tonumber(ui.maxDangerPanel.value:getText())
21+ if not value then
22+ ui.maxDangerPanel.value:setText(0)
23+ end
24+ if dontSave then return end
25+ TargetBot.save()
26+ end
27+ ui.minCapacityPanel.value.onTextChange = function()
28+ local value = tonumber(ui.minCapacityPanel.value:getText())
29+ if not value then
30+ ui.minCapacityPanel.value:setText(0)
31+ end
32+ if dontSave then return end
33+ TargetBot.save()
34+ end
35+end
36+
37+TargetBot.Looting.onItemsUpdate = function()
38+ if dontSave then return end
39+ TargetBot.save()
40+ TargetBot.Looting.updateItemsAndContainers()
41+end
42+
43+TargetBot.Looting.onContainersUpdate = function()
44+ if dontSave then return end
45+ TargetBot.save()
46+ TargetBot.Looting.updateItemsAndContainers()
47+end
48+
49+TargetBot.Looting.update = function(data)
50+ dontSave = true
51+ TargetBot.Looting.list = {}
52+ ui.items:setItems(data['items'] or {})
53+ ui.containers:setItems(data['containers'] or {})
54+ ui.everyItem:setOn(data['everyItem'])
55+ ui.maxDangerPanel.value:setText(data['maxDanger'] or 10)
56+ ui.minCapacityPanel.value:setText(data['minCapacity'] or 100)
57+ TargetBot.Looting.updateItemsAndContainers()
58+ dontSave = false
59+end
60+
61+TargetBot.Looting.save = function(data)
62+ data['items'] = ui.items:getItems()
63+ data['containers'] = ui.containers:getItems()
64+ data['maxDanger'] = tonumber(ui.maxDangerPanel.value:getText())
65+ data['minCapacity'] = tonumber(ui.minCapacityPanel.value:getText())
66+ data['everyItem'] = ui.everyItem:isOn()
67+end
68+
69+TargetBot.Looting.updateItemsAndContainers = function()
70+ items = ui.items:getItems()
71+ containers = ui.containers:getItems()
72+ itemsById = {}
73+ containersById = {}
74+ for i, item in ipairs(items) do
75+ itemsById[item.id] = 1
76+ end
77+ for i, container in ipairs(containers) do
78+ containersById[container.id] = 1
79+ end
80+end
81+
82+local waitTill = 0
83+local waitingForContainer = nil
84+local status = ""
85+local lastFoodConsumption = 0
86+
87+TargetBot.Looting.getStatus = function()
88+ return status
89+end
90+
91+TargetBot.Looting.process = function(targets, dangerLevel)
92+ if (not items[1] and not ui.everyItem:isOn()) or not containers[1] then
93+ status = ""
94+ return false
95+ end
96+ if dangerLevel > tonumber(ui.maxDangerPanel.value:getText()) then
97+ status = "High danger"
98+ return false
99+ end
100+ if player:getFreeCapacity() < tonumber(ui.minCapacityPanel.value:getText()) then
101+ status = "No cap"
102+ TargetBot.Looting.list = {}
103+ return false
104+ end
105+ local loot = TargetBot.Looting.list[1]
106+ if loot == nil then
107+ status = ""
108+ return false
109+ end
110+
111+ if waitTill > now then
112+ return true
113+ end
114+ local containers = g_game.getContainers()
115+ local lootContainers = TargetBot.Looting.getLootContainers(containers)
116+
117+ -- check if there's container for loot and has empty space for it
118+ if not lootContainers[1] then
119+ -- there's no space, don't loot
120+ status = "No space"
121+ return false
122+ end
123+
124+ status = "Looting"
125+
126+ for index, container in pairs(containers) do
127+ if container.lootContainer then
128+ TargetBot.Looting.lootContainer(lootContainers, container)
129+ return true
130+ end
131+ end
132+
133+ local pos = player:getPosition()
134+ local dist = math.max(math.abs(pos.x-loot.pos.x), math.abs(pos.y-loot.pos.y))
135+ if loot.tries > 30 or loot.pos.z ~= pos.z or dist > 20 then
136+ table.remove(TargetBot.Looting.list, 1)
137+ return true
138+ end
139+
140+ local tile = g_map.getTile(loot.pos)
141+ if dist >= 3 or not tile then
142+ loot.tries = loot.tries + 1
143+ TargetBot.walkTo(loot.pos, 20, { ignoreNonPathable = true, precision = 2 })
144+ return true
145+ end
146+
147+ local container = tile:getTopUseThing()
148+ if not container or not container:isContainer() then
149+ table.remove(TargetBot.Looting.list, 1)
150+ return true
151+ end
152+
153+ g_game.open(container)
154+ waitTill = now + 1000 -- give it 1s to open
155+ waitingForContainer = container:getId()
156+ loot.tries = loot.tries + 10
157+
158+ return true
159+end
160+
161+TargetBot.Looting.getLootContainers = function(containers)
162+ local lootContainers = {}
163+ local openedContainersById = {}
164+ local toOpen = nil
165+ for index, container in pairs(containers) do
166+ openedContainersById[container:getContainerItem():getId()] = 1
167+ if containersById[container:getContainerItem():getId()] and not container.lootContainer then
168+ if container:getItemsCount() < container:getCapacity() then
169+ table.insert(lootContainers, container)
170+ else -- it's full, open next container if possible
171+ for slot, item in ipairs(container:getItems()) do
172+ if item:isContainer() and containersById[item:getId()] then
173+ toOpen = {item, container}
174+ break
175+ end
176+ end
177+ end
178+ end
179+ end
180+ if not lootContainers[1] then
181+ if toOpen then
182+ g_game.open(toOpen[1], toOpen[2])
183+ waitTill = now + 500 -- wait 0.5s
184+ return lootContainers
185+ end
186+ -- check containers one more time, maybe there's any loot container
187+ for index, container in pairs(containers) do
188+ if not containersById[container:getContainerItem():getId()] and not container.lootContainer then
189+ for slot, item in ipairs(container:getItems()) do
190+ if item:isContainer() and containersById[item:getId()] then
191+ g_game.open(item)
192+ waitTill = now + 500 -- wait 0.5s
193+ return lootContainers
194+ end
195+ end
196+ end
197+ end
198+ -- can't find any lootContainer, let's check slots, maybe there's one
199+ for slot = InventorySlotFirst, InventorySlotLast do
200+ local item = getInventoryItem(slot)
201+ if item and item:isContainer() and not openedContainersById[item:getId()] then
202+ -- container which is not opened yet, let's open it
203+ g_game.open(item)
204+ waitTill = now + 500 -- wait 0.5s
205+ return lootContainers
206+ end
207+ end
208+ end
209+ return lootContainers
210+end
211+
212+TargetBot.Looting.lootContainer = function(lootContainers, container)
213+ -- loot items
214+ local nextContainer = nil
215+ for i, item in ipairs(container:getItems()) do
216+ if item:isContainer() and not itemsById[item:getId()] then
217+ nextContainer = item
218+ elseif itemsById[item:getId()] or (ui.everyItem:isOn() and not item:isContainer()) then
219+ item.lootTries = (item.lootTries or 0) + 1
220+ if item.lootTries < 5 then -- if can't be looted within 0.5s then skip it
221+ return TargetBot.Looting.lootItem(lootContainers, item)
222+ end
223+ elseif storage.foodItems and storage.foodItems[1] and lastFoodConsumption + 5000 < now then
224+ for _, food in ipairs(storage.foodItems) do
225+ if item:getId() == food.id then
226+ g_game.use(item)
227+ lastFoodConsumption = now
228+ return
229+ end
230+ end
231+ end
232+ end
233+
234+ -- no more items to loot, open next container
235+ if nextContainer then
236+ nextContainer.lootTries = (nextContainer.lootTries or 0) + 1
237+ if nextContainer.lootTries < 2 then -- max 0.6s to open it
238+ g_game.open(nextContainer, container)
239+ waitTill = now + 300 -- give it 0.3s to open
240+ waitingForContainer = nextContainer:getId()
241+ return
242+ end
243+ end
244+
245+ -- looting finished, remove container from list
246+ container.lootContainer = false
247+ g_game.close(container)
248+ table.remove(TargetBot.Looting.list, 1)
249+end
250+
251+TargetBot.Looting.lootItem = function(lootContainers, item)
252+ if item:isStackable() then
253+ local count = item:getCount()
254+ for _, container in ipairs(lootContainers) do
255+ for slot, citem in ipairs(container:getItems()) do
256+ if item:getId() == citem:getId() and citem:getCount() < 100 then
257+ g_game.move(item, container:getSlotPosition(slot - 1), count)
258+ waitTill = now + 300 -- give it 0.3s to move item
259+ return
260+ end
261+ end
262+ end
263+ end
264+
265+ local container = lootContainers[1]
266+ g_game.move(item, container:getSlotPosition(container:getItemsCount()), 1)
267+ waitTill = now + 300 -- give it 0.3s to move item
268+end
269+
270+onContainerOpen(function(container, previousContainer)
271+ if container:getContainerItem():getId() == waitingForContainer then
272+ container.lootContainer = true
273+ waitingForContainer = nil
274+ end
275+end)
276+
277+onCreatureDisappear(function(creature)
278+ if not TargetBot.isOn() then return end
279+ if not creature:isMonster() then return end
280+ local pos = player:getPosition()
281+ local mpos = creature:getPosition()
282+ local name = creature:getName()
283+ if pos.z ~= mpos.z or math.max(math.abs(pos.x-mpos.x), math.abs(pos.y-mpos.y)) > 6 then return end
284+ schedule(20, function() -- check in 20ms if there's container (dead body) on that tile
285+ if not containers[1] then return end
286+ if TargetBot.Looting.list[20] then return end -- too many items to loot
287+ local tile = g_map.getTile(mpos)
288+ if not tile then return end
289+ local container = tile:getTopUseThing()
290+ if not container or not container:isContainer() then return end
291+ if not findPath(player:getPosition(), mpos, 6, {ignoreNonPathable=true, ignoreCreatures=true, ignoreCost=true}) then return end
292+ table.insert(TargetBot.Looting.list, {pos=mpos, creature=name, container=container:getId(), added=now, tries=0})
293+ container:setMarked('#000088')
294+ end)
295+end)
A RD_Client/modules/game_bot/default_configs/cavebot_1.2/targetbot/looting.otui +83-0 View file
@@ -0,0 +1,83 @@
1+TargetBotLootingPanel < Panel
2+ layout:
3+ type: verticalBox
4+ fit-children: true
5+
6+ HorizontalSeparator
7+ margin-top: 5
8+
9+ Label
10+ margin-top: 5
11+ text: Items to loot
12+ text-align: center
13+
14+ BotContainer
15+ id: items
16+ margin-top: 3
17+
18+ BotSwitch
19+ id: everyItem
20+ !text: tr("Loot every item")
21+ margin-top: 2
22+
23+ Label
24+ margin-top: 5
25+ text: Containers for loot
26+ text-align: center
27+
28+ BotContainer
29+ id: containers
30+ margin-top: 3
31+ height: 45
32+
33+ Panel
34+ id: maxDangerPanel
35+ height: 20
36+ margin-top: 5
37+
38+ BotTextEdit
39+ id: value
40+ anchors.right: parent.right
41+ anchors.top: parent.top
42+ anchors.bottom: parent.bottom
43+ margin-right: 6
44+ width: 80
45+
46+ Label
47+ anchors.left: parent.left
48+ anchors.verticalCenter: prev.verticalCenter
49+ text: Max. danger:
50+ margin-left: 5
51+
52+ Panel
53+ id: minCapacityPanel
54+ height: 20
55+ margin-top: 3
56+
57+ BotTextEdit
58+ id: value
59+ anchors.right: parent.right
60+ anchors.top: parent.top
61+ anchors.bottom: parent.bottom
62+ margin-right: 6
63+ width: 80
64+
65+ Label
66+ anchors.left: parent.left
67+ anchors.verticalCenter: prev.verticalCenter
68+ text: Min. capacity:
69+ margin-left: 5
70+
71+ Label
72+ margin-top: 3
73+ margin-left: 20
74+ margin-right: 20
75+ !text: tr("Drag item or click on any of empty slot")
76+ text-align: center
77+ text-wrap: true
78+ text-auto-resize: true
79+
80+ BotButton
81+ margin-top: 3
82+ text: Help & Tutorials
83+ @onClick: g_platform.openUrl("http://bot.otclient.ovh/")
A RD_Client/modules/game_bot/default_configs/cavebot_1.2/targetbot/target.lua +267-0 View file
@@ -0,0 +1,267 @@
1+local targetbotMacro = nil
2+local config = nil
3+local lastAction = 0
4+local cavebotAllowance = 0
5+
6+-- ui
7+local configWidget = UI.Config()
8+local ui = UI.createWidget("TargetBotPanel")
9+
10+ui.list = ui.listPanel.list -- shortcut
11+TargetBot.targetList = ui.list
12+TargetBot.Looting.setup()
13+
14+ui.status.left:setText("Status:")
15+ui.status.right:setText("Off")
16+ui.target.left:setText("Target:")
17+ui.target.right:setText("-")
18+ui.config.left:setText("Config:")
19+ui.config.right:setText("-")
20+ui.danger.left:setText("Danger:")
21+ui.danger.right:setText("0")
22+
23+ui.editor.debug.onClick = function()
24+ local on = ui.editor.debug:isOn()
25+ ui.editor.debug:setOn(not on)
26+ if on then
27+ for _, spec in ipairs(getSpectators()) do
28+ spec:clearText()
29+ end
30+ end
31+end
32+
33+-- main loop, controlled by config
34+targetbotMacro = macro(100, function()
35+ local pos = player:getPosition()
36+ local creatures = g_map.getSpectatorsInRange(pos, false, 6, 6) -- 12x12 area
37+ if #creatures > 10 then -- if there are too many monsters around, limit area
38+ creatures = g_map.getSpectatorsInRange(pos, false, 3, 3) -- 6x6 area
39+ end
40+ local highestPriority = 0
41+ local dangerLevel = 0
42+ local targets = 0
43+ local highestPriorityParams = nil
44+ for i, creature in ipairs(creatures) do
45+ local path = findPath(player:getPosition(), creature:getPosition(), 7, {ignoreLastCreature=true, ignoreNonPathable=true, ignoreCost=true})
46+ if creature:isMonster() and path then
47+ local params = TargetBot.Creature.calculateParams(creature, path) -- return {craeture, config, danger, priority}
48+ dangerLevel = dangerLevel + params.danger
49+ if params.priority > 0 then
50+ targets = targets + 1
51+ if params.priority > highestPriority then
52+ highestPriority = params.priority
53+ highestPriorityParams = params
54+ end
55+ if ui.editor.debug:isOn() then
56+ creature:setText(params.config.name .. "\n" .. params.priority)
57+ end
58+ end
59+ end
60+ end
61+
62+ -- reset walking
63+ TargetBot.walkTo(nil)
64+
65+ -- looting
66+ local looting = TargetBot.Looting.process(targets, dangerLevel)
67+ local lootingStatus = TargetBot.Looting.getStatus()
68+
69+ ui.danger.right:setText(dangerLevel)
70+ if highestPriorityParams and not isInPz() then
71+ ui.target.right:setText(highestPriorityParams.creature:getName())
72+ ui.config.right:setText(highestPriorityParams.config.name)
73+ TargetBot.Creature.attack(highestPriorityParams, targets, looting)
74+ if lootingStatus:len() > 0 then
75+ TargetBot.setStatus("Attack & " .. lootingStatus)
76+ elseif cavebotAllowance > now then
77+ TargetBot.setStatus("Luring using CaveBot")
78+ else
79+ TargetBot.setStatus("Attacking")
80+ end
81+ TargetBot.walk()
82+ lastAction = now
83+ return
84+ end
85+
86+ ui.target.right:setText("-")
87+ ui.config.right:setText("-")
88+ if looting then
89+ TargetBot.walk()
90+ lastAction = now
91+ end
92+ if lootingStatus:len() > 0 then
93+ TargetBot.setStatus(lootingStatus)
94+ else
95+ TargetBot.setStatus("Waiting")
96+ end
97+end)
98+
99+-- config, its callback is called immediately, data can be nil
100+config = Config.setup("targetbot_configs", configWidget, "json", function(name, enabled, data)
101+ if not data then
102+ ui.status.right:setText("Off")
103+ return targetbotMacro.setOff()
104+ end
105+ TargetBot.Creature.resetConfigs()
106+ for _, value in ipairs(data["targeting"] or {}) do
107+ TargetBot.Creature.addConfig(value)
108+ end
109+ TargetBot.Looting.update(data["looting"] or {})
110+
111+ -- add configs
112+ if enabled then
113+ ui.status.right:setText("On")
114+ else
115+ ui.status.right:setText("Off")
116+ end
117+
118+ targetbotMacro.setOn(enabled)
119+ targetbotMacro.delay = nil
120+end)
121+
122+-- setup ui
123+ui.editor.buttons.add.onClick = function()
124+ TargetBot.Creature.edit(nil, function(newConfig)
125+ TargetBot.Creature.addConfig(newConfig, true)
126+ TargetBot.save()
127+ end)
128+end
129+
130+ui.editor.buttons.edit.onClick = function()
131+ local entry = ui.list:getFocusedChild()
132+ if not entry then return end
133+ TargetBot.Creature.edit(entry.value, function(newConfig)
134+ entry:setText(newConfig.name)
135+ entry.value = newConfig
136+ TargetBot.Creature.resetConfigsCache()
137+ TargetBot.save()
138+ end)
139+end
140+
141+ui.editor.buttons.remove.onClick = function()
142+ local entry = ui.list:getFocusedChild()
143+ if not entry then return end
144+ entry:destroy()
145+ TargetBot.Creature.resetConfigsCache()
146+ TargetBot.save()
147+end
148+
149+-- public function, you can use them in your scripts
150+TargetBot.isActive = function() -- return true if attacking or looting takes place
151+ return lastAction + 300 > now
152+end
153+
154+TargetBot.isCaveBotActionAllowed = function()
155+ return cavebotAllowance > now
156+end
157+
158+TargetBot.setStatus = function(text)
159+ return ui.status.right:setText(text)
160+end
161+
162+TargetBot.isOn = function()
163+ return config.isOn()
164+end
165+
166+TargetBot.isOff = function()
167+ return config.isOff()
168+end
169+
170+TargetBot.setOn = function(val)
171+ if val == false then
172+ return TargetBot.setOff(true)
173+ end
174+ config.setOn()
175+end
176+
177+TargetBot.setOff = function(val)
178+ if val == false then
179+ return TargetBot.setOn(true)
180+ end
181+ config.setOff()
182+end
183+
184+TargetBot.delay = function(value)
185+ targetbotMacro.delay = now + value
186+end
187+
188+TargetBot.save = function()
189+ local data = {targeting={}, looting={}}
190+ for _, entry in ipairs(ui.list:getChildren()) do
191+ table.insert(data.targeting, entry.value)
192+ end
193+ TargetBot.Looting.save(data.looting)
194+ config.save(data)
195+end
196+
197+TargetBot.allowCaveBot = function(time)
198+ cavebotAllowance = now + time
199+end
200+
201+-- attacks
202+local lastSpell = 0
203+local lastAttackSpell = 0
204+
205+TargetBot.saySpell = function(text, delay)
206+ if type(text) ~= 'string' or text:len() < 1 then return end
207+ if not delay then delay = 500 end
208+ if g_game.getProtocolVersion() < 1090 then
209+ lastAttackSpell = now -- pause attack spells, healing spells are more important
210+ end
211+ if lastSpell + delay < now then
212+ say(text)
213+ lastSpell = now
214+ return true
215+ end
216+ return false
217+end
218+
219+TargetBot.sayAttackSpell = function(text, delay)
220+ if type(text) ~= 'string' or text:len() < 1 then return end
221+ if not delay then delay = 2000 end
222+ if lastAttackSpell + delay < now then
223+ say(text)
224+ lastAttackSpell = now
225+ return true
226+ end
227+ return false
228+end
229+
230+local lastItemUse = 0
231+local lastRuneAttack = 0
232+
233+TargetBot.useItem = function(item, subType, target, delay)
234+ if not delay then delay = 200 end
235+ if lastItemUse + delay < now then
236+ local thing = g_things.getThingType(item)
237+ if not thing or not thing:isFluidContainer() then
238+ subType = g_game.getClientVersion() >= 860 and 0 or 1
239+ end
240+ if g_game.getClientVersion() < 780 then
241+ local tmpItem = g_game.findPlayerItem(item, subType)
242+ if not tmpItem then return end
243+ g_game.useWith(tmpItem, target, subType) -- using item from bp
244+ else
245+ g_game.useInventoryItemWith(item, target, subType) -- hotkey
246+ end
247+ lastItemUse = now
248+ end
249+end
250+
251+TargetBot.useAttackItem = function(item, subType, target, delay)
252+ if not delay then delay = 2000 end
253+ if lastRuneAttack + delay < now then
254+ local thing = g_things.getThingType(item)
255+ if not thing or not thing:isFluidContainer() then
256+ subType = g_game.getClientVersion() >= 860 and 0 or 1
257+ end
258+ if g_game.getClientVersion() < 780 then
259+ local tmpItem = g_game.findPlayerItem(item, subType)
260+ if not tmpItem then return end
261+ g_game.useWith(tmpItem, target, subType) -- using item from bp
262+ else
263+ g_game.useInventoryItemWith(item, target, subType) -- hotkey
264+ end
265+ lastRuneAttack = now
266+ end
267+end
A RD_Client/modules/game_bot/default_configs/cavebot_1.2/targetbot/target.otui +115-0 View file
@@ -0,0 +1,115 @@
1+TargetBotEntry < Label
2+ background-color: alpha
3+ text-offset: 2 0
4+ focusable: true
5+
6+ $focus:
7+ background-color: #00000055
8+
9+TargetBotDualLabel < Panel
10+ height: 18
11+ margin-left: 3
12+ margin-right: 4
13+
14+ Label
15+ id: left
16+ anchors.top: parent.top
17+ anchors.left: parent.left
18+ text-auto-resize: true
19+
20+ Label
21+ id: right
22+ anchors.top: parent.top
23+ anchors.right: parent.right
24+ text-auto-resize: true
25+
26+TargetBotPanel < Panel
27+ layout:
28+ type: verticalBox
29+ fit-children: true
30+
31+ HorizontalSeparator
32+ margin-top: 2
33+ margin-bottom: 5
34+
35+ TargetBotDualLabel
36+ id: status
37+ TargetBotDualLabel
38+ id: target
39+ TargetBotDualLabel
40+ id: config
41+ TargetBotDualLabel
42+ id: danger
43+
44+ Panel
45+ id: listPanel
46+ height: 40
47+
48+ TextList
49+ id: list
50+ anchors.fill: parent
51+ vertical-scrollbar: listScrollbar
52+ margin-right: 15
53+ focusable: false
54+ auto-focus: first
55+
56+ VerticalScrollBar
57+ id: listScrollbar
58+ anchors.top: parent.top
59+ anchors.bottom: parent.bottom
60+ anchors.right: parent.right
61+ pixels-scroll: true
62+ step: 10
63+
64+ BotSwitch
65+ id: configButton
66+ @onClick: |
67+ self:setOn(not self:isOn())
68+ self:getParent().listPanel:setHeight(self:isOn() and 100 or 40)
69+ self:getParent().editor:setVisible(self:isOn())
70+
71+ $on:
72+ text: Hide target editor
73+
74+ $!on:
75+ text: Show target editor
76+
77+ Panel
78+ id: editor
79+ visible: false
80+ layout:
81+ type: verticalBox
82+ fit-children: true
83+
84+ Panel
85+ id: buttons
86+ height: 20
87+ margin-top: 2
88+
89+ Button
90+ id: add
91+ anchors.top: parent.top
92+ anchors.bottom: parent.bottom
93+ anchors.left: parent.left
94+ text: Add
95+ width: 56
96+
97+ Button
98+ id: edit
99+ anchors.top: parent.top
100+ anchors.bottom: parent.bottom
101+ anchors.horizontalCenter: parent.horizontalCenter
102+ text: Edit
103+ width: 56
104+
105+ Button
106+ id: remove
107+ anchors.top: parent.top
108+ anchors.bottom: parent.bottom
109+ anchors.right: parent.right
110+ text: Remove
111+ width: 56
112+
113+ BotSwitch
114+ id: debug
115+ text: Show target priority
A RD_Client/modules/game_bot/default_configs/cavebot_1.2/targetbot/walking.lua +28-0 View file
@@ -0,0 +1,28 @@
1+local dest
2+local maxDist
3+local params
4+
5+TargetBot.walkTo = function(_dest, _maxDist, _params)
6+ dest = _dest
7+ maxDist = _maxDist
8+ params = _params
9+end
10+
11+-- called every 100ms if targeting or looting is active
12+TargetBot.walk = function()
13+ if not dest then return end
14+ if player:isWalking() then return end
15+ local pos = player:getPosition()
16+ if pos.z ~= dest.z then return end
17+ local dist = math.max(math.abs(pos.x-dest.x), math.abs(pos.y-dest.y))
18+ if params.precision and params.precision >= dist then return end
19+ if params.marginMin and params.marginMax then
20+ if dist >= params.marginMin and dist <= params.marginMax then
21+ return
22+ end
23+ end
24+ local path = getPath(pos, dest, maxDist, params)
25+ if path then
26+ walk(path[1])
27+ end
28+end
A RD_Client/modules/game_bot/default_configs/cavebot_1.2/tools.lua +147-0 View file
@@ -0,0 +1,147 @@
1+-- tools tab
2+setDefaultTab("Tools")
3+
4+-- allows to test/edit bot lua scripts ingame, you can have multiple scripts like this, just change storage.ingame_lua
5+UI.Button("Ingame macro editor", function(newText)
6+ UI.MultilineEditorWindow(storage.ingame_macros or "", {title="Macro editor", description="You can add your custom macros (or any other lua code) here"}, function(text)
7+ storage.ingame_macros = text
8+ reload()
9+ end)
10+end)
11+UI.Button("Ingame hotkey editor", function(newText)
12+ UI.MultilineEditorWindow(storage.ingame_hotkeys or "", {title="Hotkeys editor", description="You can add your custom hotkeys/singlehotkeys here"}, function(text)
13+ storage.ingame_hotkeys = text
14+ reload()
15+ end)
16+end)
17+
18+UI.Separator()
19+
20+for _, scripts in ipairs({storage.ingame_macros, storage.ingame_hotkeys}) do
21+ if type(scripts) == "string" and scripts:len() > 3 then
22+ local status, result = pcall(function()
23+ assert(load(scripts, "ingame_editor"))()
24+ end)
25+ if not status then
26+ error("Ingame edior error:\n" .. result)
27+ end
28+ end
29+end
30+
31+UI.Separator()
32+
33+UI.Button("Zoom In map [ctrl + =]", function() zoomIn() end)
34+UI.Button("Zoom Out map [ctrl + -]", function() zoomOut() end)
35+
36+UI.Separator()
37+
38+local moneyIds = {3031, 3035} -- gold coin, platinium coin
39+macro(1000, "Exchange money", function()
40+ local containers = g_game.getContainers()
41+ for index, container in pairs(containers) do
42+ if not container.lootContainer then -- ignore monster containers
43+ for i, item in ipairs(container:getItems()) do
44+ if item:getCount() == 100 then
45+ for m, moneyId in ipairs(moneyIds) do
46+ if item:getId() == moneyId then
47+ return g_game.use(item)
48+ end
49+ end
50+ end
51+ end
52+ end
53+ end
54+end)
55+
56+macro(1000, "Stack items", function()
57+ local containers = g_game.getContainers()
58+ local toStack = {}
59+ for index, container in pairs(containers) do
60+ if not container.lootContainer then -- ignore monster containers
61+ for i, item in ipairs(container:getItems()) do
62+ if item:isStackable() and item:getCount() < 100 then
63+ local stackWith = toStack[item:getId()]
64+ if stackWith then
65+ g_game.move(item, stackWith[1], math.min(stackWith[2], item:getCount()))
66+ return
67+ end
68+ toStack[item:getId()] = {container:getSlotPosition(i - 1), 100 - item:getCount()}
69+ end
70+ end
71+ end
72+ end
73+end)
74+
75+macro(10000, "Anti Kick", function()
76+ local dir = player:getDirection()
77+ turn((dir + 1) % 4)
78+ turn(dir)
79+end)
80+
81+UI.Separator()
82+UI.Label("Drop items:")
83+if type(storage.dropItems) ~= "table" then
84+ storage.dropItems = {283, 284, 285}
85+end
86+
87+local foodContainer = UI.Container(function(widget, items)
88+ storage.dropItems = items
89+end, true)
90+foodContainer:setHeight(35)
91+foodContainer:setItems(storage.dropItems)
92+
93+macro(5000, "drop items", function()
94+ if not storage.dropItems[1] then return end
95+ if TargetBot and TargetBot.isActive() then return end -- pause when attacking
96+ for _, container in pairs(g_game.getContainers()) do
97+ for __, item in ipairs(container:getItems()) do
98+ for i, dropItem in ipairs(storage.dropItems) do
99+ if item:getId() == dropItem.id then
100+ if item:isStackable() then
101+ return g_game.move(item, player:getPosition(), item:getCount())
102+ else
103+ return g_game.move(item, player:getPosition(), dropItem.count) -- count is also subtype
104+ end
105+ end
106+ end
107+ end
108+ end
109+end)
110+
111+UI.Separator()
112+
113+UI.Label("Mana training")
114+if type(storage.manaTrain) ~= "table" then
115+ storage.manaTrain = {on=false, title="MP%", text="utevo lux", min=80, max=100}
116+end
117+
118+local manatrainmacro = macro(1000, function()
119+ if TargetBot and TargetBot.isActive() then return end -- pause when attacking
120+ local mana = math.min(100, math.floor(100 * (player:getMana() / player:getMaxMana())))
121+ if storage.manaTrain.max >= mana and mana >= storage.manaTrain.min then
122+ say(storage.manaTrain.text)
123+ end
124+end)
125+manatrainmacro.setOn(storage.manaTrain.on)
126+
127+UI.DualScrollPanel(storage.manaTrain, function(widget, newParams)
128+ storage.manaTrain = newParams
129+ manatrainmacro.setOn(storage.manaTrain.on)
130+end)
131+
132+UI.Separator()
133+
134+macro(60000, "Send message on trade", function()
135+ local trade = getChannelId("advertising")
136+ if not trade then
137+ trade = getChannelId("trade")
138+ end
139+ if trade and storage.autoTradeMessage:len() > 0 then
140+ sayChannel(trade, storage.autoTradeMessage)
141+ end
142+end)
143+UI.TextEdit(storage.autoTradeMessage or "I'm using OTClientV8!", function(widget, text)
144+ storage.autoTradeMessage = text
145+end)
146+
147+UI.Separator()
A RD_Client/modules/game_bot/edit.otui +254-0 View file
@@ -0,0 +1,254 @@
1+MainWindow
2+ id: editWindow
3+ !text: tr("Config editor & manager")
4+ @onEscape: self:hide()
5+ size: 550 570
6+ $mobile:
7+ size: 550 240
8+
9+ Panel
10+ id: manager
11+ anchors.top: parent.top
12+ anchors.left: parent.left
13+ anchors.right: parent.right
14+ height: 152
15+
16+ Label
17+ anchors.top: parent.top
18+ anchors.left: parent.left
19+ anchors.right: parent.right
20+ text-auto-resize: true
21+ text-align: center
22+ text-wrap: true
23+ !text: tr("Config Manager\nYou can use config manager to share configs between different machines, especially smartphones. After you configure your config, you can upload it, then you'll get unique hash code which you can use on diffent machinge (for eg. mobile phone) to download it.")
24+
25+ HorizontalSeparator
26+ anchors.top: prev.bottom
27+ anchors.left: parent.left
28+ anchors.right: parent.right
29+ margin-top: 3
30+ height: 2
31+
32+ Panel
33+ id: upload
34+ anchors.top: prev.bottom
35+ anchors.left: parent.left
36+ anchors.right: parent.horizontalCenter
37+ anchors.bottom: parent.bottom
38+ margin-top: 3
39+
40+ Label
41+ anchors.top: parent.top
42+ anchors.left: parent.left
43+ anchors.right: parent.right
44+ text-auto-resize: true
45+ text-align: center
46+ text-wrap: true
47+ !text: tr("Upload config")
48+
49+ Label
50+ anchors.top: prev.bottom
51+ anchors.left: parent.left
52+ anchors.right: parent.right
53+ margin-top: 7
54+ text-auto-resize: true
55+ text-align: center
56+ text-wrap: true
57+ !text: tr("Select config to upload")
58+
59+ ComboBox
60+ id: config
61+ anchors.top: prev.bottom
62+ anchors.left: parent.left
63+ anchors.right: parent.right
64+ margin-top: 4
65+ margin-left: 20
66+ margin-right: 20
67+ text-offset: 3 0
68+
69+ Button
70+ id: submit
71+ anchors.top: prev.bottom
72+ anchors.left: parent.left
73+ anchors.right: parent.right
74+ !text: tr('Upload config')
75+ margin-top: 4
76+ margin-left: 40
77+ margin-right: 40
78+ @onClick: modules.game_bot.uploadConfig()
79+
80+ Panel
81+ id: download
82+ anchors.top: prev.top
83+ anchors.left: parent.horizontalCenter
84+ anchors.right: parent.right
85+ anchors.bottom: parent.bottom
86+
87+ Label
88+ anchors.top: parent.top
89+ anchors.left: parent.left
90+ anchors.right: parent.right
91+ text-auto-resize: true
92+ text-align: center
93+ text-wrap: true
94+ !text: tr("Download config")
95+
96+ Label
97+ anchors.top: prev.bottom
98+ anchors.left: parent.left
99+ anchors.right: parent.right
100+ margin-top: 7
101+ text-auto-resize: true
102+ text-align: center
103+ text-wrap: true
104+ !text: tr("Enter config hash code")
105+
106+ TextEdit
107+ id: config
108+ anchors.top: prev.bottom
109+ anchors.left: parent.left
110+ anchors.right: parent.right
111+ margin-top: 4
112+ margin-left: 20
113+ margin-right: 20
114+
115+ Button
116+ id: submit
117+ anchors.top: prev.bottom
118+ anchors.left: parent.left
119+ anchors.right: parent.right
120+ !text: tr('Download config')
121+ margin-top: 4
122+ margin-left: 40
123+ margin-right: 40
124+ @onClick: modules.game_bot.downloadConfig()
125+
126+ HorizontalSeparator
127+ anchors.top: prev.bottom
128+ anchors.left: parent.left
129+ anchors.right: parent.right
130+ margin-top: 3
131+ height: 2
132+
133+ Panel
134+ anchors.top: prev.bottom
135+ anchors.left: parent.left
136+ anchors.right: parent.right
137+ margin-top: 5
138+ height: 330
139+ $mobile:
140+ visible: false
141+
142+ Label
143+ anchors.top: parent.top
144+ anchors.left: parent.left
145+ anchors.right: parent.right
146+ text-auto-resize: true
147+ text-align: center
148+ text-wrap: true
149+ !text: tr("Bot configs are stored in:")
150+
151+ TextEdit
152+ anchors.horizontalCenter: parent.horizontalCenter
153+ anchors.top: prev.bottom
154+ height: 20
155+ width: 400
156+ margin-top: 5
157+ editable: false
158+ !text: g_resources.getWriteDir() .. "bot"
159+ text-align: center
160+
161+ Button
162+ id: documentationButton
163+ !text: tr('Click here to open bot directory')
164+ anchors.horizontalCenter: parent.horizontalCenter
165+ anchors.top: prev.bottom
166+ margin-top: 5
167+ width: 250
168+ @onClick: g_platform.openDir(g_resources.getWriteDir() .. "bot")
169+
170+ Label
171+ margin-top: 5
172+ anchors.top: prev.bottom
173+ anchors.left: parent.left
174+ anchors.right: parent.right
175+ text-auto-resize: true
176+ text-align: center
177+ text-wrap: true
178+ !text: tr("Every directory in bot directory is treated as different config.\nTo create new config just create new directory.")
179+
180+ Label
181+ margin-top: 5
182+ anchors.top: prev.bottom
183+ anchors.horizontalCenter: parent.horizontalCenter
184+ height: 175
185+ image-source: configs.png
186+ image-fixed-ratio: true
187+ image-size: 500 175
188+
189+ Label
190+ margin-top: 3
191+ anchors.top: prev.bottom
192+ anchors.left: parent.left
193+ anchors.right: parent.right
194+ text-auto-resize: true
195+ text-align: center
196+ text-wrap: true
197+ !text: tr("Inside config directory put .lua and .otui files.\nEvery file will be loaded and executed in alphabetical order, .otui first and then .lua.")
198+
199+ Label
200+ margin-top: 3
201+ anchors.top: prev.bottom
202+ anchors.left: parent.left
203+ anchors.right: parent.right
204+ text-auto-resize: true
205+ text-align: center
206+ text-wrap: true
207+ !text: tr("To reload configs just press On and Off in bot window.\nTo learn more about bot click Tutorials button.")
208+
209+ Button
210+ !text: tr('Documentation')
211+ anchors.bottom: parent.bottom
212+ anchors.left: parent.left
213+ width: 118
214+ @onClick: g_platform.openUrl("http://otclient.ovh/bot.php?documentation")
215+
216+ Button
217+ !text: tr('Tutorials')
218+ anchors.bottom: parent.bottom
219+ anchors.left: prev.right
220+ margin-left: 5
221+ width: 80
222+ @onClick: g_platform.openUrl("http://otclient.ovh/bot.php?tutorials")
223+
224+ Button
225+ !text: tr('Scripts')
226+ anchors.bottom: parent.bottom
227+ anchors.left: prev.right
228+ margin-left: 5
229+ width: 80
230+ @onClick: g_platform.openUrl("http://otclient.ovh/bot.php?scripts")
231+
232+ Button
233+ !text: tr('Forum')
234+ anchors.bottom: parent.bottom
235+ anchors.left: prev.right
236+ margin-left: 5
237+ width: 80
238+ @onClick: g_platform.openUrl("http://otclient.ovh/bot.php?forum")
239+
240+ Button
241+ !text: tr('Discord')
242+ anchors.bottom: parent.bottom
243+ anchors.left: prev.right
244+ margin-left: 5
245+ width: 80
246+ @onClick: g_platform.openUrl("http://otclient.ovh/bot.php?discord")
247+
248+ Button
249+ id: cancelButton
250+ !text: tr('Close')
251+ anchors.bottom: parent.bottom
252+ anchors.right: parent.right
253+ width: 60
254+ @onClick: self:getParent():hide()
A RD_Client/modules/game_bot/executor.lua +336-0 View file
@@ -0,0 +1,336 @@
1+function executeBot(config, storage, tabs, msgCallback, saveConfigCallback, reloadCallback, websockets)
2+ -- load lua and otui files
3+ local configFiles = g_resources.listDirectoryFiles("/bot/" .. config, true, false)
4+ local luaFiles = {}
5+ local uiFiles = {}
6+ for i, file in ipairs(configFiles) do
7+ local ext = file:split(".")
8+ if ext[#ext]:lower() == "lua" then
9+ table.insert(luaFiles, file)
10+ end
11+ if ext[#ext]:lower() == "ui" or ext[#ext]:lower() == "otui" then
12+ table.insert(uiFiles, file)
13+ end
14+ end
15+
16+ if #luaFiles == 0 then
17+ return error("Config (/bot/" .. config .. ") doesn't have lua files")
18+ end
19+
20+ -- init bot variables
21+ local context = {}
22+ context.configDir = "/bot/".. config
23+ context.tabs = tabs
24+ context.mainTab = context.tabs:addTab("Main", g_ui.createWidget('BotPanel')).tabPanel.content
25+ context.panel = context.mainTab
26+ context.saveConfig = saveConfigCallback
27+ context.reload = reloadCallback
28+
29+ context.storage = storage
30+ if context.storage._macros == nil then
31+ context.storage._macros = {} -- active macros
32+ end
33+
34+ -- websockets, macros, hotkeys, scheduler, icons, callbacks
35+ context._websockets = websockets
36+ context._macros = {}
37+ context._hotkeys = {}
38+ context._scheduler = {}
39+ context._callbacks = {
40+ onKeyDown = {},
41+ onKeyUp = {},
42+ onKeyPress = {},
43+ onTalk = {},
44+ onTextMessage = {},
45+ onAddThing = {},
46+ onRemoveThing = {},
47+ onCreatureAppear = {},
48+ onCreatureDisappear = {},
49+ onCreaturePositionChange = {},
50+ onCreatureHealthPercentChange = {},
51+ onUse = {},
52+ onUseWith = {},
53+ onContainerOpen = {},
54+ onContainerClose = {},
55+ onContainerUpdateItem = {},
56+ onMissle = {},
57+ onAnimatedText = {},
58+ onStaticText = {},
59+ onChannelList = {},
60+ onOpenChannel = {},
61+ onCloseChannel = {},
62+ onChannelEvent = {},
63+ onTurn = {}
64+ }
65+
66+ -- basic functions & classes
67+ context.print = print
68+ context.pairs = pairs
69+ context.ipairs = ipairs
70+ context.tostring = tostring
71+ context.math = math
72+ context.table = table
73+ context.string = string
74+ context.tonumber = tonumber
75+ context.type = type
76+ context.pcall = pcall
77+ context.os = {
78+ time = os.time,
79+ date = os.date,
80+ difftime = os.difftime,
81+ date = os.date,
82+ clock = os.clock
83+ }
84+ context.load = function(str) return assert(load(str, nil, nil, context)) end
85+ context.loadstring = context.load
86+ context.assert = assert
87+ context.dofile = function(file) assert(load(g_resources.readFileContents("/bot/" .. config .. "/" .. file), file, nil, context))() end
88+ context.gcinfo = gcinfo
89+ context.tr = tr
90+ context.json = json
91+ context.base64 = base64
92+ context.regexMatch = regexMatch
93+ context.getDistanceBetween = function(p1, p2)
94+ return math.max(math.abs(p1.x - p2.x), math.abs(p1.y - p2.y))
95+ end
96+ context.isMobile = g_app.isMobile
97+ context.getVersion = g_app.getVersion
98+
99+ -- classes
100+ context.g_resources = g_resources
101+ context.g_game = g_game
102+ context.g_map = g_map
103+ context.g_ui = g_ui
104+ context.g_sounds = g_sounds
105+ context.g_window = g_window
106+ context.g_mouse = g_mouse
107+ context.g_things = g_things
108+ context.g_platform = {
109+ openUrl = g_platform.openUrl,
110+ openDir = g_platform.openDir,
111+ }
112+
113+ context.Item = Item
114+ context.Creature = Creature
115+ context.ThingType = ThingType
116+ context.Effect = Effect
117+ context.Missile = Missile
118+ context.Player = Player
119+ context.Monster = Monster
120+ context.StaticText = StaticText
121+ context.HTTP = HTTP
122+ context.OutputMessage = OutputMessage
123+ context.modules = modules
124+
125+ -- log functions
126+ context.info = function(text) return msgCallback("info", tostring(text)) end
127+ context.warn = function(text) return msgCallback("warn", tostring(text)) end
128+ context.error = function(text) return msgCallback("error", tostring(text)) end
129+ context.warning = context.warn
130+
131+ -- init context
132+ context.now = g_clock.millis()
133+ context.time = g_clock.millis()
134+ context.player = g_game.getLocalPlayer()
135+
136+ -- init functions
137+ G.botContext = context
138+ dofiles("functions")
139+ context.Panels = {}
140+ dofiles("panels")
141+ G.botContext = nil
142+
143+ -- run ui scripts
144+ for i, file in ipairs(uiFiles) do
145+ g_ui.importStyle(file)
146+ end
147+
148+ -- run lua script
149+ for i, file in ipairs(luaFiles) do
150+ assert(load(g_resources.readFileContents(file), file, nil, context))()
151+ context.panel = context.mainTab -- reset default tab
152+ end
153+
154+ return {
155+ script = function()
156+ context.now = g_clock.millis()
157+ context.time = g_clock.millis()
158+
159+ for i, macro in ipairs(context._macros) do
160+ if macro.lastExecution + macro.timeout <= context.now and macro.enabled then
161+ local status, result = pcall(function()
162+ if macro.callback(macro) then
163+ macro.lastExecution = context.now
164+ end
165+ end)
166+ if not status then
167+ context.error("Macro: " .. macro.name .. " execution error: " .. result)
168+ end
169+ end
170+ end
171+
172+ while #context._scheduler > 0 and context._scheduler[1].execution <= g_clock.millis() do
173+ local status, result = pcall(function()
174+ context._scheduler[1].callback()
175+ end)
176+ if not status then
177+ context.error("Schedule execution error: " .. result)
178+ end
179+ table.remove(context._scheduler, 1)
180+ end
181+ end,
182+ callbacks = {
183+ onKeyDown = function(keyCode, keyboardModifiers)
184+ local keyDesc = determineKeyComboDesc(keyCode, keyboardModifiers)
185+ for i, macro in ipairs(context._macros) do
186+ if macro.switch and macro.hotkey == keyDesc then
187+ macro.switch:onClick()
188+ end
189+ end
190+ local hotkey = context._hotkeys[keyDesc]
191+ if hotkey then
192+ if hotkey.single then
193+ if hotkey.callback() then
194+ hotkey.lastExecution = context.now
195+ end
196+ end
197+ if hotkey.switch then
198+ hotkey.switch:setOn(true)
199+ end
200+ end
201+ for i, callback in ipairs(context._callbacks.onKeyDown) do
202+ callback(keyDesc)
203+ end
204+ end,
205+ onKeyUp = function(keyCode, keyboardModifiers)
206+ local keyDesc = determineKeyComboDesc(keyCode, keyboardModifiers)
207+ local hotkey = context._hotkeys[keyDesc]
208+ if hotkey then
209+ if hotkey.switch then
210+ hotkey.switch:setOn(false)
211+ end
212+ end
213+ for i, callback in ipairs(context._callbacks.onKeyUp) do
214+ callback(keyDesc)
215+ end
216+ end,
217+ onKeyPress = function(keyCode, keyboardModifiers, autoRepeatTicks)
218+ local keyDesc = determineKeyComboDesc(keyCode, keyboardModifiers)
219+ local hotkey = context._hotkeys[keyDesc]
220+ if hotkey and not hotkey.single then
221+ if hotkey.callback() then
222+ hotkey.lastExecution = context.now
223+ end
224+ end
225+ for i, callback in ipairs(context._callbacks.onKeyPress) do
226+ callback(keyDesc, autoRepeatTicks)
227+ end
228+ end,
229+ onTalk = function(name, level, mode, text, channelId, pos)
230+ for i, callback in ipairs(context._callbacks.onTalk) do
231+ callback(name, level, mode, text, channelId, pos)
232+ end
233+ end,
234+ onTextMessage = function(mode, text)
235+ for i, callback in ipairs(context._callbacks.onTextMessage) do
236+ callback(mode, text)
237+ end
238+ end,
239+ onAddThing = function(tile, thing)
240+ for i, callback in ipairs(context._callbacks.onAddThing) do
241+ callback(tile, thing)
242+ end
243+ end,
244+ onRemoveThing = function(tile, thing)
245+ for i, callback in ipairs(context._callbacks.onRemoveThing) do
246+ callback(tile, thing)
247+ end
248+ end,
249+ onCreatureAppear = function(creature)
250+ for i, callback in ipairs(context._callbacks.onCreatureAppear) do
251+ callback(creature)
252+ end
253+ end,
254+ onCreatureDisappear = function(creature)
255+ for i, callback in ipairs(context._callbacks.onCreatureDisappear) do
256+ callback(creature)
257+ end
258+ end,
259+ onCreaturePositionChange = function(creature, newPos, oldPos)
260+ for i, callback in ipairs(context._callbacks.onCreaturePositionChange) do
261+ callback(creature, newPos, oldPos)
262+ end
263+ end,
264+ onCreatureHealthPercentChange = function(creature, healthPercent)
265+ for i, callback in ipairs(context._callbacks.onCreatureHealthPercentChange) do
266+ callback(creature, healthPercent)
267+ end
268+ end,
269+ onUse = function(pos, itemId, stackPos, subType)
270+ for i, callback in ipairs(context._callbacks.onUse) do
271+ callback(pos, itemId, stackPos, subType)
272+ end
273+ end,
274+ onUseWith = function(pos, itemId, target, subType)
275+ for i, callback in ipairs(context._callbacks.onUseWith) do
276+ callback(pos, itemId, target, subType)
277+ end
278+ end,
279+ onContainerOpen = function(container, previousContainer)
280+ for i, callback in ipairs(context._callbacks.onContainerOpen) do
281+ callback(container, previousContainer)
282+ end
283+ end,
284+ onContainerClose = function(container)
285+ for i, callback in ipairs(context._callbacks.onContainerClose) do
286+ callback(container)
287+ end
288+ end,
289+ onContainerUpdateItem = function(container, slot, item)
290+ for i, callback in ipairs(context._callbacks.onContainerUpdateItem) do
291+ callback(container, slot, item)
292+ end
293+ end,
294+ onMissle = function(missle)
295+ for i, callback in ipairs(context._callbacks.onMissle) do
296+ callback(missle)
297+ end
298+ end,
299+ onAnimatedText = function(thing, text)
300+ for i, callback in ipairs(context._callbacks.onAnimatedText) do
301+ callback(thing, text)
302+ end
303+ end,
304+ onStaticText = function(thing, text)
305+ for i, callback in ipairs(context._callbacks.onStaticText) do
306+ callback(thing, text)
307+ end
308+ end,
309+ onChannelList = function(channels)
310+ for i, callback in ipairs(context._callbacks.onChannelList) do
311+ callback(channels)
312+ end
313+ end,
314+ onOpenChannel = function(channelId, channelName)
315+ for i, callback in ipairs(context._callbacks.onOpenChannel) do
316+ callback(channels)
317+ end
318+ end,
319+ onCloseChannel = function(channelId)
320+ for i, callback in ipairs(context._callbacks.onCloseChannel) do
321+ callback(channelId)
322+ end
323+ end,
324+ onChannelEvent = function(channelId, name, event)
325+ for i, callback in ipairs(context._callbacks.onChannelEvent) do
326+ callback(channelId, name, event)
327+ end
328+ end,
329+ onTurn = function(creature, direction)
330+ for i, callback in ipairs(context._callbacks.onTurn) do
331+ callback(creature, direction)
332+ end
333+ end,
334+ }
335+ }
336+end
A RD_Client/modules/game_bot/functions/callbacks.lua +198-0 View file
@@ -0,0 +1,198 @@
1+local context = G.botContext
2+
3+-- callback(callbackType, callback)
4+context.callback = function(callbackType, callback)
5+ if not context._callbacks[callbackType] then
6+ return error("Wrong callback type: " .. callbackType)
7+ end
8+ if callbackType == "onAddThing" or callbackType == "onRemoveThing" then
9+ g_game.enableTileThingLuaCallback(true)
10+ end
11+
12+ local desc = "lua"
13+ local info = debug.getinfo(2, "Sl")
14+ if info then
15+ desc = info.short_src .. ":" .. info.currentline
16+ end
17+
18+ local callbackData = {}
19+ table.insert(context._callbacks[callbackType], function(...)
20+ if not callbackData.delay or callbackData.delay < context.now then
21+ local prevExecution = context._currentExecution
22+ context._currentExecution = callbackData
23+ local start = g_clock.realMillis()
24+ callback(...)
25+ local executionTime = g_clock.realMillis() - start
26+ if executionTime > 100 then
27+ context.warning("Slow " .. callbackType .. " (" .. executionTime .. "ms): " .. desc)
28+ end
29+ context._currentExecution = prevExecution
30+ end
31+ end)
32+ local cb = context._callbacks[callbackType]
33+ return {
34+ remove = function()
35+ local index = nil
36+ for i, cb2 in ipairs(context._callbacks[callbackType]) do
37+ if cb == cb2 then
38+ index = i
39+ end
40+ end
41+ if index then
42+ table.remove(context._callbacks[callbackType], index)
43+ end
44+ end
45+ }
46+end
47+
48+-- onKeyDown(callback) -- callback = function(keys)
49+context.onKeyDown = function(callback)
50+ return context.callback("onKeyDown", callback)
51+end
52+
53+-- onKeyPress(callback) -- callback = function(keys)
54+context.onKeyPress = function(callback)
55+ return context.callback("onKeyPress", callback)
56+end
57+
58+-- onKeyUp(callback) -- callback = function(keys)
59+context.onKeyUp = function(callback)
60+ return context.callback("onKeyUp", callback)
61+end
62+
63+-- onTalk(callback) -- callback = function(name, level, mode, text, channelId, pos)
64+context.onTalk = function(callback)
65+ return context.callback("onTalk", callback)
66+end
67+
68+-- onTextMessage(callback) -- callback = function(mode, text)
69+context.onTextMessage = function(callback)
70+ return context.callback("onTextMessage", callback)
71+end
72+
73+-- onAddThing(callback) -- callback = function(tile, thing)
74+context.onAddThing = function(callback)
75+ return context.callback("onAddThing", callback)
76+end
77+
78+-- onRemoveThing(callback) -- callback = function(tile, thing)
79+context.onRemoveThing = function(callback)
80+ return context.callback("onRemoveThing", callback)
81+end
82+
83+-- onCreatureAppear(callback) -- callback = function(creature)
84+context.onCreatureAppear = function(callback)
85+ return context.callback("onCreatureAppear", callback)
86+end
87+
88+-- onCreatureDisappear(callback) -- callback = function(creature)
89+context.onCreatureDisappear = function(callback)
90+ return context.callback("onCreatureDisappear", callback)
91+end
92+
93+-- onCreaturePositionChange(callback) -- callback = function(creature, newPos, oldPos)
94+context.onCreaturePositionChange = function(callback)
95+ return context.callback("onCreaturePositionChange", callback)
96+end
97+
98+-- onCreatureHealthPercentChange(callback) -- callback = function(creature, healthPercent)
99+context.onCreatureHealthPercentChange = function(callback)
100+ return context.callback("onCreatureHealthPercentChange", callback)
101+end
102+
103+-- onUse(callback) -- callback = function(pos, itemId, stackPos, subType)
104+context.onUse = function(callback)
105+ return context.callback("onUse", callback)
106+end
107+
108+-- onUseWith(callback) -- callback = function(pos, itemId, target, subType)
109+context.onUseWith = function(callback)
110+ return context.callback("onUseWith", callback)
111+end
112+
113+-- onContainerOpen -- callback = function(container, previousContainer)
114+context.onContainerOpen = function(callback)
115+ return context.callback("onContainerOpen", callback)
116+end
117+
118+-- onContainerClose -- callback = function(container)
119+context.onContainerClose = function(callback)
120+ return context.callback("onContainerClose", callback)
121+end
122+
123+-- onContainerUpdateItem -- callback = function(container, slot, item)
124+context.onContainerUpdateItem = function(callback)
125+ return context.callback("onContainerUpdateItem", callback)
126+end
127+
128+-- onMissle -- callback = function(missle)
129+context.onMissle = function(callback)
130+ return context.callback("onMissle", callback)
131+end
132+
133+-- onAnimatedText -- callback = function(thing, text)
134+context.onAnimatedText = function(callback)
135+ return context.callback("onAnimatedText", callback)
136+end
137+
138+-- onStaticText -- callback = function(thing, text)
139+context.onStaticText = function(callback)
140+ return context.callback("onStaticText", callback)
141+end
142+
143+-- onChannelList -- callback = function(channels)
144+context.onChannelList = function(callback)
145+ return context.callback("onChannelList", callback)
146+end
147+
148+-- onOpenChannel -- callback = function(channelId, name)
149+context.onOpenChannel = function(callback)
150+ return context.callback("onOpenChannel", callback)
151+end
152+
153+-- onCloseChannel -- callback = function(channelId)
154+context.onCloseChannel = function(callback)
155+ return context.callback("onCloseChannel", callback)
156+end
157+
158+-- onChannelEvent -- callback = function(channelId, name, event)
159+context.onChannelEvent = function(callback)
160+ return context.callback("onChannelEvent", callback)
161+end
162+
163+-- onTurn -- callback = function(creature, direction)
164+context.onTurn = function(callback)
165+ return context.callback("onTurn", callback)
166+end
167+
168+
169+-- CUSTOM CALLBACKS
170+
171+-- listen(name, callback) -- callback = function(text, channelId, pos)
172+context.listen = function(name, callback)
173+ if not name then return context.error("listen: invalid name") end
174+ name = name:lower()
175+ return context.onTalk(function(name2, level, mode, text, channelId, pos)
176+ if name == name2:lower() then
177+ callback(text, channelId, pos)
178+ end
179+ end)
180+end
181+
182+-- onPlayerPositionChange(callback) -- callback = function(newPos, oldPos)
183+context.onPlayerPositionChange = function(callback)
184+ return context.onCreaturePositionChange(function(creature, newPos, oldPos)
185+ if creature == context.player then
186+ callback(newPos, oldPos)
187+ end
188+ end)
189+end
190+
191+-- onPlayerHealthChange(callback) -- callback = function(healthPercent)
192+context.onPlayerHealthChange = function(callback)
193+ return context.onCreatureHealthPercentChange(function(creature, healthPercent)
194+ if creature == context.player then
195+ callback(healthPercent)
196+ end
197+ end)
198+end
A RD_Client/modules/game_bot/functions/const.lua +25-0 View file
@@ -0,0 +1,25 @@
1+local context = G.botContext
2+
3+context.North = 0
4+context.East = 1
5+context.South = 2
6+context.West = 3
7+context.NorthEast = 4
8+context.SouthEast = 5
9+context.SouthWest = 6
10+context.NorthWest = 7
11+
12+context.InventorySlotOther = 0
13+context.InventorySlotHead = 1
14+context.InventorySlotNeck = 2
15+context.InventorySlotBack = 3
16+context.InventorySlotBody = 4
17+context.InventorySlotRight = 5
18+context.InventorySlotLeft = 6
19+context.InventorySlotLeg = 7
20+context.InventorySlotFeet = 8
21+context.InventorySlotFinger = 9
22+context.InventorySlotAmmo = 10
23+context.InventorySlotPurse = 11
24+context.InventorySlotFirst = 1
25+context.InventorySlotLast = 10
A RD_Client/modules/game_bot/functions/icon.lua +176-0 View file
@@ -0,0 +1,176 @@
1+local context = G.botContext
2+
3+local iconsWithoutPosition = 0
4+
5+context.addIcon = function(id, options, callback)
6+--[[
7+ Available options:
8+ item: {id=2160, count=100}
9+ outfit: outfit table ({})
10+ text: string
11+ x: float (0.0 - 1.0)
12+ y: float (0.0 - 1.0)
13+ hotkey: string
14+ switchable: true / false [default: true]
15+ movable: true / false [default: true]
16+ phantom: true / false [defaule: false]
17+]]--
18+ local panel = modules.game_interface.gameMapPanel
19+ if type(id) ~= "string" or id:len() < 1 then
20+ return context.error("Invalid id for addIcon")
21+ end
22+ if options.switchable == false and type(callback) ~= 'function' then
23+ return context.error("Invalid callback for addIcon")
24+ end
25+ if type(context.storage._icons) ~= "table" then
26+ context.storage._icons = {}
27+ end
28+ if type(context.storage._icons[id]) ~= "table" then
29+ context.storage._icons[id] = {}
30+ end
31+ local config = context.storage._icons[id]
32+ local widget = g_ui.createWidget("BotIcon", panel)
33+ widget.botWidget = true
34+ widget.botIcon = true
35+
36+ if type(config.x) ~= 'number' and type(config.y) ~= 'number' then
37+ if type(options.x) == 'number' and type(options.y) == 'number' then
38+ config.x = math.min(1.0, math.max(0.0, options.x))
39+ config.y = math.min(1.0, math.max(0.0, options.y))
40+ else
41+ config.x = 0.01 + math.floor(iconsWithoutPosition / 5) / 10
42+ config.y = 0.05 + (iconsWithoutPosition % 5) / 5
43+ iconsWithoutPosition = iconsWithoutPosition + 1
44+ end
45+ end
46+
47+ if options.item then
48+ if type(options.item) == 'number' then
49+ widget.item:setItemId(options.item)
50+ else
51+ widget.item:setItemId(options.item.id)
52+ widget.item:setItemCount(options.item.count or 1)
53+ widget.item:setShowCount(false)
54+ end
55+ end
56+
57+ if options.outfit then
58+ widget.creature:setOutfit(options.outfit)
59+ end
60+
61+ if options.switchable == false then
62+ widget.status:hide()
63+ widget.status:setOn(true)
64+ else
65+ if config.enabled ~= true then
66+ config.enabled = false
67+ end
68+ widget.status:setOn(config.enabled)
69+ end
70+
71+ if options.text then
72+ if options.switchable ~= false then
73+ widget.status:hide()
74+ if widget.status:isOn() then
75+ widget.text:setColor('green')
76+ else
77+ widget.text:setColor('red')
78+ end
79+ end
80+ widget.text:setText(options.text)
81+ end
82+
83+ widget.setOn = function(val)
84+ widget.status:setOn(val)
85+ if widget.status:isOn() then
86+ widget.text:setColor('green')
87+ else
88+ widget.text:setColor('red')
89+ end
90+ config.enabled = widget.status:isOn()
91+ end
92+
93+ widget.onClick = function(widget)
94+ if options.switchable ~= false then
95+ widget.setOn(not widget.status:isOn())
96+ if type(callback) == 'table' then
97+ callback.setOn(config.enabled)
98+ return
99+ end
100+ end
101+
102+ callback(widget, widget.status:isOn())
103+ end
104+
105+ if options.hotkey then
106+ widget.hotkey:setText(options.hotkey)
107+ context.hotkey(options.hotkey, "", function()
108+ widget:onClick()
109+ end, nil, options.switchable ~= false)
110+ else
111+ widget.hotkey:hide()
112+ end
113+
114+ if options.movable ~= false then
115+ widget.onDragEnter = function(widget, mousePos)
116+ if not g_keyboard.isCtrlPressed() then
117+ return false
118+ end
119+ widget:breakAnchors()
120+ widget.movingReference = { x = mousePos.x - widget:getX(), y = mousePos.y - widget:getY() }
121+ return true
122+ end
123+
124+ widget.onDragMove = function(widget, mousePos, moved)
125+ local parentRect = widget:getParent():getRect()
126+ local x = math.min(math.max(parentRect.x, mousePos.x - widget.movingReference.x), parentRect.x + parentRect.width - widget:getWidth())
127+ local y = math.min(math.max(parentRect.y - widget:getParent():getMarginTop(), mousePos.y - widget.movingReference.y), parentRect.y + parentRect.height - widget:getHeight())
128+ widget:move(x, y)
129+ return true
130+ end
131+
132+ widget.onDragLeave = function(widget, pos)
133+ local parent = widget:getParent()
134+ local parentRect = parent:getRect()
135+ local x = widget:getX() - parentRect.x
136+ local y = widget:getY() - parentRect.y
137+ local width = parentRect.width - widget:getWidth()
138+ local height = parentRect.height - widget:getHeight()
139+
140+ config.x = math.min(1, math.max(0, x / width))
141+ config.y = math.min(1, math.max(0, y / height))
142+
143+ widget:addAnchor(AnchorHorizontalCenter, 'parent', AnchorHorizontalCenter)
144+ widget:addAnchor(AnchorVerticalCenter, 'parent', AnchorVerticalCenter)
145+ widget:setMarginTop(math.max(height * (-0.5) - parent:getMarginTop(), height * (-0.5 + config.y)))
146+ widget:setMarginLeft(width * (-0.5 + config.x))
147+ return true
148+ end
149+ end
150+
151+ widget.onGeometryChange = function(widget)
152+ if widget:isDragging() then return end
153+ local parent = widget:getParent()
154+ local parentRect = parent:getRect()
155+ local width = parentRect.width - widget:getWidth()
156+ local height = parentRect.height - widget:getHeight()
157+ widget:setMarginTop(math.max(height * (-0.5) - parent:getMarginTop(), height * (-0.5 + config.y)))
158+ widget:setMarginLeft(width * (-0.5 + config.x))
159+ end
160+
161+ if options.phantom ~= true then
162+ widget.onMouseRelease = function()
163+ return true
164+ end
165+ end
166+
167+ if options.switchable ~= false then
168+ if type(callback) == 'table' then
169+ callback.setOn(config.enabled)
170+ callback.icon = widget
171+ else
172+ callback(widget, widget.status:isOn())
173+ end
174+ end
175+ return widget
176+end
A RD_Client/modules/game_bot/functions/main.lua +211-0 View file
@@ -0,0 +1,211 @@
1+local context = G.botContext
2+
3+-- MAIN BOT FUNCTION
4+-- macro(timeout, callback)
5+-- macro(timeout, name, callback)
6+-- macro(timeout, name, callback, parent)
7+-- macro(timeout, name, hotkey, callback)
8+-- macro(timeout, name, hotkey, callback, parent)
9+context.macro = function(timeout, name, hotkey, callback, parent)
10+ if type(timeout) ~= 'number' or timeout < 1 then
11+ error("Invalid timeout for macro: " .. tostring(timeout))
12+ end
13+ if type(name) == 'function' then
14+ callback = name
15+ name = ""
16+ hotkey = ""
17+ elseif type(hotkey) == 'function' then
18+ parent = callback
19+ callback = hotkey
20+ hotkey = ""
21+ elseif type(callback) ~= 'function' then
22+ error("Invalid callback for macro: " .. tostring(callback))
23+ end
24+ if hotkey == nil then
25+ hotkey = ""
26+ end
27+ if type(name) ~= 'string' or type(hotkey) ~= 'string' then
28+ error("Invalid name or hotkey for macro")
29+ end
30+ if not parent then
31+ parent = context.panel
32+ end
33+ if hotkey:len() > 0 then
34+ hotkey = retranslateKeyComboDesc(hotkey)
35+ end
36+
37+ -- min timeout is 50, to avoid lags
38+ if timeout < 50 then
39+ timeout = 50
40+ end
41+
42+ table.insert(context._macros, {
43+ enabled = false,
44+ name = name,
45+ timeout = timeout,
46+ lastExecution = context.now + math.random(0, 100),
47+ hotkey = hotkey,
48+ })
49+ local macro = context._macros[#context._macros]
50+
51+ macro.isOn = function()
52+ return macro.enabled
53+ end
54+ macro.isOff = function()
55+ return not macro.enabled
56+ end
57+ macro.toggle = function(widget)
58+ if macro.isOn() then
59+ macro.setOff()
60+ else
61+ macro.setOn()
62+ end
63+ end
64+ macro.setOn = function(val)
65+ if val == false then
66+ return macro.setOff()
67+ end
68+ macro.enabled = true
69+ context.storage._macros[name] = true
70+ if macro.switch then
71+ macro.switch:setOn(true)
72+ end
73+ if macro.icon then
74+ macro.icon.setOn(true)
75+ end
76+ end
77+ macro.setOff = function(val)
78+ if val == false then
79+ return macro.setOn()
80+ end
81+ macro.enabled = false
82+ context.storage._macros[name] = false
83+ if macro.switch then
84+ macro.switch:setOn(false)
85+ end
86+ if macro.icon then
87+ macro.icon.setOn(false)
88+ end
89+ end
90+
91+ if name:len() > 0 then
92+ -- creature switch
93+ local text = name
94+ if hotkey:len() > 0 then
95+ text = name .. " [" .. hotkey .. "]"
96+ end
97+ macro.switch = context.addSwitch("macro_" .. (#context._macros + 1), text, macro.toggle, parent)
98+
99+ -- load state
100+ if context.storage._macros[name] == true then
101+ macro.setOn()
102+ end
103+ else
104+ macro.enabled = true -- unnamed macros are enabled by default
105+ end
106+
107+ local desc = "lua"
108+ local info = debug.getinfo(2, "Sl")
109+ if info then
110+ desc = info.short_src .. ":" .. info.currentline
111+ end
112+
113+ macro.callback = function(macro)
114+ if not macro.delay or macro.delay < context.now then
115+ context._currentExecution = macro
116+ local start = g_clock.realMillis()
117+ callback(macro)
118+ local executionTime = g_clock.realMillis() - start
119+ if executionTime > 100 then
120+ context.warning("Slow macro (" .. executionTime .. "ms): " .. macro.name .. " - " .. desc)
121+ end
122+ context._currentExecution = nil
123+ return true
124+ end
125+ end
126+ return macro
127+end
128+
129+-- hotkey(keys, callback)
130+-- hotkey(keys, name, callback)
131+-- hotkey(keys, name, callback, parent)
132+context.hotkey = function(keys, name, callback, parent, single)
133+ if type(name) == 'function' then
134+ callback = name
135+ name = ""
136+ end
137+ if not parent then
138+ parent = context.panel
139+ end
140+ keys = retranslateKeyComboDesc(keys)
141+ if not keys or #keys == 0 then
142+ return context.error("Invalid hotkey keys " .. tostring(name))
143+ end
144+ if context._hotkeys[keys] then
145+ return context.error("Duplicated hotkey: " .. keys)
146+ end
147+
148+ local switch = nil
149+ if name:len() > 0 then
150+ switch = context._addHotkeySwitch(name, keys, parent)
151+ end
152+
153+ context._hotkeys[keys] = {
154+ name = name,
155+ lastExecution = context.now,
156+ switch = switch,
157+ single = single
158+ }
159+
160+ local desc = "lua"
161+ local info = debug.getinfo(2, "Sl")
162+ if info then
163+ desc = info.short_src .. ":" .. info.currentline
164+ end
165+
166+ local hotkeyData = context._hotkeys[keys]
167+ hotkeyData.callback = function()
168+ if not hotkeyData.delay or hotkeyData.delay < context.now then
169+ context._currentExecution = hotkeyData
170+ local start = g_clock.realMillis()
171+ callback()
172+ local executionTime = g_clock.realMillis() - start
173+ if executionTime > 100 then
174+ context.warning("Slow hotkey (" .. executionTime .. "ms): " .. hotkeyData.name .. " - " .. desc)
175+ end
176+ context._currentExecution = nil
177+ return true
178+ end
179+ end
180+
181+ return hotkeyData
182+end
183+
184+-- singlehotkey(keys, callback)
185+-- singlehotkey(keys, name, callback)
186+-- singlehotkey(keys, name, callback, parent)
187+context.singlehotkey = function(keys, name, callback, parent)
188+ if type(name) == 'function' then
189+ callback = name
190+ name = ""
191+ end
192+ return context.hotkey(keys, name, callback, parent, true)
193+end
194+
195+-- schedule(timeout, callback)
196+context.schedule = function(timeout, callback)
197+ local extecute_time = g_clock.millis() + timeout
198+ table.insert(context._scheduler, {
199+ execution = extecute_time,
200+ callback = callback
201+ })
202+ table.sort(context._scheduler, function(a, b) return a.execution < b.execution end)
203+end
204+
205+-- delay(duration) -- block execution of current macro/hotkey/callback for x milliseconds
206+context.delay = function(duration)
207+ if not context._currentExecution then
208+ return context.error("Invalid usage of delay function, it should be used inside callbacks")
209+ end
210+ context._currentExecution.delay = context.now + duration
211+end
A RD_Client/modules/game_bot/functions/map.lua +241-0 View file
@@ -0,0 +1,241 @@
1+local context = G.botContext
2+
3+context.getMapView = function() return modules.game_interface.getMapPanel() end
4+context.getMapPanel = context.getMapView
5+context.zoomIn = function() modules.game_interface.getMapPanel():zoomIn() end
6+context.zoomOut = function() modules.game_interface.getMapPanel():zoomOut() end
7+
8+context.getSpectators = function(param1, param2)
9+--[[
10+ if param1 is table (position) then it's used for central position, then param2 is used as param1
11+ if param1 is creature, then creature position and direction of creature is used, then param2 is used as param1
12+ if param1 is true/false then it's used for multifloor, example: getSpectators(true)
13+ if param1 is string then it's used for getSpectatorsByPattern
14+]]--
15+ local pos = context.player:getPosition()
16+ local direction = context.player:getDirection()
17+ if type(param1) == 'table' then
18+ pos = param1
19+ direction = 8 -- invalid direction
20+ param1 = param2
21+ end
22+ if type(param1) == 'userdata' then
23+ pos = param1:getPosition()
24+ direction = param1:getDirection()
25+ param1 = param2
26+ end
27+
28+ if type(param1) == 'string' then
29+ return g_map.getSpectatorsByPattern(pos, param1, direction)
30+ end
31+
32+ local multifloor = false
33+ if type(param1) == 'boolean' and param1 == true then
34+ multifloor = true
35+ end
36+ return g_map.getSpectators(pos, multifloor)
37+end
38+
39+context.getCreatureById = function(id, multifloor)
40+ if type(id) ~= 'number' then return nil end
41+ if multifloor ~= true then
42+ multifloor = false
43+ end
44+ for i, spec in ipairs(g_map.getSpectators(context.player:getPosition(), multifloor)) do
45+ if spec:getId() == id then
46+ return spec
47+ end
48+ end
49+ return nil
50+end
51+
52+context.getCreatureByName = function(name, multifloor)
53+ if not name then return nil end
54+ name = name:lower()
55+ if multifloor ~= true then
56+ multifloor = false
57+ end
58+ for i, spec in ipairs(g_map.getSpectators(context.player:getPosition(), multifloor)) do
59+ if spec:getName():lower() == name then
60+ return spec
61+ end
62+ end
63+ return nil
64+end
65+
66+context.getPlayerByName = function(name, multifloor)
67+ if not name then return nil end
68+ name = name:lower()
69+ if multifloor ~= true then
70+ multifloor = false
71+ end
72+ for i, spec in ipairs(g_map.getSpectators(context.player:getPosition(), multifloor)) do
73+ if spec:isPlayer() and spec:getName():lower() == name then
74+ return spec
75+ end
76+ end
77+ return nil
78+end
79+
80+context.findAllPaths = function(start, maxDist, params)
81+ --[[
82+ Available params:
83+ ignoreLastCreature
84+ ignoreCreatures
85+ ignoreNonPathable
86+ ignoreNonWalkable
87+ ignoreStairs
88+ ignoreCost
89+ allowUnseen
90+ allowOnlyVisibleTiles
91+ ]]--
92+ if type(params) ~= 'table' then
93+ params = {}
94+ end
95+ for key, value in pairs(params) do
96+ if value == nil or value == false then
97+ params[key] = 0
98+ elseif value == true then
99+ params[key] = 1
100+ end
101+ end
102+ return g_map.findEveryPath(start, maxDist, params)
103+end
104+context.findEveryPath = context.findAllPaths
105+
106+context.translateAllPathsToPath = function(paths, destPos)
107+ local predirections = {}
108+ local directions = {}
109+ local destPosStr = destPos
110+ if type(destPos) ~= 'string' then
111+ destPosStr = destPos.x .. "," .. destPos.y .. "," .. destPos.z
112+ end
113+
114+ while destPosStr:len() > 0 do
115+ local node = paths[destPosStr]
116+ if not node then
117+ break
118+ end
119+ if node[3] < 0 then
120+ break
121+ end
122+ table.insert(predirections, node[3])
123+ destPosStr = node[4]
124+ end
125+ -- reverse
126+ for i=#predirections,1,-1 do
127+ table.insert(directions, predirections[i])
128+ end
129+ return directions
130+end
131+context.translateEveryPathToPath = context.translateAllPathsToPath
132+
133+
134+context.findPath = function(startPos, destPos, maxDist, params)
135+ --[[
136+ Available params:
137+ ignoreLastCreature
138+ ignoreCreatures
139+ ignoreNonPathable
140+ ignoreNonWalkable
141+ ignoreStairs
142+ ignoreCost
143+ allowUnseen
144+ allowOnlyVisibleTiles
145+ precision
146+ marginMin
147+ marginMax
148+ ]]--
149+ if not destPos or startPos.z ~= destPos.z then
150+ return
151+ end
152+ if type(maxDist) ~= 'number' then
153+ maxDist = 100
154+ end
155+ if type(params) ~= 'table' then
156+ params = {}
157+ end
158+ local destPosStr = destPos.x .. "," .. destPos.y .. "," .. destPos.z
159+ params["destination"] = destPosStr
160+ local paths = context.findAllPaths(startPos, maxDist, params)
161+ local marginMin = params.marginMin or params.minMargin
162+ local marginMax = params.marginMax or params.maxMargin
163+ if type(marginMin) == 'number' and type(marginMax) == 'number' then
164+ local bestCandidate = nil
165+ local bestCandidatePos = nil
166+ for x = -marginMax, marginMax do
167+ for y = -marginMax, marginMax do
168+ if math.abs(x) >= marginMin or math.abs(y) >= marginMin then
169+ local dest = (destPos.x + x) .. "," .. (destPos.y + y) .. "," .. destPos.z
170+ local node = paths[dest]
171+ if node and (not bestCandidate or bestCandidate[1] > node[1]) then
172+ bestCandidate = node
173+ bestCandidatePos = dest
174+ end
175+ end
176+ end
177+ end
178+ if bestCandidate then
179+ return context.translateAllPathsToPath(paths, bestCandidatePos)
180+ end
181+ return
182+ end
183+
184+ if not paths[destPosStr] then
185+ local precision = params.precision
186+ if type(precision) == 'number' then
187+ for p = 1, precision do
188+ local bestCandidate = nil
189+ local bestCandidatePos = nil
190+ for x = -p, p do
191+ for y = -p, p do
192+ local dest = (destPos.x + x) .. "," .. (destPos.y + y) .. "," .. destPos.z
193+ local node = paths[dest]
194+ if node and (not bestCandidate or bestCandidate[1] > node[1]) then
195+ bestCandidate = node
196+ bestCandidatePos = dest
197+ end
198+ end
199+ end
200+ if bestCandidate then
201+ return context.translateAllPathsToPath(paths, bestCandidatePos)
202+ end
203+ end
204+ end
205+ return nil
206+ end
207+
208+ return context.translateAllPathsToPath(paths, destPos)
209+end
210+context.getPath = context.findPath
211+
212+-- also works as autoWalk(dirs) where dirs is a list eg.: {1,2,3,0,1,1,2,}
213+context.autoWalk = function(destination, maxDist, params)
214+ if type(destination) == "table" and table.isList(destination) and not maxDist and not params then
215+ g_game.autoWalk(destination, {x=0,y=0,z=0})
216+ return true
217+ end
218+
219+ -- Available params same as for findPath
220+ local path = context.findPath(context.player:getPosition(), destination, maxDist, params)
221+ if not path then
222+ return false
223+ end
224+ -- autowalk without prewalk animation
225+ g_game.autoWalk(path, {x=0,y=0,z=0})
226+ return true
227+end
228+
229+context.getTileUnderCursor = function()
230+ if not modules.game_interface.gameMapPanel.mousePos then return end
231+ return modules.game_interface.gameMapPanel:getTile(modules.game_interface.gameMapPanel.mousePos)
232+end
233+
234+context.canShoot = function(pos, distance)
235+ if not distance then distance = 5 end
236+ local tile = g_map.getTile(pos, distance)
237+ if tile then
238+ return tile:canShoot(distance)
239+ end
240+ return false
241+end
A RD_Client/modules/game_bot/functions/npc.lua +130-0 View file
@@ -0,0 +1,130 @@
1+local context = G.botContext
2+
3+context.NPC = {}
4+
5+context.NPC.talk = function(text)
6+ if g_game.getClientVersion() >= 810 then
7+ g_game.talkChannel(11, 0, text)
8+ else
9+ return context.say(text)
10+ end
11+end
12+context.NPC.say = context.NPC.talk
13+
14+context.NPC.isTrading = function()
15+ return modules.game_npctrade.npcWindow and modules.game_npctrade.npcWindow:isVisible()
16+end
17+context.NPC.hasTrade = context.NPC.isTrading
18+context.NPC.hasTradeWindow = context.NPC.isTrading
19+context.NPC.isTradeOpen = context.NPC.isTrading
20+
21+context.NPC.getSellItems = function()
22+ if not context.NPC.isTrading() then return {} end
23+ local items = {}
24+ for i, item in ipairs(modules.game_npctrade.tradeItems[modules.game_npctrade.SELL]) do
25+ table.insert(items, {
26+ item = item.ptr,
27+ id = item.ptr:getId(),
28+ count = item.ptr:getCount(),
29+ name = item.name,
30+ subType = item.ptr:getSubType(),
31+ weight = item.weight / 100,
32+ price = item.price
33+ })
34+ end
35+ return items
36+end
37+
38+context.NPC.getBuyItems = function()
39+ if not context.NPC.isTrading() then return {} end
40+ local items = {}
41+ for i, item in ipairs(modules.game_npctrade.tradeItems[modules.game_npctrade.BUY]) do
42+ table.insert(items, {
43+ item = item.ptr,
44+ id = item.ptr:getId(),
45+ count = item.ptr:getCount(),
46+ name = item.name,
47+ subType = item.ptr:getSubType(),
48+ weight = item.weight / 100,
49+ price = item.price
50+ })
51+ end
52+ return items
53+end
54+
55+context.NPC.getSellQuantity = function(item)
56+ if not context.NPC.isTrading() then return 0 end
57+ if type(item) == 'number' then
58+ item = Item.create(item)
59+ end
60+ return modules.game_npctrade.getSellQuantity(item)
61+end
62+
63+context.NPC.canTradeItem = function(item)
64+ if not context.NPC.isTrading() then return false end
65+ if type(item) == 'number' then
66+ item = Item.create(item)
67+ end
68+ return modules.game_npctrade.canTradeItem(item)
69+end
70+
71+context.NPC.sell = function(item, count, ignoreEquipped)
72+ if type(item) == 'number' then
73+ for i, entry in ipairs(context.NPC.getSellItems()) do
74+ if entry.id == item then
75+ item = entry.item
76+ break
77+ end
78+ end
79+ if type(item) == 'number' then
80+ item = Item.create(item)
81+ end
82+ end
83+ if count == 0 then
84+ count = 1
85+ end
86+ if count == nil or count == -1 then
87+ count = context.NPC.getSellQuantity(item)
88+ end
89+ if ignoreEquipped == nil then
90+ ignoreEquipped = true
91+ end
92+ g_game.sellItem(item, count, ignoreEquipped)
93+end
94+
95+context.NPC.buy = function(item, count, ignoreCapacity, withBackpack)
96+ if type(item) == 'number' then
97+ for i, entry in ipairs(context.NPC.getBuyItems()) do
98+ if entry.id == item then
99+ item = entry.item
100+ break
101+ end
102+ end
103+ if type(item) == 'number' then
104+ item = Item.create(item)
105+ end
106+ end
107+ if count == nil or count <= 0 then
108+ count = 1
109+ end
110+ if ignoreCapacity == nil then
111+ ignoreCapacity = false
112+ end
113+ if withBackpack == nil then
114+ withBackpack = false
115+ end
116+ g_game.buyItem(item, count, ignoreCapacity, withBackpack)
117+end
118+
119+context.NPC.sellAll = function()
120+ if not context.NPC.isTrading() then return false end
121+ modules.game_npctrade.sellAll()
122+end
123+
124+context.NPC.closeTrade = function()
125+ modules.game_npctrade.closeNpcTrade()
126+end
127+context.NPC.close = context.NPC.closeTrade
128+context.NPC.finish = context.NPC.closeTrade
129+context.NPC.endTrade = context.NPC.closeTrade
130+context.NPC.finishTrade = context.NPC.closeTrade
A RD_Client/modules/game_bot/functions/player.lua +166-0 View file
@@ -0,0 +1,166 @@
1+local context = G.botContext
2+
3+context.name = function() return context.player:getName() end
4+
5+context.hp = function() return context.player:getHealth() end
6+context.mana = function() return context.player:getMana() end
7+context.hppercent = function() return context.player:getHealthPercent() end
8+context.manapercent = function() if context.player:getMaxMana() <= 1 then return 100 else return math.floor(context.player:getMana() * 100 / context.player:getMaxMana()) end end
9+context.maxhp = function() return context.player:getMaxHealth() end
10+context.maxmana = function() return context.player:getMaxMana() end
11+context.hpmax = function() return context.player:getMaxHealth() end
12+context.manamax = function() return context.player:getMaxMana() end
13+
14+context.cap = function() return context.player:getCapacity() end
15+context.freecap = function() return context.player:getFreeCapacity() end
16+context.maxcap = function() return context.player:getTotalCapacity() end
17+context.capmax = function() return context.player:getTotalCapacity() end
18+
19+context.exp = function() return context.player:getExperience() end
20+context.lvl = function() return context.player:getLevel() end
21+context.level = function() return context.player:getLevel() end
22+
23+context.mlev = function() return context.player:getMagicLevel() end
24+context.magic = function() return context.player:getMagicLevel() end
25+context.mlevel = function() return context.player:getMagicLevel() end
26+
27+context.soul = function() return context.player:getSoul() end
28+context.stamina = function() return context.player:getStamina() end
29+context.voc = function() return context.player:getVocation() end
30+context.vocation = function() return context.player:getVocation() end
31+
32+context.bless = function() return context.player:getBlessings() end
33+context.blesses = function() return context.player:getBlessings() end
34+context.blessings = function() return context.player:getBlessings() end
35+
36+
37+context.pos = function() return context.player:getPosition() end
38+context.posx = function() return context.player:getPosition().x end
39+context.posy = function() return context.player:getPosition().y end
40+context.posz = function() return context.player:getPosition().z end
41+
42+context.direction = function() return context.player:getDirection() end
43+context.speed = function() return context.player:getSpeed() end
44+context.skull = function() return context.player:getSkull() end
45+context.outfit = function() return context.player:getOutfit() end
46+
47+context.setOutfit = function(outfit)
48+ modules.game_outfit.ignoreNextOutfitWindow = g_clock.millis()
49+ g_game.requestOutfit()
50+ context.schedule(100, function()
51+ g_game.changeOutfit(outfit)
52+ end)
53+end
54+context.changeOutfit = context.setOutfit
55+context.setSpeed = function(value) context.player:setSpeed(value) end
56+
57+context.walk = function(dir) return modules.game_walking.walk(dir) end
58+context.turn = function(dir) return g_game.turn(dir) end
59+
60+-- game releated
61+context.getChannels = function()
62+ -- return { channelId = channelName }
63+ return modules.game_console.channels
64+end
65+context.getChannelId = function(name)
66+ for id, channel in pairs(context.getChannels()) do
67+ if name:lower() == channel:lower() then
68+ return id
69+ end
70+ end
71+ return nil
72+end
73+context.getChannel = context.getChannelId
74+
75+context.say = g_game.talk
76+context.talk = g_game.talk
77+context.yell = function(text) g_game.talkChannel(3, 0, text) end
78+context.talkChannel = function(channel, text) g_game.talkChannel(7, channel, text) end
79+context.sayChannel = context.talkChannel
80+context.talkPrivate = function(receiver, text) g_game.talkPrivate(5, receiver, text) end
81+context.sayPrivate = context.talkPrivate
82+
83+context.talkNpc = function(text)
84+ if g_game.getClientVersion() >= 810 then
85+ g_game.talkChannel(11, 0, text)
86+ else
87+ return context.say(text)
88+ end
89+end
90+context.sayNpc = context.talkNpc
91+context.sayNPC = context.talkNpc
92+context.talkNPC = context.talkNpc
93+
94+context.saySpell = function(text, lastSpellTimeout)
95+ if not text or text:len() < 1 then
96+ return
97+ end
98+ if context.lastSpell == nil then
99+ context.lastSpell = 0
100+ end
101+ if not lastSpellTimeout then
102+ lastSpellTimeout = 1000
103+ end
104+ if context.lastSpell + lastSpellTimeout > context.now then
105+ return false
106+ end
107+ context.say(text)
108+ context.lastSpell = context.now
109+ return true
110+end
111+
112+context.setSpellTimeout = function()
113+ context.lastSpell = context.now
114+end
115+
116+context.use = function(thing, subtype)
117+ if type(thing) == 'number' then
118+ return g_game.useInventoryItem(thing, subtype)
119+ else
120+ return g_game.use(thing)
121+ end
122+end
123+context.usewith = function(thing, target, subtype)
124+ if type(thing) == 'number' then
125+ return g_game.useInventoryItemWith(thing, target, subtype)
126+ else
127+ return g_game.useWith(item, target, subtype)
128+ end
129+end
130+context.useWith = context.usewith
131+
132+context.useRune = function(itemid, target, lastSpellTimeout)
133+ if context.lastRuneUse == nil then
134+ context.lastRuneUse = 0
135+ end
136+ if not lastRuneTimeout then
137+ lastRuneTimeout = 1000
138+ end
139+ if context.lastRuneUse + lastRuneTimeout > context.now then
140+ return false
141+ end
142+ context.usewith(itemid, target)
143+ context.lastRuneUse = context.now
144+ return true
145+end
146+context.userune = context.useRune
147+
148+context.findItem = function(itemId, subType)
149+ if subType == nil then
150+ subType = -1
151+ end
152+ return g_game.findItemInContainers(itemId, subType)
153+end
154+
155+context.attack = g_game.attack
156+context.cancelAttack = g_game.cancelAttack
157+context.follow = g_game.follow
158+context.cancelFollow = g_game.cancelFollow
159+context.cancelAttackAndFollow = g_game.cancelAttackAndFollow
160+
161+context.logout = g_game.forceLogout
162+context.ping = g_game.getPing
163+
164+modules.game_cooldown.isGroupCooldownIconActive(id)
165+modules.game_cooldown.isCooldownIconActive(id)
166+
A RD_Client/modules/game_bot/functions/player_conditions.lua +32-0 View file
@@ -0,0 +1,32 @@
1+local context = G.botContext
2+
3+for i, state in ipairs(PlayerStates) do
4+ context[state] = state
5+end
6+
7+context.hasCondition = function(condition) return bit.band(context.player:getStates(), condition) > 0 end
8+
9+context.isPoisioned = function() return context.hasCondition(PlayerStates.Poison) end
10+context.isBurnining = function() return context.hasCondition(PlayerStates.Burn) end
11+context.isEnergized = function() return context.hasCondition(PlayerStates.Energy) end
12+context.isDrunk = function() return context.hasCondition(PlayerStates.Drunk) end
13+context.hasManaShield = function() return context.hasCondition(PlayerStates.ManaShield) end
14+context.isParalyzed = function() return context.hasCondition(PlayerStates.Paralyze) end
15+context.hasHaste = function() return context.hasCondition(PlayerStates.Haste) end
16+context.hasSwords = function() return context.hasCondition(PlayerStates.Swords) end
17+context.isInFight = function() return context.hasCondition(PlayerStates.Swords) end
18+context.canLogout = function() return not context.hasCondition(PlayerStates.Swords) end
19+context.isDrowning = function() return context.hasCondition(PlayerStates.Drowning) end
20+context.isFreezing = function() return context.hasCondition(PlayerStates.Freezing) end
21+context.isDazzled = function() return context.hasCondition(PlayerStates.Dazzled) end
22+context.isCursed = function() return context.hasCondition(PlayerStates.Cursed) end
23+context.hasPartyBuff = function() return context.hasCondition(PlayerStates.PartyBuff) end
24+context.hasPzLock = function() return context.hasCondition(PlayerStates.PzBlock) end
25+context.hasPzBlock = function() return context.hasCondition(PlayerStates.PzBlock) end
26+context.isPzLocked = function() return context.hasCondition(PlayerStates.PzBlock) end
27+context.isPzBlocked = function() return context.hasCondition(PlayerStates.PzBlock) end
28+context.isInProtectionZone = function() return context.hasCondition(PlayerStates.Pz) end
29+context.hasPz = function() return context.hasCondition(PlayerStates.Pz) end
30+context.isInPz = function() return context.hasCondition(PlayerStates.Pz) end
31+context.isBleeding = function() return context.hasCondition(PlayerStates.Bleeding) end
32+context.isHungry = function() return context.hasCondition(PlayerStates.Hungry) end
A RD_Client/modules/game_bot/functions/player_inventory.lua +45-0 View file
@@ -0,0 +1,45 @@
1+local context = G.botContext
2+
3+context.SlotOther = InventorySlotOther
4+context.SlotHead = InventorySlotHead
5+context.SlotNeck = InventorySlotNeck
6+context.SlotBack = InventorySlotBack
7+context.SlotBody = InventorySlotBody
8+context.SlotRight = InventorySlotRight
9+context.SlotLeft = InventorySlotLeft
10+context.SlotLeg = InventorySlotLeg
11+context.SlotFeet = InventorySlotFeet
12+context.SlotFinger = InventorySlotFinger
13+context.SlotAmmo = InventorySlotAmmo
14+context.SlotPurse = InventorySlotPurse
15+
16+context.getInventoryItem = function(slot) return context.player:getInventoryItem(slot) end
17+context.getSlot = context.getInventoryItem
18+
19+context.getHead = function() return context.getInventoryItem(context.SlotHead) end
20+context.getNeck = function() return context.getInventoryItem(context.SlotNeck) end
21+context.getBack = function() return context.getInventoryItem(context.SlotBack) end
22+context.getBody = function() return context.getInventoryItem(context.SlotBody) end
23+context.getRight = function() return context.getInventoryItem(context.SlotRight) end
24+context.getLeft = function() return context.getInventoryItem(context.SlotLeft) end
25+context.getLeg = function() return context.getInventoryItem(context.SlotLeg) end
26+context.getFeet = function() return context.getInventoryItem(context.SlotFeet) end
27+context.getFinger = function() return context.getInventoryItem(context.SlotFinger) end
28+context.getAmmo = function() return context.getInventoryItem(context.SlotAmmo) end
29+context.getPurse = function() return context.getInventoryItem(context.SlotPurse) end
30+
31+context.getContainers = function() return g_game.getContainers() end
32+context.getContainer = function(index) return g_game.getContainer(index) end
33+
34+context.moveToSlot = function(item, slot, count)
35+ if type(item) == 'number' then
36+ item = context.findItem(item)
37+ end
38+ if not item then
39+ return
40+ end
41+ if count == nil then
42+ count = item:getCount()
43+ end
44+ return g_game.move(item, {x=65535, y=slot, z=0}, count)
45+end
A RD_Client/modules/game_bot/functions/script_loader.lua +59-0 View file
@@ -0,0 +1,59 @@
1+local context = G.botContext
2+
3+context.loadScript = function(path, onLoadCallback)
4+ if type(path) ~= 'string' then
5+ return context.error("Invalid path for loadScript: " .. tostring(path))
6+ end
7+ if path:lower():find("http") == 1 then
8+ return context.loadRemoteScript(path)
9+ end
10+ if not g_resources.fileExists(path) then
11+ return context.error("File " .. path .. " doesn't exist")
12+ end
13+
14+ local status, result = pcall(function()
15+ assert(load(g_resources.readFileContents(path), path, nil, context))()
16+ end)
17+ if not status then
18+ return context.error("Error while loading script from: " .. path .. ":\n" .. result)
19+ end
20+ if onLoadCallback then
21+ onLoadCallback()
22+ end
23+end
24+
25+context.loadRemoteScript = function(url, onLoadCallback)
26+ if type(url) ~= 'string' or url:lower():find("http") ~= 1 then
27+ return context.error("Invalid url for loadRemoteScript: " .. tostring(url))
28+ end
29+
30+ HTTP.get(url, function(data, err)
31+ if err or data:len() == 0 then
32+ -- try to load from cache
33+ if type(context.storage.scriptsCache) ~= 'table' then
34+ context.storage.scriptsCache = {}
35+ end
36+ local cache = context.storage.scriptsCache[url]
37+ if cache and type(cache) == 'string' and cache:len() > 0 then
38+ data = cache
39+ else
40+ return context.error("Can't load script from: " .. url .. ", error: " .. err)
41+ end
42+ end
43+
44+ local status, result = pcall(function()
45+ assert(load(data, url, nil, context))()
46+ end)
47+ if not status then
48+ return context.error("Error while loading script from: " .. url .. ":\n" .. result)
49+ end
50+ -- cache script
51+ if type(context.storage.scriptsCache) ~= 'table' then
52+ context.storage.scriptsCache = {}
53+ end
54+ context.storage.scriptsCache[url] = data
55+ if onLoadCallback then
56+ onLoadCallback()
57+ end
58+ end)
59+end
A RD_Client/modules/game_bot/functions/server.lua +91-0 View file
@@ -0,0 +1,91 @@
1+local context = G.botContext
2+
3+context.BotServer = {}
4+context.BotServer.url = "ws://bot.otclient.ovh:8000/"
5+context.BotServer.timeout = 3
6+context.BotServer.ping = 0
7+context.BotServer._callbacks = {}
8+context.BotServer._lastMessageId = 0
9+context.BotServer._wasConnected = true -- show first warning
10+
11+context.BotServer.init = function(name, channel)
12+ if not channel or not name or channel:len() < 1 or name:len() < 1 then
13+ return context.error("Invalid params for BotServer.init")
14+ end
15+ if context.BotServer._websocket then
16+ return context.error("BotServer is already initialized")
17+ end
18+ context.BotServer._websocket = HTTP.WebSocketJSON(context.BotServer.url, {
19+ onMessage = function(message, socketId)
20+ if not context._websockets[socketId] then
21+ return g_http.cancel(socketId)
22+ end
23+ if not context.BotServer._websocket or context.BotServer._websocket.id ~= socketId then
24+ return g_http.cancel(socketId)
25+ end
26+ context.BotServer._wasConnected = true
27+ if message["type"] == "ping" then
28+ context.BotServer.ping = message["ping"]
29+ return context.BotServer._websocket.send({type="ping"})
30+ end
31+ if message["type"] == "message" then
32+ context.BotServer._lastMessageId = message["id"]
33+ local topics = context.BotServer._callbacks[message["topic"]]
34+ if topics then
35+ for i=1,#topics do
36+ topics[i](message["name"], message["message"], message["topic"])
37+ end
38+ end
39+ topics = context.BotServer._callbacks["*"]
40+ if topics then
41+ for i=1,#topics do
42+ topics[i](message["name"], message["message"], message["topic"])
43+ end
44+ end
45+ return
46+ end
47+ end,
48+ onClose = function(message, socketId)
49+ if not context._websockets[socketId] then
50+ return
51+ end
52+ context._websockets[socketId] = nil
53+ if not context.BotServer._websocket or context.BotServer._websocket.id ~= socketId then
54+ return
55+ end
56+ if context.BotServer._wasConnected then
57+ context.warn("BotServer disconnected")
58+ end
59+ context.BotServer._wasConnected = false
60+ context.BotServer._websocket = nil
61+ context.BotServer.ping = 0
62+ context.BotServer.init(name, channel)
63+ end
64+ }, context.BotServer.timeout)
65+ context._websockets[context.BotServer._websocket.id] = 1
66+ context.BotServer._websocket.send({type="init", name=name, channel=channel, lastMessage=context.BotServer._lastMessageId})
67+end
68+
69+context.BotServer.terminate = function()
70+ if context.BotServer._websocket then
71+ context.BotServer._websocket:close()
72+ context.BotServer._websocket = nil
73+ end
74+end
75+
76+context.BotServer.listen = function(topic, callback) -- callback = function(name, message, topic) -- message is parsed json = table
77+ if not context.BotServer._websocket then
78+ return context.error("BotServer is not initialized")
79+ end
80+ if not context.BotServer._callbacks[topic] then
81+ context.BotServer._callbacks[topic] = {}
82+ end
83+ table.insert(context.BotServer._callbacks[topic], callback)
84+end
85+
86+context.BotServer.send = function(topic, message)
87+ if not context.BotServer._websocket then
88+ return context.error("BotServer is not initialized")
89+ end
90+ context.BotServer._websocket.send({type="message", topic=topic, message=message})
91+end
A RD_Client/modules/game_bot/functions/sound.lua +31-0 View file
@@ -0,0 +1,31 @@
1+local context = G.botContext
2+
3+context.getSoundChannel = function()
4+ if not g_sounds then
5+ return
6+ end
7+ return g_sounds.getChannel(SoundChannels.Bot)
8+end
9+
10+context.playSound = function(file)
11+ local botSoundChannel = context.getSoundChannel()
12+ if not botSoundChannel then
13+ return
14+ end
15+ botSoundChannel:setEnabled(true)
16+ botSoundChannel:stop(0)
17+ botSoundChannel:play(file, 0, 1.0)
18+ return botSoundChannel
19+end
20+
21+context.stopSound = function()
22+ local botSoundChannel = context.getSoundChannel()
23+ if not botSoundChannel then
24+ return
25+ end
26+ botSoundChannel:stop()
27+end
28+
29+context.playAlarm = function()
30+ return context.playSound("/sounds/alarm.ogg")
31+end
A RD_Client/modules/game_bot/functions/test.lua +3-0 View file
@@ -0,0 +1,3 @@
1+local context = G.botContext
2+
3+context.test = function() return context.info("test") end
A RD_Client/modules/game_bot/functions/tools.lua +19-0 View file
@@ -0,0 +1,19 @@
1+local context = G.botContext
2+
3+context.encode = function(data, indent) return json.encode(data, indent or 2) end
4+context.decode = function(text) local status, result = pcall(function() return json.decode(text) end) if status then return result end return {} end
5+
6+context.displayGeneralBox = function(title, message, buttons, onEnterCallback, onEscapeCallback)
7+ local box = displayGeneralBox(title, message, buttons, onEnterCallback, onEscapeCallback)
8+ box.botWidget = true
9+ return box
10+end
11+
12+context.doScreenshot = function(filename)
13+ g_app.doScreenshot(filename)
14+end
15+context.screenshot = context.doScreenshot
16+
17+context.getVersion = function()
18+ return g_app.getVersion()
19+end
Top