· 8 years ago · Jun 16, 2018, 06:54 PM
1local print = print
2local pairs = pairs
3local ipairs = ipairs
4local setfenv = setfenv
5local tonumber = tonumber
6local tostring = tostring
7local assert = assert
8local type = type
9local collectgarbage = collectgarbage
10local unpack = unpack
11local pcall = pcall
12local next = next
13local pnext = function(t, i) return pcall(function() return next(t, i) end) end
14local os = os
15local io = io
16local string = string
17local math = math
18local table = table
19
20local xeno = {}
21xeno.isRealTibia = isRealTibia
22xeno.showConfigEditor = showConfigEditor
23xeno.getVersionNumber = getVersionNumber
24xeno.ping = Self.Ping
25xeno.delayWalker = delayWalker
26xeno.gotoLabel = function(name, relative) gotoLabel(name, relative and 1 or 0) end
27xeno.loadSettings = loadSettings
28xeno.sendScriptSignal = sendScriptSignal
29xeno.getWalkerStuck = getWalkerStuck
30xeno.getWalkerLuring = getWalkerLuring
31xeno.getTargetingIgnoring = getTargetingIgnoring
32xeno.getXenoBotStatus = getXenoBotStatus
33xeno.registerNativeEventListener = registerNativeEventListener
34xeno.getContainerOpen = getContainerOpen
35xeno.getContainerName = getContainerName
36xeno.getContainerID = getContainerID
37xeno.getContainerSpotData = getContainerSpotData
38xeno.getContainerItemCount = getContainerItemCount
39xeno.getContainerItemCapacity = getContainerItemCapacity
40xeno.getItemNameByID = getItemNameByID
41xeno.getItemIDByName = getItemIDByName
42xeno.minimizeContainer = minimizeContainer
43xeno.closeContainer = closeContainer
44xeno.containerUseItem = containerUseItem
45xeno.containerUseWithContainer = containerUseWithContainer
46xeno.containerUseItemWithGround = containerUseItemWithGround
47xeno.containerUseItemWithCreature = containerUseItemWithCreature
48xeno.containerMoveItemToSlot = containerMoveItemToSlot
49xeno.containerMoveItemToGround = containerMoveItemToGround
50xeno.containerMoveItemToContainer = containerMoveItemToContainer
51xeno.containerBack = containerBack
52xeno.isItemContainer = isItemContainer
53xeno.isItemCorpse = isItemCorpse
54xeno.isItemStackable = isItemStackable
55xeno.isItemUseWithable = isItemUseWithable
56xeno.isItemFood = isItemFood
57xeno.isItemWalkable = isItemWalkable
58xeno.isItemFurniture = isItemFurniture
59xeno.isItemField = isItemField
60xeno.getTileUseTargetID = getTileUseTargetID
61xeno.getTileUseID = getTileUseID
62xeno.getTileIsWalkable = getTileIsWalkable
63xeno.getItemWeight = getItemWeight
64xeno.getItemValue = getItemValue
65xeno.getItemCost = getItemCost
66xeno.setWalkerEnabled = setWalkerEnabled
67xeno.setLooterEnabled = setLooterEnabled
68xeno.setTargetingEnabled = setTargetingEnabled
69xeno.getSelfID = getSelfID
70xeno.getSelfTargetID = getSelfTargetID
71xeno.getSelfFollowID = getSelfFollowID
72xeno.getSelfPosition = getSelfPosition
73xeno.getSelfLookDirection = getSelfLookDirection
74xeno.getSelfMaxHealth = getSelfMaxHealth
75xeno.getSelfMaxMana = getSelfMaxMana
76xeno.getSelfMana = getSelfMana
77xeno.getSelfHealth = getSelfHealth
78xeno.getSelfStamina = getSelfStamina
79xeno.getSelfCap = getSelfCap
80xeno.getSelfLevel = getSelfLevel
81xeno.getSelfExperience = getSelfExperience
82xeno.getSelfFlag = getSelfFlag
83xeno.getSelfSpellCooldown = getSelfSpellCooldown
84xeno.getSelfSpellRequirementsMet = getSelfSpellRequirementsMet
85xeno.selfSay = selfSay
86xeno.selfYell = selfYell
87xeno.selfWhisper = selfWhisper
88xeno.selfNpcSay = selfNpcSay
89xeno.selfPrivateMessage = selfPrivateMessage
90xeno.selfUseItem = selfUseItem
91xeno.selfUseItemFromGround = selfUseItemFromGround
92xeno.selfUseItemWithGround = selfUseItemWithGround
93xeno.selfUseItemWithCreature = selfUseItemWithCreature
94xeno.selfBrowseField = selfBrowseField
95xeno.doSelfTurn = doSelfTurn
96xeno.doSelfStep = doSelfStep
97xeno.doSelfWalkTo = doSelfWalkTo
98xeno.doSelfLogout = doSelfLogout
99xeno.slotUseItem = slotUseItem
100xeno.slotMoveItemToContainer = slotMoveItemToContainer
101xeno.shopBuyItemByName = shopBuyItemByName
102xeno.shopBuyItemByID = shopBuyItemByID
103xeno.shopSellItemByName = shopSellItemByName
104xeno.shopSellItemByID = shopSellItemByID
105xeno.shopGetItemBuyPriceByName = shopGetItemBuyPriceByName
106xeno.shopGetItemBuyPriceByID = shopGetItemBuyPriceByID
107xeno.shopGetItemSaleCountByName = shopGetItemSaleCountByName
108xeno.shopGetItemSaleCountByID = shopGetItemSaleCountByID
109xeno.luaOpenCustomChannel = luaOpenCustomChannel
110xeno.luaCloseCustomChannel = luaCloseCustomChannel
111xeno.luaSendChannelMessage = luaSendChannelMessage
112xeno.HUDGetDimensions = HUDGetDimensions
113xeno.HUDGetContainerDimensions = HUDGetContainerDimensions
114xeno.HUDCreateItemImage = HUDCreateItemImage
115xeno.HUDCreateText = HUDCreateText
116xeno.HUDUpdateLocation = HUDUpdateLocation
117xeno.HUDUpdateTextText = HUDUpdateTextText
118xeno.HUDUpdateTextColor = HUDUpdateTextColor
119xeno.HUDUpdateItemImageID = HUDUpdateItemImageID
120xeno.HUDUpdateItemImageSize = HUDUpdateItemImageSize
121xeno.HUDUpdateItemImageCount = HUDUpdateItemImageCount
122xeno.getHeadSlotData = getHeadSlotData
123xeno.getArmorSlotData = getArmorSlotData
124xeno.getLegsSlotData = getLegsSlotData
125xeno.getFeetSlotData = getFeetSlotData
126xeno.getAmuletSlotData = getAmuletSlotData
127xeno.getWeaponSlotData = getWeaponSlotData
128xeno.getRingSlotData = getRingSlotData
129xeno.getBackpackSlotData = getBackpackSlotData
130xeno.getShieldSlotData = getShieldSlotData
131xeno.getAmmoSlotData = getAmmoSlotData
132xeno.getSelfCreatureKillCount = getSelfCreatureKillCount
133xeno.resetSelfCreatureKillCount = resetSelfCreatureKillCount
134xeno.doSelfSelectTrainingMode = doSelfSelectTrainingMode
135xeno.getCreatureName = getCreatureName
136xeno.getCreatureSkull = getCreatureSkull
137xeno.getCreaturePartyStatus = getCreaturePartyStatus
138xeno.getCreaturePvPIcon = getCreaturePvPIcon
139xeno.getCreatureHealthPercent = getCreatureHealthPercent
140xeno.getCreatureVisible = getCreatureVisible
141xeno.getCreatureListIndex = getCreatureListIndex
142xeno.getCreatureIDFromIndex = getCreatureIDFromIndex
143xeno.getCreaturePosition = getCreaturePosition
144xeno.isCreaturePlayer = isCreaturePlayer
145xeno.isCreatureNpc = isCreatureNpc
146xeno.isCreatureMonster = isCreatureMonster
147xeno.isCreatureSummon = isCreatureSummon
148xeno.isCreatureSelfSummon = isCreatureSelfSummon
149xeno.isCreatureStrangeSummon = isCreatureStrangeSummon
150xeno.isCreatureMother = isCreatureMother
151xeno.attackCreature = attackCreature
152xeno.followCreature = followCreature
153xeno.screenshot = screenshot
154xeno.getUserName = getUserName
155xeno.getCreatureSpectators = getCreatureSpectators
156xeno.enterCriticalMode = veryUnsafeFunctionEnterCriticalMode
157xeno.exitCriticalMode = veryUnsafeFunctionExitCriticalMode
158xeno.setDiagnosticsEnabled = setDiagnosticsEnabled
159xeno.setTargetingIgnoreEnabled = setTargetingIgnoreEnabled
160xeno.alert = alert
161local MINIMUM_XENO_VERSION = 1169
162local LIB_REVISION = 'v1.3.12'
163
164-- Configure the script in the folder: Documents/XenoBot/Configs
165-- Do NOT edit this file. If you do, don't make a support thread about it.
166-- Regards, Syntax <3
167local LIB_CONFIG = string.reverse([[?emit dessap fo daetsni animats desu rep stiforp dna pxe ylruoh erusaem ot tnaw uoy oD ; eslaf = tnemerusaeM-animatS-reP
168?SS-MM-HH-DD fo daetsni MM-HH ni emit yalpsid ot tnaw uoy oD ; eslaf = tamroF-emiT-elpmiS
169)elprup ,eulb ,neerg ,knip ,egnaro ,krad ,thgil( .DUH eht rof snoitpo emeht roloC ; thgil = emehT
170)eslaf/eurt( .DUH eht ni snoci tool eht wohs ot rehtehW ; eurt = snocI-wohS
171)eslaf/eurt( ?tool laudividni wohs ot ekil uoy dluoW ; eurt = tooL-wohS
172)eslaf/eurt( ?seilppus laudividni wohs ot ekil uoy dluoW ; eurt = seilppuS-wohS
173)eslaf/eurt( ?seman reniatnoc wohs ot ekil uoy dluoW ; eurt = sreniatnoC-wohS
174)eslaf/eurt( ?DUH eht elbane ot ekil uoy dluoW ; eurt = delbanE
175]DUH[
176
177)eslaf/eurt( .tixe lliw tneilc ,gniniart ton & tuo gniggol nehW ; eslaf = goL-tixE
178)cigam ,raeps ,bulc ,exa ,drows ,enon( .esu ot eutats gniniart ehT ; enon = llikS-niarT
179)elbasid ot 0( .tnuh ot sruoh rebmun mumixam ehT ; 0 = timiL-emiT
180)elbasid ot 0( ?tuogol ot ekil uoy dluow evas revres erofeb sruoh ynam woH ; 0 = evaS-revreS
181)elbasid ot 0( ?tuogol ot ekil uoy dluow level tahw tA ; 0 = leveL
182)sruoh ni( ?tuo gol ot ekil uoy dluow animats hcum woh tA ; 41 = animatS
183]tuogoL[
184
185)elbasid ot 0( .dlog pord lliw tpircs ,eulav siht woleb seog yticapac fI ; 08 = dloG-porD
186)elbasid ot 0( .sksalf pord lliw tpircs ,eulav siht woleb seog yticapac fI ; 001 = sksalF-porD
187)elbasid ot 0( .nwaps eht tixe lliw tpircs ,eulav siht woleb seog yticapac fI ; 001 = muminiM-tnuH
188]yticapaC[
189
190?anam gnirotser erofeb serutaerc redisnoc ot tnaw uoy od egnar tahW ; 7 = egnaR
191)elbasid ot 0( .neercs-no era serutaerc on elihw ot pu anam erotser ot tnecrep anaM ; 07 = tnecreP-erotseR
192]rerotseR anaM[
193
194)elbasid ot 0( ?stoobtfos gnisu nigeb ot anam fo tnecrep tahW ; 0 = tnecreP-anaM
195]stooB tfoS[
196
197)elbasid ot 0( .mrala na reggirt ot srevomer tsur fo tnuoma ehT ; 0 = mralAksalF
198)elbasid ot 0( .wardhtiw ot tnaw uoy srevomer tsur fo tnuoma ehT ; 0 = xaMksalF
199)elbasid ot 0( .llifer og ot evael lliw uoy hcihw ta srevomer tsur fo tnuoma ehT ; 0 = niMksalF
200.revomer tsur eht fo ,di meti ro ,emaN ; revomer tsur fo ksalf = emaNksalF
201]seilppuS[
202
203)elbasid ot 0( .teluma eht piuqe ot serutaerc fo tnuoma ehT ; 0 = tnuoCerutaerCtelumA
204noics eramthgin ,eramthgin ,repaer mirg = serutaerCtelumA
205)elbasid ot 0( .mrala na reggirt ot steluma fo tnuoma ehT ; 0 = mralAtelumA
206)elbasid ot 0( .teluma eht piuqe ot anam fo tnecrep ehT ; 01 = PMniMtelumA
207)elbasid ot 0( .teluma eht piuqe ot htlaeh fo tnecrep ehT ; 03 = PHniMtelumA
208)elbasid ot 0( .toped eht morf wardhtiw ot tnaw uoy steluma fo tnuoma ehT ; 0 = xaMtelumA
209)elbasid ot 0( .llifer og ot evael uoy hcihw ta steluma fo tnuoma ehT ; 0 = niMtelumA
210.teluma eht fo ,di meti ro ,emaN ; teluma noitcetorp = emaNtelumA
211]telumA[
212
213)elbasid ot 0( .gnir eht piuqe ot serutaerc fo tnuoma ehT ; 0 = tnuoCerutaerCgniR
214noics eramthgin ,eramthgin ,repaer mirg = serutaerCgniR
215)elbasid ot 0( .mrala na reggirt ot sgnir fo tnuoma ehT ; 0 = mralAgniR
216)elbasid ot 0( .gnir eht piuqe ot anam fo tnecrep ehT ; 01 = PMniMgniR
217)elbasid ot 0( .gnir eht piuqe htlaeh fo tnecrep ehT ; 03 = PHniMgniR
218)elbasid ot 0( .toped eht morf wardhtiw ot tnaw uoy sgnir fo tnuoma ehT ; 0 = xaMgniR
219)elbasid ot 0( .llifer og ot evael uoy hcihw ta sgnir fo tnuoma ehT ; 0 = niMgniR
220.gnir eht fo ,di meti ro ,emaN ; gnir drows = emaNgniR
221]gniR[
222
223noics eramthgin ,eramthgin ,repaer mirg = stegraTCiroxE
224.lleps siht tsac ot ,woleb tsil eht ni ,serutaerc fo htlaeh muminim ehT ; 0 = PHniMCiroxE
225.lleps siht tsac ot ,woleb tsil eht ni ,serutaerc fo htlaeh mumixam ehT ; 001 = PHxaMCiroxE
226)elbasid ot 0( .gnikcatta nigeb ot ,woleb tsil eht ni ,serutaerc fo tnuoma muminim ehT ; 1 = niMtegraTCiroxE
227)tnatropmi erom si rewol( .slleps dna senur rehto ot derapmoc ytiroirP ; 4 = ytiroirPCiroxE
228)eslaf/eurt( ?lleps siht esu ot tnaw uoy oD ; eurt = delbanECiroxE
229.lleps eht fo emaN ; noc iroxe = emaNCiroxE
230
231noics eramthgin ,eramthgin ,repaer mirg = stegraTSiroxE
232.lleps siht tsac ot ,woleb tsil eht ni ,serutaerc fo htlaeh muminim ehT ; 0 = PHniMSiroxE
233.lleps siht tsac ot ,woleb tsil eht ni ,serutaerc fo htlaeh mumixam ehT ; 001 = PHxaMSiroxE
234)elbasid ot 0( .gnikcatta nigeb ot ,woleb tsil eht ni ,serutaerc fo tnuoma muminim ehT ; 1 = niMtegraTSiroxE
235)tnatropmi erom si rewol( .slleps dna senur rehto ot derapmoc ytiroirP ; 3 = ytiroirPSiroxE
236)eslaf/eurt( ?lleps siht esu ot tnaw uoy oD ; eurt = delbanESiroxE
237.lleps eht fo emaN ; nas iroxe = emaNSiroxE
238
239repaer mirg = stegraTsaMovexE
240.lleps siht tsac ot ,woleb tsil eht ni ,serutaerc fo htlaeh muminim ehT ; 0 = PHniMsaMovexE
241.lleps siht tsac ot ,woleb tsil eht ni ,serutaerc fo htlaeh mumixam ehT ; 001 = PHxaMsaMovexE
242)elbasid ot 0( .gnikcatta nigeb ot ,woleb tsil eht ni ,serutaerc fo tnuoma muminim ehT ; 2 = niMtegraTsaMovexE
243)tnatropmi erom si rewol( .slleps dna senur rehto ot derapmoc ytiroirP ; 1 = ytiroirPsaMovexE
244)eslaf/eurt( ?lleps siht esu ot tnaw uoy oD ; eurt = delbanEsaMovexE
245.lleps eht fo emaN ; nas sam ovexe = emaNsaMovexE
246]sllepS[
247
248repaer mirg = stegraTaerA
249.lleps siht tsac ot ,woleb tsil eht ni ,serutaerc fo htlaeh muminim ehT ; 0 = PHniMaerA
250.lleps siht tsac ot ,woleb tsil eht ni ,serutaerc fo htlaeh mumixam ehT ; 001 = PHxaMaerA
251)elbasid ot 0( .gnikcatta nigeb ot ,woleb tsil eht ni ,serutaerc fo tnuoma muminim ehT ; 2 = niMtegraTaerA
252)tnatropmi erom si rewol( .slleps dna senur rehto ot derapmoc ytiroirP ; 2 = ytiroirPaerA
253)elbasid ot 0( .mrala na reggirt ot senur fo tnuoma ehT ; 0 = mralAaerA
254)elbasid ot 0( .yub ro wardhtiw ot tnaw uoy senur fo tnuoma ehT ; 004 = xaMaerA
255.llifer og ot evael lliw uoy hcihw ta senur fo tnuoma ehT ; 05 = niMaerA
256)eslaf/eurt( ?enur siht esu ot tnaw uoy oD ; eurt = delbanEaerA
257.enur eht fo ,di meti ro ,emaN ; enur llaberif taerg = emaNaerA
258]senuR[
259
260)elbasid ot 0( .mrala na reggirt ot omma fo tnuoma ehT ; 0 = mralAommA
261)elbasid ot 0( .yub ro wardhtiw ot tnaw uoy omma fo tnuoma ehT ; 0051 = xaMommA
262.llifer og ot lliw uoy hcihw ta omma fo tnuoma ehT ; 002 = niMommA
263.noitinumma eht fo ,di meti ro ,emaN ; worra enillatsyrc = emaNommA
264]ommA[
265
266)elbasid ot 0( .mrala na reggirt ot snoitop fo tnuoma ehT ; 0 = mralAanaM
267)elbasid ot 0( .yub ro wardhtiw ot tnaw uoy snoitop fo tnuoma ehT ; 0001 = xaManaM
268.llifer og ot evael lliw uoy hcihw ta snoitop fo tnuoma ehT ; 002 = niManaM
269)!siht egnahc uoy fi troppuS ruoy etadpU( .noitop eht fo ,di meti ro ,emaN ; noitop anam gnorts = emaNanaM
270
271)elbasid ot 0( .mrala na reggirt ot snoitop fo tnuoma ehT ; 0 = mralAhtlaeH
272)elbasid ot 0( .yub ro wardhtiw ot tnaw uoy snoitop fo tnuoma ehT ; 05 = xaMhtlaeH
273.llifer og ot evael lliw uoy hcihw ta snoitop fo tnuoma ehT ; 52 = niMhtlaeH
274)!siht egnahc uoy fi troppuS ruoy etadpU( .noitop eht fo ,di meti ro ,emaN ; noitop tirips taerg = emaNhtlaeH
275]snoitoP[
276
277)elbasid ot 0( .mrala na reggirt ot doof fo tnuoma ehT ; 0 = mralAdooF
278)elbasid ot 0( .yub ro wardhtiw ot tnaw uoy doof fo tnuoma ehT ; 002 = xaMdooF
279.llifer og ot evael lliw uoy hcihw ta doof fo tnuoma ehT ; 52 = niMdooF
280.doof eht fo ,di meti ro ,emaN ; moorhsum etihw = emaNdooF
281]dooF[
282
283repaer mirg = serutaerC
284.serutaerc rof kcehc ot retcarahc ruoy dnuora ecnatsid ehT ; 7 = egnaR
285)elbasid ot 0( .edom erul itna etavitca ot serutaerc fo tnuoma ehT ; 5 = tnuomA
286]eruL itnA[
287
288)elbasid ot 0( .niaga gnirul trats ot serutaerc delliknu fo tnuoma ehT ; 0 = litnU
289)9-0( .redisnoc ot stegrat eht fo ytiroirp muminiM ; 1 = ytiroirPniM
290)eslaf/eurt( .serutaerc erul uoy sa tool dna tegrat ,delbane nehW ; eslaf = eruLelihWkcattA
291)elbasid ot 0( .edom erul etavitca ot serutaerc fo tnuoma ehT ; 0 = tnuomA
292]eruL[
293
294)eslaf/eurt( .etuor roolf dnoces ,eloh htfif eht hguorht seunitnoc retcarahc eht rehtehW ; eslaf = roolF-dnoceS-eloH-htfiF
295)eslaf/eurt( .etuor eloh htfif eht hguorht seunitnoc retcarahc eht rehtehW ; eurt = eloH-htfiF
296)eslaf/eurt( .etuor roolf dnoces ,eloh htruof eht hguorht seunitnoc retcarahc eht rehtehW ; eslaf = roolF-dnoceS-eloH-htruoF
297)eslaf/eurt( .etuor eloh htruof eht hguorht seunitnoc retcarahc eht rehtehW ; eurt = eloH-htruoF
298)eslaf/eurt( .etuor roolf dnoces ,eloh driht eht hguorht seunitnoc retcarahc eht rehtehW ; eslaf = roolF-dnoceS-eloH-drihT
299)eslaf/eurt( .etuor eloh driht eht hguorht seunitnoc retcarahc eht rehtehW ; eurt = eloH-drihT
300)eslaf/eurt( .etuor roolf dnoces ,eloh dnoces eht hguorht seunitnoc retcarahc eht rehtehW ; eslaf = roolF-dnoceS-eloH-dnoceS
301)eslaf/eurt( .etuor eloh dnoces eht hguorht seunitnoc retcarahc eht rehtehW ; eurt = eloH-dnoceS
302)eslaf/eurt( .etuor eloh tsrif eht hguorht seunitnoc retcarahc eht rehtehW ; eurt = eloH-tsriF
303)eslaf/eurt( .seloh nwod/pu eunitnoc ot efas sti erus ekam ot kcehc yps level a seod retcarahc eht rehtehW ; eurt = kcehC-ypS-leveL
304]etuoR[
305
306dleihs tneicna ,dleihs krad ,ehtycs ,dnaw thgil cigam ,dleihs leets ,temleh leets = tsiLkcalB-tooL
307lraep muclahciro ,etalocohc fo rab ,bro luos ,rolav fo drows ,temleh nedlog = tsiLetihW-tooL
308)elbasid ot 0( .tool ot ,meti rep ,thgiew mumixam ehT ; 0 = thgieW-xaM-tooL
309)elbasid ot 0( .tool ot ,meti rep ,eulav muminim ehT ; 05 = eulaV-niM-tooL
310)eslaf/eurt( ?dlog tool ot tnaw uoy oD ; eurt = dloG-tooL
311)tsal/tsrif( .serutaerc tool uoy hcihw ni redro ehT ; tsal = elytS-tooL
312)eslaf/eurt( ?tool lles yllacitamotua ot tnaw uoy oD ; eurt = lleS-cpN
313]tooL[
314
315)eslaf/eurt( ?erutaerc elbisivni na yb kcuts nehw lleps a tsac yllacitamotua ot tnaw uoy oD ; eslaf = kcutS-itnA-sivnI
316)eslaf/eurt( ?nwot ot gninruter nehw snoitidnoc lufmrah eruc yllacitamotua ot tnaw uoy oD ; eslaf = snoitidnoC-eruC
317)eslaf/eurt( .TSBX eht ni retoohs edirrevo ton lliw tpircs ,yllaunam retoohScigaM toBoneX eht erugifnoc ot elbanE ; eslaf = retoohS-launaM
318)eslaf/eurt( ?hcnual no skcapkcab ruoy fo stnetnoc eht ezinagro yllacitamotua ot tnaw uoy oD ; eurt = skcapkcaB-ezinagrO
319)eslaf/eurt( ?skcapkcab ruoy eziminim ot tnaw uoy oD ; eurt = skcapkcaB-eziminiM
320]lareneG[
321
3223b1338cd8ff515503e4ea4592858ead8:: ;
323toBoneX rof srepaeR mirG rahalaY ;]])
324local LIB_PRICES_CONFIG = [[; Market prices for official XenoBot scripts
325; ::a9d428293c579bc4f8ca9f45bad18249
326
327; These prices are loaded by every char
328[Default]
329"golden helmet" = 250
330"dragon scale legs" = 3000
331
332; These prices are loaded only if your character name is "Character One".
333; Change the name to your character name.
334[Character One]
335"longsword" = 25
336
337[Character Two]
338"pickaxe" = 2
339]]
340local FOLDER_SETTINGS_PATH = '..\\Settings\\'
341local FOLDER_LOGS_PATH = '..\\Log\\'
342local FOLDER_CONFIG_PATH = '..\\Configs\\'
343local MASTER_CONFIG_PATH = '..\\Scripts.ini'
344local PRICES_CONFIG_PATH = '..\\Prices.ini'
345
346local NORTH = NORTH
347local EAST = EAST
348local SOUTH = SOUTH
349local WEST = WEST
350local SOUTHWEST = SOUTHWEST
351local SOUTHEAST = SOUTHEAST
352local NORTHWEST = NORTHWEST
353local NORTHEAST = NORTHEAST
354
355local CREATURES_LOW = CREATURES_LOW
356local CREATURES_HIGH = CREATURES_HIGH
357
358local TIMER_TICK = TIMER_TICK
359local WALKER_SELECTLABEL = WALKER_SELECTLABEL
360local ERROR_MESSAGE = ERROR_MESSAGE
361local LOOT_MESSAGE = LOOT_MESSAGE
362local BATTLE_MESSAGE = BATTLE_MESSAGE
363local EVENT_SELF_CHANNELSPEECH = EVENT_SELF_CHANNELSPEECH
364local LOGOUT_COMMAND = LOGOUT_COMMAND
365local EVENT_SELF_CHANNELCLOSE = EVENT_SELF_CHANNELCLOSE
366local PRIVATE_MESSAGE = PRIVATE_MESSAGE
367local NPC_MESSAGE = NPC_MESSAGE
368local CONTAINER_OPEN = CONTAINER_OPEN
369
370local CHANNEL_ORANGE = CHANNEL_ORANGE
371local CHANNEL_YELLOW = CHANNEL_YELLOW
372local CHANNEL_RED = CHANNEL_RED
373
374local EVENT_LABEL = 'label'
375local EVENT_ERROR = 'error'
376local EVENT_BATTLE = 'battle'
377local EVENT_LOOT = 'loot'
378local EVENT_NPC = 'npc'
379local EVENT_COMMAND = 'command'
380local EVENT_PATH_END = 'pathend'
381local EVENT_DEPOT_END = 'depotend'
382local EVENT_CONTAINER = 'container'
383
384local DELAY = {}
385DELAY.WALKER_TIMEOUT = 2 * 60 * 1000
386DELAY.USE_EQUIPMENT = 800
387DELAY.CONTAINER_MOVE_ITEM = 500
388DELAY.CONTAINER_USE_ITEM = 1000
389DELAY.CONTAINER_USEWITH = 1500
390DELAY.MAP_USE_ITEM = {1000, 1200}
391DELAY.CONTAINER_BACK = 300
392DELAY.BROWSE_FIELD = 800
393DELAY.FOLLOW_WAIT = 2000
394DELAY.TRADE_TRANSACTION = 500
395DELAY.RANGE_TALK = {700, 1200}
396DELAY.CLEAN_CONTAINERS_INTERVAL = 5 * 60 * 1000
397DELAY.AlARM_INTERVAL = 10 * 1000
398DELAY.CONTAINER_TIMEOUT = 3000
399DELAY.CONTAINER_WAIT = {100, 1000}
400DELAY.ANTILURE_DELAY = {6000, 10000}
401
402local THROTTLE_CLEAR_PATH = {limit=2000, last=0}
403local THROTTLE_CAP_DROP = {limit=7000, last=0}
404
405local ERROR_CONTAINER_FULL = 'You cannot put more objects in this container.'
406local ERROR_NOT_POSSIBLE = 'Sorry, not possible.'
407
408local ITEMID = {
409 SPIDER_WEB = 182,
410 SOFTBOOTS_ACTIVE = 3549,
411 SOFTBOOTS = 6529,
412 SOFTBOOTS_WORN = 6530,
413 OBSIDIAN_KNIFE = 5908,
414 BLESSED_STAKE = 5942,
415 FISHING_ROD = 3483,
416 MECHANICAL_ROD = 9306,
417 ADVENTURERS_STONE = 16277,
418 GOLDEN_MUG = 2903,
419 RUST_REMOVER = 9016
420}
421
422local PRICE = {}
423PRICE.SOFTBOOTS_REFILL = 10000
424PRICE.BACKPACK = 20
425
426local PATTERN = {
427 XML_ATTR = "(%w+)=([\"'])(.-)%2",
428 XML_TAG = "<(%/?)([%w:]+)(.-)(%/?)>",
429 LABEL_PATH = "(.+)%~(.+)"
430}
431
432local CONT_NAME_LOCKER = "Locker"
433local CONT_NAME_DEPOT = "Depot Chest"
434
435local SUPPLY_CHECK_THRESHOLD = 3
436
437local LOG_STATUS = 'STATUS'
438local LOG_WARNING = 'WARNING'
439local LOG_ERROR = 'ERROR'
440local LOG_PROMPT = 'PROMPT'
441
442local TOWN_POSITIONS = {
443 {name='Venore', x=32940, y=32081},
444 {name='Edron', x=33209, y=31829},
445 {name='Carlin', x=32350, y=31799},
446 {name='Ankrahmun', x=33151, y=32825},
447 {name='Darashia', x=33236, y=32431},
448 {name='Liberty Bay', x=32314, y=32828},
449 {name='Thais', x=32367, y=32223},
450 {name='Svargrond', x=32248, y=31145},
451 {name='Ab\'dendriel', x=32675, y=31651},
452 {name='Port Hope', x=32628, y=32770},
453 {name='Yalahar', x=32802, y=31204},
454 {name='Farmine', x=33022, y=31513},
455 {name='Gray Island', x=33458, y=31320},
456 {name='Kazordoon', x=32630, y=31923},
457 {name='Gnomebase', x=32797, y=31776},
458 {name='Rathleton', x=33626, y=31894}
459}
460
461local THEME = {
462 light = {
463 title = {255, 255, 255},
464 primary = {255, 243, 204},
465 secondary = {206, 206, 206}
466 },
467 dark = {
468 title = {255, 255, 255},
469 primary = {135, 135, 135},
470 secondary = {206, 206, 206}
471 },
472 orange = {
473 title = {255, 255, 255},
474 primary = {240, 199, 166},
475 secondary = {206, 206, 206}
476 },
477 pink = {
478 title = {255, 255, 255},
479 primary = {242, 193, 193},
480 secondary = {206, 206, 206}
481 },
482 green = {
483 title = {255, 255, 255},
484 primary = {193, 240, 183},
485 secondary = {206, 206, 206}
486 },
487 blue = {
488 title = {255, 255, 255},
489 primary = {135, 210, 207},
490 secondary = {206, 206, 206}
491 },
492 purple = {
493 title = {255, 255, 255},
494 primary = {247, 225, 252},
495 secondary = {206, 206, 206}
496 }
497}
498
499-- True = 'town|depot~loot'
500-- Name = 'town|depot~loot.name'
501local SELLABLE_LOOT = {
502 ['thais'] = {
503 [3274] = true, -- axe
504 [3266] = true, -- battle axe
505 [3305] = true, -- battle hammer
506 [3413] = true, -- battle shield
507 [3337] = true, -- bone club
508 [3338] = true, -- bone sword
509 [3359] = true, -- brass armor
510 [3354] = true, -- brass helmet
511 [3372] = true, -- brass legs
512 [3411] = true, -- brass shield
513 [3283] = true, -- carlin sword
514 [3358] = true, -- chain armor
515 [3352] = true, -- chain helmet
516 [3558] = true, -- chain legs
517 [3270] = true, -- club
518 [3562] = true, -- coat
519 [3430] = true, -- copper shield
520 [3304] = true, -- crowbar
521 [3267] = true, -- dagger
522 [3275] = true, -- double axe
523 [3379] = true, -- doublet
524 [3425] = true, -- dwarven shield
525 [3269] = true, -- halberd
526 [3268] = true, -- hand axe
527 [3276] = true, -- hatchet
528 [3353] = true, -- iron helmet
529 [3561] = true, -- jacket
530 [3300] = true, -- katana
531 [3361] = true, -- leather armor
532 [3552] = true, -- leather boots
533 [3355] = true, -- leather helmet
534 [3559] = true, -- leather legs
535 [3374] = true, -- legion helmet
536 [3285] = true, -- long sword
537 [3286] = true, -- mace
538 [3282] = true, -- morning star
539 [3316] = true, -- orcish axe
540 [3357] = true, -- plate armor
541 [3557] = true, -- plate legs
542 [3410] = true, -- plate shield
543 [3272] = true, -- rapier
544 [3273] = true, -- sabre
545 [3377] = true, -- scale armor
546 [3294] = true, -- short sword
547 [3293] = true, -- sickle
548 [3462] = true, -- small axe
549 [3375] = true, -- soldier helmet
550 [3409] = true, -- steel shield
551 [3378] = true, -- studded armor
552 [3336] = true, -- studded club
553 [3376] = true, -- studded helmet
554 [3362] = true, -- studded legs
555 [3426] = true, -- studded shield
556 [17824] = true, -- swampling club
557 [3264] = true, -- sword
558 [3298] = true, -- throwing knife
559 [3265] = true, -- two handed sword
560 [3367] = true, -- viking helmet
561 [3412] = true -- wooden shield
562 },
563 ['svargrond'] = {
564 [3274] = true, -- axe
565 [3266] = true, -- battle axe
566 [3305] = true, -- battle hammer
567 [3413] = true, -- battle shield
568 [3337] = true, -- bone club
569 [3338] = true, -- bone sword
570 [3350] = true, -- bow
571 [3359] = true, -- brass armor
572 [3354] = true, -- brass helmet
573 [3372] = true, -- brass legs
574 [3411] = true, -- brass shield
575 [3283] = true, -- carlin sword
576 [3358] = true, -- chain armor
577 [3352] = true, -- chain helmet
578 [3558] = true, -- chain legs
579 [3270] = true, -- club
580 [3562] = true, -- coat
581 [3430] = true, -- copper shield
582 [3349] = true, -- crossbow
583 [3304] = true, -- crowbar
584 [3267] = true, -- dagger
585 [3275] = true, -- double axe
586 [3379] = true, -- doublet
587 [3425] = true, -- dwarven shield
588 [3269] = true, -- halberd
589 [3268] = true, -- hand axe
590 [3276] = true, -- hatchet
591 [3353] = true, -- iron helmet
592 [3561] = true, -- jacket
593 [3300] = true, -- katana
594 [3361] = true, -- leather armor
595 [3552] = true, -- leather boots
596 [3355] = true, -- leather helmet
597 [3559] = true, -- leather legs
598 [3374] = true, -- legion helmet
599 [3285] = true, -- long sword
600 [3286] = true, -- mace
601 [3282] = true, -- morning star
602 [3316] = true, -- orcish axe
603 [3357] = true, -- plate armor
604 [3557] = true, -- plate legs
605 [3410] = true, -- plate shield
606 [3272] = true, -- rapier
607 [3273] = true, -- sabre
608 [3377] = true, -- scale armor
609 [3294] = true, -- short sword
610 [3293] = true, -- sickle
611 [3462] = true, -- small axe
612 [3375] = true, -- soldier helmet
613 [3277] = true, -- spear
614 [3351] = true, -- steel helmet
615 [3409] = true, -- steel shield
616 [3378] = true, -- studded armor
617 [3336] = true, -- studded club
618 [3376] = true, -- studded helmet
619 [3362] = true, -- studded legs
620 [3426] = true, -- studded shield
621 [17824] = true, -- swampling club
622 [3264] = true, -- sword
623 [3298] = true, -- throwing knife
624 [3265] = true, -- two handed sword
625 [3367] = true, -- viking helmet
626 [3431] = true, -- viking shield
627 [3412] = true -- wooden shield
628 },
629 ['port hope'] = {
630 [3274] = true, -- axe
631 [11511] = true, -- banana sash
632 [3266] = true, -- battle axe
633 [3305] = true, -- battle hammer
634 [3413] = true, -- battle shield
635 [3337] = true, -- bone club
636 [3338] = true, -- bone sword
637 [3350] = true, -- bow
638 [3359] = true, -- brass armor
639 [3354] = true, -- brass helmet
640 [3372] = true, -- brass legs
641 [3411] = true, -- brass shield
642 [3283] = true, -- carlin sword
643 [3358] = true, -- chain armor
644 [3352] = true, -- chain helmet
645 [3558] = true, -- chain legs
646 [3407] = true, -- charmer's tiara
647 [3270] = true, -- club
648 [3562] = true, -- coat
649 [3430] = true, -- copper shield
650 [3556] = true, -- crocodile boots
651 [3349] = true, -- crossbow
652 [3304] = true, -- crowbar
653 [3267] = true, -- dagger
654 [3275] = true, -- double axe
655 [3379] = true, -- doublet
656 [3425] = true, -- dwarven shield
657 [3406] = true, -- feather headdress
658 [3269] = true, -- halberd
659 [3268] = true, -- hand axe
660 [3276] = true, -- hatchet
661 [3405] = true, -- horseman helmet
662 [3353] = true, -- iron helmet
663 [3561] = true, -- jacket
664 [3300] = true, -- katana
665 [11471] = true, -- kongra's shoulderpad
666 [3361] = true, -- leather armor
667 [3552] = true, -- leather boots
668 [3355] = true, -- leather helmet
669 [3559] = true, -- leather legs
670 [3374] = true, -- legion helmet
671 [3285] = true, -- long sword
672 [3286] = true, -- mace
673 [3282] = true, -- morning star
674 [3316] = true, -- orcish axe
675 [3357] = true, -- plate armor
676 [3557] = true, -- plate legs
677 [3410] = true, -- plate shield
678 [3272] = true, -- rapier
679 [3346] = true, -- ripper lance
680 [3273] = true, -- sabre
681 [3445] = true, -- salamander shield
682 [3377] = true, -- scale armor
683 [3444] = true, -- sentinel shield
684 [3294] = true, -- short sword
685 [3293] = true, -- sickle
686 [3462] = true, -- small axe
687 [3375] = true, -- soldier helmet
688 [3351] = true, -- steel helmet
689 [3409] = true, -- steel shield
690 [3378] = true, -- studded armor
691 [3336] = true, -- studded club
692 [3376] = true, -- studded helmet
693 [3362] = true, -- studded legs
694 [3426] = true, -- studded shield
695 [17824] = true, -- swampling club
696 [3264] = true, -- sword
697 [3345] = true, -- templar scytheblade
698 [3298] = true, -- throwing knife
699 [3265] = true, -- two handed sword
700 [3367] = true, -- viking helmet
701 [3431] = true, -- viking shield
702 [3412] = true -- wooden shield
703 },
704 ['liberty bay'] = {
705 [3274] = true, -- axe
706 [3266] = true, -- battle axe
707 [3305] = true, -- battle hammer
708 [3413] = true, -- battle shield
709 [3337] = true, -- bone club
710 [3338] = true, -- bone sword
711 [3350] = true, -- bow
712 [3359] = true, -- brass armor
713 [3354] = true, -- brass helmet
714 [3372] = true, -- brass legs
715 [3411] = true, -- brass shield
716 [3283] = true, -- carlin sword
717 [3358] = true, -- chain armor
718 [3352] = true, -- chain helmet
719 [3558] = true, -- chain legs
720 [3270] = true, -- club
721 [3562] = true, -- coat
722 [3430] = true, -- copper shield
723 [3349] = true, -- crossbow
724 [3304] = true, -- crowbar
725 [3267] = true, -- dagger
726 [3275] = true, -- double axe
727 [3379] = true, -- doublet
728 [3425] = true, -- dwarven shield
729 [3269] = true, -- halberd
730 [3268] = true, -- hand axe
731 [3276] = true, -- hatchet
732 [3353] = true, -- iron helmet
733 [3561] = true, -- jacket
734 [3300] = true, -- katana
735 [3361] = true, -- leather armor
736 [3552] = true, -- leather boots
737 [3355] = true, -- leather helmet
738 [3559] = true, -- leather legs
739 [3374] = true, -- legion helmet
740 [3285] = true, -- long sword
741 [3286] = true, -- mace
742 [3282] = true, -- morning star
743 [3316] = true, -- orcish axe
744 [3357] = true, -- plate armor
745 [3557] = true, -- plate legs
746 [3410] = true, -- plate shield
747 [3272] = true, -- rapier
748 [3273] = true, -- sabre
749 [3377] = true, -- scale armor
750 [3294] = true, -- short sword
751 [3293] = true, -- sickle
752 [3462] = true, -- small axe
753 [3375] = true, -- soldier helmet
754 [3409] = true, -- steel shield
755 [3378] = true, -- studded armor
756 [3336] = true, -- studded club
757 [3376] = true, -- studded helmet
758 [3362] = true, -- studded legs
759 [3426] = true, -- studded shield
760 [17824] = true, -- swampling club
761 [3264] = true, -- sword
762 [3298] = true, -- throwing knife
763 [3265] = true, -- two handed sword
764 [3367] = true, -- viking helmet
765 [3431] = true, -- viking shield
766 [3412] = true -- wooden shield
767 },
768 ['edron'] = {
769 [3274] = true, -- axe
770 [3317] = true, -- barbarian axe
771 [3266] = true, -- battle axe
772 [3305] = true, -- battle hammer
773 [3413] = true, -- battle shield
774 [3337] = true, -- bone club
775 [3338] = true, -- bone sword
776 [3350] = true, -- bow
777 [3359] = true, -- brass armor
778 [3354] = true, -- brass helmet
779 [3372] = true, -- brass legs
780 [3411] = true, -- brass shield
781 [3283] = true, -- carlin sword
782 [3358] = true, -- chain armor
783 [3352] = true, -- chain helmet
784 [3558] = true, -- chain legs
785 [3311] = true, -- clerical mace
786 [3270] = true, -- club
787 [3562] = true, -- coat
788 [3430] = true, -- copper shield
789 [3349] = true, -- crossbow
790 [3304] = true, -- crowbar
791 [3267] = true, -- dagger
792 [3275] = true, -- double axe
793 [3379] = true, -- doublet
794 [3425] = true, -- dwarven shield
795 [3269] = true, -- halberd
796 [3268] = true, -- hand axe
797 [3276] = true, -- hatchet
798 [3353] = true, -- iron helmet
799 [3561] = true, -- jacket
800 [3300] = true, -- katana
801 [3361] = true, -- leather armor
802 [3552] = true, -- leather boots
803 [3355] = true, -- leather helmet
804 [3559] = true, -- leather legs
805 [3374] = true, -- legion helmet
806 [3285] = true, -- long sword
807 [3286] = true, -- mace
808 [3282] = true, -- morning star
809 [3316] = true, -- orcish axe
810 [3357] = true, -- plate armor
811 [3557] = true, -- plate legs
812 [3410] = true, -- plate shield
813 [3272] = true, -- rapier
814 [3273] = true, -- sabre
815 [3377] = true, -- scale armor
816 [3294] = true, -- short sword
817 [3293] = true, -- sickle
818 [3462] = true, -- small axe
819 [3375] = true, -- soldier helmet
820 [3277] = true, -- spear
821 [3351] = true, -- steel helmet
822 [3409] = true, -- steel shield
823 [3378] = true, -- studded armor
824 [3336] = true, -- studded club
825 [3376] = true, -- studded helmet
826 [3362] = true, -- studded legs
827 [3426] = true, -- studded shield
828 [17824] = true, -- swampling club
829 [3264] = true, -- sword
830 [3298] = true, -- throwing knife
831 [3265] = true, -- two handed sword
832 [3367] = true, -- viking helmet
833 [3412] = true -- wooden shield
834 },
835 ['venore'] = {
836 [3274] = 'romella', -- axe
837 [3266] = 'romella', -- battle axe
838 [3305] = 'romella', -- battle hammer
839 [3337] = 'romella', -- bone club
840 [3338] = 'romella', -- bone sword
841 [3283] = 'romella', -- carlin sword
842 [3270] = 'romella', -- club
843 [3304] = 'romella', -- crowbar
844 [3267] = 'romella', -- dagger
845 [3275] = 'romella', -- double axe
846 [3269] = 'romella', -- halberd
847 [3268] = 'romella', -- hand axe
848 [3276] = 'romella', -- hatchet
849 [3300] = 'romella', -- katana
850 [3285] = 'romella', -- long sword
851 [3286] = 'romella', -- mace
852 [3282] = 'romella', -- morning star
853 [3316] = 'romella', -- orcish axe
854 [3272] = 'romella', -- rapier
855 [3273] = 'romella', -- sabre
856 [3294] = 'romella', -- short sword
857 [3462] = 'romella', -- small axe
858 [3293] = 'romella', -- sickle
859 [3336] = 'romella', -- studded club
860 [17824] = 'romella', -- swampling club
861 [3264] = 'romella', -- sword
862 [3298] = 'romella', -- throwing knife
863 [3265] = 'romella', -- two handed sword
864
865 [3413] = 'yanni', -- battle shield
866 [3359] = 'yanni', -- brass armor
867 [3354] = 'yanni', -- brass helmet
868 [3372] = 'yanni', -- brass legs
869 [3411] = 'yanni', -- brass shield
870 [3358] = 'yanni', -- chain armor
871 [3352] = 'yanni', -- chain helmet
872 [3558] = 'yanni', -- chain legs
873 [3562] = 'yanni', -- coat
874 [3430] = 'yanni', -- copper shield
875 [3379] = 'yanni', -- doublet
876 [3425] = 'yanni', -- dwarven shield
877 [3353] = 'yanni', -- iron helmet
878 [3561] = 'yanni', -- jacket
879 [3361] = 'yanni', -- leather armor
880 [3552] = 'yanni', -- leather boots
881 [3355] = 'yanni', -- leather helmet
882 [3559] = 'yanni', -- leather legs
883 [3374] = 'yanni', -- legion helmet
884 [3357] = 'yanni', -- plate armor
885 [3557] = 'yanni', -- plate legs
886 [3410] = 'yanni', -- plate shield
887 [3377] = 'yanni', -- scale armor
888 [3375] = 'yanni', -- soldier helmet
889 [3409] = 'yanni', -- steel shield
890 [3378] = 'yanni', -- studded armor
891 [3376] = 'yanni', -- studded helmet
892 [3362] = 'yanni', -- studded legs
893 [3426] = 'yanni', -- studded shield
894 [3367] = 'yanni', -- viking helmet
895 [3412] = 'yanni' -- wooden shield
896 },
897 ['darashia'] = {
898 [3274] = 'habdel', -- axe
899 [3266] = 'habdel', -- battle axe
900 [3305] = 'habdel', -- battle hammer
901 [3337] = 'habdel', -- bone club
902 [3338] = 'habdel', -- bone sword
903 [3283] = 'habdel', -- carlin sword
904 [3270] = 'habdel', -- club
905 [3304] = 'habdel', -- crowbar
906 [3267] = 'habdel', -- dagger
907 [3275] = 'habdel', -- double axe
908 [3269] = 'habdel', -- halberd
909 [3268] = 'habdel', -- hand axe
910 [3276] = 'habdel', -- hatchet
911 [3300] = 'habdel', -- katana
912 [3285] = 'habdel', -- long sword
913 [3286] = 'habdel', -- mace
914 [3282] = 'habdel', -- morning star
915 [3316] = 'habdel', -- orcish axe
916 [3272] = 'habdel', -- rapier
917 [3273] = 'habdel', -- sabre
918 [3294] = 'habdel', -- short sword
919 [3462] = 'habdel', -- small axe
920 [3293] = 'habdel', -- sickle
921 [3336] = 'habdel', -- studded club
922 [17824] = 'habdel', -- swampling club
923 [3264] = 'habdel', -- sword
924 [3298] = 'habdel', -- throwing knife
925 [3265] = 'habdel', -- two handed sword
926
927 [3413] = 'azil', -- battle shield
928 [3359] = 'azil', -- brass armor
929 [3354] = 'azil', -- brass helmet
930 [3372] = 'azil', -- brass legs
931 [3411] = 'azil', -- brass shield
932 [3358] = 'azil', -- chain armor
933 [3352] = 'azil', -- chain helmet
934 [3558] = 'azil', -- chain legs
935 [3562] = 'azil', -- coat
936 [3430] = 'azil', -- copper shield
937 [3379] = 'azil', -- doublet
938 [3425] = 'azil', -- dwarven shield
939 [3353] = 'azil', -- iron helmet
940 [3561] = 'azil', -- jacket
941 [3361] = 'azil', -- leather armor
942 [3552] = 'azil', -- leather boots
943 [3355] = 'azil', -- leather helmet
944 [3559] = 'azil', -- leather legs
945 [3374] = 'azil', -- legion helmet
946 [3357] = 'azil', -- plate armor
947 [3557] = 'azil', -- plate legs
948 [3410] = 'azil', -- plate shield
949 [3377] = 'azil', -- scale armor
950 [3375] = 'azil', -- soldier helmet
951 [3409] = 'azil', -- steel shield
952 [3378] = 'azil', -- studded armor
953 [3376] = 'azil', -- studded helmet
954 [3362] = 'azil', -- studded legs
955 [3351] = 'azil', -- steel helmet
956 [3426] = 'azil', -- studded shield
957 [3367] = 'azil', -- viking helmet
958 [3431] = 'azil', -- viking helmet
959 [3412] = 'azil' -- wooden shield
960 },
961 ['ankrahmun'] = {
962 [3274] = true, --axe
963 [3266] = true, --battle axe
964 [3305] = true, --battle hammer
965 [3413] = true, --battle shield
966 [3337] = true, --bone club
967 [3338] = true, --bone sword
968 [3359] = true, --brass armor
969 [3354] = true, --brass helmet
970 [3372] = true, --brass legs
971 [3411] = true, --brass shield
972 [3283] = true, --carlin sword
973 [3358] = true, --chain armor
974 [3352] = true, --chain helmet
975 [3558] = true, --chain legs
976 [3270] = true, --club
977 [3562] = true, --coat
978 [3430] = true, --copper shield
979 [3304] = true, --crowbar
980 [3267] = true, --dagger
981 [3275] = true, --double axe
982 [3379] = true, --doublet
983 [3425] = true, --dwarven shield
984 [3280] = true, --fire sword
985 [3269] = true, --halberd
986 [3268] = true, --hand axe
987 [3276] = true, --hatchet
988 [3353] = true, --iron helmet
989 [3561] = true, --jacket
990 [3300] = true, --katana
991 [3361] = true, --leather armor
992 [3552] = true, --leather boots
993 [3355] = true, --leather helmet
994 [3559] = true, --leather legs
995 [3374] = true, --legion helmet
996 [3285] = true, --longsword
997 [3286] = true, --mace
998 [3282] = true, --morning star
999 [3316] = true, --orcish axe
1000 [3357] = true, --plate armor
1001 [3557] = true, --plate legs
1002 [3410] = true, --plate shield
1003 [3272] = true, --rapier
1004 [3273] = true, --sabre
1005 [3377] = true, --scale armor
1006 [3294] = true, --short sword
1007 [3293] = true, --sickle
1008 [3462] = true, --small axe
1009 [3375] = true, --soldier helmet
1010 [3351] = true, --steel helmet
1011 [3409] = true, --steel shield
1012 [3378] = true, --studded armor
1013 [3336] = true, --studded club
1014 [3376] = true, --studded helmet
1015 [3362] = true, --studded legs
1016 [3426] = true, --studded shield
1017 [17824] = true, --swampling club
1018 [3264] = true, --sword
1019 [3298] = true, --throwing knife
1020 [3265] = true, --two handed sword
1021 [3367] = true, --viking helmet
1022 [3431] = true, --viking shield
1023 [3412] = true, --wooden shield
1024 [3287] = true --throwing star
1025 },
1026 ['yalahar'] = {
1027 [3274] = 'morpel', -- axe
1028 [3317] = 'morpel', -- barbarian axe
1029 [3266] = 'morpel', -- battle axe
1030 [3305] = 'morpel', -- battle hammer
1031 [3413] = 'morpel', -- battle shield
1032 [3337] = 'morpel', -- bone club
1033 [3338] = 'morpel', -- bone sword
1034 [3359] = 'morpel', -- brass armor
1035 [3354] = 'morpel', -- brass helmet
1036 [3372] = 'morpel', -- brass legs
1037 [3411] = 'morpel', -- brass shield
1038 [3283] = 'morpel', -- carlin sword
1039 [3358] = 'morpel', -- chain armor
1040 [3352] = 'morpel', -- chain helmet
1041 [3558] = 'morpel', -- chain legs
1042 [3311] = 'morpel', -- clerical mace
1043 [3270] = 'morpel', -- club
1044 [3562] = 'morpel', -- coat
1045 [3430] = 'morpel', -- copper shield
1046 [3304] = 'morpel', -- crowbar
1047 [3267] = 'morpel', -- dagger
1048 [3275] = 'morpel', -- double axe
1049 [3379] = 'morpel', -- doublet
1050 [3425] = 'morpel', -- dwarven shield
1051 [3269] = 'morpel', -- halberd
1052 [3268] = 'morpel', -- hand axe
1053 [3276] = 'morpel', -- hatchet
1054 [3353] = 'morpel', -- iron helmet
1055 [3561] = 'morpel', -- jacket
1056 [3300] = 'morpel', -- katana
1057 [3361] = 'morpel', -- leather armor
1058 [3552] = 'morpel', -- leather boots
1059 [3355] = 'morpel', -- leather helmet
1060 [3559] = 'morpel', -- leather legs
1061 [3374] = 'morpel', -- legion helmet
1062 [3285] = 'morpel', -- longsword
1063 [3286] = 'morpel', -- mace
1064 [3282] = 'morpel', -- morning star
1065 [3316] = 'morpel', -- orcish axe
1066 [3357] = 'morpel', -- plate armor
1067 [3557] = 'morpel', -- plate legs
1068 [3410] = 'morpel', -- plate shield
1069 [3272] = 'morpel', -- rapier
1070 [3273] = 'morpel', -- sabre
1071 [3377] = 'morpel', -- scale armor
1072 [3294] = 'morpel', -- short sword
1073 [3293] = 'morpel', -- sickle
1074 [3462] = 'morpel', -- small axe
1075 [3375] = 'morpel', -- soldier helmet
1076 [3351] = 'morpel', -- steel helmet
1077 [3409] = 'morpel', -- steel shield
1078 [3378] = 'morpel', -- studded armor
1079 [3336] = 'morpel', -- studded club
1080 [3376] = 'morpel', -- studded helmet
1081 [3362] = 'morpel', -- studded legs
1082 [3426] = 'morpel', -- studded shield
1083 [17824] = 'morpel', -- swampling club
1084 [3264] = 'morpel', -- sword
1085 [3298] = 'morpel', -- throwing knife
1086 [3265] = 'morpel', -- two handed sword
1087 [3367] = 'morpel', -- viking helmet
1088 [3431] = 'morpel', -- viking shield
1089 [3412] = 'morpel', -- wooden shield
1090
1091 [3027] = 'oiriz', --black pearl
1092 [16119] = 'oiriz', --blue crystal shard
1093 [16124] = 'oiriz', --blue crystal splinter
1094 [16123] = 'oiriz', --brown crystal splinter
1095 [16125] = 'oiriz', --cyan crystal fragment
1096 [281] = 'oiriz', --giant shimmering pearl
1097 [282] = 'oiriz', --giant shimmering pearl
1098 [9058] = 'oiriz', --gold ingot
1099 [16127] = 'oiriz', --green crystal fragment
1100 [16121] = 'oiriz', --green crystal shard
1101 [16122] = 'oiriz', --green crystal splinter
1102 [16126] = 'oiriz', --red crystal fragment
1103 [3033] = 'oiriz', --small amethyst
1104 [3028] = 'oiriz', --small diamond
1105 [3032] = 'oiriz', --small emerald
1106 [3030] = 'oiriz', --small ruby
1107 [3029] = 'oiriz', --small sapphire
1108 [9057] = 'oiriz', --small topaz
1109 [16120] = 'oiriz', --violet crystal shard
1110 [3004] = 'oiriz', --wedding ring
1111 [3026] = 'oiriz' --white pearl
1112 },
1113 ['farmine'] = {
1114 [3274] = 'esrik', --axe
1115 [3266] = 'esrik', --battle axe
1116 [3305] = 'esrik', --battle hammer
1117 [3413] = 'esrik', --battle shield
1118 [3337] = 'esrik', --bone club
1119 [10404] = 'esrik', --bone shoulderplate
1120 [3338] = 'esrik', --bone sword
1121 [3359] = 'esrik', --brass armor
1122 [3354] = 'esrik', --brass helmet
1123 [3372] = 'esrik', --brass legs
1124 [3411] = 'esrik', --brass shield
1125 [11660] = 'esrik', --broken draken mail
1126 [10418] = 'esrik', --broken halberd
1127 [11661] = 'esrik', --broken slicer
1128 [3283] = 'esrik', --carlin sword
1129 [3358] = 'esrik', --chain armor
1130 [3352] = 'esrik', --chain helmet
1131 [3558] = 'esrik', --chain legs
1132 [3270] = 'esrik', --club
1133 [3562] = 'esrik', --coat
1134 [3430] = 'esrik', --copper shield
1135 [3304] = 'esrik', --crowbar
1136 [10410] = 'esrik', --cursed shoulder spikes
1137 [3267] = 'esrik', --dagger
1138 [3275] = 'esrik', --double axe
1139 [3379] = 'esrik', --doublet
1140 [10391] = 'esrik', --drachaku
1141 [4033] = 'esrik', --draken boots
1142 [11659] = 'esrik', --draken wristbands
1143 [10388] = 'esrik', --drakinata
1144 [3425] = 'esrik', --dwarven shield
1145 [10323] = 'esrik', --guardian boots
1146 [3269] = 'esrik', --halberd
1147 [3268] = 'esrik', --hand axe
1148 [3276] = 'esrik', --hatchet
1149 [10416] = 'esrik', --high guard shoulderplates
1150 [3353] = 'esrik', --iron helmet
1151 [3561] = 'esrik', --jacket
1152 [3300] = 'esrik', --katana
1153 [3361] = 'esrik', --leather armor
1154 [3552] = 'esrik', --leather boots
1155 [3355] = 'esrik', --leather helmet
1156 [3559] = 'esrik', --leather legs
1157 [3374] = 'esrik', --legion helmet
1158 [3285] = 'esrik', --longsword
1159 [3286] = 'esrik', --mace
1160 [3282] = 'esrik', --morning star
1161 [3316] = 'esrik', --orcish axe
1162 [3357] = 'esrik', --plate armor
1163 [3557] = 'esrik', --plate legs
1164 [3410] = 'esrik', --plate shield
1165 [3272] = 'esrik', --rapier
1166 [10289] = 'esrik', --red lantern
1167 [3273] = 'esrik', --sabre
1168 [10386] = 'esrik', --sai
1169 [3377] = 'esrik', --scale armor
1170 [3294] = 'esrik', --short sword
1171 [3293] = 'esrik', --sickle
1172 [3462] = 'esrik', --small axe
1173 [3375] = 'esrik', --soldier helmet
1174 [10408] = 'esrik', --spiked iron ball
1175 [3271] = 'esrik', --spike sword
1176 [3351] = 'esrik', --steel helmet
1177 [3409] = 'esrik', --steel shield
1178 [3378] = 'esrik', --studded armor
1179 [3336] = 'esrik', --studded club
1180 [3376] = 'esrik', --studded helmet
1181 [3362] = 'esrik', --studded legs
1182 [3426] = 'esrik', --studded shield
1183 [17824] = 'esrik', --swampling club
1184 [3264] = 'esrik', --sword
1185 [3298] = 'esrik', --throwing knife
1186 [11657] = 'esrik', --twiceslicer
1187 [10392] = 'esrik', --twin hooks
1188 [3265] = 'esrik', --two handed sword
1189 [3367] = 'esrik', --viking helmet
1190 [3431] = 'esrik', --viking shield
1191 [10412] = 'esrik', --wailing widow's necklace
1192 [10405] = 'esrik', --warmaster's wristguards
1193 [3412] = 'esrik', --wooden shield
1194 [10384] = 'esrik', --zaoan armor
1195 [10406] = 'esrik', --zaoan halberd
1196 [10385] = 'esrik', --zaoan helmet
1197 [10387] = 'esrik', --zaoan legs
1198 [10386] = 'esrik', --zaoan shoes
1199 [10390] = 'esrik', --zaoan sword
1200 [10414] = 'esrik', --zaogun shoulderplates
1201
1202 [3350] = 'pompan', --bow
1203 [10409] = 'pompan', --corrupted flag
1204 [3349] = 'pompan', --crossbow
1205 [10415] = 'pompan', --high guard flag
1206 [10417] = 'pompan', --legionnaire flags
1207 [10413] = 'pompan' --zaogun flag
1208 }
1209}
1210
1211local TRAVEL_ROUTES = {
1212 ['thais~venore'] = {
1213 cost = 170,
1214 transcript = {
1215 ['Captain Bluebear'] = {'hi', 'venore', 'yes'}
1216 }
1217 },
1218 ['thais~edron'] = {
1219 cost = 160,
1220 transcript = {
1221 ['Captain Bluebear'] = {'hi', 'edron', 'yes'}
1222 }
1223 },
1224 ['thais~carlin'] = {
1225 cost = 110,
1226 transcript = {
1227 ['Captain Bluebear'] = {'hi', 'carlin', 'yes'}
1228 }
1229 },
1230 ['thais~ab\'dendriel'] = {
1231 cost = 130,
1232 transcript = {
1233 ['Captain Bluebear'] = {'hi', 'ab\'dendriel', 'yes'}
1234 }
1235 },
1236 ['thais~port hope'] = {
1237 cost = 160,
1238 transcript = {
1239 ['Captain Bluebear'] = {'hi', 'port hope', 'yes'}
1240 }
1241 },
1242 ['thais~liberty bay'] = {
1243 cost = 330,
1244 transcript = {
1245 ['Captain Bluebear'] = {'hi', 'edron', 'yes'},
1246 ['Captain Seahorse'] = {'hi', 'thais', 'yes'}
1247 }
1248 },
1249 ['thais~svargrond'] = {
1250 cost = 180,
1251 transcript = {
1252 ['Captain Bluebear'] = {'hi', 'svargrond', 'yes'}
1253 }
1254 },
1255 ['thais~yalahar'] = {
1256 cost = 200,
1257 transcript = {
1258 ['Captain Bluebear'] = {'hi', 'yalahar', 'yes'}
1259 }
1260 },
1261 ['thais~roshamuul'] = {
1262 cost = 210,
1263 transcript = {
1264 ['Captain Bluebear'] = {'hi', 'roshamuul', 'yes'}
1265 }
1266 },
1267 ['thais~oramond'] = {
1268 cost = 150,
1269 transcript = {
1270 ['Captain Bluebear'] = {'hi', 'oramond', 'yes'}
1271 }
1272 },
1273 ['thais~darashia'] = {
1274 cost = 340,
1275 transcript = {
1276 ['Captain Bluebear'] = {'hi', 'port hope', 'yes'},
1277 ['Charles'] = {'hi', 'darashia', 'yes'}
1278 }
1279 },
1280 ['thais~ankrahmun'] = {
1281 cost = 270,
1282 transcript = {
1283 ['Captain Bluebear'] = {'hi', 'port hope', 'yes'},
1284 ['Charles'] = {'hi', 'ankrahmun', 'yes'}
1285 }
1286 },
1287 ['thais~gray island'] = {
1288 cost = 320,
1289 transcript = {
1290 ['Captain Bluebear'] = {'hi', 'edron', 'yes'},
1291 ['Captain Seahorse'] = {'hi', 'gray island', 'yes'}
1292 }
1293 },
1294 ['venore~thais'] = {
1295 cost = 170,
1296 transcript = {
1297 ['Captain Fearless'] = {'hi', 'thais', 'yes'}
1298 }
1299 },
1300 ['venore~carlin'] = {
1301 cost = 130,
1302 transcript = {
1303 ['Captain Fearless'] = {'hi', 'carlin', 'yes'}
1304 }
1305 },
1306 ['venore~ab\'dendriel'] = {
1307 cost = 90,
1308 transcript = {
1309 ['Captain Fearless'] = {'hi', 'ab\'dendriel', 'yes'}
1310 }
1311 },
1312 ['venore~port hope'] = {
1313 cost = 160,
1314 transcript = {
1315 ['Captain Fearless'] = {'hi', 'port hope', 'yes'}
1316 }
1317 },
1318 ['venore~edron'] = {
1319 cost = 40,
1320 transcript = {
1321 ['Captain Fearless'] = {'hi', 'edron', 'yes'}
1322 }
1323 },
1324 ['venore~darashia'] = {
1325 cost = 340,
1326 transcript = {
1327 ['Captain Fearless'] = {'hi', 'port hope', 'yes'},
1328 ['Charles'] = {'hi', 'darashia', 'yes'}
1329 }
1330 },
1331 ['venore~liberty bay'] = {
1332 cost = 180,
1333 transcript = {
1334 ['Captain Fearless'] = {'hi', 'liberty bay', 'yes'}
1335 }
1336 },
1337 ['venore~svargrond'] = {
1338 cost = 150,
1339 transcript = {
1340 ['Captain Fearless'] = {'hi', 'svargrond', 'yes'}
1341 }
1342 },
1343 ['venore~yalahar'] = {
1344 cost = 185,
1345 transcript = {
1346 ['Captain Fearless'] = {'hi', 'yalahar', 'yes'}
1347 }
1348 },
1349 ['venore~gray island'] = {
1350 cost = 160,
1351 transcript = {
1352 ['Captain Fearless'] = {'hi', 'gray island', 'yes'}
1353 }
1354 },
1355 ['venore~oramond'] = {
1356 cost = 320,
1357 transcript = {
1358 ['Captain Fearless'] = {'hi', 'thais', 'yes'},
1359 ['Captain Bluebear'] = {'hi', 'oramond', 'yes'}
1360 }
1361 },
1362 ['venore~roshamuul'] = {
1363 cost = 380,
1364 transcript = {
1365 ['Captain Fearless'] = {'hi', 'thais', 'yes'},
1366 ['Captain Bluebear'] = {'hi', 'roshamuul', 'yes'}
1367 }
1368 },
1369 ['venore~ankrahmun'] = {
1370 cost = 150,
1371 transcript = {
1372 ['Captain Fearless'] = {'hi', 'ankrahmun', 'yes'}
1373 }
1374 },
1375 ['darashia~edron'] = {
1376 route = 'carpet',
1377 cost = 40,
1378 transcript = {
1379 ['Chemar'] = {'hi', 'edron', 'yes'}
1380 }
1381 },
1382 ['darashia~svargrond'] = {
1383 route = 'carpet',
1384 cost = 60,
1385 transcript = {
1386 ['Chemar'] = {'hi', 'svargrond', 'yes'}
1387 }
1388 },
1389 ['darashia~ankrahmun'] = {
1390 cost = 100,
1391 transcript = {
1392 ['Petros'] = {'hi', 'ankrahmun', 'yes'}
1393 }
1394 },
1395 ['darashia~liberty bay'] = {
1396 cost = 200,
1397 transcript = {
1398 ['Petros'] = {'hi', 'liberty bay', 'yes'}
1399 }
1400 },
1401 ['darashia~port hope'] = {
1402 cost = 180,
1403 transcript = {
1404 ['Petros'] = {'hi', 'port hope', 'yes'}
1405 }
1406 },
1407 ['darashia~venore'] = {
1408 cost = 60,
1409 transcript = {
1410 ['Petros'] = {'hi', 'venore', 'yes'}
1411 }
1412 },
1413 ['darashia~yalahar'] = {
1414 cost = 210,
1415 transcript = {
1416 ['Petros'] = {'hi', 'yalahar', 'yes'}
1417 }
1418 },
1419 ['darashia~thais'] = {
1420 cost = 230,
1421 transcript = {
1422 ['Petros'] = {'hi', 'venore', 'yes'},
1423 ['Captain Fearless'] = {'hi', 'thais', 'yes'}
1424 }
1425 },
1426 ['darashia~roshamuul'] = {
1427 cost = 440,
1428 transcript = {
1429 ['Petros'] = {'hi', 'venore', 'yes'},
1430 ['Captain Fearless'] = {'hi', 'thais', 'yes'},
1431 ['Captain Bluebear'] = {'hi', 'roshamuul', 'yes'}
1432 }
1433 },
1434 ['darashia~oramond'] = {
1435 cost = 380,
1436 transcript = {
1437 ['Petros'] = {'hi', 'venore', 'yes'},
1438 ['Captain Fearless'] = {'hi', 'thais', 'yes'},
1439 ['Captain Bluebear'] = {'hi', 'oramond', 'yes'}
1440 }
1441 },
1442 ['darashia~ab\'dendriel'] = {
1443 cost = 150,
1444 transcript = {
1445 ['Petros'] = {'hi', 'venore', 'yes'},
1446 ['Captain Fearless'] = {'hi', 'ab\'dendriel', 'yes'}
1447 }
1448 },
1449 ['darashia~carlin'] = {
1450 cost = 190,
1451 transcript = {
1452 ['Petros'] = {'hi', 'venore', 'yes'},
1453 ['Captain Fearless'] = {'hi', 'carlin', 'yes'}
1454 }
1455 },
1456 ['darashia~gray island'] = {
1457 cost = 160,
1458 transcript = {
1459 ['Petros'] = {'hi', 'gray island', 'yes'}
1460 }
1461 },
1462 ['svargrond~darashia'] = {
1463 route = 'carpet',
1464 cost = 60,
1465 transcript = {
1466 ['Iyad'] = {'hi', 'darashia', 'yes'}
1467 }
1468 },
1469 ['svargrond~edron'] = {
1470 route = 'carpet',
1471 cost = 60,
1472 transcript = {
1473 ['Iyad'] = {'hi', 'edron', 'yes'}
1474 }
1475 },
1476 ['svargrond~venore'] = {
1477 cost = 150,
1478 transcript = {
1479 ['Captain Breezelda'] = {'hi', 'venore', 'yes'}
1480 }
1481 },
1482 ['svargrond~thais'] = {
1483 cost = 180,
1484 transcript = {
1485 ['Captain Breezelda'] = {'hi', 'thais', 'yes'}
1486 }
1487 },
1488 ['svargrond~carlin'] = {
1489 cost = 110,
1490 transcript = {
1491 ['Captain Breezelda'] = {'hi', 'carlin', 'yes'}
1492 }
1493 },
1494 ['svargrond~yalahar'] = {
1495 cost = 335,
1496 transcript = {
1497 ['Captain Breezelda'] = {'hi', 'venore', 'yes'},
1498 ['Captain Fearless'] = {'hi', 'yalahar', 'yes'}
1499 }
1500 },
1501 ['svargrond~ab\'dendriel'] = {
1502 cost = 240,
1503 transcript = {
1504 ['Captain Breezelda'] = {'hi', 'venore', 'yes'},
1505 ['Captain Fearless'] = {'hi', 'ab\'dendriel', 'yes'}
1506 }
1507 },
1508 ['svargrond~ankrahmun'] = {
1509 cost = 300,
1510 transcript = {
1511 ['Captain Breezelda'] = {'hi', 'venore', 'yes'},
1512 ['Captain Fearless'] = {'hi', 'ankrahmun', 'yes'}
1513 }
1514 },
1515 ['svargrond~gray island'] = {
1516 cost = 310,
1517 transcript = {
1518 ['Captain Breezelda'] = {'hi', 'venore', 'yes'},
1519 ['Captain Fearless'] = {'hi', 'gray island', 'yes'}
1520 }
1521 },
1522 ['svargrond~liberty bay'] = {
1523 cost = 330,
1524 transcript = {
1525 ['Captain Breezelda'] = {'hi', 'venore', 'yes'},
1526 ['Captain Fearless'] = {'hi', 'liberty bay', 'yes'}
1527 }
1528 },
1529 ['svargrond~port hope'] = {
1530 cost = 310,
1531 transcript = {
1532 ['Captain Breezelda'] = {'hi', 'venore', 'yes'},
1533 ['Captain Fearless'] = {'hi', 'port hope', 'yes'}
1534 }
1535 },
1536 ['svargrond~roshamuul'] = {
1537 cost = 390,
1538 transcript = {
1539 ['Captain Breezelda'] = {'hi', 'thais', 'yes'},
1540 ['Captain Bluebear'] = {'hi', 'roshamuul', 'yes'}
1541 }
1542 },
1543 ['svargrond~oramond'] = {
1544 cost = 330,
1545 transcript = {
1546 ['Captain Breezelda'] = {'hi', 'thais', 'yes'},
1547 ['Captain Bluebear'] = {'hi', 'oramond', 'yes'}
1548 }
1549 },
1550 ['svargrond~nibelor'] = {
1551 cost = 0,
1552 transcript = {
1553 ['Iskan'] = {'hi', 'passage', 'yes'}
1554 }
1555 },
1556 ['nibelor~svargrond'] = {
1557 cost = 0,
1558 transcript = {
1559 ['Nilsor'] = {'hi', 'passage', 'yes'}
1560 }
1561 },
1562 ['edron~thais'] = {
1563 cost = 160,
1564 transcript = {
1565 ['Captain Seahorse'] = {'hi', 'thais', 'yes'}
1566 }
1567 },
1568 ['edron~cormaya'] = {
1569 cost = 0,
1570 transcript = {
1571 ['Captain Seahorse'] = {'hi', 'cormaya', 'yes'}
1572 }
1573 },
1574 ['cormaya~edron'] = {
1575 cost = 0,
1576 transcript = {
1577 ['Pemaret'] = {'hi', 'edron', 'yes'}
1578 }
1579 },
1580 ['edron~ab\'dendriel'] = {
1581 cost = 70,
1582 transcript = {
1583 ['Captain Seahorse'] = {'hi', 'ab\'dendriel', 'yes'}
1584 }
1585 },
1586 ['edron~ankrahmun'] = {
1587 cost = 160,
1588 transcript = {
1589 ['Captain Seahorse'] = {'hi', 'ankrahmun', 'yes'}
1590 }
1591 },
1592 ['edron~carlin'] = {
1593 cost = 110,
1594 transcript = {
1595 ['Captain Seahorse'] = {'hi', 'carlin', 'yes'}
1596 }
1597 },
1598 ['edron~darashia'] = {
1599 route = 'carpet',
1600 cost = 40,
1601 transcript = {
1602 ['Pino'] = {'hi', 'darashia', 'yes'}
1603 }
1604 },
1605 ['edron~gray island'] = {
1606 cost = 160,
1607 transcript = {
1608 ['Captain Seahorse'] = {'hi', 'gray island', 'yes'}
1609 }
1610 },
1611 ['edron~liberty bay'] = {
1612 cost = 170,
1613 transcript = {
1614 ['Captain Seahorse'] = {'hi', 'liberty bay', 'yes'}
1615 }
1616 },
1617 ['edron~port hope'] = {
1618 cost = 150,
1619 transcript = {
1620 ['Captain Seahorse'] = {'hi', 'port hope', 'yes'}
1621 }
1622 },
1623 ['edron~roshamuul'] = {
1624 cost = 370,
1625 transcript = {
1626 ['Captain Seahorse'] = {'hi', 'thais', 'yes'},
1627 ['Captain Bluebear'] = {'hi', 'roshamuul', 'yes'}
1628 }
1629 },
1630 ['edron~oramond'] = {
1631 cost = 310,
1632 transcript = {
1633 ['Captain Seahorse'] = {'hi', 'thais', 'yes'},
1634 ['Captain Bluebear'] = {'hi', 'oramond', 'yes'}
1635 }
1636 },
1637 ['edron~svargrond'] = {
1638 route = 'carpet',
1639 cost = 60,
1640 transcript = {
1641 ['Pino'] = {'hi', 'svargrond', 'yes'}
1642 }
1643 },
1644 ['edron~venore'] = {
1645 cost = 190,
1646 transcript = {
1647 ['Captain Seahorse'] = {'hi', 'venore', 'yes'},
1648 ['Captain Fearless'] = {'hi', 'svargrond', 'yes'}
1649 }
1650 },
1651 ['edron~yalahar'] = {
1652 cost = 225,
1653 transcript = {
1654 ['Captain Seahorse'] = {'hi', 'venore', 'yes'},
1655 ['Captain Fearless'] = {'hi', 'yalahar', 'yes'}
1656 }
1657 },
1658 ['goroma~liberty bay'] = {
1659 cost = 0,
1660 transcript = {
1661 ['Jack Fate'] = {'hi', 'liberty bay', 'yes'}
1662 }
1663 },
1664 ['liberty bay~goroma'] = {
1665 cost = 0,
1666 transcript = {
1667 ['Jack Fate'] = {'hi', 'goroma', 'yes'}
1668 }
1669 },
1670 ['liberty bay~edron'] = {
1671 cost = 170,
1672 transcript = {
1673 ['Jack Fate'] = {'hi', 'edron', 'yes'}
1674 }
1675 },
1676 ['liberty bay~thais'] = {
1677 cost = 180,
1678 transcript = {
1679 ['Jack Fate'] = {'hi', 'thais', 'yes'}
1680 }
1681 },
1682 ['liberty bay~venore'] = {
1683 cost = 180,
1684 transcript = {
1685 ['Jack Fate'] = {'hi', 'venore', 'yes'}
1686 }
1687 },
1688 ['liberty bay~darashia'] = {
1689 cost = 200,
1690 transcript = {
1691 ['Jack Fate'] = {'hi', 'darashia', 'yes'}
1692 }
1693 },
1694 ['liberty bay~ankrahmun'] = {
1695 cost = 90,
1696 transcript = {
1697 ['Jack Fate'] = {'hi', 'ankrahmun', 'yes'}
1698 }
1699 },
1700 ['liberty bay~yalahar'] = {
1701 cost = 275,
1702 transcript = {
1703 ['Jack Fate'] = {'hi', 'yalahar', 'yes'}
1704 }
1705 },
1706 ['liberty bay~port hope'] = {
1707 cost = 50,
1708 transcript = {
1709 ['Jack Fate'] = {'hi', 'port hope', 'yes'}
1710 }
1711 },
1712 ['liberty bay~svargrond'] = {
1713 cost = 330,
1714 transcript = {
1715 ['Jack Fate'] = {'hi', 'thais', 'yes'},
1716 ['Captain Fearless'] = {'hi', 'svargrond', 'yes'}
1717 }
1718 },
1719 ['liberty bay~ab\'dendriel'] = {
1720 cost = 270,
1721 transcript = {
1722 ['Jack Fate'] = {'hi', 'venore', 'yes'},
1723 ['Captain Fearless'] = {'hi', 'ab\'dendriel', 'yes'}
1724 }
1725 },
1726 ['liberty bay~carlin'] = {
1727 cost = 290,
1728 transcript = {
1729 ['Jack Fate'] = {'hi', 'thais', 'yes'},
1730 ['Captain Bluebear'] = {'hi', 'carlin', 'yes'}
1731 }
1732 },
1733 ['liberty bay~gray island'] = {
1734 cost = 330,
1735 transcript = {
1736 ['Jack Fate'] = {'hi', 'edron', 'yes'},
1737 ['Captain Seahorse'] = {'hi', 'gray island', 'yes'}
1738 }
1739 },
1740 ['liberty bay~roshamuul'] = {
1741 cost = 320,
1742 transcript = {
1743 ['Jack Fate'] = {'hi', 'thais', 'yes'},
1744 ['Captain Bluebear'] = {'hi', 'roshamuul', 'yes'}
1745 }
1746 },
1747 ['liberty bay~oramond'] = {
1748 cost = 260,
1749 transcript = {
1750 ['Jack Fate'] = {'hi', 'thais', 'yes'},
1751 ['Captain Bluebear'] = {'hi', 'oramond', 'yes'}
1752 }
1753 },
1754 ['old adall~lorek'] = {
1755 cost = 0,
1756 transcript = {
1757 ['Old Adall'] = {'hi', 'east', 'yes'}
1758 }
1759 },
1760 ['lorek~banuta'] = {
1761 cost = 0,
1762 transcript = {
1763 ['Lorek'] = {'hi', 'banuta', 'yes'}
1764 }
1765 },
1766 ['lorek~mountain'] = {
1767 cost = 0,
1768 transcript = {
1769 ['Lorek'] = {'hi', 'mountain', 'yes'}
1770 }
1771 },
1772 ['mountain~forbidden lands'] = {
1773 cost = 0,
1774 transcript = {
1775 ['The Blind Prophet'] = {'hi', 'transport', 'yes'}
1776 }
1777 },
1778 ['liberty bay~meriana'] = {
1779 cost = 0,
1780 transcript = {
1781 ['Captain Waverider'] = {'hi', 'peg leg', 'yes'}
1782 }
1783 },
1784 ['meriana~liberty bay'] = {
1785 cost = 0,
1786 transcript = {
1787 ['Sebastian'] = {'hi', 'passage', 'yes'}
1788 }
1789 },
1790 ['port hope~ab\'dendriel'] = {
1791 cost = 250,
1792 transcript = {
1793 ['Charles'] = {'hi', 'venore', 'yes'},
1794 ['Captain Fearless'] = {'hi', 'ab\'dendriel', 'yes'}
1795 }
1796 },
1797 ['port hope~svargrond'] = {
1798 cost = 310,
1799 transcript = {
1800 ['Charles'] = {'hi', 'venore', 'yes'},
1801 ['Captain Fearless'] = {'hi', 'svargrond', 'yes'}
1802 }
1803 },
1804 ['port hope~roshamuul'] = {
1805 cost = 370,
1806 transcript = {
1807 ['Charles'] = {'hi', 'thais', 'yes'},
1808 ['Captain Bluebear'] = {'hi', 'roshamuul', 'yes'}
1809 }
1810 },
1811 ['port hope~oramond'] = {
1812 cost = 310,
1813 transcript = {
1814 ['Charles'] = {'hi', 'thais', 'yes'},
1815 ['Captain Bluebear'] = {'hi', 'oramond', 'yes'}
1816 }
1817 },
1818 ['port hope~carlin'] = {
1819 cost = 260,
1820 transcript = {
1821 ['Charles'] = {'hi', 'edron', 'yes'},
1822 ['Captain Seahorse'] = {'hi', 'carlin', 'yes'}
1823 }
1824 },
1825 ['port hope~gray island'] = {
1826 cost = 210,
1827 transcript = {
1828 ['Charles'] = {'hi', 'edron', 'yes'},
1829 ['Captain Seahorse'] = {'hi', 'gray island', 'yes'}
1830 }
1831 },
1832 ['port hope~ankrahmun'] = {
1833 cost = 110,
1834 transcript = {
1835 ['Charles'] = {'hi', 'ankrahmun', 'yes'}
1836 }
1837 },
1838 ['port hope~darashia'] = {
1839 cost = 180,
1840 transcript = {
1841 ['Charles'] = {'hi', 'darashia', 'yes'}
1842 }
1843 },
1844 ['port hope~edron'] = {
1845 cost = 150,
1846 transcript = {
1847 ['Charles'] = {'hi', 'edron', 'yes'}
1848 }
1849 },
1850 ['port hope~liberty bay'] = {
1851 cost = 50,
1852 transcript = {
1853 ['Charles'] = {'hi', 'liberty bay', 'yes'}
1854 }
1855 },
1856 ['port hope~thais'] = {
1857 cost = 160,
1858 transcript = {
1859 ['Charles'] = {'hi', 'thais', 'yes'}
1860 }
1861 },
1862 ['port hope~venore'] = {
1863 cost = 160,
1864 transcript = {
1865 ['Charles'] = {'hi', 'venore', 'yes'}
1866 }
1867 },
1868 ['port hope~yalahar'] = {
1869 cost = 260,
1870 transcript = {
1871 ['Charles'] = {'hi', 'yalahar', 'yes'}
1872 }
1873 },
1874 ['ankrahmun~darashia'] = {
1875 cost = 100,
1876 transcript = {
1877 ['Captain Sinbeard'] = {'hi', 'darashia', 'yes'}
1878 }
1879 },
1880 ['ankrahmun~venore'] = {
1881 cost = 150,
1882 transcript = {
1883 ['Captain Sinbeard'] = {'hi', 'venore', 'yes'}
1884 }
1885 },
1886 ['ankrahmun~liberty bay'] = {
1887 cost = 90,
1888 transcript = {
1889 ['Captain Sinbeard'] = {'hi', 'liberty bay', 'yes'}
1890 }
1891 },
1892 ['ankrahmun~port hope'] = {
1893 cost = 80,
1894 transcript = {
1895 ['Captain Sinbeard'] = {'hi', 'port hope', 'yes'}
1896 }
1897 },
1898 ['ankrahmun~yalahar'] = {
1899 cost = 230,
1900 transcript = {
1901 ['Captain Sinbeard'] = {'hi', 'yalahar', 'yes'}
1902 }
1903 },
1904 ['ankrahmun~edron'] = {
1905 cost = 150,
1906 transcript = {
1907 ['Captain Sinbeard'] = {'hi', 'edron', 'yes'}
1908 }
1909 },
1910 ['ankrahmun~thais'] = {
1911 cost = 320,
1912 transcript = {
1913 ['Captain Sinbeard'] = {'hi', 'edron', 'yes'},
1914 ['Captain Seahorse'] = {'hi', 'thais', 'yes'}
1915 }
1916 },
1917
1918 ['ankrahmun~roshamuul'] = {
1919 cost = 530,
1920 transcript = {
1921 ['Captain Sinbeard'] = {'hi', 'edron', 'yes'},
1922 ['Captain Seahorse'] = {'hi', 'thais', 'yes'},
1923 ['Captain Bluebear'] = {'hi', 'roshamuul', 'yes'}
1924 }
1925 },
1926 ['ankrahmun~oramond'] = {
1927 cost = 470,
1928 transcript = {
1929 ['Captain Sinbeard'] = {'hi', 'edron', 'yes'},
1930 ['Captain Seahorse'] = {'hi', 'thais', 'yes'},
1931 ['Captain Bluebear'] = {'hi', 'oramond', 'yes'}
1932 }
1933 },
1934 ['port hope~ab\'dendriel'] = {
1935 cost = 240,
1936 transcript = {
1937 ['Captain Sinbeard'] = {'hi', 'venore', 'yes'},
1938 ['Captain Fearless'] = {'hi', 'ab\'dendriel', 'yes'}
1939 }
1940 },
1941 ['ankrahmun~carlin'] = {
1942 cost = 270,
1943 transcript = {
1944 ['Captain Sinbeard'] = {'hi', 'edron', 'yes'},
1945 ['Captain Seahorse'] = {'hi', 'carlin', 'yes'}
1946 }
1947 },
1948 ['ankrahmun~gray island'] = {
1949 cost = 320,
1950 transcript = {
1951 ['Captain Sinbeard'] = {'hi', 'edron', 'yes'},
1952 ['Captain Seahorse'] = {'hi', 'gray island', 'yes'}
1953 }
1954 },
1955 ['ankrahmun~svargrond'] = {
1956 cost = 300,
1957 transcript = {
1958 ['Captain Sinbeard'] = {'hi', 'venore', 'yes'},
1959 ['Captain Fearless'] = {'hi', 'svargrond', 'yes'}
1960 }
1961 },
1962 ['yalahar~fenrock'] = {
1963 cost = 0,
1964 transcript = {
1965 ['Maris'] = {'hi', 'fenrock', 'yes'}
1966 }
1967 },
1968 ['fenrock~yalahar'] = {
1969 cost = 0,
1970 transcript = {
1971 ['Maris'] = {'hi', 'yalahar', 'yes'}
1972 }
1973 },
1974 ['yalahar~mistrock'] = {
1975 cost = 0,
1976 transcript = {
1977 ['Maris'] = {'hi', 'mistrock', 'yes'}
1978 }
1979 },
1980 ['mistrock~yalahar'] = {
1981 cost = 0,
1982 transcript = {
1983 ['Maris'] = {'hi', 'yalahar', 'yes'}
1984 }
1985 },
1986 ['yalahar~vengoth'] = {
1987 cost = 0,
1988 transcript = {
1989 ['Harlow'] = {'hi', 'vengoth', 'yes'}
1990 }
1991 },
1992 ['vengoth~yalahar'] = {
1993 cost = 0,
1994 transcript = {
1995 ['Harlow'] = {'hi', 'yalahar', 'yes'}
1996 }
1997 },
1998 ['yalahar~quaras'] = {
1999 cost = 0,
2000 transcript = {
2001 ['Tarak'] = {'hi', 'trip', 'yes'}
2002 }
2003 },
2004 ['quaras~yalahar'] = {
2005 cost = 0,
2006 transcript = {
2007 ['Tarak'] = {'hi', 'passage', 'yes'}
2008 }
2009 },
2010 ['yalahar~ab\'dendriel'] = {
2011 cost = 160,
2012 transcript = {
2013 ['Karith'] = {'hi', 'ab\'dendriel', 'yes'}
2014 }
2015 },
2016 ['yalahar~ankrahmun'] = {
2017 cost = 230,
2018 transcript = {
2019 ['Karith'] = {'hi', 'ankrahmun', 'yes'}
2020 }
2021 },
2022 ['yalahar~carlin'] = {
2023 cost = 185,
2024 transcript = {
2025 ['Karith'] = {'hi', 'carlin', 'yes'}
2026 }
2027 },
2028 ['yalahar~darashia'] = {
2029 cost = 210,
2030 transcript = {
2031 ['Karith'] = {'hi', 'darashia', 'yes'}
2032 }
2033 },
2034 ['yalahar~liberty bay'] = {
2035 cost = 275,
2036 transcript = {
2037 ['Karith'] = {'hi', 'liberty bay', 'yes'}
2038 }
2039 },
2040 ['yalahar~port hope'] = {
2041 cost = 260,
2042 transcript = {
2043 ['Karith'] = {'hi', 'port hope', 'yes'}
2044 }
2045 },
2046 ['yalahar~thais'] = {
2047 cost = 200,
2048 transcript = {
2049 ['Karith'] = {'hi', 'thais', 'yes'}
2050 }
2051 },
2052 ['yalahar~venore'] = {
2053 cost = 185,
2054 transcript = {
2055 ['Karith'] = {'hi', 'venore', 'yes'}
2056 }
2057 },
2058 ['yalahar~edron'] = {
2059 cost = 360,
2060 transcript = {
2061 ['Karith'] = {'hi', 'thais', 'yes'},
2062 ['Captain Bluebear'] = {'hi', 'edron', 'yes'}
2063 }
2064 },
2065 ['yalahar~roshamuul'] = {
2066 cost = 410,
2067 transcript = {
2068 ['Karith'] = {'hi', 'thais', 'yes'},
2069 ['Captain Bluebear'] = {'hi', 'roshamuul', 'yes'}
2070 }
2071 },
2072 ['yalahar~oramond'] = {
2073 cost = 350,
2074 transcript = {
2075 ['Karith'] = {'hi', 'thais', 'yes'},
2076 ['Captain Bluebear'] = {'hi', 'roshamuul', 'yes'}
2077 }
2078 },
2079 ['yalahar~gray island'] = {
2080 cost = 350,
2081 transcript = {
2082 ['Karith'] = {'hi', 'thais', 'yes'},
2083 ['Captain Bluebear'] = {'hi', 'gray island', 'yes'}
2084 }
2085 },
2086 ['yalahar~svargrond'] = {
2087 cost = 380,
2088 transcript = {
2089 ['Karith'] = {'hi', 'thais', 'yes'},
2090 ['Captain Bluebear'] = {'hi', 'roshamuul', 'yes'}
2091 }
2092 }
2093}
2094
2095local SPAWN_TRAVELLING = {
2096 -- Script Name = Price (gold)
2097 ['goroma ss (ms)'] = 1000,
2098 ['goroma ss (ed)'] = 1000,
2099 ['goroma ss (rp)'] = 1000,
2100 ['goroma demons (ms)'] = 1000,
2101 ['goroma demons (ed)'] = 1000,
2102 ['goroma demons (rp)'] = 1000,
2103 ['meriana gargoyle cave (ek)'] = 100,
2104 ['meriana gargoyle cave (ms)'] = 100,
2105 ['meriana gargoyle cave (ed)'] = 100,
2106 ['meriana gargoyle cave (rp)'] = 100,
2107 ['cormaya leaf golems (ek)'] = 40,
2108 ['cormaya leaf golems (ms)'] = 40,
2109 ['cormaya leaf golems (ed)'] = 40,
2110 ['cormaya leaf golems (rp)'] = 40,
2111 ['fenrock dragon lords (ek)'] = 200,
2112 ['fenrock dragon lords (ms)'] = 200,
2113 ['fenrock dragon lords (ed)'] = 200,
2114 ['fenrock dragon lords (rp)'] = 200,
2115 ['fenrock turtles (ek)'] = 200,
2116 ['fenrock turtles (ms)'] = 200,
2117 ['fenrock turtles (ed)'] = 200,
2118 ['fenrock turtles (rp)'] = 200,
2119 ['mistrock cyclops (ek)'] = 200,
2120 ['mistrock cyclops (ms)'] = 200,
2121 ['mistrock cyclops (ed)'] = 200,
2122 ['mistrock cyclops (rp)'] = 200,
2123 ['vengoth haunted treelings (ek)'] = 200,
2124 ['vengoth haunted treelings (ms)'] = 200,
2125 ['vengoth haunted treelings (ed)'] = 200,
2126 ['vengoth haunted treelings (rp)'] = 200,
2127 ['yalahar quaras (ek)'] = 100,
2128 ['yalahar quaras (ms)'] = 100,
2129 ['yalahar quaras (ed)'] = 100,
2130 ['yalahar quaras (rp)'] = 100,
2131 ['banuta -2 (ek)'] = 100,
2132 ['forbidden lands behemoths (ms)'] = 100,
2133 ['forbidden lands behemoths (ed)'] = 100,
2134 ['forbidden lands behemoths (rp)'] = 100
2135}
2136
2137local MONSTER_LOOT = {
2138 ["Acid Blob"] = {9054},
2139 ["Acolyte of Darkness"] = {9615},
2140 ["Acolyte of the Cult"] = {2828, 3032, 3052, 3065, 3085, 3282, 5810, 6087, 9639, 11455, 11492, 11652},
2141 ["Adept of the Cult"] = {2828, 3030, 3053, 3054, 3067, 3311, 3566, 5810, 6087, 7424, 7426, 9639, 11455, 11492, 11652},
2142 ["Amazon"] = {2920, 3008, 3030, 3114, 3267, 3273, 3602, 11443, 11444},
2143 ["Ancient Scarab"] = {236, 830, 3018, 3025, 3032, 3033, 3042, 3046, 3328, 3357, 3440, 8084, 9631},
2144 ["Apprentice Sheng"] = {2920, 3003, 3046, 3291, 3355, 3457, 3559, 3595, 4000, 5878},
2145 ["Arachir the Ancient One"] = {236, 3098, 3114, 3434, 8192, 11449},
2146 ["Armadile"] = {236, 237, 238, 239, 268, 813, 3053, 3428, 7413, 7428, 8050, 11447, 12600, 16122, 16127, 16138, 16142, 16143},
2147 ["Arthei"] = {236, 3098, 11449},
2148 ["Ashmunrah"] = {238, 3017, 3023, 3048, 3332, 3381, 10290},
2149 ["Askarak Demon"] = {236, 237, 812, 2995, 3032, 3051, 3725, 5904, 7368, 7440, 8084},
2150 ["Askarak Lord"] = {236, 237, 811, 3032, 3051, 3725, 5904, 7368, 7419, 7440, 8084},
2151 ["Askarak Prince"] = {236, 237, 811, 3032, 3049, 3281, 3725, 5904, 7440, 8084, 12541},
2152 ["Assassin"] = {2920, 3028, 3287, 3291, 3292, 3351, 3404, 3405, 3409, 3410, 3413, 7366},
2153 ["Azure Frog"] = {3492},
2154 ["Badger"] = {903, 8017, 10296},
2155 ["Bandit"] = {3274, 3286, 3352, 3353, 3359, 3411, 3559, 3596},
2156 ["Bane Bringer"] = {11982},
2157 ["Bane Lord"] = {11982, 12519, 12548, 12549, 12550, 12802},
2158 ["Bane of Light"] = {9615},
2159 ["Banshee"] = {237, 811, 2917, 2949, 3004, 3007, 3017, 3026, 3027, 3054, 3059, 3061, 3081, 3098, 3122, 3299, 3566, 3567, 3568, 10420, 11446, 12320},
2160 ["Barbaria"] = {2839, 3347, 3358, 7343, 7463},
2161 ["Barbarian Bloodwalker"] = {266, 2914, 3266, 3269, 3344, 3352, 3358, 5911, 7290, 7457},
2162 ["Barbarian Brutetamer"] = {268, 2839, 3289, 3347, 3358, 3597, 7343, 7379, 7457, 7463, 7464},
2163 ["Barbarian Headsplitter"] = {266, 2920, 3052, 3114, 3291, 3354, 3367, 3377, 5913, 7457, 7461},
2164 ["Barbarian Skullhunter"] = {266, 2920, 3052, 3114, 3291, 3354, 3367, 3377, 5913, 7449, 7457, 7462},
2165 ["Bat"] = {5894},
2166 ["Battlemaster Zunzu"] = {3032, 10289, 10384, 10386, 10387, 10413, 10414},
2167 ["Bear"] = {5896, 5902},
2168 ["Behemoth"] = {239, 2893, 3008, 3033, 3058, 3116, 3265, 3275, 3281, 3304, 3342, 3357, 3383, 3456, 3554, 5893, 5930, 7368, 7396, 7413, 11447},
2169 ["Betrayed Wraith"] = {238, 3028, 3057, 3450, 5021, 5741, 5799, 5944, 6299, 6499, 6558, 7368, 7386, 7416, 7643, 10316},
2170 ["Bibby Bloodbath"] = {266, 268, 817, 3049, 3265, 3281, 3287, 3316, 3383, 3391, 3557, 3578, 7395, 7412},
2171 ["Big Boss Trolliver"] = {3054},
2172 ["Black Knight"] = {822, 2995, 3003, 3016, 3079, 3265, 3269, 3275, 3277, 3302, 3305, 3318, 3351, 3357, 3369, 3370, 3371, 3372, 3383, 3384, 3602},
2173 ["Black Sheep"] = {11448},
2174 ["Blazing Fire Elemental"] = {763, 941, 9636, 12600},
2175 ["Blightwalker"] = {238, 281, 647, 811, 812, 3057, 3063, 3067, 3083, 3147, 3306, 3324, 3453, 3605, 5944, 6299, 6499, 7368, 7643, 9058, 9688},
2176 ["Blistering Fire Elemental"] = {941, 3030, 8093, 9636, 12600},
2177 ["Blood Crab"] = {3026, 3358, 3372, 3578, 9633},
2178 ["Blood Hand"] = {237, 3069, 3079, 3574, 5909, 5911, 7456, 8072, 8073, 10320, 18925, 18926, 18928, 18929, 18930},
2179 ["Blood Priest"] = {237, 3030, 3039, 3079, 3324, 3574, 5909, 5911, 8074, 8082, 10320, 18925, 18926, 18928, 18929, 18930},
2180 ["Blue Djinn"] = {268, 2829, 2933, 3029, 3574, 3595, 3659, 5912, 7378, 11456},
2181 ["Boar"] = {12310},
2182 ["Bog Raider"] = {239, 3557, 7642, 7643, 8044, 8063, 8084, 9667},
2183 ["Bolfrim"] = {6571, 6572, 6578, 14672},
2184 ["Bonebeast"] = {266, 3114, 3115, 3337, 3357, 3441, 3732, 5925, 10244, 10277},
2185 ["Bonelord"] = {268, 3059, 3065, 3265, 3282, 3285, 3409, 3418, 5898, 11512},
2186 ["Bones"] = {3029, 3061, 3366, 5741, 5944, 6299, 6499, 6570, 6571},
2187 ["Boogey"] = {9378, 9379, 9384, 9385},
2188 ["Boreth"] = {236, 3434, 11449},
2189 ["Braindeath"] = {3059, 3311, 3338, 3408, 3409, 3418, 5898, 7364, 7407, 7452, 9663},
2190 ["Bretzecutioner"] = {238, 239, 281, 3008, 3028, 3029, 3033, 3281, 3383, 3554, 5741, 6299, 6499, 7368, 7419, 7427, 7452, 7642, 10298},
2191 ["Bride of Night"] = {9615},
2192 ["Brimstone Bug"] = {236, 237, 3032, 3049, 3055, 5904, 9640, 10305, 10315, 11702, 11703},
2193 ["Bruise Payne"] = {3027, 3033, 3051, 3429, 3736, 5894, 7386, 9103, 9662},
2194 ["Brutus Bloodbeard"] = {239, 3114, 3357, 3370, 6099},
2195 ["Bug"] = {3590},
2196 ["Cake Golem"] = {12143},
2197 ["Calamary"] = {3581},
2198 ["Captain Jones"] = {3049},
2199 ["Carniphila"] = {647, 3597, 3728, 3738, 3740, 10300, 12311},
2200 ["Carrion Worm"] = {3492, 10275, 12600},
2201 ["Cave Rat"] = {3492, 3598, 3607},
2202 ["Centipede"] = {3299, 10301},
2203 ["Chakoya Toolshaper"] = {3286, 3441, 3456, 3578, 3580, 7158, 7159, 7381, 7441},
2204 ["Chakoya Tribewarden"] = {3294, 3441, 3578, 3580, 7158, 7159, 7381},
2205 ["Chakoya Windcaller"] = {3354, 3441, 3578, 7158},
2206 ["Charged Energy Elemental"] = {761, 945},
2207 ["Chayenne"] = {281, 6571, 14681, 14682},
2208 ["Chicken"] = {3492, 3606, 5890, 6541, 6542, 6543, 6544, 6545},
2209 ["Chocking Fear"] = {238, 813, 3051, 3052, 3098, 5911, 5913, 5914, 7642, 7643, 8074, 16121, 16123, 16124, 20062, 20202, 20206},
2210 ["Chopper"] = {238, 239, 3010, 3027, 3037, 9057, 14080, 14081, 14083, 14753},
2211 ["Clay Guardian"] = {774, 1781, 3147, 9057, 10305, 10422},
2212 ["Cliff Strider"] = {238, 3026, 3027, 3039, 3041, 3281, 3332, 3371, 3381, 3391, 3554, 5880, 5904, 5944, 7437, 7452, 7643, 9028, 9067, 10310, 16096, 16118, 16119, 16124, 16125, 16133, 16134, 16135, 16141, 16160, 16163},
2213 ["Cobra"] = {9634},
2214 ["Cockroach"] = {7882},
2215 ["Coral Frog"] = {3492},
2216 ["Corym Charlatan"] = {3607, 17809, 17810, 17812, 17813, 17817, 17818, 17819, 17820, 17821, 17846},
2217 ["Corym Skirmisher"] = {3607, 17809, 17810, 17812, 17813, 17817, 17818, 17819, 17820, 17821, 17825, 17846},
2218 ["Corym Vanguard"] = {3607, 17809, 17810, 17812, 17813, 17817, 17818, 17819, 17820, 17821, 17825, 17846, 17859},
2219 ["Count Tofifti"] = {6571, 14681},
2220 ["Countess Sorrow"] = {3049, 3084, 3123, 3312, 3557, 3567, 5944, 6499, 6536},
2221 ["Crab"] = {3578, 10272},
2222 ["Craban"] = {3155, 6571, 8759, 9642, 11609},
2223 ["Crawler"] = {238, 239, 3037, 3279, 8084, 9057, 14079, 14083, 14087},
2224 ["Crazed Beggar"] = {2389, 2950, 3097, 3122, 3459, 3470, 3473, 3601, 3658, 3738, 5552, 6091, 8894},
2225 ["Crimson Frog"] = {3492},
2226 ["Crocodile"] = {3556, 10279},
2227 ["Crustacea Gigantica"] = {236, 237, 12317},
2228 ["Crypt Defiler"] = {3274, 3286, 3353, 3359, 3409, 7533, 8010, 11456, 11492},
2229 ["Crypt Shambler"] = {3028, 3112, 3115, 3265, 3287, 3338, 3353, 3441, 3492, 10283},
2230 ["Crystal Crusher"] = {15793, 16122, 16123, 16124, 16138},
2231 ["Crystal Spider"] = {237, 829, 3007, 3008, 3053, 3055, 3351, 3357, 3370, 3371, 5801, 5879, 7290, 7364, 7437, 7441, 7449},
2232 ["Crystal Wolf"] = {762, 3067, 5897, 8050},
2233 ["Cublarc the Plunderer"] = {3316, 3350, 8029, 10196, 10407, 10421, 11479},
2234 ["Cyclops Drone"] = {236, 3093, 3269, 3294, 3384, 3410, 3413, 7398, 9657},
2235 ["Cyclops Smith"] = {236, 3093, 3266, 3275, 3305, 3330, 3384, 3410, 3413, 7398, 7452, 9657},
2236 ["Cyclops"] = {266, 3012, 3093, 3269, 3294, 3384, 3410, 3413, 7398, 9657},
2237 ["Damaged Worker Golem"] = {953, 3091, 5880, 8894, 9655},
2238 ["Dark Apprentice"] = {266, 268, 3072, 3075, 3147, 5934, 12308},
2239 ["Dark Magician"] = {236, 237, 266, 268, 678, 3069, 3147, 12308},
2240 ["Dark Monk"] = {268, 347, 2885, 2914, 3050, 3061, 3077, 3289, 3551, 3600, 9646, 10303, 11492, 11493},
2241 ["Dark Torturer"] = {238, 239, 3364, 3461, 3554, 5021, 5479, 5801, 5944, 6299, 6499, 6558, 7368, 7388, 7412, 9058},
2242 ["Deadeye Devious"] = {239, 3028, 3114, 3267, 3357, 3370, 5926, 6102},
2243 ["Death Blob"] = {9055},
2244 ["Death Priest"] = {266, 268, 3026, 3042, 3059, 3098, 5021, 12482},
2245 ["Deathbine"] = {647, 813, 814, 3032, 3728, 3740, 5014, 8084, 10300, 12320},
2246 ["Deathstrike"] = {16136, 16155, 16160, 16161, 16162, 16163, 16164, 16175},
2247 ["Deepling Brawler"] = {3578, 5895, 12730, 14017},
2248 ["Deepling Elite"] = {238, 239, 3032, 3052, 5895, 12683, 12730, 14012, 14013, 14040, 14041, 14042, 14085, 14252},
2249 ["Deepling Guard"] = {238, 239, 3029, 12683, 12730, 14010, 14011, 14043, 14044, 14142, 14247, 14248, 14250},
2250 ["Deepling Master Librarian"] = {3029, 3052, 3578, 5895, 12730, 13987, 13990, 14008, 14009, 14085, 14247},
2251 ["Deepling Scout"] = {3032, 3052, 3347, 5895, 8895, 9016, 12683, 12730},
2252 ["Deepling Spellsinger"] = {3029, 3052, 3578, 5895, 12730, 13987, 13990, 14008, 14009, 14085, 14247},
2253 ["Deepling Tyrant"] = {238, 239, 3029, 12683, 12730, 14010, 14011, 14043, 14044, 14142, 14247, 14248, 14250},
2254 ["Deepling Warrior"] = {238, 239, 3032, 3052, 5895, 12683, 12730, 14012, 14013, 14040, 14041, 14042, 14085, 14252},
2255 ["Deepling Worker"] = {3032, 3578, 5895, 12683, 12730, 14017},
2256 ["Deer"] = {10297},
2257 ["Defiler"] = {3028, 3030, 3032, 3034, 3037, 3038, 3039, 3041, 5944, 6299, 6499, 9054, 9055},
2258 ["Delany"] = {3598, 6571, 14671},
2259 ["Demodras"] = {239, 2842, 2903, 3029, 3051, 3280, 3386, 3450, 3732, 5791, 5919, 5948, 7365},
2260 ["Demon Outcast"] = {238, 3028, 3029, 3030, 3032, 3048, 3049, 3055, 3098, 3281, 3284, 3356, 3381, 3391, 3419, 3420, 3731, 5906, 5911, 7368, 7643, 9057, 20062},
2261 ["Demon Skeleton"] = {266, 268, 2920, 3027, 3030, 3062, 3078, 3287, 3305, 3353, 3413, 3415, 9647},
2262 ["Demon"] = {238, 2848, 3030, 3032, 3033, 3034, 3039, 3048, 3049, 3055, 3060, 3063, 3098, 3281, 3284, 3306, 3320, 3356, 3364, 3366, 3414, 3420, 3731, 5954, 6499, 7368, 7382, 7393, 7642, 7643, 9057},
2263 ["Denson Larika"] = {281, 3032, 5875, 5910, 11634},
2264 ["Desperate White Deer"] = {12544, 12545},
2265 ["Destroyer"] = {239, 3008, 3033, 3062, 3281, 3304, 3357, 3383, 3449, 3456, 3554, 5741, 5944, 6299, 6499, 7419, 7427, 10298},
2266 ["Dharalion"] = {238, 3037, 3082, 3103, 3147, 3593, 5922, 9635, 11465},
2267 ["Diabolic Imp"] = {826, 827, 3033, 3049, 3069, 3147, 3275, 3307, 3415, 3451, 3471, 5944, 6299, 6499, 6558},
2268 ["Diamond Servant"] = {236, 237, 816, 3037, 3048, 3061, 3073, 5944, 7428, 7440, 8050, 8775, 9063, 9304, 9655, 12601},
2269 ["Diblis the Fair"] = {236, 3098, 3114, 3434, 8075, 8192, 11449},
2270 ["Dipthrah"] = {238, 3029, 3041, 3051, 3062, 3077, 3241, 3324, 3334, 10290},
2271 ["Dirtbeard"] = {9374, 9375, 9382, 9401},
2272 ["Doctor Perhaps"] = {9372, 9373, 9383, 9399},
2273 ["Doomsday Cultist"] = {9615},
2274 ["Dracola"] = {238, 239, 3052, 3061, 3383, 5741, 5925, 5944, 6299, 6499, 6546, 7420},
2275 ["Dragon Hatchling"] = {266, 11457},
2276 ["Dragon Lord Hatchling"] = {268, 818, 3732},
2277 ["Dragon Lord"] = {236, 2842, 2903, 3029, 3051, 3061, 3280, 3373, 3386, 3392, 3428, 3450, 3732, 5882, 5948, 7378, 7399, 7402},
2278 ["Dragon"] = {236, 3028, 3061, 3071, 3275, 3285, 3297, 3301, 3322, 3349, 3351, 3409, 3416, 3449, 3557, 5877, 5920, 7430, 11457},
2279 ["Dragonling"] = {236, 237, 16131},
2280 ["Draken Abomination"] = {237, 238, 830, 4033, 7642, 7643, 8094, 9057, 10384, 10385, 10387, 11671, 11672, 11673, 11688, 11691, 12549},
2281 ["Draken Elite"] = {238, 3028, 4033, 5904, 7404, 7643, 10384, 10385, 10387, 10390, 11651, 11657, 11658, 11659, 11660, 11661, 11674, 11691, 11693},
2282 ["Draken Spellweaver"] = {238, 3006, 3030, 3038, 3071, 8043, 10386, 10387, 10397, 10398, 10438, 10439, 11454, 11658, 12307, 12549},
2283 ["Draken Warmaster"] = {239, 3006, 3030, 3428, 7643, 10384, 10386, 10387, 10388, 10404, 10405, 10406},
2284 ["Draptor"] = {236, 237, 8039, 12309},
2285 ["Dreadbeast"] = {3114, 3115, 3116, 3337, 3357, 3441, 3732, 5925},
2286 ["Dreadmaw"] = {9058, 10279},
2287 ["Drillworm"] = {814, 3097, 3456, 3492, 5880, 7452, 10305, 10422, 12600, 16122, 16123, 16124, 16126, 16133, 16135, 16142},
2288 ["Dryad"] = {647, 3033, 3723, 3726, 9013, 9014, 9015, 9017, 12311},
2289 ["Duskbringer"] = {9615},
2290 ["Dwarf Geomancer"] = {813, 3029, 3046, 3059, 3097, 3147, 3311, 3584, 3723, 5880, 11458, 11463},
2291 ["Dwarf Guard"] = {266, 3033, 3092, 3275, 3305, 3351, 3377, 3413, 3552, 3723, 5880, 12600},
2292 ["Dwarf Miner"] = {3097, 3274, 3378, 3456, 3559, 5880},
2293 ["Dwarf Soldier"] = {3092, 3266, 3349, 3358, 3375, 3425, 3446, 3457, 3723, 5880, 7363},
2294 ["Dwarf"] = {3097, 3274, 3276, 3378, 3430, 3456, 3505, 3559, 3723, 5880},
2295 ["Dworc Fleshhunter"] = {2920, 3114, 3299, 3346, 3347, 3361, 3403, 3441, 3471},
2296 ["Dworc Venomsniper"] = {647, 2920, 3114, 3298, 3299, 3361, 3403, 3448, 3560},
2297 ["Dworc Voodoomaster"] = {266, 2920, 3002, 3056, 3058, 3114, 3115, 3116, 3299, 3361, 3403},
2298 ["Earth Elemental"] = {237, 774, 1781, 3147, 8894, 9057, 10305, 10422, 12600},
2299 ["Earth Overlord"] = {811, 947, 8052, 10305, 10310, 12600},
2300 ["Eclipse Knight"] = {9615},
2301 ["Efreet"] = {237, 647, 827, 2647, 2933, 3032, 3038, 3071, 3330, 3574, 3584, 5910, 7378, 11470, 11486},
2302 ["Elder Bonelord"] = {237, 3059, 3265, 3408, 3409, 3418, 7364, 10276, 10280, 11512},
2303 ["Elder Mummy"] = {3007, 3017, 3027, 3042, 3045, 3046, 3299, 3492, 9649, 11466, 12482, 12483},
2304 ["Elder Wyrm"] = {236, 237, 816, 820, 822, 825, 3028, 3349, 5944, 7430, 7451, 8027, 8043, 8092, 8093, 9665},
2305 ["Elephant"] = {3044, 3443},
2306 ["Elf Arcanist"] = {237, 266, 347, 2917, 3037, 3061, 3073, 3082, 3147, 3447, 3509, 3551, 3563, 3593, 3600, 3661, 3738, 5922, 9635, 11465},
2307 ["Elf Overseer"] = {3413, 9635},
2308 ["Elf Scout"] = {2901, 3350, 3447, 3448, 3449, 3551, 3592, 5921, 7438, 9635, 11464},
2309 ["Elf"] = {3285, 3376, 3378, 3410, 3447, 3552, 5921, 8011, 9635},
2310 ["Elvira Hammerthrust"] = {13429},
2311 ["Emerald Damselfly"] = {266, 268, 3447, 17458, 17463},
2312 ["Energy Elemental"] = {237, 268, 761, 3007, 3033, 3051, 3054, 3073, 3287, 3313, 3415, 7449},
2313 ["Energy Overlord"] = {948, 8051},
2314 ["Enlightened of the Cult"] = {237, 2828, 2995, 3029, 3051, 3055, 3071, 3084, 3324, 3567, 5668, 5801, 5810, 6087, 7426, 9638, 11455, 11652},
2315 ["Enraged Crystal Golem"] = {236, 237, 7449, 7454, 15793, 16124, 16125, 16138},
2316 ["Enraged Soul"] = {3282, 3292, 3432, 3740, 9690},
2317 ["Enraged Squirrel"] = {836},
2318 ["Enraged White Deer"] = {12544, 12545},
2319 ["Enslaved Dwarf"] = {238, 239, 3032, 3033, 3092, 3279, 3369, 3415, 3428, 3432, 3725, 5880, 7413, 7437, 7452, 7454, 10310, 12600, 16121, 16122, 16123, 16126, 16142},
2320 ["Esmeralda"] = {811, 3030, 3098, 3269, 3326, 3370, 3428, 3735, 9668},
2321 ["Eternal Guardian"] = {1781, 3315, 3428, 5880, 9632, 10310, 10390, 10406, 10408, 10422, 10426, 12600},
2322 ["Ethershreck"] = {238, 239, 281, 6499, 7642, 7643, 9057, 10310, 10323, 10384, 10385, 10386, 10387, 10388, 10389, 10390, 10406, 10438, 10449, 10450, 10451, 12801},
2323 ["Evil Mastermind"] = {9391},
2324 ["Fahim the Wise"] = {237, 647, 827, 2933, 2948, 3041, 3574, 3588, 5912, 7378, 10310, 11470, 11486},
2325 ["Fan"] = {2948, 2949, 2950, 2954, 3178},
2326 ["Fazzrah"] = {236, 239, 3032, 5876, 5881, 10289, 10384, 10386, 10387, 10413, 10414},
2327 ["Fernfang"] = {347, 2885, 2902, 2905, 2914, 3012, 3037, 3050, 3061, 3077, 3105, 3147, 3289, 3551, 3563, 3600, 3661, 3736, 3738, 5786, 9646, 11492, 11493},
2328 ["Feverish Citizen"] = {3115, 3492, 12551, 12552, 12553, 12554, 12555, 12556, 12786, 12787},
2329 ["Feversleep"] = {238, 3030, 3032, 3033, 3567, 7643, 9057, 16124, 16125, 20203, 20204},
2330 ["Filth Toad"] = {3286, 3578, 9640},
2331 ["Fire Devil"] = {2920, 3033, 3069, 3075, 3147, 3275, 3307, 3415, 3471, 11513},
2332 ["Fire Overlord"] = {826, 946, 8049, 9636},
2333 ["Firestarter"] = {763, 3285, 3350, 3592, 5921, 7438, 9635, 12600, 12806},
2334 ["Flameborn"] = {239, 3369, 3371, 3419, 3724, 6499, 7368, 7421, 7439, 7452, 7643, 9034, 9056, 9057, 10304, 12311},
2335 ["Flamecaller Zazrak"] = {238, 3052, 10328, 10386, 10439, 10444},
2336 ["Flamingo"] = {11684},
2337 ["Fleabringer"] = {3492, 10407},
2338 ["Fleshcrawler"] = {236, 811, 3018, 3025, 3032, 3033, 3042, 3370, 3440, 7426, 8084, 9631, 11468},
2339 ["Fleshslicer"] = {238, 3026, 3030, 3039, 3346, 7413, 7643, 14082, 14083, 14753},
2340 ["Fluffy"] = {3115, 3271, 3318, 5944, 6499, 6558, 6570, 6571},
2341 ["Foreman Kneebiter"] = {3351, 3413, 5880},
2342 ["Forest Fury"] = {3349, 3446, 7363, 7438, 9057, 18994, 18995},
2343 ["Frazzlemaw"] = {238, 239, 3104, 3110, 3111, 3114, 3115, 3116, 3125, 3265, 3578, 5880, 5895, 5925, 5951, 7404, 7407, 7418, 9058, 10389, 16120, 16123, 16126, 16279, 20062, 20198, 20199},
2344 ["Frost Dragon Hatchling"] = {266, 8072, 9661},
2345 ["Frost Dragon"] = {2842, 2903, 3029, 3051, 3061, 3284, 3373, 3386, 3392, 3428, 3450, 3732, 7290, 7402, 7441},
2346 ["Frost Giant"] = {266, 3093, 3269, 3294, 3384, 3413, 7290, 7441, 7460, 9658},
2347 ["Frost Giantess"] = {266, 268, 1781, 3093, 3294, 3384, 3413, 7290, 7441, 7460, 9658},
2348 ["Frost Troll"] = {3130, 3272, 3277, 3412, 3562, 3578, 9648},
2349 ["Furious Troll"] = {3279, 9689},
2350 ["Fury"] = {239, 3007, 3033, 3065, 3364, 3554, 5021, 5911, 5944, 6499, 6558, 7368, 7404, 7456, 8016, 8899},
2351 ["Gang Member"] = {3093, 3286, 3362, 3559, 3602},
2352 ["Gargoyle"] = {1781, 3012, 3093, 3282, 3351, 3383, 3413, 3591, 8010, 10278, 10310, 10426},
2353 ["Gazer"] = {11512},
2354 ["General Murius"] = {236, 3275, 3359, 3413, 3450, 3483, 3558, 5878, 7363, 7401, 11472},
2355 ["Ghastly Dragon"] = {238, 812, 813, 3032, 3383, 3557, 5944, 6499, 7642, 7643, 8896, 10310, 10323, 10384, 10385, 10386, 10387, 10388, 10390, 10392, 10406, 10438, 10449, 10450, 10451},
2356 ["Ghost"] = {2828, 3049, 3282, 3292, 3432, 3565, 3740, 5909, 9690},
2357 ["Ghoul"] = {2920, 3052, 3114, 3291, 3367, 3377, 3492, 5913, 10291, 11467, 11484},
2358 ["Ghoulish Hyaena"] = {266, 3030, 3492},
2359 ["Giant Spider"] = {236, 828, 3053, 3055, 3265, 3351, 3357, 3370, 3371, 3372, 3448, 3557, 5879},
2360 ["Gladiator"] = {3264, 3286, 3352, 3353, 3359, 3409, 3410, 8044},
2361 ["Gnarlhound"] = {3492, 10407},
2362 ["Goblin Assassin"] = {1781, 3115, 3120, 3267, 3294, 3337, 3355, 3361, 3462, 3578},
2363 ["Goblin Leader"] = {3115, 3120, 3267, 3294, 3337, 3355, 3361, 3462, 3578},
2364 ["Goblin Scavenger"] = {1781, 3115, 3120, 3267, 3294, 3337, 3355, 3361, 3462, 3578, 11539},
2365 ["Goblin"] = {1781, 3115, 3120, 3267, 3294, 3337, 3355, 3361, 3462, 3578, 11539},
2366 ["Golden Servant"] = {266, 268, 3049, 3063, 3269, 3360, 3732, 8072, 8775, 12601, 12801},
2367 ["Gorgo"] = {238, 811, 812, 814, 3032, 3436, 7413, 7643, 9302, 10309},
2368 ["Gozzler"] = {2885, 3029, 3097, 3266, 3273, 3282, 3297, 3311, 3410},
2369 ["Grand Mother Foulscale"] = {3275, 3322, 3349, 3416, 3557, 5877, 5920, 7430},
2370 ["Grandfather Tridian"] = {237, 2995, 3725, 5801, 6087},
2371 ["Grave Guard"] = {266, 268, 3042, 3328, 3661, 6299, 12482},
2372 ["Grave Robber"] = {3274, 3286, 3353, 3359, 3409, 7533, 8010, 11456, 11492},
2373 ["Gravedigger"] = {236, 237, 3037, 3071, 3155, 3324, 5668, 5925, 6299, 9692, 10316, 11484, 11493},
2374 ["Gravelord Oshuran"] = {237, 3027, 3059, 3098, 3567, 8076},
2375 ["Green Djinn"] = {268, 2831, 2933, 3032, 3574, 3607, 3661, 5910, 7378, 11456},
2376 ["Grim Reaper"] = {238, 823, 3046, 3421, 3453, 5021, 6299, 6499, 6558, 7418, 7643, 8061, 8082, 8896, 9660},
2377 ["Grimrat"] = {6571},
2378 ["Groam"] = {3052, 3347, 8894},
2379 ["Grorlam"] = {1781, 2920, 3007, 3033, 3039, 3050, 3283, 3377, 3409, 3456, 3554, 5880, 10310, 10315},
2380 ["Groupie"] = {2948, 2949, 2950, 2954, 3178},
2381 ["Grynch Clan Goblin"] = {836, 841, 2392, 2875, 2906, 2950, 2983, 2992, 2995, 3003, 3042, 3046, 3147, 3454, 3463, 3572, 3585, 3586, 3590, 3598, 3599, 3606, 4871, 5021, 5792, 5890, 5894, 5902, 6276, 6392, 6393, 6496, 6500},
2382 ["Guzzlemaw"] = {238, 239, 3104, 3110, 3111, 3114, 3115, 3116, 3125, 3265, 3578, 5880, 5895, 5925, 5951, 7404, 7407, 7418, 10389, 16120, 16123, 16126, 16279, 20062, 20198, 20199},
2383 ["Hacker"] = {2914, 3266, 3269, 3274, 3279, 6570, 6571},
2384 ["Hairman The Huge"] = {3093, 3587},
2385 ["Hand of Cursed Fate"] = {238, 3010, 3029, 3036, 3037, 3041, 3051, 3055, 3062, 3071, 3079, 3084, 3155, 3324, 3370, 3381, 5799, 5944, 6299, 6499, 6558, 7368, 7414, 7643, 9058},
2386 ["Hatebreeder"] = {6499, 7642, 7643, 10323, 10385, 10386, 10387, 10388, 10390, 10392, 10406, 10438, 10449, 10450, 10451},
2387 ["Haunted Treeling"] = {236, 266, 3032, 3097, 3723, 3724, 3726, 7443, 9683},
2388 ["Hellfire Fighter"] = {821, 826, 3010, 3019, 3028, 3071, 3124, 3147, 3280, 3320, 5944, 6499, 9636, 9664, 12600},
2389 ["Hellhound"] = {238, 239, 817, 818, 821, 826, 827, 3027, 3038, 3071, 3116, 3271, 3280, 3281, 3318, 4871, 5910, 5914, 5925, 5944, 6499, 6553, 6558, 7368, 7421, 7426, 8896, 9057, 9058, 9636, 9637, 16131},
2390 ["Hellspawn"] = {239, 3282, 3369, 3371, 3413, 3724, 6499, 7368, 7421, 7439, 7452, 7643, 8895, 8896, 9034, 9056, 9057, 10304},
2391 ["Hemming"] = {3027, 3053, 3081, 3725, 3741, 5479, 5897, 7419, 7428, 7439, 7643, 10317, 10389},
2392 ["Herald of Gloom"] = {9615},
2393 ["Hero"] = {239, 347, 2949, 2995, 3003, 3004, 3048, 3265, 3279, 3280, 3350, 3381, 3382, 3385, 3419, 3447, 3563, 3572, 3592, 3658, 5911, 7364, 11450, 11510},
2394 ["Hide"] = {830, 3053, 3351, 3371, 5879},
2395 ["Hideous Fungus"] = {238, 239, 268, 811, 812, 813, 814, 3279, 5909, 5910, 5911, 5912, 16099, 16103, 16117, 16140, 16143, 16164},
2396 ["High Templar Cobrass"] = {3345, 3351, 3357, 3445, 5876, 5881},
2397 ["Hirintror"] = {236, 237, 819, 829, 3028, 3284, 3373, 5912, 7290, 7441, 7449, 19362, 19363},
2398 ["Hive Overseer"] = {238, 281, 3030, 3554, 7643, 9058, 14077, 14083, 14086, 14088, 14089, 14172, 14246},
2399 ["Honour Guard"] = {3042, 3286, 3307, 3725, 11481},
2400 ["Humongous Fungus"] = {236, 237, 238, 239, 268, 811, 812, 813, 814, 5909, 5911, 5912, 5913, 7436, 16099, 16103, 16117, 16139, 16142, 16164},
2401 ["Hunter"] = {2920, 3030, 3085, 3350, 3354, 3359, 3447, 3448, 3449, 3586, 3601, 5875, 5907, 7394, 7397, 7400, 11469},
2402 ["Hyaena"] = {3492},
2403 ["Hydra"] = {237, 3029, 3061, 3079, 3081, 3098, 3369, 3370, 3392, 3436, 4839, 8014, 10282},
2404 ["Ice Golem"] = {236, 237, 829, 3027, 3028, 3029, 3284, 3373, 7290, 7441, 7449, 9661},
2405 ["Ice Overlord"] = {942, 8050},
2406 ["Ice Witch"] = {237, 819, 823, 3067, 3311, 3574, 3732, 7290, 7387, 7441, 7449, 7459},
2407 ["Infected Weeper"] = {12600},
2408 ["Infernalist"] = {238, 239, 676, 818, 2852, 2995, 3051, 3324, 5904, 5911, 8012, 8074, 9045, 9056, 9058, 9067},
2409 ["Insectoid Scout"] = {266, 3093, 3346},
2410 ["Insectoid Worker"] = {266, 3032, 3326, 14083, 14225},
2411 ["Iron Servant"] = {266, 3269, 8775, 8894, 12601},
2412 ["Ironblight"] = {238, 812, 3032, 3033, 3039, 3041, 3326, 3333, 5904, 7437, 7643, 8027, 8084, 9028, 9067, 9654, 10310, 10451, 16118, 16121, 16123, 16126, 16138},
2413 ["Island Troll"] = {901, 3003, 3054, 3268, 3277, 3336, 3355, 3412, 3552, 5096, 5901},
2414 ["Jagged Earth Elemental"] = {647, 940, 1781, 3032, 3130, 5880, 10305, 10422, 12600},
2415 ["Jellyfish"] = {3581},
2416 ["Jesse the Wicked"] = {13429},
2417 ["Juggernaut"] = {238, 239, 281, 3019, 3030, 3032, 3036, 3038, 3039, 3105, 3113, 3322, 3340, 3360, 3364, 3414, 5944, 6499, 6558, 7368, 7413, 7452, 8061, 8896, 9058},
2418 ["Kerberos"] = {238, 817, 3027, 3038, 3280, 3318, 3360, 4871, 6499, 6553, 6558, 9058, 9637},
2419 ["Killer Caiman"] = {281, 3032, 3313, 3556, 10279, 10328},
2420 ["Kollos"] = {238, 281, 3030, 3098, 3554, 7643, 9058, 14077, 14083, 14086, 14088, 14089, 14249, 14251},
2421 ["Kongra"] = {266, 3050, 3084, 3093, 3357, 3587, 5883, 11471},
2422 ["Lancer Beetle"] = {3033, 9640, 9692, 10455, 10457},
2423 ["Latrivan"] = {239, 3026, 3027, 3028, 3029, 3032, 3033, 3038, 3041, 3046, 3048, 3049, 3051, 3054, 3062, 3063, 3066, 3069, 3070, 3076, 3079, 3081, 3098, 3275, 3281, 3284, 3290, 3320, 3324, 3356, 3364, 3414, 3420, 6299, 6499, 7365, 7368, 9058},
2424 ["Lava Golem"] = {236, 237, 238, 268, 817, 818, 826, 3037, 3039, 3071, 3280, 3320, 3419, 5880, 5909, 5911, 5914, 7643, 8074, 9636, 16115, 16120, 16122, 16126, 16130, 16131, 16141},
2425 ["Leaf Golem"] = {3032, 3723, 17824, 19110, 19111},
2426 ["Lersatio"] = {236, 3027, 3098, 3434, 7419, 11449},
2427 ["Lethal Lissy"] = {3028, 3114, 3275, 3357, 3370, 5926, 6100},
2428 ["Leviathan"] = {237, 3029, 7428, 8059, 8895, 8898, 9303, 9604, 9613},
2429 ["Lich"] = {237, 820, 3026, 3027, 3032, 3037, 3055, 3059, 3062, 3098, 3289, 3324, 3373, 3432, 3435, 3567, 9057, 12304},
2430 ["Lion"] = {9691},
2431 ["Lionet"] = {6571, 11681, 12737, 14173},
2432 ["Lizard Chosen"] = {239, 3028, 3428, 5876, 5881, 10384, 10385, 10386, 10387, 10408, 10409, 10410, 11673},
2433 ["Lizard Dragon Priest"] = {237, 238, 3033, 3037, 3052, 3065, 3071, 5876, 5881, 8043, 10328, 10386, 10439, 10444},
2434 ["Lizard Gate Guardian"] = {5881, 7643, 10385, 10387, 10390},
2435 ["Lizard High Guard"] = {236, 239, 3032, 3428, 5876, 5881, 10289, 10328, 10384, 10386, 10387, 10408, 10415, 10416},
2436 ["Lizard Legionnaire"] = {236, 3028, 5876, 5881, 10289, 10328, 10384, 10386, 10388, 10406, 10417, 10418, 10419},
2437 ["Lizard Magistratus"] = {237, 238, 3030, 5881},
2438 ["Lizard Noble"] = {236, 239, 3030, 5876, 3048},
2439 ["Lizard Sentinel"] = {266, 3028, 3269, 3277, 3313, 3347, 3358, 3377, 3444, 5876, 5881},
2440 ["Lizard Snakecharmer"] = {268, 3033, 3037, 3052, 3061, 3065, 3066, 3122, 3407, 3565, 4000, 5876, 5881},
2441 ["Lizard Templar"] = {266, 3032, 3264, 3282, 3294, 3345, 3351, 3357, 3445, 5876, 5881},
2442 ["Lizard Zaogun"] = {236, 239, 3032, 3428, 5876, 5881, 10289, 10384, 10386, 10387, 10413, 10414},
2443 ["Lost Basher"] = {238, 813, 2995, 3097, 3318, 3320, 3342, 3371, 3429, 3725, 5880, 7427, 7452, 7643, 9057, 12600, 16119, 17826, 17827, 17828, 17829, 17830, 17831, 17847, 17855, 17856, 17857},
2444 ["Lost Berserker"] = {238, 239, 813, 2995, 3097, 3318, 3320, 3392, 3415, 3428, 3429, 3725, 5880, 5904, 7427, 7452, 9057, 10422, 12600, 16120, 16123, 16124, 16127, 16142},
2445 ["Lost Husher"] = {236, 238, 812, 813, 3097, 3318, 3320, 3324, 3415, 3428, 3725, 7452, 9057, 10422, 12600, 17829, 17830, 17831, 17847, 17848, 17849, 17850, 17855, 17856, 17857},
2446 ["Lost Soul"] = {238, 239, 3016, 3026, 3027, 3039, 3081, 3147, 3324, 3428, 5741, 5806, 5944, 6299, 6499, 6525, 7407, 7413, 8895, 8896, 10316},
2447 ["Lost Thrower"] = {238, 239, 3725, 5880, 12600, 17827, 17829, 17851, 17852, 17853, 17854, 17855, 17856, 17857},
2448 ["Lyxoph"] = {6571, 14683, 14684, 14685, 14741},
2449 ["Mad Scientist"] = {266, 268, 678, 3046, 3061, 3598, 3723, 3739, 6393, 7440},
2450 ["Mad Technomancer"] = {396},
2451 ["Magma Crawler"] = {238, 239, 817, 818, 3028, 3037, 3051, 3280, 3429, 5880, 5909, 5911, 5914, 8093, 9636, 12600, 15793, 16115, 16119, 16123, 16127, 16130, 16131},
2452 ["Mahrdis"] = {239, 3024, 3030, 3039, 3052, 3240, 3320, 3439, 10290},
2453 ["Mamma Longlegs"] = {239, 3049, 3051, 3053, 3055, 3351, 3370, 3371, 5879, 5886, 7416, 7419},
2454 ["Mammoth"] = {3443, 7381, 7432, 10307, 10321},
2455 ["Man in the Cave"] = {3003, 5913, 7290, 7386, 7458},
2456 ["Marid"] = {237, 647, 827, 2659, 2933, 2948, 3029, 3041, 3067, 3330, 3574, 3588, 3659, 5912, 7378, 11470, 11486},
2457 ["Marsh Stalker"] = {647, 3003, 3285, 3492, 3578, 17461, 17462},
2458 ["Marziel"] = {236, 3098},
2459 ["Massacre"] = {238, 239, 3106, 3116, 3340, 3360, 3422, 5021, 5944, 6104, 6499, 6540, 7403},
2460 ["Massive Earth Elemental"] = {814, 1781, 3028, 3081, 3084, 3097, 7387, 8895, 9057, 10305, 10422, 12600},
2461 ["Massive Energy Elemental"] = {237, 238, 761, 794, 816, 822, 3033, 8073, 8092, 8895, 9304},
2462 ["Massive Fire Elemental"] = {817, 818, 821, 3030, 3071, 3280, 8895},
2463 ["Massive Water Elemental"] = {238, 239, 3028, 3032, 3051, 3052, 7158, 7159},
2464 ["Maw"] = {238, 281, 3027, 3030, 3039, 7643, 9058, 14077, 14083, 14172, 14246, 14753},
2465 ["Medusa"] = {238, 811, 812, 814, 3032, 3370, 3436, 7413, 7643, 8896, 9302, 10309},
2466 ["Mephiles"] = {9376, 9377, 9387, 9400},
2467 ["Mercury Blob"] = {9053},
2468 ["Merikh the Slaughterer"] = {237, 827, 3032, 3038, 3330, 3574, 5910, 7378, 10310, 11470, 11486},
2469 ["Merlkin"] = {268, 3033, 3046, 3072, 3348, 3586, 3587, 5883, 11511},
2470 ["Midnight Panther"] = {3052, 12039, 12040},
2471 ["Midnight Spawn"] = {9615},
2472 ["Midnight Warrior"] = {9615},
2473 ["Mindmasher"] = {238, 239, 3029, 3032, 3097, 3346, 10392, 14083, 14753},
2474 ["Minotaur Archer"] = {3349, 3359, 3377, 3446, 5878, 7363, 11451, 11472, 11483},
2475 ["Minotaur Guard"] = {266, 3275, 3358, 3359, 3413, 3483, 5878, 7401, 11472, 11482},
2476 ["Minotaur Mage"] = {268, 2920, 3073, 3355, 3559, 3595, 5878, 7425, 11472, 11473},
2477 ["Minotaur"] = {3056, 3264, 3274, 3286, 3354, 3358, 3410, 3457, 5878, 11472},
2478 ["Monk"] = {347, 2885, 2914, 3050, 3061, 3077, 3289, 3551, 3600, 9646, 11492, 11493},
2479 ["Monstor"] = {9380, 9381, 9386, 9396},
2480 ["Morguthis"] = {239, 3019, 3027, 3081, 3237, 3318, 3331, 3554, 7368, 10290},
2481 ["Morik the Gladiator"] = {8820},
2482 ["Mornenion"] = {13429},
2483 ["Muddy Earth Elemental"] = {940, 1781, 3129, 10305, 10422, 12600},
2484 ["Mummy"] = {3007, 3017, 3027, 3045, 3046, 3054, 3299, 3429, 3492, 5914, 9649, 10290, 11466},
2485 ["Munster"] = {3337, 3492, 3607, 5792},
2486 ["Mutated Bat"] = {3027, 3033, 3051, 3313, 3413, 3429, 3736, 5894, 7386, 8894, 8895, 9103, 9662},
2487 ["Mutated Human"] = {841, 3045, 3054, 3111, 3264, 3377, 3492, 3607, 3737, 8894, 10308},
2488 ["Mutated Rat"] = {266, 3049, 3114, 3120, 3269, 3410, 3428, 3732, 3735, 8072, 9668},
2489 ["Mutated Tiger"] = {236, 3052, 3415, 7436, 7454, 9046, 10293, 10311},
2490 ["Necromancer Servant"] = {3448, 10320, 11475, 18933},
2491 ["Necromancer"] = {237, 3079, 3311, 3324, 3448, 3574, 3732, 7456, 8073, 10320, 11475},
2492 ["Necropharus"] = {3070, 3079, 3114, 3116, 3311, 3324, 3337, 3441, 3574, 3732, 5809, 10320, 11475},
2493 ["Nightfiend"] = {236, 237, 3010, 3030, 3039, 9685, 11449, 18924},
2494 ["Nightmare Scion"] = {3385, 6299, 6574, 7387, 7451, 8043, 9027, 10306, 10312},
2495 ["Nightmare"] = {3079, 3342, 3371, 3432, 3450, 5668, 5944, 6299, 6499, 6525, 6558, 10306, 10312},
2496 ["Nightslayer"] = {9615},
2497 ["Nightstalker"] = {237, 3007, 3055, 3079, 3084, 3740, 7407, 7427, 8042, 9028},
2498 ["Noble Lion"] = {3582, 3577, 9691},
2499 ["Nomad"] = {3274, 3286, 3353, 3359, 3409, 7533, 8010, 11456, 11492},
2500 ["Novice of the Cult"] = {2828, 3028, 3074, 3083, 3097, 3572, 5810, 6087, 9639, 11492},
2501 ["Ocyakao"] = {3026, 3441, 3578, 3580, 5909, 7381, 7441, 19369},
2502 ["Omnivora"] = {3723, 3582, 3578, 3052, 16123, 6299, 8072, 12311},
2503 ["Omruc"] = {239, 3028, 3049, 3079, 3239, 3332, 3447, 3448, 3449, 3450, 3585, 7365, 10290},
2504 ["Orc Berserker"] = {2914, 3266, 3269, 3347, 3358, 10196, 11477, 11479},
2505 ["Orc Leader"] = {266, 3091, 3285, 3298, 3301, 3307, 3357, 3369, 3372, 3410, 3557, 3578, 3725, 7378, 10196, 11479, 11480},
2506 ["Orc Marauder"] = {3313, 3316, 3349, 3350, 8029, 10196, 10407, 11451, 11479},
2507 ["Orc Rider"] = {2920, 3012, 3313, 3316, 3377, 3413, 10196, 10318, 11479},
2508 ["Orc Shaman"] = {2839, 3072, 3277, 3358, 3597, 10196, 11452, 11478, 11479},
2509 ["Orc Spearman"] = {3277, 3308, 3362, 3376, 10196, 11479},
2510 ["Orc Warlord"] = {266, 818, 3049, 3063, 3084, 3265, 3287, 3307, 3316, 3322, 3347, 3357, 3359, 3384, 3391, 3393, 3394, 3437, 3557, 3578, 7395, 10196, 11453, 11479, 11480},
2511 ["Orc Warrior"] = {3299, 3358, 3430, 10196, 11453, 11479, 11480},
2512 ["Orc"] = {3244, 3273, 3274, 3376, 3378, 3426, 10196, 11479},
2513 ["Orchid Frog"] = {3492},
2514 ["Orewalker"] = {236, 237, 238, 268, 3037, 3097, 3371, 3381, 3385, 5880, 5904, 7413, 7454, 7643, 8050, 9057, 10310, 10315, 16096, 16121, 16124, 16125, 16133, 16135, 16141, 16163},
2515 ["Overcharged Energy Element"] = {239, 945, 3033, 3098, 7439, 8092},
2516 ["Paiz the Pauperizer"] = {238, 239, 3032, 3037, 3038, 3039, 3041, 3386, 5881, 5904, 7642, 8052, 10384, 10389, 10390, 11651, 11657, 11658, 11659, 11660, 11661, 12307},
2517 ["Panda"] = {11445},
2518 ["Penciljack"] = {6571, 14683, 14684, 14685, 14741},
2519 ["Penguin"] = {3578, 7158, 7159},
2520 ["Phantasm"] = {238, 3030, 3032, 3033, 3049, 3147, 3381, 3740, 6299, 6499, 7414, 7451, 7643, 9057},
2521 ["Pig"] = {9693},
2522 ["Pirate Buccaneer"] = {236, 2920, 3123, 3273, 3298, 3357, 3413, 5552, 5706, 5792, 5926, 6095, 6097, 6098, 6126, 10302},
2523 ["Pirate Corsair"] = {236, 2995, 3273, 3287, 3383, 3421, 5461, 5552, 5813, 5926, 6096, 6097, 6098, 6126, 10302},
2524 ["Pirate Cutthroat"] = {3377, 3409, 5552, 5706, 5710, 5792, 5918, 5927, 6097, 6098, 6126, 10302},
2525 ["Pirate Ghost"] = {2814, 3049, 3271, 3566, 9684},
2526 ["Pirate Marauder"] = {2920, 3277, 3358, 3410, 5552, 5706, 5792, 5917, 5927, 5928, 6097, 6098, 6126, 10302},
2527 ["Pirate Skeleton"] = {3114, 3115, 3116, 3264, 3294, 3337, 9642},
2528 ["Plaguesmith"] = {239, 2958, 3010, 3017, 3033, 3092, 3093, 3110, 3120, 3122, 3265, 3279, 3282, 3305, 3332, 3371, 3409, 3554, 5887, 5888, 5889, 5944, 6499, 7365, 8896},
2529 ["Poacher"] = {2920, 3350, 3355, 3447, 3448, 3481, 3559, 3601},
2530 ["Poison Spider"] = {11485},
2531 ["Polar Bear"] = {9650},
2532 ["Priestess"] = {268, 2828, 2948, 2995, 3008, 3034, 3067, 3076, 3311, 3429, 3585, 3674, 3727, 3738, 3739, 9639, 9645, 10303},
2533 ["Primitive"] = {3273, 3274, 3376, 3378, 3426, 6570, 6571},
2534 ["Quara Constrictor Scout"] = {3033, 3285, 3359, 3581, 5895, 11487},
2535 ["Quara Constrictor"] = {3033, 3285, 3359, 3581, 5895, 11487},
2536 ["Quara Hydromancer Scout"] = {3026, 3027, 3032, 3073, 3098, 3313, 3370, 3578, 3581, 5895, 11488},
2537 ["Quara Hydromancer"] = {238, 3026, 3027, 3032, 3073, 3098, 3370, 3581, 5895, 11488},
2538 ["Quara Mantassin Scout"] = {3029, 3049, 3114, 3265, 3358, 5895, 11489},
2539 ["Quara Mantassin"] = {3029, 3049, 3265, 3269, 3373, 3565, 3567, 3581, 5895, 11489},
2540 ["Quara Pincher Scout"] = {3030, 3061, 3269, 3357, 5895, 11490, 12318},
2541 ["Quara Pincher"] = {239, 824, 3030, 3269, 3369, 3381, 3581, 5895, 11490, 12318},
2542 ["Quara Predator Scout"] = {3028, 3265, 3275, 3377, 3581, 5895, 8083, 11491},
2543 ["Quara Predator"] = {239, 824, 3028, 3275, 3581, 5741, 5895, 7368, 7378, 7383, 11491, 12318},
2544 ["Rabbit"] = {3595, 6541, 6542, 6543, 6544, 6545},
2545 ["Rahemos"] = {238, 3033, 3036, 3060, 3068, 3098, 3235, 3335, 3573, 10290},
2546 ["Rat"] = {3607},
2547 ["Renegade Orc"] = {266, 3091, 3285, 3298, 3301, 3307, 3369, 3410, 3557, 3578, 3725, 10196, 11479},
2548 ["Retching Horror"] = {238, 239, 3280, 3344, 3419, 3428, 3725, 7386, 8082, 8092, 20029, 20062, 20205, 20207},
2549 ["Ribstride"] = {3028, 3441, 3732, 5741, 5925, 10244, 10277, 12304},
2550 ["Roaring Lion"] = {3048, 3577, 3582, 9691, 9057, 3033, 3030, 3029, 3077, 3385},
2551 ["Roaring Water Elemental"] = {281, 944, 3029, 8083},
2552 ["Robby the Reckless"] = {13429},
2553 ["Ron the Ripper"] = {239, 3028, 3114, 3267, 3357, 3370, 5926, 6101},
2554 ["Rorc"] = {3012, 3313, 3316, 3410, 18993, 18996, 18997},
2555 ["Rotspit"] = {238, 3033, 3725, 7449, 14078, 14753},
2556 ["Rottie the Rotworm"] = {3264, 3286, 3374, 3430, 3492, 9692},
2557 ["Rotworm Queen"] = {3492, 8143},
2558 ["Rotworm"] = {3264, 3286, 3492, 9692},
2559 ["Rukor Zad"] = {3409, 7366},
2560 ["Sacred Spider"] = {3042, 3357, 8031, 9058},
2561 ["Salamander"] = {266, 3003, 3286, 3307, 3350, 3354, 3447, 17457},
2562 ["Sandcrawler"] = {10456},
2563 ["Sandstone Scorpion"] = {3032, 3327, 3351, 3429, 12546},
2564 ["Scarab"] = {3032, 3033, 3042, 3327, 9641},
2565 ["Scorpion"] = {9651},
2566 ["Sea Serpent"] = {236, 237, 238, 815, 823, 3029, 3049, 3098, 3297, 3557, 8042, 8043, 8050, 8083, 9303, 9666},
2567 ["Serpent Spawn"] = {238, 2903, 3029, 3051, 3052, 3061, 3066, 3369, 3373, 3381, 3392, 3407, 3428, 3450, 3732, 4831, 7386, 7456, 8052, 8074, 9694, 10313},
2568 ["Shaburak Demon"] = {236, 237, 821, 2995, 3030, 3051, 3071, 3725, 5904, 7378, 7443},
2569 ["Shaburak Lord"] = {236, 237, 826, 3030, 3051, 3071, 3554, 3725, 5904, 7443},
2570 ["Shaburak Prince"] = {236, 237, 826, 3030, 3049, 3071, 3554, 3725, 5904, 7412, 7443, 12541},
2571 ["Shadow Hound"] = {9615},
2572 ["Shadow Pupil"] = {237, 3079, 3574, 3725, 8072, 10320, 18926, 18929, 18930},
2573 ["Shadowstalker"] = {238, 239, 3032, 3037, 9057, 14079, 14083, 14753},
2574 ["Shardhead"] = {236, 3027, 3028, 3029, 7290, 7441, 9661},
2575 ["Shark"] = {281, 3029, 3578, 5895, 12730, 14017},
2576 ["Sharptooth"] = {3111},
2577 ["Sheep"] = {10319},
2578 ["Shiversleep"] = {238, 3030, 3032, 3281, 3369, 3370, 3567, 5909, 5911, 7643, 9057, 16119, 16124, 16125, 20203, 20204},
2579 ["Shock Head"] = {3029, 3392, 20205},
2580 ["Sibang"] = {1781, 3586, 3587, 3589, 3593, 5883, 11511},
2581 ["Sight of Surrender"] = {238, 3048, 3081, 3332, 3333, 3366, 3391, 3428, 3554, 7421, 7422, 7642, 7643, 16119, 16120, 16121, 16122, 16123, 16124, 20062, 20183, 20184, 20208},
2582 ["Silencer"] = {239, 812, 813, 3049, 3079, 3421, 7368, 7387, 7407, 7413, 7451, 7454, 20062, 20200, 20201},
2583 ["Silver Rabbit"] = {3595, 6541, 6542, 6543, 6544, 6545, 10292},
2584 ["Sir Valorcrest"] = {236, 3091, 3098, 3114, 3434, 7427, 8192},
2585 ["Siramal"] = {6571, 14673},
2586 ["Skeleton Warrior"] = {3115, 3264, 3286, 3723, 3725, 11481},
2587 ["Skeleton"] = {2920, 3115, 3264, 3276, 3286, 3367, 3411, 11481},
2588 ["Skunk"] = {8197, 10274},
2589 ["Skyrr"] = {6571},
2590 ["Slick Water Elemental"] = {762, 944},
2591 ["Slug"] = {3492},
2592 ["Smuggler Baron Silvertoe"] = {3286, 3294},
2593 ["Smuggler"] = {2920, 3264, 3291, 3292, 3294, 3355, 3559, 7397, 8012},
2594 ["Souleater"] = {238, 3069, 3073, 5884, 6299, 7643, 11679, 11680, 11681},
2595 ["Spectre"] = {238, 2949, 3017, 3019, 3049, 3073, 3147, 5909, 5944, 6299, 6499, 7383, 7451, 10310},
2596 ["Spider"] = {8031},
2597 ["Spidris Elite"] = {238, 281, 3030, 3036, 6299, 7413, 7643, 14082, 14083, 14086, 14088, 14089},
2598 ["Spidris"] = {238, 281, 3030, 3036, 6299, 7413, 7643, 14082, 14083, 14086, 14088, 14089},
2599 ["Spit Nettle"] = {647, 3661, 3738, 3740, 10314, 11476},
2600 ["Spitter"] = {238, 239, 3033, 3038, 3053, 3055, 3391, 3725, 7440, 7449, 14078, 14083, 14086, 14087},
2601 ["Squirrel"] = {836, 841, 10296},
2602 ["Stalker"] = {3147, 3298, 3300, 3313, 3372, 3411, 11474},
2603 ["Stampor"] = {236, 237, 3279, 3370, 7452, 9057, 12312, 12313, 12314},
2604 ["Starving Wolf"] = {3105, 5897},
2605 ["Stone Devourer"] = {236, 237, 238, 268, 3081, 3097, 3281, 3333, 3342, 7437, 7452, 7454, 7643, 9632, 12600, 15793, 16122, 16125, 16137, 16138},
2606 ["Stone Golem"] = {1781, 3007, 3039, 3050, 3283, 5880, 9632, 10310, 10315, 10426, 12600},
2607 ["Stonecracker"] = {3033, 3265, 3275, 3281, 3342, 3554, 5893, 5930, 7368, 7396, 10310},
2608 ["Sulphur Scuttler"] = {236, 237, 3032, 3049, 3055, 5904, 9640, 10305, 10315, 11702, 11703},
2609 ["Swamp Troll"] = {2920, 3120, 3277, 3483, 3552, 3578, 3741, 5901, 9686, 12517},
2610 ["Swampling"] = {3003, 3723, 17822, 17823, 17824},
2611 ["Swarmer"] = {3032, 3326, 14076, 14083},
2612 ["Tarantula"] = {3053, 3351, 3372, 3410, 8031, 10281},
2613 ["Tarnished Spirit"] = {2828, 3282, 3292, 3432, 3565, 3740, 5909, 9690},
2614 ["Teleskor"] = {2920, 3264, 3276, 3286, 3367, 3411, 11481},
2615 ["Terofar"] = {3038, 3415, 3420, 5954, 7642, 7643, 9058, 16121, 20062},
2616 ["Terramite"] = {10452, 10453, 10454},
2617 ["Terrified Elephant"] = {3044, 3443},
2618 ["Terror Bird"] = {266, 647, 3406, 3492, 10273, 11514},
2619 ["Terrorsleep"] = {238, 3030, 3032, 3281, 3369, 3370, 3567, 5909, 5911, 7643, 9057, 16119, 16124, 16125, 20203, 20204},
2620 ["Thalas"] = {239, 3032, 3038, 3049, 3053, 3238, 3297, 3299, 3339, 10290},
2621 ["The Abomination"] = {3027, 3029, 3030, 3032, 3033, 5944, 6499},
2622 ["The Blightfather"] = {3033, 9640, 9692, 10455},
2623 ["The Bloodtusk"] = {3044, 3443, 5911, 7432, 7463, 10321},
2624 ["The Bloodweb"] = {237, 823, 829, 3053, 3370, 3371, 5801, 5879, 7290, 7437, 7441, 10389},
2625 ["The Count"] = {3434, 7924},
2626 ["The Evil Eye"] = {238, 811, 3059, 3265, 3282, 3285, 3409, 3418, 5898, 11512},
2627 ["The Handmaiden"] = {3049, 3050, 3051, 3110, 3116, 3421, 3554, 3567, 5944, 6499, 6539},
2628 ["The Horned Fox"] = {236, 3049, 3275, 3276, 3359, 3396, 3413, 3483, 3558, 5804, 5878, 7363, 11472, 11482},
2629 ["The Imperor"] = {826, 3019, 3030, 3033, 3364, 3382, 3442, 3451, 5944, 6499, 6534},
2630 ["The Keeper"] = {11367},
2631 ["The Many"] = {237, 3029, 3081, 3369, 3370, 3392, 3436, 9058, 9302, 9606},
2632 ["The Mutated Pumpkin"] = {3594, 6491, 6525, 8032, 8177, 8178},
2633 ["The Noxious Spawn"] = {238, 2903, 3032, 3052, 3381, 3392, 3428, 3732, 7368, 7386, 7456, 8052, 8074, 9394, 9694, 10313},
2634 ["The Old Whopper"] = {7398, 9657},
2635 ["The Old Widow"] = {239, 3049, 3051, 3053, 3055, 3351, 3370, 3371, 5879, 5886, 7416, 7419, 12320},
2636 ["The Pale Count"] = {236, 237, 3027, 3029, 3032, 3049, 3098, 3326, 3434, 5909, 5911, 5912, 7419, 7427, 8075, 8192, 9057, 18927, 18935, 18936, 19373, 19374},
2637 ["The Plasmother"] = {3027, 3029, 3032, 3033, 5944, 6499, 6535},
2638 ["The Snapper"] = {266, 3032, 3052, 3357, 3370, 3556, 3557},
2639 ["The Voice of Ruin"] = {10408, 10410},
2640 ["The Weakened Count"] = {7924},
2641 ["The Welter"] = {236, 237, 281, 3029, 3079, 3081, 3284, 3369, 3370, 3392, 3436, 4839, 9058, 9302, 19083, 19356, 19357},
2642 ["Thieving Squirrel"] = {836, 9843},
2643 ["Thornback Tortoise"] = {266, 3026, 3027, 3279, 3578, 3723, 3725, 5678, 5899, 9643},
2644 ["Thornfire Wolf"] = {763, 5897, 9636},
2645 ["Thul"] = {238, 901, 3033, 3381, 3391, 5895, 7383},
2646 ["Tiger"] = {10293},
2647 ["Tiquandas Revenge"] = {647, 3728, 5014, 12311},
2648 ["Toad"] = {3279, 3286, 3578, 9640},
2649 ["Tomb Servant"] = {3042, 3112, 3115, 3285, 3441, 3492, 10283, 12546},
2650 ["Tormentor"] = {3079, 3342, 3371, 5668, 6299, 6499, 6525, 6558, 7418, 10306, 10312},
2651 ["Tortoise"] = {3305, 3410, 3578, 5678, 5899, 6131},
2652 ["Troll Champion"] = {3054, 3277, 3336, 3412, 3447, 3552, 9689, 11515},
2653 ["Troll Guard"] = {3003, 3336},
2654 ["Troll Legionnaire"] = {3049, 3287, 9648},
2655 ["Troll"] = {3003, 3054, 3268, 3277, 3336, 3355, 3412, 3552, 9689},
2656 ["Tromphonyte"] = {236, 237, 3370, 7452, 9057, 12312, 12313, 12314},
2657 ["Tyrn"] = {236, 237, 816, 3028, 3029, 3030, 3032, 3033, 3037, 3039, 3041, 3155, 3415, 5911, 7368, 7430, 9057, 9304, 9665, 19083},
2658 ["Undead Cavebear"] = {266, 12315, 12316},
2659 ["Undead Dragon"] = {238, 239, 2903, 3027, 3029, 3041, 3061, 3342, 3360, 3370, 3392, 3450, 5925, 6299, 6499, 7368, 7402, 7430, 8057, 8061, 9058, 10316, 10438},
2660 ["Undead Gladiator"] = {266, 3049, 3084, 3265, 3287, 3307, 3318, 3347, 3357, 3359, 3372, 3384, 3391, 3557, 5885, 8044, 9656},
2661 ["Undead Jester"] = {123, 651, 900, 901, 2995, 3578, 5909, 5910, 5911, 5912, 5913, 5914, 6574, 7158, 7159, 7377, 8779, 8780, 8781, 8782, 8783, 8784, 8853},
2662 ["Undead Mine Worker"] = {3115, 3264, 3286, 3723, 3725},
2663 ["Undead Minion"] = {3147, 3305, 3413, 3415, 6570, 6571},
2664 ["Undead Prospector"] = {2920, 3052, 3114, 3291, 3354, 3367, 3377, 3492, 5913},
2665 ["Valkyrie"] = {266, 3028, 3084, 3114, 3275, 3277, 3347, 3357, 3358, 3585, 11443, 11444},
2666 ["Vampire Bride"] = {236, 237, 649, 3010, 3028, 3070, 3079, 5668, 8045, 8531, 8895, 8923, 9685, 11449, 12306},
2667 ["Vampire Viscount"] = {236, 237, 3027, 3030, 3039, 3284, 3434, 5911, 9685, 11449, 18924, 18927},
2668 ["Vampire"] = {236, 3010, 3027, 3056, 3114, 3271, 3284, 3300, 3373, 3434, 3661, 9685, 11449},
2669 ["Vashresamun"] = {238, 2950, 2953, 3007, 3022, 3026, 3236, 3333, 3567, 10290},
2670 ["Vicious Squire"] = {3446, 239, 3349, 3003, 3048, 3563, 3415, 3371, 3369, 2995, 3028, 3033, 3029, 3030, 3032},
2671 ["Vulcongra"] = {236, 237, 817, 826, 3071, 3091, 3280, 3587, 9636, 12600, 16123, 16126, 16130, 16131},
2672 ["Wailing Widow"] = {266, 268, 3269, 3410, 3732, 10406, 10411, 10412},
2673 ["War Golem"] = {238, 820, 953, 3061, 3093, 3097, 3265, 3282, 3326, 3410, 3413, 3554, 5880, 7403, 7422, 7428, 7439, 7643, 8895, 9063, 9067, 9654, 12305},
2674 ["War Wolf"] = {5897, 7394, 10318},
2675 ["Warlock"] = {238, 239, 825, 2852, 2917, 2995, 3006, 3007, 3029, 3034, 3051, 3062, 3081, 3299, 3324, 3360, 3509, 3567, 3590, 3600, 3728, 7368, 11454},
2676 ["Warlord Ruzad"] = {818, 3084, 3287, 3307, 3316, 3357, 3372, 3384, 3557, 3578},
2677 ["Wasp"] = {5902},
2678 ["Waspoid"] = {3010, 3027, 3037, 14080, 14081, 14083, 14087, 14088, 14089},
2679 ["Water Elemental"] = {236, 237, 281, 3026, 3028, 3029, 3032, 3051, 3052, 3578, 7158, 7159, 9303},
2680 ["Weak Gloombringer"] = {3029, 3030, 3032, 9058, 9615},
2681 ["Weak Harbinger of Darkness"] = {3028, 3032, 9058, 9615},
2682 ["Weak Spawn of Despair"] = {3028, 3030, 9058, 9615},
2683 ["Weakened Shlorg"] = {238, 3032, 3037, 3038, 3297, 5910, 5911, 5914, 7642, 7643, 8044, 8084, 9057, 9667, 19083, 19371, 19372},
2684 ["Weeper"] = {238, 821, 826, 3030, 3280, 3320, 7643, 9636, 12600, 16115, 16120, 16123, 16126, 16130, 16131, 16132, 16141},
2685 ["Werewolf"] = {236, 3053, 3055, 3081, 3269, 3326, 3410, 3725, 3741, 5897, 7383, 7419, 7428, 7439, 7643, 8895, 10317},
2686 ["White Pale"] = {3028, 3052, 3327, 9692, 10275, 19083, 19358, 19359},
2687 ["White Shade"] = {5909},
2688 ["Wiggler"] = {236, 237, 3065, 3297, 3429, 3723, 5912, 5914, 15793, 16122, 16127, 16142},
2689 ["Wild Warrior"] = {2991, 3274, 3286, 3352, 3353, 3359, 3409, 3411, 3606},
2690 ["Willi Wasp"] = {3054, 5902, 9057},
2691 ["Wilting Leaf Golem"] = {3032, 3723, 17824, 19110, 19111},
2692 ["Winter Wolf"] = {10295},
2693 ["Wisp"] = {9604},
2694 ["Witch"] = {3012, 3069, 3083, 3290, 3293, 3552, 3562, 3565, 3598, 3736, 9652, 9653, 10294, 12548},
2695 ["Wolf"] = {5897},
2696 ["Worker Golem"] = {238, 239, 953, 3028, 3048, 3061, 3279, 5880, 7428, 7439, 7452, 7642, 8775, 8895, 8898, 9063, 9655},
2697 ["Wyrm"] = {236, 237, 816, 3028, 3349, 3449, 7430, 8027, 8043, 8045, 8092, 8093, 9304, 9665},
2698 ["Wyvern"] = {236, 3010, 3029, 3071, 3450, 7408, 9644},
2699 ["Xenia"] = {3114, 3273, 3426},
2700 ["Yaga the Crone"] = {3012, 3069, 3083, 3454, 3562, 3565, 3598, 3736, 8074, 12548},
2701 ["Yakchal"] = {238, 823, 824, 3052, 3079, 3085, 3324, 3333, 3732, 5912, 7290, 7410, 7439, 7440, 7443, 7449, 7459, 9058},
2702 ["Yeti"] = {2992, 3553},
2703 ["Yielothax"] = {236, 237, 816, 822, 3028, 3034, 3048, 3073, 3326, 3725, 7440, 9304, 12737, 12742, 12805},
2704 ["Young Sea Serpent"] = {236, 237, 3029, 3049, 3061, 3266, 3282, 3305, 8894, 8895, 9666},
2705 ["Young Troll"] = {3003, 3277, 3355, 3412, 3552, 9689},
2706 ["Zanakeph"] = {238, 239, 2903, 3029, 3032, 3360, 3370, 3385, 3392, 5741, 5925, 6299, 6499, 7430, 7642, 8057, 8896, 9058, 10316, 10451, 12304},
2707 ["Zarabustor"] = {825, 3006, 3029, 3051, 3299, 3324, 3567, 7368},
2708 ["Zavarash"] = {238, 281, 3038, 3340, 3414, 3415, 3420, 5954, 7387, 7421, 7428, 7642, 8063, 16119, 16120, 16121, 20062, 20276},
2709 ["Zevelon Duskbringer"] = {236, 3098, 3434, 7419, 8192},
2710 ["Zomba"] = {3052, 3084, 9691},
2711 ["Zombie"] = {268, 3052, 3269, 3286, 3305, 3351, 3354, 3568, 8894, 9659},
2712 ["Blood Beast"] = {21195, 9640, 21194, 21146, 236, 7366, 21179, 21158, 21178, 21183, 21180},
2713 ["Bullwark"] = {3028, 3033, 239, 238, 21199, 21200, 5878, 5911, 21219},
2714 ["Death Priest Shargon"] = {238, 239, 9058, 9056, 3069, 8531},
2715 ["Devourer"] = {3028, 3033, 3032, 3030, 9057, 3029, 21182, 21179, 21180, 21178, 21158, 21183, 8084, 3034, 3037, 21164},
2716 ["Execowtioner"] = {9057, 3030, 11472, 21201, 5944, 239, 238, 5911, 3318, 7412, 3381, 21176, 7401},
2717 ["Glooth Anemone"] = {9057, 3032, 21144, 3732, 21197, 236, 237, 21180, 21178, 21179, 21172, 21164, 7643},
2718 ["Glooth Blob"] = {9057, 3033, 21182, 21183, 21180, 21178, 21179},
2719 ["Glooth Fairy"] = {3030, 3033, 3028, 3029, 21158, 21178, 21183, 21180, 21167, 239, 21292, 21144, 21143, 21103, 5880, 8775},
2720 ["Glooth Golem"] = {21103, 21143, 238, 7643, 3032, 9057, 3037, 8775, 21158, 21180, 21179, 21178, 5880, 21170, 21183, 21167, 21165, 3038},
2721 ["Lisa"] = {3028, 3033, 9057, 3030, 239, 238, 7642, 21144, 21146, 21143, 21158, 21197, 21183, 21179, 21180, 21218},
2722 ["Metal Gargoyle"] = {236, 237, 21193, 21171, 21169, 21168, 3051, 3052, 10310, 8082},
2723 ["Minotaur Amazon"] = {7368, 5878, 11472, 3032, 3033, 3030, 9057, 239, 238, 21204, 3098, 5911, 21174, 21175},
2724 ["Minotaur Hunter"] = {11472, 3033, 3030, 237, 236, 7378, 3147, 3347, 5944, 3037, 3039, 5878, 3049, 5912, 5910, 5911, 21175, 7401},
2725 ["Mooh'Tah Warrior"] = {11472, 21202, 3032, 3030, 9057, 3032, 3033, 237, 236, 3091, 5911, 21177, 3415, 3371, 3370, 21166, 7401},
2726 ["Moohtant"] = {21200, 3028, 3030, 238, 3098, 5911, 21199, 3037, 7427, 21173},
2727 ["Rot Elemental"] = {3032, 9057, 3029, 236, 21182, 3052, 237, 21158, 21180, 21183},
2728 ["Rustheap Golem"] = {21196, 3026, 3027, 953, 3279, 236, 237, 5880, 9016, 8894, 8897, 21170, 21171, 7452},
2729 ["The Ravager"] = {238, 239, 3042, 3027, 3025},
2730 ["Walker"] = {3032, 9057, 3033, 239, 7642, 21169, 21170, 21198, 3554},
2731 ["Worm Priestess"] = {11472, 3028, 3033, 3032, 9057, 3029, 3030, 3037, 3039, 11473, 5878, 2920, 3066, 5912, 5911, 5910, 7425, 8082, 7401}
2732}
2733
2734local SPELL_RANGE = {
2735 BIGWAVE = 1, -- Big Wave (flam hur, frigo hur)
2736 EXORI = 2, -- Adjacent (exori, exori gran)
2737 WAVE3X3 = 3, -- 3x3 Wave (vis hur, tera hur)
2738 SMALLAREA = 4, -- Small Area (mas san, exori mas)
2739 MEDIUMAREA = 5, -- Medium Area (mas flam, mas frigo)
2740 LARGEAREA = 6, -- Large Area (mas vis, mas tera)
2741 BEAM5 = 7, -- Short Beam (vis lux)
2742 BEAM8 = 8, -- Large Beam (gran vis lux)
2743 SWEEP = 9, -- Sweep (exori min)
2744 SMALLWAVE = 10 -- Small Wave (gran frigo hur)
2745}
2746
2747local RUNE_RANGE = {
2748 EXPLO = 1, -- Cross (explosion)
2749 BOMB = 2, -- Bomb (fire bomb)
2750 THREE = 3, -- 3 Sqm
2751 BALL = 4, -- Ball (gfb, avalanche)
2752 FIVE = 5, -- 5 Sqm
2753 SIX = 6, -- 6 Sqm
2754 SEVEN = 7, -- 7 Sqm
2755 EIGHT = 8, -- 8 Sqm
2756 NINE = 9, -- 9 Sqm
2757 TEN = 10 -- 10 Sqm
2758}
2759
2760local MAGIC_SHOOTER_ITEM = {
2761 -- Targeted Spells (type=0)
2762
2763 ['exori ico'] = {type=0, srange=1, mana=30},
2764 ['exori gran ico'] = {type=0, srange=1, mana=300},
2765 ['exori hur'] = {type=0, srange=5, mana=40},
2766
2767 ['exori san'] = {type=0, srange=4, mana=20},
2768 ['exori con'] = {type=0, srange=7, mana=25},
2769 ['exori gran con'] = {type=0, srange=7, mana=55},
2770
2771 ['exori moe ico'] = {type=0, srange=3, mana=20},
2772
2773 ['exori mort'] = {type=0, srange=3, mana=20},
2774
2775 ['exori infir vis'] = {type=0, srange=3, mana=6},
2776 ['exori vis'] = {type=0, srange=3, mana=20},
2777 ['exori amp vis'] = {type=0, srange=5, mana=60},
2778 ['exori gran vis'] = {type=0, srange=3, mana=60},
2779 ['exori max vis'] = {type=0, srange=3, mana=100},
2780
2781 ['exori min flam'] = {type=0, srange=3, mana=6},
2782 ['exori flam'] = {type=0, srange=3, mana=20},
2783 ['exori gran flam'] = {type=0, srange=3, mana=60},
2784 ['exori max flam'] = {type=0, srange=3, mana=100},
2785
2786 ['exori frigo'] = {type=0, srange=3, mana=20},
2787 ['exori gran frigo'] = {type=0, srange=3, mana=60},
2788 ['exori max frigo'] = {type=0, srange=3, mana=100},
2789
2790 ['exori infir tera'] = {type=0, srange=3, mana=6},
2791 ['exori tera'] = {type=0, srange=3, mana=20},
2792 ['exori gran tera'] = {type=0, srange=3, mana=60},
2793 ['exori max tera'] = {type=0, srange=3, mana=100},
2794
2795 -- Area Spells (type=2)
2796
2797 ['exori'] = {type=2, srange=SPELL_RANGE.EXORI, mana=115},
2798 ['exori gran'] = {type=2, srange=SPELL_RANGE.EXORI, mana=340},
2799
2800 ['exori mas'] = {type=2, srange=SPELL_RANGE.SMALLAREA, mana=160},
2801 ['exevo mas san'] = {type=2, srange=SPELL_RANGE.SMALLAREA, mana=160},
2802
2803 ['exori min'] = {type=2, srange=SPELL_RANGE.SWEEP, mana=200},
2804
2805 ['exevo infir flam hur'] = {type=2, srange=SPELL_RANGE.BIGWAVE, mana=8},
2806 ['exevo flam hur'] = {type=2, srange=SPELL_RANGE.BIGWAVE, mana=25},
2807 ['exevo infir frigo hur'] = {type=2, srange=SPELL_RANGE.BIGWAVE, mana=8},
2808 ['exevo frigo hur'] = {type=2, srange=SPELL_RANGE.BIGWAVE, mana=25},
2809
2810 ['exevo vis lux'] = {type=2, srange=SPELL_RANGE.BEAM5, mana=40},
2811
2812 ['exevo gran vis lux'] = {type=2, srange=SPELL_RANGE.BEAM8, mana=110},
2813
2814 ['exevo tera hur'] = {type=2, srange=SPELL_RANGE.WAVE3X3, mana=210},
2815 ['exevo vis hur'] = {type=2, srange=SPELL_RANGE.WAVE3X3, mana=170},
2816
2817 ['exevo gran frigo hur'] = {type=2, srange=SPELL_RANGE.SMALLWAVE, mana=170},
2818
2819 ['exevo gran mas flam'] = {type=2, srange=SPELL_RANGE.MEDIUMAREA, mana=1100},
2820 ['exevo gran mas frigo'] = {type=2, srange=SPELL_RANGE.MEDIUMAREA, mana=1050},
2821
2822 ['exevo gran mas vis'] = {type=2, srange=SPELL_RANGE.LARGEAREA, mana=600},
2823 ['exevo gran mas tera'] = {type=2, srange=SPELL_RANGE.LARGEAREA, mana=700},
2824
2825 -- Targeted Runes (type=1)
2826 [3155] = {type=1, srange=7}, -- sudden death
2827 [3189] = {type=1, srange=7}, -- fireball
2828 [3182] = {type=1, srange=7}, -- holy missile
2829 [3158] = {type=1, srange=7}, -- icicle
2830 [3165] = {type=1, srange=7}, -- paralyze
2831 [3195] = {type=1, srange=7}, -- soulfire
2832 [3198] = {type=1, srange=7}, -- heavy magic missle
2833 [3174] = {type=1, srange=7}, -- light magic missle
2834 [3179] = {type=1, srange=7}, -- stalagmite
2835
2836 -- Area Runes (type=3)
2837 [3200] = {type=3, srange=RUNE_RANGE.EXPLO}, -- explosion
2838
2839 [3192] = {type=3, srange=RUNE_RANGE.BOMB}, -- fire bomb
2840 [3149] = {type=3, srange=RUNE_RANGE.BOMB}, -- energy bomb
2841 [3173] = {type=3, srange=RUNE_RANGE.BOMB}, -- poison bomb
2842
2843 [3161] = {type=3, srange=RUNE_RANGE.BALL}, -- avalanche
2844 [3191] = {type=3, srange=RUNE_RANGE.BALL}, -- great fireball
2845 [3175] = {type=3, srange=RUNE_RANGE.BALL}, -- stone shower
2846 [3202] = {type=3, srange=RUNE_RANGE.BALL} -- thunderstorm
2847}
2848
2849-- TODO: parse items.xml for durations
2850local ITEM_LIST_DURATIONS = {
2851 -- Rings
2852 [3086] = 10 * 60, -- stealth ring
2853 [3087] = 30 * 60, -- power ring
2854 [3088] = 10 * 60, -- energy ring
2855 [3089] = 20 * 60, -- life ring
2856 [3090] = 10 * 60, -- time ring
2857 [3094] = 30 * 60, -- sword ring
2858 [3095] = 30 * 60, -- axe ring
2859 [3096] = 30 * 60, -- club ring
2860 [3099] = 60 * 60, -- dwarven ring
2861 [3100] = 7.5 * 60, -- ring of healing
2862 [6300] = 8 * 60, -- death ring
2863 [12670] = 10 * 60, -- star ring
2864 [16264] = 60 * 60, -- prismatic ring
2865
2866 -- Boots
2867 [3549] = 240 * 60, -- soft boots
2868 [9018] = 60 * 60 -- firewalker boots
2869}
2870
2871local ITEM_LIST_POTIONS = {
2872 [236] = true,
2873 [237] = true,
2874 [238] = true,
2875 [239] = true,
2876 [266] = true,
2877 [268] = true,
2878 [7439] = true,
2879 [7440] = true,
2880 [7443] = true,
2881 [7642] = true,
2882 [7643] = true
2883}
2884
2885local ITEM_LIST_ACTIVE_RINGS = {
2886 [3092] = 3095,
2887 [3091] = 3094,
2888 [3093] = 3096,
2889 [3052] = 3089,
2890 [3098] = 3100,
2891 [3097] = 3099,
2892 [3051] = 3088,
2893 [3053] = 3090,
2894 [3049] = 3086,
2895 [9593] = 9593,
2896 [9393] = 9392,
2897 [3007] = 3007,
2898 [6299] = 6300,
2899 [9585] = 9585,
2900 [3048] = 3048,
2901 [3050] = 3087,
2902 [3245] = 3245,
2903 [3006] = 3006,
2904 [349] = 349,
2905 [3004] = 3004,
2906 [16114] = 16264
2907}
2908
2909local ITEM_LIST_RUSTYARMORS = {
2910 [8895] = true,
2911 [8896] = true,
2912 [8897] = true,
2913 [8898] = true,
2914 [8899] = true
2915}
2916
2917local ITEM_LIST_RUSTYTRASH = {
2918 [3557] = true,
2919 [3372] = true,
2920 [3558] = true,
2921 [3362] = true,
2922 [3358] = true,
2923 [3377] = true,
2924 [3357] = true,
2925 [3359] = true
2926}
2927
2928local ITEM_LIST_MONEY = {
2929 [3031] = true,
2930 [3035] = true,
2931 [3043] = true
2932}
2933
2934local ITEM_LIST_FLASKS = {
2935 [283] = true,
2936 [284] = true,
2937 [285] = true
2938}
2939
2940local ITEM_LIST_FURNITURE_DESTROYERS = {
2941 [3267] = true,
2942 [3292] = true,
2943 [3291] = true
2944}
2945
2946local ITEM_LIST_SKINNABLE_LOOT = {
2947 [281] = true,
2948 [282] = true,
2949 [3026] = true,
2950 [3029] = true,
2951 [3032] = true,
2952 [5876] = true,
2953 [5877] = true,
2954 [5878] = true,
2955 [5893] = true,
2956 [5905] = true,
2957 [5906] = true,
2958 [5925] = true,
2959 [5948] = true,
2960 [9303] = true
2961}
2962
2963local ITEM_LIST_BACKPACKS = {
2964 [2854] = true,
2965 [2865] = true,
2966 [2866] = true,
2967 [2867] = true,
2968 [2868] = true,
2969 [2869] = true,
2970 [2870] = true,
2971 [2871] = true,
2972 [2872] = true,
2973 [5926] = true,
2974 [5949] = true,
2975 [7342] = true,
2976 [8860] = true,
2977 [9601] = true,
2978 [9602] = true,
2979 [9604] = true,
2980 [9605] = true,
2981 [10202] = true,
2982 [10324] = true,
2983 [10326] = true,
2984 [10327] = true,
2985 [10346] = true,
2986 [14248] = true,
2987 [14249] = true,
2988 [16099] = true,
2989 [16100] = true,
2990 [21295] = true
2991}
2992
2993local RUNES_EXOTIC = {
2994 [3203] = true, -- animate dead rune
2995 [3147] = true, -- blank rune
2996 [3197] = true, -- desintegrate rune
2997 [3149] = true, -- energy bomb rune
2998 [3189] = true, -- fireball rune
2999 [3182] = true, -- holy missile rune
3000 [3158] = true, -- icicle rune
3001 [3180] = true, -- magic wall rune
3002 [3165] = true, -- paralyze rune
3003 [3173] = true, -- poison bomb rune
3004 [3195] = true, -- soulfire rune
3005 [3175] = true, -- stone shower rune
3006 [3202] = true, -- thunderstorm rune
3007 [3156] = true -- wild growth rune
3008}
3009
3010local RUNES_NORMAL = {
3011 [3161] = true, -- avalanche rune
3012 [3178] = true, -- chameleon rune
3013 [3177] = true, -- convince creature rune
3014 [3153] = true, -- cure poison rune
3015 [3148] = true, -- destroy field rune
3016 [3164] = true, -- energy field rune
3017 [3166] = true, -- energy wall rune
3018 [3200] = true, -- explosion rune
3019 [3192] = true, -- fire bomb rune
3020 [3188] = true, -- fire field rune
3021 [3190] = true, -- fire wall rune
3022 [3191] = true, -- great fireball rune
3023 [3198] = true, -- heavy magic missile rune
3024 [3152] = true, -- intense healing rune
3025 [3174] = true, -- light magic missile rune
3026 [3172] = true, -- poison field rune
3027 [3176] = true, -- poison wall rune
3028 [3179] = true, -- stalagmite rune
3029 [3155] = true, -- sudden death rune
3030 [3160] = true -- ultimate healing rune
3031}
3032
3033local EXOTIC_RUNE_TOWNS = {
3034 ['edron'] = true,
3035 ['gray island'] = true,
3036 ['rathleton'] = true
3037}
3038
3039local FISHING_RODS = {
3040 [3483] = true,
3041 [9306] = true
3042}
3043
3044local ROPE_TOOLS = {
3045 [3003] = 0,
3046 [646] = 1,
3047 [9594] = 2,
3048 [9596] = 3,
3049 [9598] = 4
3050}
3051
3052local SHOVEL_TOOLS = {
3053 [3457] = 0,
3054 [5710] = 1,
3055 [9594] = 2,
3056 [9596] = 3,
3057 [9598] = 4
3058}
3059
3060local PICK_TOOLS = {
3061 [3456] = true,
3062 [9594] = true,
3063 [9596] = true,
3064 [9598] = true
3065}
3066
3067local RODS = {
3068 [3065] = true,
3069 [3066] = true,
3070 [3067] = true,
3071 [3069] = true,
3072 [3070] = true,
3073 [8082] = true,
3074 [8083] = true,
3075 [8084] = true,
3076 [16117] = true,
3077 [16118] = true
3078}
3079
3080local WANDS = {
3081 [3071] = true,
3082 [3072] = true,
3083 [3073] = true,
3084 [3074] = true,
3085 [3075] = true,
3086 [8092] = true,
3087 [8093] = true,
3088 [8094] = true,
3089 [12603] = true,
3090 [16096] = true,
3091 [16115] = true
3092}
3093
3094local DISTANCE_WEAPONS = {
3095 [3277] = true,
3096 [7378] = true,
3097 [3347] = true,
3098 [7367] = true,
3099 [1781] = true,
3100 [3287] = true,
3101 [3298] = true,
3102 [7366] = true,
3103 [7368] = true,
3104 [20082] = true,
3105 [20083] = true,
3106 [20084] = true,
3107 [20085] = true,
3108 [20086] = true,
3109 [20087] = true
3110}
3111
3112local NORMAL_BOOTS = {
3113 [10201] = true,
3114 [3246] = true,
3115 [3550] = true,
3116 [3553] = true,
3117 [10200] = true,
3118 [13997] = true,
3119 [3555] = true,
3120 [16112] = true,
3121 [4033] = true,
3122 [3079] = true,
3123 [10323] = true,
3124 [3554] = true,
3125 [818] = true,
3126 [813] = true,
3127 [819] = true,
3128 [820] = true,
3129 [10386] = true,
3130 [5461] = true,
3131 [7457] = true,
3132 [9017] = true,
3133 [3556] = true,
3134 [3552] = true,
3135 [3551] = true,
3136 [21981] = true
3137}
3138
3139local DEPOT = {
3140 SLOT_NONSTACK = 0,
3141 SLOT_STACK = 1,
3142 SLOT_SUPPLY = 2
3143}
3144
3145-- XenoBot user accounts automatically open the debug channel
3146local DEBUG_ACCOUNTS = {
3147 ['syntax'] = true,
3148 ['joshwa534'] = true,
3149 ['darkstar'] = true,
3150 ['spectrus'] = true,
3151 ['fatality'] = true,
3152 ['rydan'] = true,
3153 ['shadowart'] = true,
3154 ['jontor'] = true,
3155 ['draadloos'] = true
3156}
3157local _script = {
3158 xenoversion = xeno.getVersionNumber(),
3159 start = os.time(),
3160 startStamina = xeno.getSelfStamina(),
3161 baseExp = xeno.getSelfExperience(),
3162 round = 1,
3163 balance = 0,
3164 unsafeQueue = 0,
3165 pingSum = 0,
3166 pingEntries = 0,
3167 pingLast = 0,
3168 strangers = 0,
3169
3170 theme = 'light',
3171
3172 name = "Yalahar Grim Reapers (RP)",
3173 slug = "[RP] Yalahar Grim Reapers.xbst",
3174 town = "yalahar",
3175 vocation = "Paladin",
3176 configHash = "8dae8582954ae4e305515ff8dc8331b3",
3177 pricesConfigHash = "a9d428293c579bc4f8ca9f45bad18249",
3178
3179 townexit = nil,
3180 channel = nil,
3181 historyChannel = nil,
3182 debugChannel = nil,
3183 disableWithdraw = false,
3184 firstResupply = true,
3185 rope = nil,
3186 shovel = nil,
3187 pick = nil,
3188
3189 ropeCode = 0,
3190 shovelCode = 0,
3191
3192 state = 'Setting up backpacks',
3193 reason = 'Initializing',
3194 route = '--',
3195 lastDestination = nil,
3196
3197 ready = false,
3198 inSpawn = false,
3199 stuck = false,
3200 luring = false,
3201 dynamicLuring = false,
3202 ignoring = false,
3203
3204 logoutQueued = false,
3205 trainingQueued = false,
3206 returnQueued = false,
3207 forceLogoutQueued = false,
3208
3209 alarmInterval = nil,
3210
3211 wasted = 0,
3212 looted = 0,
3213 profit = 0,
3214 experience = 0,
3215
3216 doors = {},
3217
3218 equipped = {
3219 ['ring'] = false,
3220 ['amulet'] = false,
3221 ['feet'] = false
3222 }
3223}
3224
3225local _timerIndex = 0
3226local _eventIndex = 0
3227local _timers = {} -- execute callbacks at a specific time
3228local _events = { -- execute callbacks on events
3229 [EVENT_LABEL] = {},
3230 [EVENT_ERROR] = {},
3231 [EVENT_BATTLE] = {},
3232 [EVENT_LOOT] = {},
3233 [EVENT_NPC] = {},
3234 [EVENT_COMMAND] = {},
3235 [EVENT_PATH_END] = {},
3236 [EVENT_DEPOT_END] = {},
3237 [EVENT_CONTAINER] = {}
3238}
3239
3240local _path = { -- current path we're taking
3241 town = nil, -- ex: thais
3242 route = nil, -- ex: depot~hunt
3243 from = nil, -- ex: depot
3244 to = nil, -- ex: hunt
3245 dest = nil -- ex: startHunt
3246}
3247
3248local _settings = {}
3249local _config = {}
3250local _supplies = {}
3251local _backpacks = {
3252 ['Main'] = 0,
3253 ['Loot'] = 1,
3254 -- remaining dynamically assigned...
3255}
3256
3257local _hud = {
3258 index = {},
3259 gamewindow = {x=0, y=0, w=0, h=0},
3260 leftcolumn = {x=10, y=65, w=200, h=0, panels={}},
3261 rightcolumn = {x=0, y=10, w=200, h=0, panels={}},
3262 lootSnapshots = {},
3263 supplySnapshots = {}
3264}
3265Core = (function()
3266
3267 local function debug(message, type)
3268 xeno.luaSendChannelMessage(_script.debugChannel, type or CHANNEL_YELLOW, ':', message)
3269 end
3270
3271 local function overflowText(str, len, ellipsis)
3272 return #str > len and string.sub(str, 1, len) .. ellipsis or str
3273 end
3274
3275 local function formatTime(seconds, simpleFormat)
3276 local d, h, m, s = math.floor(seconds / 86400),
3277 math.floor(seconds / 3600) % 24,
3278 math.floor(seconds / 60) % 60,
3279 seconds % 60
3280 return simpleFormat and string.format('%02d:%02d', 24*d+h, m) or string.format('%02d:%02d:%02d:%02d', d, h, m, s)
3281 end
3282
3283 local function formatNumber(n)
3284 local n = tonumber(string.format('%.2f', n))
3285 if not n then
3286 return '0'
3287 end
3288 local left, num, right = string.match(n,'^([^%d]*%d)(%d*)(.-)$')
3289 return left .. string.reverse(string.gsub(string.reverse(num), '(%d%d%d)','%1,')) .. right
3290 end
3291
3292 local function titlecase(str)
3293 return string.gsub(str, "(%a)([%w_']*)", function(first, rest)
3294 return string.upper(first) .. string.lower(rest)
3295 end)
3296 end
3297
3298 local function flattenItemCounts(items)
3299 -- Iterate through itemid indexed counts
3300 -- flatten to array with count and itemid combined
3301 local list = {}
3302 for itemid, count in pairs(items) do
3303 list[#list+1] = xeno.getItemNameByID(itemid) .. (count > 1 and ' (x' .. count .. ')' or '')
3304 end
3305 return list
3306 end
3307
3308 local function formatList(items, delim, prefix)
3309 prefix = prefix or ''
3310 local message = nil
3311 if #items > 1 then
3312 if delim then
3313 message = string.rep(prefix .. '%s' .. delim, #items-1) .. prefix .. '%s'
3314 else
3315 message = string.rep('%s, ', #items-1) .. 'and %s'
3316 end
3317 else
3318 message = '%s'
3319 end
3320 return string.format(message, unpack(items))
3321 end
3322
3323 local function getMemoryUsage()
3324 return collectgarbage('count') * 1024
3325 end
3326
3327 local function freeMemory()
3328 local prev = getMemoryUsage()
3329 collectgarbage()
3330 return prev - getMemoryUsage()
3331 end
3332
3333 local function pingDelay(delay)
3334 if type(delay) == 'table' then
3335 return math.max(math.random(unpack(delay)), xeno.ping())
3336 end
3337 return math.max(delay, xeno.ping())
3338 end
3339
3340 local function throttle(action, func, ...)
3341 local currentTime = os.clock() * 1000
3342 if currentTime - action.last > action.limit then
3343 action.last = currentTime
3344 func(...)
3345 end
3346 end
3347
3348 local function clearTimeout(id)
3349 _timers[id] = nil
3350 end
3351
3352 local function clearWhen(event, id)
3353 _events[event][id] = nil
3354 end
3355
3356 local function setTimeout(callback, timeout)
3357 _timerIndex = _timerIndex + 1
3358 _timers[_timerIndex] = {callback, (os.clock() * 1000) + timeout, nil, 0}
3359 return _timerIndex
3360 end
3361
3362 local function setInterval(callback, interval, loops)
3363 _timerIndex = _timerIndex + 1
3364 _timers[_timerIndex] = {callback, (os.clock() * 1000) + interval, interval, loops}
3365 return _timerIndex
3366 end
3367
3368 local function when(event, condition, callback)
3369 -- Labels are simpler, simple hash lookup with callback
3370 if event == EVENT_LABEL then
3371 _events[EVENT_LABEL][condition] = callback
3372 return condition
3373 end
3374
3375 -- Valid conditions:
3376 -- string: Message matches string exactly
3377 -- table: Contains a pattern and optional match comparisons {pattern, firstMatch, secondMatch, ...}
3378 -- Any other event needs to match a pattern condition
3379 local id = #_events[event]+1
3380 _events[event][id] = {
3381 condition = condition,
3382 callback = callback,
3383 timeout = nil
3384 }
3385
3386 return id
3387 end
3388
3389 local function checkTimers()
3390 local currentTime = os.clock() * 1000
3391 local success = nil
3392 local index = nil
3393 local timer = nil
3394 repeat
3395 success, index, timer = pnext(_timers, index)
3396 -- Timer is false, must be pending a remove, remove now
3397 if not timer and index then
3398 _timers[index] = nil
3399 return
3400 end
3401 if success and index and timer then
3402 -- Check expiration
3403 if currentTime >= timer[2] then
3404 -- Execute
3405 timer[1](index)
3406 -- Timer may have killed itself, check if it still exists
3407 if _timers[index] then
3408 -- Increase expiration for next interval
3409 if timer[3] then
3410 _timers[index][2] = currentTime + timer[3]
3411 end
3412 -- Timer expires at some point
3413 if timer[4] then
3414 -- Decrement loop
3415 _timers[index][4] = timer[4] - 1
3416 -- Loops finished
3417 if timer[4] < 1 then
3418 _timers[index] = false
3419 end
3420 end
3421 end
3422 end
3423 end
3424 until not index or not success or not timer
3425 end
3426
3427 local function checkEvents(eventType, message, ...)
3428 local success = nil
3429 local index = nil
3430 local event = nil
3431 if _events[eventType] then
3432 repeat
3433 success, index, event = pnext(_events[eventType], index)
3434 -- Event is false, must be pending a remove, remove now
3435 if not event and index then
3436 _events[eventType][index] = nil
3437 return
3438 end
3439 if success and index and event then
3440 local conditionType = type(event.condition)
3441 -- Always true
3442 if conditionType == 'nil' then
3443 _events[eventType][index] = nil
3444 event.callback(message, ...)
3445 -- Exact string check (or wildcard)
3446 elseif conditionType == 'string' then
3447 if event.condition == message then
3448 _events[eventType][index] = nil
3449 event.callback(message, ...)
3450 end
3451 -- Pattern check
3452 elseif conditionType == 'table' then
3453 local match = {string.match(event.condition)}
3454 if match[1] then
3455 _events[eventType][index] = nil
3456 event.callback(message, match, ...)
3457 end
3458 end
3459 end
3460 until not index or not success or not event
3461 end
3462 end
3463
3464 local function split(str, sep)
3465 local res = {}
3466 for v in string.gmatch(str, "([^" .. sep .. "]+)") do
3467 res[#res + 1] = v
3468 end
3469 return res
3470 end
3471
3472 local function trim(str)
3473 return string.gsub(str, "^%s*(.-)%s*$", "%1")
3474 end
3475
3476 local function isXenoBotBinary()
3477 return _script.xenoversion:find('Binary') ~= nil
3478 end
3479
3480 local function getXenoVersion()
3481 local data = split(_script.xenoversion, '.')
3482 return tonumber(data[#data])
3483 end
3484
3485 local function getPositionFromDirection(pos, dir, len)
3486 local n = len or 1
3487 if (dir == NORTH) then
3488 pos.y = pos.y - n
3489 elseif (dir == SOUTH) then
3490 pos.y = pos.y + n
3491 elseif (dir == WEST) then
3492 pos.x = pos.x - n
3493 elseif (dir == EAST) then
3494 pos.x = pos.x + n
3495 elseif (dir == NORTHWEST) then
3496 pos.y = pos.y - n
3497 pos.x = pos.x - n
3498 elseif (dir == NORTHEAST) then
3499 pos.y = pos.y - n
3500 pos.x = pos.x + n
3501 elseif (dir == SOUTHWEST) then
3502 pos.y = pos.y + n
3503 pos.x = pos.x - n
3504 elseif (dir == SOUTHEAST) then
3505 pos.y = pos.y + n
3506 pos.x = pos.x + n
3507 end
3508 return pos
3509 end
3510
3511 local function getSelfLookPosition(range)
3512 return getPositionFromDirection(xeno.getSelfPosition(), xeno.getSelfLookDirection(), range or 1)
3513 end
3514
3515 local function getPosFromString(str)
3516 local pos = {}
3517 string.gsub(str, "(%d+)", function(w) return table.insert(pos,tonumber(w)) end)
3518 return {x=(pos[1] or 0), y=(pos[2] or 0), z=(pos[3] or 0)}
3519 end
3520
3521 local function cleanLabel(str)
3522 -- Transform entity to '>'
3523 --str = string.gsub(str, '>', '>')
3524 -- Remove semi-colon
3525 return string.sub(str, 1, -2)
3526 end
3527
3528 local function countPairs(tbl)
3529 local n = 0
3530 for k,v in pairs(tbl) do
3531 n = n + 1
3532 end
3533 return n
3534 end
3535
3536 function indexTable(tbl, lowercase)
3537 if not tbl or #tbl == 0 then
3538 return nil
3539 end
3540 if type(tbl) ~= 'table' then
3541 tbl = {tbl}
3542 end
3543 local keyValues = {}
3544 for i = 1, #tbl do
3545 local key = tbl[i]
3546 keyValues[lowercase and key:lower() or key] = true
3547 end
3548 return keyValues
3549 end
3550
3551 local function getTimeUntilServerSave()
3552 local curTime = os.time()
3553 local utc, loc = os.date("!*t", curTime), os.date("*t", curTime)
3554 loc.isdst = false
3555 local offset = os.time(loc) - os.time(utc)
3556 local UTCNow = os.time()
3557 local UTCTable = os.date("*t", UTCNow)
3558 local dstValue = UTCTable.isdst and 7200 or 3600
3559 local targetTime = {year=UTCTable.year, month=UTCTable.month, day=UTCTable.day, hour=9, min=59}
3560 local timeDifference = (((os.time(targetTime) - (UTCNow + dstValue)) + offset)/60)/60
3561 local hoursLeft = timeDifference
3562 if hoursLeft < 0 then
3563 hoursLeft = 24 + hoursLeft
3564 end
3565 return hoursLeft
3566 end
3567
3568 local function getDistanceBetween(pos1, pos2)
3569 return math.max(math.abs(pos1.x - pos2.x), math.abs(pos1.y - pos2.y))
3570 end
3571
3572 local function sortPositionsByDistance(pos, positions, floorWeight)
3573 local sorted = positions
3574 table.sort(sorted, function(a, b)
3575 local floorA = 0
3576 local floorB = 0
3577 if floorWeight and pos.z and a.z and b.z then
3578 floorA = floorWeight * (math.abs(pos.z - a.z))
3579 floorB = floorWeight * (math.abs(pos.z - b.z))
3580 end
3581 return (getDistanceBetween(pos, a) + floorA) < (getDistanceBetween(pos, b) + floorB)
3582 end)
3583 return sorted
3584 end
3585
3586 local function talk(messages, callback)
3587 local msgCount = #messages
3588 local responses = {}
3589 local function sayMessage(index)
3590 -- Delay messages
3591 local msg = messages[index]
3592 setTimeout(function()
3593 -- Last message, callback
3594 if index == msgCount then
3595 callback(responses)
3596 -- Recurse
3597 else
3598 sayMessage(index+1)
3599 end
3600 end, pingDelay(DELAY.RANGE_TALK))
3601
3602 -- Record response to this message
3603 when(EVENT_NPC, nil, function(response, npc)
3604 -- Update response
3605 responses[#responses+1] = response
3606 end)
3607
3608 -- Send a single message to the NPC
3609 xeno.selfNpcSay(msg)
3610 debug('NPC:: ' .. msg)
3611 end
3612
3613 -- Send the first message and start recursing
3614 sayMessage(1)
3615 end
3616
3617 local function clearWalkerPath()
3618
3619 local function destroyFurniture(x, y, z)
3620 local weapon = xeno.getWeaponSlotData().id
3621 -- Wand, rods, and distance weapons CANNOT destroy furniture
3622 -- Attempt to detect an item in the main backpack that can
3623 if weapon == 0 or DISTANCE_WEAPONS[weapon] or RODS[weapon] or WANDS[weapon] then
3624 weapon = 0
3625 -- Look through main backpack
3626 local mainbp = _backpacks['Main']
3627 for spot = 0, xeno.getContainerItemCount(mainbp) - 1 do
3628 local item = xeno.getContainerSpotData(mainbp, spot)
3629 -- This item can kill furniture
3630 if ITEM_LIST_FURNITURE_DESTROYERS[item.id] then
3631 -- Save item
3632 weapon = item.id
3633 break
3634 end
3635 end
3636 end
3637
3638 -- TODO: make Nick add moveGroundItemToGround(fromX, fromY, toX, toY) or pushTopObject...
3639 -- once added, push item to free position if a breaking weapon isn't found
3640 if weapon > 0 then
3641 xeno.selfUseItemWithGround(weapon, x, y, z)
3642 end
3643 end
3644
3645 local function attackCreature(cid)
3646 -- Make sure we're not already attacking the creature
3647 if cid ~= xeno.getSelfTargetID() then
3648 xeno.attackCreature(cid)
3649 end
3650 -- TODO: read spell/rune from shooter, apply directly to creature's face
3651 end
3652
3653 local function clearTile(x, y, z)
3654 local item = xeno.getTileUseTargetID(x, y, z)
3655 local selfPos = xeno.getSelfPosition()
3656 local distance = getDistanceBetween(selfPos, {x=x,y=y,z=z})
3657
3658 -- Creature
3659 if item.id == 99 and distance < 3 then
3660 local cid = item.count
3661 local index = xeno.getCreatureListIndex(cid)
3662 -- Monster
3663 if xeno.isCreatureMonster(index) then
3664 attackCreature(cid)
3665 return true
3666 end
3667 -- Not attackable, look at other tiles
3668 return false
3669 end
3670
3671 -- Furniture
3672 if xeno.isItemFurniture(item.id) then
3673 destroyFurniture(x, y, z)
3674 return true
3675 end
3676
3677 return false
3678 end
3679
3680 -- Attempt to clear tiles in a specific order
3681 -- There's a better way, I guarantee it.
3682 local priorities = {
3683 [NORTH] = {NORTH, NORTHWEST, NORTHEAST, WEST, EAST, SOUTH, SOUTHWEST, SOUTHEAST},
3684 [EAST] = {EAST, NORTHEAST, SOUTHEAST, NORTH, SOUTH, WEST, NORTHWEST, SOUTHWEST},
3685 [SOUTH] = {SOUTH, SOUTHWEST, SOUTHEAST, WEST, EAST, NORTH, NORTHWEST, NORTHEAST},
3686 [WEST] = {WEST, NORTHWEST, SOUTHWEST, NORTH, SOUTH, EAST, NORTHEAST, SOUTHEAST}
3687 }
3688
3689 -- Search for valid tiles around player to clear
3690 local pos = xeno.getSelfPosition()
3691 local directions = priorities[xeno.getSelfLookDirection()]
3692 local index = 1
3693 local valid = false
3694 repeat
3695 local tilePos = getPositionFromDirection({x=pos.x, y=pos.y}, directions[index], 1)
3696 valid = clearTile(tilePos.x, tilePos.y, pos.z)
3697 index = index + 1
3698 until valid or index > #directions
3699
3700 -- Search entire screen if nothing found nearby
3701 if not valid then
3702 -- Populate all non-walkable tiles on-screen
3703 local tiles = {}
3704 for x = -7, 7 do
3705 for y = -7, 7 do
3706 -- Tile not walkable
3707 if xeno.getTileIsWalkable(pos.x+x, pos.y+y, pos.z) then
3708 tiles[#tiles+1] = {x=pos.x+x, y=pos.y+y, z=pos.z}
3709 end
3710 end
3711 end
3712 -- Sort by distance
3713 tiles = sortPositionsByDistance(pos, tiles)
3714 -- Find tile to clear
3715 local screenIndex = 1
3716 local screenValid = false
3717 repeat
3718 local tpos = tiles[screenIndex]
3719 screenValid = clearTile(tpos.x, tpos.y, tpos.z)
3720 screenIndex = screenIndex + 1
3721 until screenValid or screenIndex > #tiles
3722 end
3723 end
3724
3725 local function cast(words)
3726 return xeno.getSelfSpellCooldown(words) == 0
3727 and xeno.getSelfSpellRequirementsMet(words)
3728 and xeno.selfSay(words)
3729 end
3730
3731 local function isSpell(words)
3732 local spellPrefixes = {'exan', 'exura', 'utan', 'utevo', 'exevo', 'exor', 'utor'}
3733 local isSpell = false
3734 for i = 1, #spellPrefixes do
3735 local prefix = spellPrefixes[i]
3736 local pattern = ("^%s.*$"):format(prefix)
3737 if words:match(pattern) then
3738 isSpell = true
3739 break
3740 end
3741 end
3742 return isSpell
3743 end
3744
3745 local function cureConditions(callback)
3746 local conditions = {
3747 poisoned = {
3748 spell = 'exana pox',
3749 voc = {druid=true, sorcerer=true, paladin=true, knight=true}
3750 },
3751 burning = {
3752 spell = 'exana flam',
3753 voc = {druid=true}
3754 },
3755 cursed = {
3756 spell = 'exana mort',
3757 voc = {paladin=true}
3758 },
3759 bleeding = {
3760 spell = 'exana kor',
3761 voc = {druid=true, knight=true}
3762 },
3763 electrified = {
3764 spell = 'exana vis',
3765 voc = {druid=true}
3766 }
3767 }
3768
3769 local casted = false
3770 for condition, data in pairs(conditions) do
3771 if xeno.getSelfFlag(condition) and not data.cured then
3772 -- Only attempt once per condition
3773 data.cured = true
3774 -- Cure the condition
3775 if data.voc[_script.vocation:lower()] then
3776 casted = true
3777 cast(data.spell)
3778 break
3779 end
3780 end
3781 end
3782
3783 -- Attempted to heal, recurse to heal more conditions
3784 if casted then
3785 setTimeout(function()
3786 cureConditions(callback)
3787 end, 1500)
3788 elseif callback then
3789 callback()
3790 end
3791 end
3792
3793 local function isCorpseOpen()
3794 local index = -1
3795 while (index < 16) do
3796 index = index + 1
3797 local container = xeno.getContainerName(index)
3798 local isCorpse = string.find(container, "The") or
3799 string.find(container, "Demonic") or
3800 string.find(container, "Dead") or
3801 string.find(container, "Slain") or
3802 string.find(container, "Dissolved") or
3803 string.find(container, "Remains") or
3804 string.find(container, "Elemental") or
3805 string.find(container, "Split") or
3806 xeno.isItemCorpse(xeno.getContainerID(index))
3807 if(xeno.getContainerOpen(index) and isCorpse)then
3808 return true
3809 end
3810 end
3811 return false
3812 end
3813
3814 local function getWalkableTiles(center, range)
3815 local walkables = {}
3816 local base = center
3817 range = (range > 10) and 10 or range
3818 for x = -range, range do
3819 for y = -range, range do
3820 if xeno.getTileIsWalkable(base.x + x, base.y + y, base.z) then
3821 walkables[#walkables+1] = {x = base.x + x, y = base.y + y, z = base.z}
3822 end
3823 end
3824 end
3825 return walkables
3826 end
3827
3828 local function getDirectionTo(pos1, pos2)
3829 local dir = NORTH
3830 if (pos1.x > pos2.x) then
3831 dir = WEST
3832 if (pos1.y > pos2.y) then
3833 dir = NORTHWEST
3834 elseif (pos1.y < pos2.y) then
3835 dir = SOUTHWEST
3836 end
3837 elseif (pos1.x < pos2.x) then
3838 dir = EAST
3839 if (pos1.y > pos2.y) then
3840 dir = NORTHEAST
3841 elseif (pos1.y < pos2.y) then
3842 dir = SOUTHEAST
3843 end
3844 else
3845 if (pos1.y > pos2.y) then
3846 dir = NORTH
3847 elseif (pos1.y < pos2.y) then
3848 dir = SOUTH
3849 end
3850 end
3851 return dir
3852 end
3853
3854 local function getSelfName()
3855 return xeno.getCreatureName(xeno.getCreatureListIndex(xeno.getSelfID()))
3856 end
3857
3858 local function checkSoftBoots()
3859 -- Check softboots
3860 local playerMana = math.abs((xeno.getSelfMana() / xeno.getSelfMaxMana()) * 100)
3861 if _config['Soft Boots']['Mana-Percent'] > 0 then
3862 local playerBoots = xeno.getFeetSlotData().id
3863 local inProtectionZone = xeno.getSelfFlag('inpz')
3864 local needSoftBoots = not inProtectionZone and playerMana <= _config['Soft Boots']['Mana-Percent']
3865 local mainbp = _backpacks['Main']
3866 -- Needs soft boots
3867 if needSoftBoots then
3868 -- Needs to swap normal or worn boots with softboots
3869 if playerBoots ~= ITEMID.SOFTBOOTS_ACTIVE then
3870 -- Search for soft boots in main backpack
3871 for spot = 0, xeno.getContainerItemCount(mainbp) - 1 do
3872 -- Equip soft boots
3873 if xeno.getContainerSpotData(mainbp, spot).id == ITEMID.SOFTBOOTS then
3874 xeno.containerMoveItemToSlot(mainbp, spot, "feet", 1)
3875 _script.equipped['feet'] = true;
3876 break
3877 end
3878 end
3879 end
3880 -- Need to swap active or worn softboots with normal boots
3881 elseif playerBoots == 0 or playerBoots == ITEMID.SOFTBOOTS_ACTIVE or playerBoots == ITEMID.SOFTBOOTS_WORN then
3882 -- Search for regular boots
3883 for spot = 0, xeno.getContainerItemCount(mainbp) - 1 do
3884 -- Equip regular boots
3885 if NORMAL_BOOTS[xeno.getContainerSpotData(mainbp, spot).id] then
3886 xeno.containerMoveItemToSlot(mainbp, spot, "feet", 1)
3887 _script.equipped['feet'] = false;
3888 break
3889 end
3890 end
3891 end
3892 end
3893 end
3894
3895 local _delayTimeout = nil
3896 local function delayWalker()
3897 if _delayTimeout then
3898 clearTimeout(_delayTimeout)
3899 _delayTimeout = nil
3900 end
3901 _delayTimeout = setTimeout(function()
3902 xeno.setWalkerEnabled(false)
3903 error('Action timed out. Please contact support.')
3904 end, DELAY.WALKER_TIMEOUT - 100)
3905 return xeno.delayWalker(DELAY.WALKER_TIMEOUT)
3906 end
3907
3908 local function resumeWalker()
3909 if _delayTimeout then
3910 clearTimeout(_delayTimeout)
3911 _delayTimeout = nil
3912 end
3913 return xeno.delayWalker(0)
3914 end
3915
3916 -- Export global functions
3917 return {
3918 debug = debug,
3919 overflowText = overflowText,
3920 formatTime = formatTime,
3921 formatNumber = formatNumber,
3922 titlecase = titlecase,
3923 formatList = formatList,
3924 getMemoryUsage = getMemoryUsage,
3925 freeMemory = freeMemory,
3926 pingDelay = pingDelay,
3927 throttle = throttle,
3928 clearTimeout = clearTimeout,
3929 clearWhen = clearWhen,
3930 setTimeout = setTimeout,
3931 setInterval = setInterval,
3932 when = when,
3933 checkTimers = checkTimers,
3934 checkEvents = checkEvents,
3935 split = split,
3936 trim = trim,
3937 isXenoBotBinary = isXenoBotBinary,
3938 getXenoVersion = getXenoVersion,
3939 getSelfLookPosition = getSelfLookPosition,
3940 getPosFromString = getPosFromString,
3941 cleanLabel = cleanLabel,
3942 countPairs = countPairs,
3943 indexTable = indexTable,
3944 getTimeUntilServerSave = getTimeUntilServerSave,
3945 sortPositionsByDistance = sortPositionsByDistance,
3946 talk = talk,
3947 clearWalkerPath = clearWalkerPath,
3948 cast = cast,
3949 isSpell = isSpell,
3950 cureConditions = cureConditions,
3951 isCorpseOpen = isCorpseOpen,
3952 getWalkableTiles = getWalkableTiles,
3953 getDirectionTo = getDirectionTo,
3954 getPositionFromDirection = getPositionFromDirection,
3955 getDistanceBetween = getDistanceBetween,
3956 getSelfName = getSelfName,
3957 checkSoftBoots = checkSoftBoots,
3958 flattenItemCounts = flattenItemCounts,
3959 delayWalker = delayWalker,
3960 resumeWalker = resumeWalker
3961 }
3962end)()
3963Console = (function()
3964
3965 -- Imports
3966 local when = Core.when
3967 local getSelfName = Core.getSelfName
3968
3969 local _lastConsoleMessage = nil
3970
3971 local function log(message)
3972 if message == _lastConsoleMessage then
3973 return
3974 end
3975 _lastConsoleMessage = message
3976 xeno.luaSendChannelMessage(_script.channel, CHANNEL_ORANGE, ':', message)
3977 end
3978
3979 local function info(message)
3980 if message == _lastConsoleMessage then
3981 return
3982 end
3983 _lastConsoleMessage = message
3984 xeno.luaSendChannelMessage(_script.channel, CHANNEL_YELLOW, ':', message)
3985 end
3986
3987 local function warn(message)
3988 if message == _lastConsoleMessage then
3989 return
3990 end
3991 _lastConsoleMessage = message
3992 xeno.luaSendChannelMessage(_script.channel, CHANNEL_RED, ':', message)
3993 end
3994
3995 local function error(message)
3996 xeno.luaSendChannelMessage(_script.channel, CHANNEL_RED, ':', 'ERROR :: ' .. message)
3997 xeno.setWalkerEnabled(false)
3998 xeno.setTargetingEnabled(false)
3999 xeno.setLooterEnabled(false)
4000 print(message)
4001 _script.crashed = true
4002 end
4003
4004 local function prompt(message, callback)
4005 _lastConsoleMessage = nil
4006 log(message)
4007 when(EVENT_COMMAND, nil, function(response)
4008 callback(response)
4009 end)
4010 end
4011
4012 local function openConsole()
4013 _script.channel = xeno.luaOpenCustomChannel('XenoBot')
4014 -- Welcome message
4015 log(string.rep('\n ', 54) .. '\n' .. string.rep(':', 190) .. '\n::::: X E N O B O T ::::: ' .. _script.name .. ' ('.. LIB_REVISION ..')\n' .. string.rep(':', 190) .. ' \n ')
4016 warn('You can control your script from this channel. Type /help for a list of available commands.')
4017 warn('Configure this script with the command /config or open the file at: Documents/XenoBot/Configs/[' .. getSelfName() .. '] ' .. _script.name .. '.ini')
4018 end
4019
4020 local function openPrivateMessageConsole()
4021 _script.historyChannel = xeno.luaOpenCustomChannel('History')
4022 end
4023
4024 local function openDebugChannel()
4025 _script.debugChannel = xeno.luaOpenCustomChannel('Debug')
4026 end
4027
4028 -- Export global functions
4029 return {
4030 log = log,
4031 info = info,
4032 warn = warn,
4033 error = error,
4034 prompt = prompt,
4035 openConsole = openConsole,
4036 openDebugChannel = openDebugChannel,
4037 openPrivateMessageConsole = openPrivateMessageConsole
4038 }
4039end)()
4040Container = (function()
4041
4042 -- Imports
4043 local pingDelay = Core.pingDelay
4044 local clearTimeout = Core.clearTimeout
4045 local clearWhen = Core.clearWhen
4046 local setTimeout = Core.setTimeout
4047 local setInterval = Core.setInterval
4048 local getWalkableTiles = Core.getWalkableTiles
4049 local when = Core.when
4050 local formatList = Core.formatList
4051 local debug = Core.debug
4052 local log = Console.log
4053 local info = Console.info
4054 local error = Console.error
4055 local prompt = Console.prompt
4056
4057 local function getLastContainer()
4058 local last = 0
4059 for i = 0, 16 do
4060 if xeno.getContainerOpen(i) then
4061 last = i
4062 end
4063 end
4064 return last
4065 end
4066
4067 local function getContainerByName(name)
4068 for i = 0, 16 do
4069 if xeno.getContainerName(i) == name and xeno.getContainerOpen(i) then
4070 return i
4071 end
4072 end
4073 return false
4074 end
4075
4076 local function compareContainers(a, b)
4077 -- Different item count, return false
4078 local itemcount = xeno.getContainerItemCount(a)
4079 local compcount = xeno.getContainerItemCount(b)
4080 if itemcount ~= compcount then
4081 return false
4082 end
4083 -- Different name, return false
4084 if xeno.getContainerName(a) ~= xeno.getContainerName(b) then
4085 return false
4086 end
4087 -- Different slot data, return false
4088 for spot = 0, itemcount do
4089 local item = xeno.getContainerSpotData(a, spot)
4090 local compitem = xeno.getContainerSpotData(b, spot)
4091 if item.id ~= compitem.id or item.count ~= compitem.count then
4092 return false
4093 end
4094 end
4095 -- Containers are similar (shallow comparison)
4096 return true
4097 end
4098
4099 local function whenContainerUpdates(container, callback)
4100 local timeout = nil
4101 local success = nil
4102 timeout = setTimeout(function()
4103 clearWhen(EVENT_CONTAINER, success)
4104 callback(false)
4105 end, DELAY.CONTAINER_TIMEOUT)
4106 success = when(EVENT_CONTAINER, tostring(container), function(index, id, name)
4107 clearTimeout(timeout)
4108 callback(true)
4109 end)
4110 end
4111
4112 local function resetContainerDepths(source, destination, sDepth, dDepth, callback)
4113 -- Go back in source container
4114 if sDepth > 0 then
4115 xeno.containerBack(source)
4116 whenContainerUpdates(source, function(success)
4117 if success then
4118 resetContainerDepths(source, destination, sDepth-1, dDepth, callback)
4119 else
4120 resetContainerDepths(source, destination, sDepth, dDepth, callback)
4121 end
4122 end)
4123
4124 -- Go back in destination container
4125 elseif dDepth > 0 then
4126 xeno.containerBack(destination)
4127 whenContainerUpdates(destination, function(success)
4128 if success then
4129 resetContainerDepths(source, destination, sDepth, dDepth-1, callback)
4130 else
4131 resetContainerDepths(source, destination, sDepth, dDepth, callback)
4132 end
4133 end)
4134 -- Return
4135 elseif callback then
4136 callback()
4137 end
4138 end
4139
4140 local function containerMoveItems(options, callback)
4141 local fromContainer = options.src
4142 local toContainer = options.dest
4143 local toSlot = options.slot
4144
4145 local whiteList = options.items
4146 local blackList = options.ignore
4147 local itemFilter = options.filter
4148
4149 local disableSourceCascade = options.disableSourceCascade or fromContainer == _backpacks['Main']
4150 local openWindow = options.openwindow -- open first destination cascade in new window
4151
4152 local spotOffset = 0
4153 local sourceDepth = 0
4154 local destDepth = 0
4155
4156 local isMovingPaused = false
4157
4158 local moveItem = nil
4159 local watchContainerFullError = nil
4160 local onContainerFull = nil
4161 local moveInterval = nil
4162 local fullEvent = nil
4163
4164 local moveCounts = {}
4165 local moveHits = {}
4166 local lastMove = {
4167 itemid = nil,
4168 count = nil
4169 }
4170
4171 if not fromContainer or not toContainer then
4172 callback()
4173 return
4174 end
4175
4176 -- Target move slot is last slot in the container by default
4177 if toSlot == nil then
4178 toSlot = xeno.getContainerItemCapacity(toContainer) - 1
4179 end
4180
4181 -- Registers the error watcher
4182 watchContainerFullError = function()
4183 debug('containerMoveItems: container full event')
4184 return when(EVENT_ERROR, ERROR_CONTAINER_FULL, onContainerFull)
4185 end
4186
4187 local item = nil
4188
4189 -- Called to trigger a single item move
4190 moveItem = function(self)
4191
4192 -- Moving is paused, stop recurse
4193 if isMovingPaused then
4194 debug('containerMoveItems: moving paused')
4195 return
4196 end
4197
4198 -- Remaining items
4199 local itemCount = xeno.getContainerItemCount(fromContainer)
4200
4201 -- No items and cascade OR skipped all filled spots, stop entirely
4202 if itemCount == 0 or spotOffset >= itemCount then
4203 -- Kill monitoring container full errors
4204 if fullEvent then
4205 clearWhen(EVENT_ERROR, fullEvent)
4206 end
4207 -- Kill move event
4208 if moveInterval then
4209 clearTimeout(moveInterval)
4210 end
4211 -- Reset depths and finish
4212 resetContainerDepths(fromContainer, toContainer, sourceDepth, destDepth, function()
4213 debug('containerMoveItems: resetContainerDepths -> callback')
4214 callback(true, moveHits)
4215 end)
4216 return
4217 end
4218
4219 -- Das item
4220 item = xeno.getContainerSpotData(fromContainer, spotOffset)
4221
4222 -- Amount of this stack to move (defaults to entire stack)
4223 local moveStackCount = item.count
4224
4225 -- Cascade backpack, last slot of backpack
4226 if xeno.isItemContainer(item.id) and (spotOffset + 1 >= itemCount) then
4227 -- Cascade source enabled, open it, update source depth, reset spot offset
4228 if not disableSourceCascade then
4229 sourceDepth = sourceDepth + 1
4230 spotOffset = 0
4231 xeno.containerUseItem(fromContainer, itemCount-1, true, true)
4232 debug('containerMoveItems: cascade [last slot]')
4233 else
4234 spotOffset = spotOffset + 1
4235 -- Skip to next item immediately
4236 debug('containerMoveItems: moveItem() [last slot]')
4237 moveItem(self)
4238 end
4239 return
4240 end
4241
4242 -- Item is in blacklist, try next spot
4243 if blackList and blackList[item.id] then
4244 spotOffset = spotOffset + 1
4245 -- Skip to next item immediately
4246 debug('containerMoveItems: moveItem() [blacklist]')
4247 moveItem(self)
4248 return
4249 end
4250
4251 -- Item not in filter list, try next spot
4252 if whiteList and not whiteList[item.id] then
4253 spotOffset = spotOffset + 1
4254 -- Skip to next item immediately
4255 debug('containerMoveItems: moveItem() [filter]')
4256 moveItem(self)
4257 return
4258 end
4259
4260 -- Item filter is set, run function to test validity
4261 if itemFilter and not itemFilter(item) then
4262 spotOffset = spotOffset + 1
4263 -- Skip to next item immediately
4264 debug('containerMoveItems: moveItem() [filter func]')
4265 moveItem(self)
4266 return
4267 end
4268
4269 -- Item list, and entry has a number (count) value
4270 if whiteList and whiteList[item.id] and type(whiteList[item.id]) == 'number' then
4271 -- Check if we've moved any of this item yet
4272 if not moveCounts[item.id] then
4273 moveCounts[item.id] = whiteList[item.id]
4274 end
4275
4276 -- We don't need anymore of this item, skip
4277 if moveCounts[item.id] <= 0 then
4278 spotOffset = spotOffset + 1
4279 -- Skip to next item immediately
4280 debug('containerMoveItems: moveItem() [no need]')
4281 moveItem(self)
4282 return
4283 end
4284
4285 -- Now we know how much to move, don't go over the needed (smallest value)
4286 moveStackCount = math.min(item.count, moveCounts[item.id])
4287
4288 -- Decrease needed (we'll reverse this if we fail)
4289 moveCounts[item.id] = moveCounts[item.id] - moveStackCount
4290 end
4291
4292 -- Track move counts by itemid
4293 local itemMoves = moveHits[item.id]
4294 if not itemMoves then itemMoves = 0 end
4295 moveHits[item.id] = itemMoves + moveStackCount
4296
4297 -- Track last move
4298 lastMove.itemid = item.id
4299 lastMove.count = moveStackCount
4300
4301 -- Move item to destination
4302 debug(('containerMoveItems: move item %d, %d => %d, %d x%d'):format(fromContainer, spotOffset, toContainer, toSlot, moveStackCount))
4303 xeno.containerMoveItemToContainer(fromContainer, spotOffset, toContainer, toSlot, moveStackCount)
4304 end
4305
4306 -- Triggered when the destination fills up
4307 onContainerFull = function()
4308 -- Set flag so we stop moving items
4309 -- No need to kill fullEvent, it dies after one use
4310 isMovingPaused = true
4311
4312 -- Check if we need to reverse a move
4313 if item and lastMove.itemid and lastMove.count > -1 then
4314 -- Increase move counts
4315 if whiteList and moveCounts[lastMove.itemid] then
4316 moveCounts[item.id] = moveCounts[item.id] + lastMove.count
4317 end
4318 -- Decrease total item moves
4319 local itemMoves = moveHits[item.id]
4320 if not itemMoves then itemMoves = 0 end
4321 moveHits[item.id] = math.max(0, itemMoves - lastMove.count)
4322 end
4323
4324 setTimeout(function()
4325 -- We weren't depositing into a backpack, no cascade available
4326 if not xeno.isItemContainer(xeno.getContainerSpotData(toContainer, toSlot).id) then
4327 -- Kill move event
4328 if moveInterval then
4329 clearTimeout(moveInterval)
4330 end
4331 -- Reset depths and finish
4332 debug('containerMoveItems: reset depths [no bp, no cascade]')
4333 resetContainerDepths(fromContainer, toContainer, sourceDepth, destDepth, function()
4334 debug('containerMoveItems: callback() [no bp, no cascade]')
4335 callback(false, moveHits)
4336 end)
4337 return
4338 end
4339
4340 local targetContainer = openWindow and getLastContainer() or toContainer
4341 local function processCascade()
4342 -- Update toContainer index if we opened in a new window
4343 if openWindow then
4344 toContainer = targetContainer
4345 debug('containerMoveItems: new window = ' .. toContainer)
4346 -- Reset destination depth, since we're in a new destination
4347 destDepth = 0
4348 end
4349
4350 -- Check if last item is a container
4351 local lastSlot = xeno.getContainerItemCount(toContainer) - 1
4352 local cascadeID = xeno.getContainerSpotData(toContainer, lastSlot).id
4353 if xeno.isItemContainer(cascadeID) then
4354 -- Found cascade backpack, update slot, update destination depth
4355 openWindow = false
4356 destDepth = destDepth + 1
4357 toSlot = lastSlot
4358
4359 -- Start moving again
4360 isMovingPaused = false
4361
4362 -- Register full event again
4363 fullEvent = watchContainerFullError()
4364 debug('containerMoveItems: cascade depth = ' .. destDepth)
4365 return
4366 end
4367 -- No cascade backpack, stop entirely
4368 -- Reset depths and finish
4369 debug('containerMoveItems: reset depths [cascaded]')
4370 resetContainerDepths(fromContainer, toContainer, sourceDepth, destDepth, function()
4371 debug('containerMoveItems: callback() [cascaded]')
4372 callback(false, moveHits)
4373 -- Kill move event
4374 if moveInterval then
4375 clearTimeout(moveInterval)
4376 end
4377 end)
4378 return
4379 end
4380
4381 debug('containerMoveItems: open destination')
4382 xeno.containerUseItem(toContainer, toSlot, not openWindow, true)
4383 whenContainerUpdates(targetContainer, function(success)
4384 if success then
4385 processCascade()
4386 else
4387 onContainerFull() -- retry
4388 end
4389 end)
4390 end, DELAY.CONTAINER_MOVE_ITEM)
4391 end
4392
4393 -- Begin moving
4394 moveInterval = setInterval(moveItem, DELAY.CONTAINER_MOVE_ITEM)
4395
4396 -- Start watching for container full errors
4397 fullEvent = watchContainerFullError()
4398 end
4399
4400 local function moveItems(containers, options, callback, dirty)
4401 local source = table.remove(containers)
4402 containerMoveItems({
4403 src = source,
4404 dest = options.dest,
4405 items = options.items,
4406 disableSourceCascade = options.disableSourceCascade,
4407 openwindow = options.openwindow
4408 }, function(cascaded, moveCounts)
4409 -- Count moved items
4410 local totalCount = 0
4411 if moveCounts then
4412 for itemid, count in pairs(moveCounts) do
4413 totalCount = totalCount + count
4414 end
4415 end
4416 -- Moved items, flag entire move as "dirty"
4417 -- means cleanContainer will reloop
4418 -- we do this incase we freed up any slots
4419 if totalCount > 0 then
4420 dirty = true
4421 end
4422
4423 -- Successfully moved items,
4424 -- Last container, finish
4425 if #containers == 0 then
4426 callback(dirty)
4427 -- Recurse
4428 else
4429 moveItems(containers, options, callback, dirty)
4430 end
4431 end)
4432 end
4433
4434 local function cleanContainers(destination, itemList, callback, shallow)
4435 -- Check all backpacks except for destination
4436 -- for items in the list and move them to the destination
4437 local containers = {}
4438 for i = 0, 15 do
4439 if i ~= destination and xeno.getContainerOpen(i) then
4440 containers[#containers+1] = i
4441 end
4442 end
4443 moveItems(containers, {
4444 dest = destination,
4445 slot = 0,
4446 items = itemList,
4447 -- Do not dig into the main backpack
4448 disableSourceCascade = shallow,
4449 openwindow = false
4450 }, function(dirty)
4451 -- Something was moved
4452 if dirty then
4453 cleanContainers(destination, itemList, callback, shallow)
4454 elseif callback then
4455 callback()
4456 end
4457 end)
4458 end
4459
4460 local function openMainBackpack(callback, tries)
4461 -- Retry attempts
4462 tries = tries or 3
4463
4464 -- Wait for Main Backpack
4465 xeno.slotUseItem(3)
4466 whenContainerUpdates(0, function(success)
4467 if success then
4468 if _config['General']['Minimize-Backpacks'] then
4469 xeno.minimizeContainer(0)
4470 end
4471 callback(true)
4472 elseif tries <= 0 then
4473 error('Unable to open main backpack. Check your equipment slot.')
4474 else
4475 tries = tries - 1
4476 openMainBackpack(callback, tries)
4477 end
4478 end)
4479 end
4480
4481 local function resetContainers(callback)
4482 _script.openingContainers = true
4483
4484 -- Close all containers
4485 for i = 0, 16 do
4486 xeno.closeContainer(i)
4487 end
4488
4489 local minimizeBackpacks = _config['General']['Minimize-Backpacks']
4490 openMainBackpack(function()
4491 _script.openingContainers = nil
4492 local backpacks = {}
4493 local function openChild(index)
4494 local spot = backpacks[index]
4495 local newcontainer = getLastContainer() + 1
4496
4497 -- Wait for child to open
4498 xeno.containerUseItem(0, spot, false, true)
4499 whenContainerUpdates(newcontainer, function(success)
4500 if not success then
4501 openChild(index)
4502 return
4503 end
4504
4505 -- Minimize all containers
4506 if minimizeBackpacks then
4507 for i = 1, 16 do
4508 if xeno.getContainerOpen(i) then
4509 xeno.minimizeContainer(i)
4510 end
4511 end
4512 end
4513 -- Open next child
4514 if #backpacks > index then
4515 openChild(index+1)
4516 -- Complete, main callback
4517 else
4518 callback()
4519 end
4520 end)
4521 end
4522
4523 -- Loop through all items, add containers to list
4524 local itemcount = xeno.getContainerItemCount(0)
4525 for spot = 0, itemcount - 1 do
4526 local item = xeno.getContainerSpotData(0, spot)
4527 if xeno.isItemContainer(item.id) then
4528 backpacks[#backpacks+1] = spot
4529 debug('resetContainers - found bp: ' .. spot)
4530 end
4531 end
4532
4533 -- Call openChild for every backpack
4534 openChild(1)
4535 end)
4536 end
4537
4538 local function setupContainers(callback)
4539 local needGoldContainer = false
4540 local needPotionContainer = false
4541 local needRuneContainer = false
4542 local needAmmoContainer = false
4543 local needSuppliesContainer = false
4544
4545 -- Pause the bot
4546 xeno.attackCreature(0)
4547 xeno.followCreature(0)
4548 xeno.setWalkerEnabled(false)
4549 xeno.setLooterEnabled(false)
4550 xeno.setTargetingEnabled(false)
4551
4552 log('Setting up your backpacks, please wait...')
4553 local function finishSetup()
4554 log('Your backpacks have been setup. Do NOT rearrange containers!')
4555
4556 -- Resume the bot
4557 xeno.setWalkerEnabled(true)
4558 xeno.setLooterEnabled(true)
4559 xeno.setTargetingEnabled(true)
4560
4561 callback()
4562 end
4563
4564 -- Move items in main backpack to the correct backpacks
4565 local function organizeMainBackpack()
4566 local destinations = {}
4567 local itemLists = {}
4568
4569 -- Move gold to gold backpack
4570 local goldBackpack = _backpacks['Gold']
4571 destinations[#destinations+1] = {'Gold', goldBackpack}
4572 itemLists[goldBackpack] = {[3031] = true}
4573
4574 -- Move platinum and crystal coins to main
4575 local mainBackpack = _backpacks['Main']
4576 destinations[#destinations+1] = {'Main', mainBackpack}
4577 itemLists[mainBackpack] = {
4578 [3035] = true,
4579 [3043] = true
4580 }
4581
4582 -- Create itemid list for each supply group
4583 for itemid, supply in pairs(_supplies) do
4584 if itemid ~= ITEMID.GOLDEN_MUG then
4585 -- Amulets and Rings always go in the Supplies backpack
4586 local name = supply.group
4587 if name == 'Amulet' or name == 'Ring' then
4588 name = 'Supplies'
4589 end
4590 -- Send to Main backpack list if it matches the index
4591 local backpack = _backpacks[name]
4592 if backpack then
4593 if backpack == _backpacks['Main'] then
4594 name = 'Main'
4595 end
4596 -- Init table if it doesn't exist
4597 if not itemLists[backpack] then
4598 itemLists[backpack] = {}
4599 -- Add backpack to index for easy iteration
4600 destinations[#destinations+1] = {name, backpack}
4601 end
4602 -- Add supply item to the list
4603 itemLists[backpack][itemid] = true
4604 end
4605 end
4606 end
4607
4608 local function moveNextItems(index)
4609 local backpack = destinations[index]
4610
4611 -- Check if destination exists (no items to move)
4612 if not backpack then
4613 finishSetup()
4614 return
4615 end
4616
4617 local backpackName = backpack[1]
4618 local destination = backpack[2]
4619
4620 -- Get itemid list for this backpack
4621 local itemList = itemLists[destination]
4622
4623 -- Clean all backpacks and move valid items to this backpack
4624 cleanContainers(destination, itemList, function(success)
4625 -- Proceed to next destination
4626 moveNextItems(index+1)
4627 end)
4628 end
4629
4630 -- Move items to proper backpack, then finish
4631 if #destinations > 0 then
4632 moveNextItems(1)
4633 -- None to move, finish up
4634 else
4635 finishSetup()
4636 end
4637 end
4638
4639 -- Assign backpacks
4640 local function assignBackpacks()
4641 -- Setup containers (start at index 2 since Main = 0, Loot = 1)
4642 local index = 2
4643 local list = {}
4644
4645 list[#list+1] = '#1 - Main backpack'
4646 list[#list+1] = '#2 - Loot backpack'
4647
4648 local containerStatuses = {
4649 {'Gold', needGoldContainer},
4650 {'Potions', needPotionContainer},
4651 {'Runes', needRuneContainer},
4652 {'Ammo', needAmmoContainer},
4653 {'Supplies', needSuppliesContainer}
4654 }
4655
4656 for i = 1, #containerStatuses do
4657 local status = containerStatuses[i]
4658 if status and status[2] then
4659 _backpacks[status[1]] = index
4660 list[#list+1] = ('#%d - %s backpack'):format(index+1, status[1])
4661 index = index + 1
4662 else
4663 _backpacks[status[1]] = 0
4664 end
4665 end
4666
4667 info('Your backpack setup:\n' .. formatList(list, '\n', ' '));
4668
4669 if _config['General']['Organize-Backpacks'] then
4670 log('Organizing your backpacks, please wait...')
4671 organizeMainBackpack()
4672 else
4673 finishSetup()
4674 end
4675 end
4676
4677 -- Open main bp and children
4678 resetContainers(function()
4679 -- Count how many are open (excluding main bp)
4680 -- Determine how many we need
4681 -- If we have enough, continue
4682 -- If not, prompt user, wait for response, recheck
4683
4684 -- Loop through supplies, flag each "type" of container we will need
4685 local neededContainers = 0
4686 local runeCount = 0
4687 local potionCount = 0
4688 local ammoCount = 0
4689 local supplyCount = 0
4690
4691 if _config['Loot']['Loot-Gold'] then
4692 neededContainers = neededContainers + 1
4693 needGoldContainer = true
4694 end
4695
4696 for supplyId, supply in pairs(_supplies) do
4697 if supply.group == 'Potions' and not needPotionContainer then
4698 potionCount = potionCount + supply.max
4699 if potionCount > 100 then
4700 neededContainers = neededContainers + 1
4701 needPotionContainer = true
4702 end
4703 elseif supply.group == 'Runes' and not needRuneContainer then
4704 runeCount = runeCount + supply.max
4705 if runeCount > 100 then
4706 neededContainers = neededContainers + 1
4707 needRuneContainer = true
4708 end
4709 elseif supply.group == 'Ammo' and not needAmmoContainer then
4710 ammoCount = ammoCount + supply.max
4711 if ammoCount > 100 then
4712 neededContainers = neededContainers + 1
4713 needAmmoContainer = true
4714 end
4715 elseif (supply.group == 'Ring' or supply.group == 'Amulet') and not needSuppliesContainer then
4716 supplyCount = supplyCount + supply.max
4717 if supplyCount > 5 then
4718 neededContainers = neededContainers + 1
4719 needSuppliesContainer = true
4720 end
4721 end
4722 end
4723
4724 -- Backpacks inside main bp we can use (ignore mainbp and loot bp)
4725 local availableContainers = getLastContainer() - 1
4726
4727 -- Make sure we have enough, if not prompt, then retry
4728 if availableContainers < neededContainers then
4729 -- prompt user, then assignBackpacks
4730 local missingCount = (neededContainers - availableContainers)
4731 local singularMsg = 'Not enough backpacks. Add one more backpack to your main backpack. Type "retry" to continue.'
4732 local pluralMsg = 'Not enough backpacks. Add ' .. missingCount .. ' more backpacks to your main backpack. Type "retry" to continue.'
4733 local message = missingCount > 1 and pluralMsg or singularMsg
4734 local function promptBackpacks()
4735 prompt(message, function(response)
4736 if string.find(string.lower(response), 'retry') then
4737 setupContainers(callback)
4738 else
4739 promptBackpacks()
4740 end
4741 end)
4742 end
4743 promptBackpacks()
4744 return
4745 end
4746
4747 -- All is well, start setup
4748 assignBackpacks()
4749 end)
4750 end
4751
4752 local function getContainerItemCounts(index, callback, deepSearch, startLevel)
4753 local items = {}
4754 local function countLevel(level)
4755 -- Iterate through container spots
4756 local lastSlot = xeno.getContainerItemCount(index) - 1
4757 for spot = 0, lastSlot do
4758 local item = xeno.getContainerSpotData(index, spot)
4759 -- Do not count the cascade backpack
4760 if spot ~= lastSlot or not xeno.isItemContainer(item.id) then
4761 -- Create table and entries on-demand
4762 if not items[item.id] then
4763 items[item.id] = 0
4764 end
4765 -- Increment count
4766 items[item.id] = items[item.id] + math.max(item.count, 1)
4767 end
4768 end
4769
4770 -- Only counting the current container level OR reached top level
4771 if not deepSearch or level <= 1 then
4772 if callback then
4773 callback(items)
4774 end
4775 return items
4776 -- Not done, keep counting backwards
4777 else
4778 -- Navigate back a level
4779 local function goBack()
4780 xeno.containerBack(index)
4781 whenContainerUpdates(index, function(success)
4782 if not success then
4783 goBack()
4784 return
4785 end
4786 countLevel(level-1)
4787 end)
4788 end
4789 goBack()
4790 end
4791 return nil
4792 end
4793 local function gotoBottom(depth)
4794 local lastSlot = xeno.getContainerItemCount(index) - 1
4795 local cascadeID = xeno.getContainerSpotData(index, lastSlot).id
4796 -- Another cascade, go deeper
4797 if xeno.isItemContainer(cascadeID) then
4798 xeno.containerUseItem(index, lastSlot, true, true)
4799 whenContainerUpdates(index, function(success)
4800 if not success then
4801 gotoBottom(depth)
4802 else
4803 gotoBottom(depth + 1)
4804 end
4805 end)
4806 return
4807 end
4808 -- At the last level, start counting items
4809 countLevel(depth)
4810 end
4811 -- Deep count specified, go to end of container and count backwards
4812 if deepSearch then
4813 gotoBottom(startLevel or 1)
4814 -- Shallow count, function can be used synchronously
4815 else
4816 return countLevel(1)
4817 end
4818 end
4819
4820 local function getTotalItemCount(itemIdList, ignoreEquipment)
4821 local totals = {}
4822 local numberReturn = false
4823
4824 -- Wrap single itemid in an index table
4825 if type(itemIdList) ~= 'table' then
4826 numberReturn = itemIdList
4827 itemIdList = {[itemIdList] = true}
4828 end
4829
4830 -- Count equipment
4831 if not ignoreEquipment then
4832 local slots = {
4833 xeno.getHeadSlotData,
4834 xeno.getArmorSlotData,
4835 xeno.getLegsSlotData,
4836 xeno.getFeetSlotData,
4837 xeno.getAmuletSlotData,
4838 xeno.getWeaponSlotData,
4839 xeno.getRingSlotData,
4840 xeno.getShieldSlotData,
4841 xeno.getAmmoSlotData
4842 }
4843 for i = 1, #slots do
4844 local slot = slots[i]()
4845 if slot and slot.id then
4846 -- Counting this item
4847 if itemIdList[slot.id] then
4848 local itemTotal = totals[slot.id]
4849 local itemcount = math.max(slot.count, 1)
4850 if not itemTotal then
4851 totals[slot.id] = itemcount
4852 else
4853 itemTotal = itemTotal + itemcount
4854 end
4855 end
4856 end
4857 end
4858 end
4859
4860 -- Iterate all possible containers
4861 for i = 0, 16 do
4862 -- No more containers, stop searching
4863 if not xeno.getContainerOpen(i) then
4864 break
4865 end
4866 -- Count this container
4867 local counts = getContainerItemCounts(i)
4868 for itemid, itemcount in pairs(counts) do
4869 -- Counting this item
4870 if itemIdList[itemid] then
4871 local itemTotal = totals[itemid]
4872 if not itemTotal then
4873 totals[itemid] = itemcount
4874 else
4875 totals[itemid] = itemTotal + itemcount
4876 end
4877 end
4878 end
4879 end
4880
4881 -- Returns number if single number was provided
4882 -- otherwise returns a pairs table with itemid = count
4883 if numberReturn then
4884 totals = totals[numberReturn]
4885 end
4886 return totals or 0
4887 end
4888
4889 local function getMoney()
4890 local counts = getTotalItemCount(ITEM_LIST_MONEY)
4891
4892 local gold = counts[3031] or 0
4893 local plat = counts[3035] or 0
4894 local crystal = counts[3043] or 0
4895
4896 local total = gold
4897 total = total + (plat * 100)
4898 total = total + (crystal * 10000)
4899
4900 return total
4901 end
4902
4903 local function getFlasks()
4904 local counts = getTotalItemCount(ITEM_LIST_FLASKS)
4905
4906 local small = counts[285] or 0
4907 local med = counts[284] or 0
4908 local large = counts[283] or 0
4909
4910 return small + med + large
4911 end
4912
4913 local function getFlaskWeight()
4914 local counts = getTotalItemCount(ITEM_LIST_FLASKS)
4915
4916 local small = counts[285] or 0
4917 local med = counts[284] or 0
4918 local large = counts[283] or 0
4919
4920 return (small * xeno.getItemWeight(285))
4921 + (med * xeno.getItemWeight(284))
4922 + (large * xeno.getItemWeight(283))
4923 end
4924
4925 local function unrustLoot()
4926 -- Make sure a corpse is not currently open
4927 -- Loop through each slot in the loot container
4928 -- If loot is a "rusty" item, use oil on it.
4929 -- Wait x delay and look at the same slot.
4930 --ITEM_LIST_RUSTYARMORS
4931 local rustRemoverSlot = nil
4932 local supplybp = _backpacks['Supplies']
4933 for spot = 0, xeno.getContainerItemCount(supplybp) - 1 do
4934 local item = xeno.getContainerSpotData(supplybp, spot)
4935 if item.id == ITEMID.RUST_REMOVER then
4936 rustRemoverSlot = spot
4937 break
4938 end
4939 end
4940
4941 if rustRemoverSlot then
4942 local lootbp = _backpacks['Loot']
4943 for spot = 0, xeno.getContainerItemCount(lootbp) - 1 do
4944 local item = xeno.getContainerSpotData(lootbp, spot)
4945 if ITEM_LIST_RUSTYARMORS[item.id] then
4946 -- TODO: Remove rusty armor from supplies (HUD)
4947 -- TODO: Add oil as supply waste (HUD)
4948 xeno.containerUseWithContainer(supplybp, rustRemoverSlot, lootbp, spot)
4949 setTimeout(function()
4950 -- If loot is shitty, toss it out.
4951 local unrusted = xeno.getContainerSpotData(lootbp, spot)
4952 if ITEM_LIST_RUSTYTRASH[unrusted.id] then
4953 -- Get random position
4954 local pos = xeno.getSelfPosition()
4955 local tiles = getWalkableTiles(pos, 3)
4956 if not tiles or #tiles < 1 then
4957 tiles = {pos}
4958 end
4959 local destination = tiles[math.random(1, #tiles)]
4960 xeno.containerMoveItemToGround(lootbp, spot, destination.x, destination.y, destination.z, -1)
4961 end
4962 end, pingDelay(DELAY.CONTAINER_USEWITH))
4963 break
4964 end
4965 end
4966 end
4967 end
4968
4969 -- Export global functions
4970 return {
4971 getLastContainer = getLastContainer,
4972 getContainerByName = getContainerByName,
4973 whenContainerUpdates = whenContainerUpdates,
4974 containerMoveItems = containerMoveItems,
4975 setupContainers = setupContainers,
4976 resetContainers = resetContainers,
4977 getContainerItemCounts = getContainerItemCounts,
4978 cleanContainers = cleanContainers,
4979 getTotalItemCount = getTotalItemCount,
4980 getMoney = getMoney,
4981 getFlasks = getFlasks,
4982 getFlaskWeight = getFlaskWeight,
4983 unrustLoot = unrustLoot
4984 }
4985end)()
4986Hud = (function()
4987
4988 -- Imports
4989 local formatNumber = Core.formatNumber
4990 local overflowText = Core.overflowText
4991 local titlecase = Core.titlecase
4992 local isCorpseOpen = Core.isCorpseOpen
4993
4994 -- All HUD pointers are referenced here
4995 local hudPointers = {}
4996 local containerHud = {}
4997
4998 local function hudGetContainerDimensions()
4999 local dimensions = xeno.HUDGetContainerDimensions()
5000 local containers = {}
5001 for i = 1, #dimensions, 5 do
5002 local temp = {}
5003 temp.x = dimensions[i+0]
5004 temp.y = dimensions[i+1]
5005 temp.w = dimensions[i+2]
5006 temp.h = dimensions[i+3]
5007 temp.id = dimensions[i+4]
5008 containers[temp.id+1] = temp
5009 end
5010 return containers
5011 end
5012
5013 local function hudUpdateDimensions()
5014 local screen = xeno.HUDGetDimensions()
5015 local changed = _hud.gamewindow.x ~= screen.gamewindowx or
5016 _hud.gamewindow.y ~= screen.gamewindowy or
5017 _hud.gamewindow.w ~= screen.gamewindoww or
5018 _hud.gamewindow.h ~= screen.gamewindowh
5019
5020 if not changed and _hud.rightcolumn.x > 0 then
5021 return false
5022 end
5023
5024 _hud.gamewindow = {
5025 x = screen.gamewindowx,
5026 y = screen.gamewindowy,
5027 w = screen.gamewindoww,
5028 h = screen.gamewindowh
5029 }
5030
5031 _hud.rightcolumn.x = screen.gamewindowx + screen.gamewindoww + 10
5032
5033 return true
5034 end
5035
5036 local function hudUpdatePositions()
5037 -- Reposition columns
5038 local leftcolumn = _hud.leftcolumn
5039 local rightcolumn = _hud.rightcolumn
5040
5041 -- Threshold for y-axis (before wrapping)
5042 local maxAxisY = (_hud.gamewindow.y + _hud.gamewindow.h) - 10
5043 local wrappedColumn = false
5044
5045 -- Track our current position while we add/update
5046 local currentAxisY = leftcolumn.y
5047 local currentAxisX = leftcolumn.x
5048
5049 if _config['HUD']['Show-Containers'] then
5050 -- Update container hud
5051 local containers = hudGetContainerDimensions()
5052 local containerNames = {} -- by index
5053 for name, index in pairs(_backpacks) do
5054 containerNames[index+1] = name
5055 -- Remove missing containers
5056 if not xeno.getContainerOpen(index) then
5057 xeno.HUDUpdateLocation(containerHud[index+1], -100, -100)
5058 end
5059 end
5060 for id, container in pairs(containers) do
5061 local pointer = containerHud[id]
5062 local name = id == 1 and 'Main' or containerNames[id] or ''
5063 xeno.HUDUpdateTextText(pointer, name)
5064 xeno.HUDUpdateLocation(pointer, container.x + 15, container.y - 12)
5065 end
5066 end
5067
5068 -- Loop through all panels in the column
5069 for i = 1, #leftcolumn.panels do
5070 -- Track the total height the panels take up
5071 local panel = leftcolumn.panels[i]
5072 local itemCount = #panel.items
5073
5074 -- If panel has items we show the title of the panel
5075 -- add 25 pixels to the y-axis for the title (after adding/re-positioning the title)
5076 if itemCount > 0 then
5077 -- Detect if at least 1 item can fit on the screen
5078 -- if not, wrap the entire panel (including title)
5079 if currentAxisY + 20 > maxAxisY then
5080 currentAxisY = leftcolumn.y
5081 currentAxisX = currentAxisX + leftcolumn.w
5082 end
5083
5084 -- Add panel title if it doesn't yet exist
5085 if not panel.pointer
5086 and (_config['HUD']['Show-Supplies'] or panel.title ~= 'Supplies')
5087 and (_config['HUD']['Show-Loot'] or panel.title ~= 'Loot') then
5088 local pointer = xeno.HUDCreateText(currentAxisX, currentAxisY, '[' .. panel.title .. ']', unpack(THEME[_script.theme].title))
5089 leftcolumn.panels[i].pointer = pointer
5090 hudPointers[#hudPointers+1] = pointer
5091 -- Update position
5092 else
5093 xeno.HUDUpdateLocation(panel.pointer, currentAxisX, currentAxisY)
5094 end
5095 currentAxisY = currentAxisY + 20
5096
5097 -- Look at first item, if value is a number, sort all items higher to lower
5098 if type(panel.items[1].value) == 'number' then
5099 table.sort(panel.items, function(a, b)
5100 return a.value > b.value
5101 end)
5102 end
5103 end
5104
5105 -- Loop through all items in the panel
5106 local wrappedPanel = false
5107 for j = 1, itemCount do
5108 -- Add or update title & value
5109 local item = panel.items[j]
5110
5111 -- Add item title if pointer doesn't exist
5112 if not item.tpointer then
5113 local itemTitle = item.title .. ':'
5114 -- Title is an item id
5115 if type(item.title) == 'number' then
5116 itemTitle = overflowText(titlecase(xeno.getItemNameByID(item.title)), 11, '...')
5117 -- Icon doesn't exist, add it
5118 if not item.ipointer then
5119 -- Only add an icon if they are enabled
5120 if _config['HUD']['Show-Icons']
5121 and (_config['HUD']['Show-Supplies'] or panel.title ~= 'Supplies')
5122 and (_config['HUD']['Show-Loot'] or panel.title ~= 'Loot') then
5123 -- Images need slight y-axis offset
5124 local pointer = xeno.HUDCreateItemImage(currentAxisX, currentAxisY-5, item.title, 16, 100)
5125 panel.items[j].ipointer = pointer
5126 hudPointers[#hudPointers+1] = pointer
5127 end
5128 -- Update icon position (even if disabled, since we can't destroy them)
5129 else
5130 xeno.HUDUpdateLocation(item.ipointer, currentAxisX, currentAxisY-5)
5131 end
5132 end
5133 -- Indent title if there's an icon
5134 local xOffset = item.ipointer and 25 or 0
5135
5136 if (_config['HUD']['Show-Supplies'] or panel.title ~= 'Supplies')
5137 and (_config['HUD']['Show-Loot'] or panel.title ~= 'Loot') then
5138 local pointer = xeno.HUDCreateText(currentAxisX + xOffset, currentAxisY, itemTitle, unpack(THEME[_script.theme].primary))
5139 panel.items[j].tpointer = pointer
5140 hudPointers[#hudPointers+1] = pointer
5141 end
5142 -- Update item title position
5143 else
5144 -- Update icon position (even if disabled, since we can't destroy them)
5145 if item.ipointer then
5146 xeno.HUDUpdateLocation(item.ipointer, currentAxisX, currentAxisY-5)
5147 end
5148 local xOffset = item.ipointer and 25 or 0
5149 xeno.HUDUpdateLocation(item.tpointer, currentAxisX + xOffset, currentAxisY)
5150 end
5151
5152 -- Add item value if pointer doesn't exist
5153 if not item.vpointer then
5154 local itemValue = item.value
5155 if type(item.value) == 'number' then
5156 itemValue = formatNumber(item.value) .. ' gp'
5157 end
5158 if (_config['HUD']['Show-Supplies'] or panel.title ~= 'Supplies')
5159 and (_config['HUD']['Show-Loot'] or panel.title ~= 'Loot') then
5160 local pointer = xeno.HUDCreateText(currentAxisX + 110, currentAxisY, itemValue, unpack(THEME[_script.theme].secondary))
5161 panel.items[j].vpointer = pointer
5162 hudPointers[#hudPointers+1] = pointer
5163 end
5164 -- Update item value position
5165 else
5166 xeno.HUDUpdateLocation(item.vpointer, currentAxisX + 110, currentAxisY)
5167 end
5168
5169 -- Each items adds 25px to the y-axis
5170 currentAxisY = currentAxisY + 15
5171
5172 -- Test if we need to wrap the column
5173 if not wrappedColumn and currentAxisY >= maxAxisY then
5174 -- Reset y-axis (from panel title)
5175 currentAxisY = leftcolumn.y + 20
5176 -- Wrap to next "column"
5177 currentAxisX = currentAxisX + leftcolumn.w
5178 -- Flag as we wrapped once, do not show anymore panels
5179 wrappedPanel = true
5180 end
5181 end
5182
5183 -- Wrapped items in the panel, flag the column as wrapped
5184 if wrappedPanel then
5185 wrappedColumn = true
5186 end
5187
5188 -- Add padding to bottom if there were items
5189 if itemCount > 0 then
5190 currentAxisY = currentAxisY + 15
5191 end
5192
5193 -- Wrapped the last panel (hide the rest)
5194 if wrappedColumn then
5195 currentAxisX = currentAxisX - (leftcolumn.w * 2)
5196 end
5197 end
5198
5199 -- Track our current position while we add/update
5200 currentAxisY = rightcolumn.y
5201 currentAxisX = rightcolumn.x
5202
5203 -- Loop through all panels in the column
5204 for i = 1, #rightcolumn.panels do
5205 -- Track the total height the panels take up
5206 local panel = rightcolumn.panels[i]
5207 local itemCount = #panel.items
5208
5209 -- If panel has items we show the title of the panel
5210 -- add 25 pixels to the y-axis for the title (after adding/re-positioning the title)
5211 if itemCount > 0 then
5212 -- Detect if at least 1 item can fit on the screen
5213 -- if not, wrap the entire panel (including title)
5214 if currentAxisY + 20 > maxAxisY then
5215 currentAxisY = rightcolumn.y
5216 currentAxisX = currentAxisX + rightcolumn.w
5217 end
5218
5219 -- Add panel title if it doesn't yet exist
5220 if not panel.pointer
5221 and (_config['HUD']['Show-Supplies'] or panel.title ~= 'Supplies')
5222 and (_config['HUD']['Show-Loot'] or panel.title ~= 'Loot') then
5223 local pointer = xeno.HUDCreateText(currentAxisX, currentAxisY, '[' .. panel.title .. ']', unpack(THEME[_script.theme].title))
5224 rightcolumn.panels[i].pointer = pointer
5225 hudPointers[#hudPointers+1] = pointer
5226 -- Update position
5227 else
5228 xeno.HUDUpdateLocation(panel.pointer, currentAxisX, currentAxisY)
5229 end
5230 currentAxisY = currentAxisY + 20
5231
5232 -- Look at first item, if value is a number, sort all items higher to lower
5233 if type(panel.items[1].value) == 'number' then
5234 table.sort(panel.items, function(a, b)
5235 return a.value > b.value
5236 end)
5237 end
5238 end
5239
5240 -- Loop through all items in the panel
5241 local wrappedPanel = false
5242 for j = 1, itemCount do
5243 -- Add or update title & value
5244 local item = panel.items[j]
5245
5246 -- Add item title if pointer doesn't exist
5247 if not item.tpointer then
5248 local itemTitle = item.title .. ':'
5249 -- Title is an item id
5250 if type(item.title) == 'number' then
5251 itemTitle = overflowText(titlecase(xeno.getItemNameByID(item.title)), 11, '...')
5252 -- Icon doesn't exist, add it
5253 if not item.ipointer then
5254 -- Only add an icon if they are enabled
5255 if _config['HUD']['Show-Icons']
5256 and (_config['HUD']['Show-Supplies'] or panel.title ~= 'Supplies')
5257 and (_config['HUD']['Show-Loot'] or panel.title ~= 'Loot') then
5258 -- Images need slight y-axis offset
5259 local pointer = xeno.HUDCreateItemImage(currentAxisX, currentAxisY-5, item.title, 16, 100)
5260 panel.items[j].ipointer = pointer
5261 hudPointers[#hudPointers+1] = pointer
5262 end
5263 -- Update icon position (even if disabled, since we can't destroy them)
5264 else
5265 xeno.HUDUpdateLocation(item.ipointer, currentAxisX, currentAxisY-5)
5266 end
5267 end
5268 -- Indent title if there's an icon
5269 local xOffset = item.ipointer and 25 or 0
5270 if (_config['HUD']['Show-Supplies'] or panel.title ~= 'Supplies')
5271 and (_config['HUD']['Show-Loot'] or panel.title ~= 'Loot') then
5272 local pointer = xeno.HUDCreateText(currentAxisX + xOffset, currentAxisY, itemTitle, unpack(THEME[_script.theme].primary))
5273 panel.items[j].tpointer = pointer
5274 hudPointers[#hudPointers+1] = pointer
5275 end
5276 -- Update item title position
5277 else
5278 -- Update icon position (even if disabled, since we can't destroy them)
5279 if item.ipointer then
5280 xeno.HUDUpdateLocation(item.ipointer, currentAxisX, currentAxisY-5)
5281 end
5282 local xOffset = item.ipointer and 25 or 0
5283 xeno.HUDUpdateLocation(item.tpointer, currentAxisX + xOffset, currentAxisY)
5284 end
5285
5286 -- Add item value if pointer doesn't exist
5287 if not item.vpointer then
5288 local itemValue = item.value
5289 if type(item.value) == 'number' then
5290 itemValue = formatNumber(item.value) .. ' gp'
5291 end
5292 if (_config['HUD']['Show-Supplies'] or panel.title ~= 'Supplies')
5293 and (_config['HUD']['Show-Loot'] or panel.title ~= 'Loot') then
5294 local pointer = xeno.HUDCreateText(currentAxisX + 110, currentAxisY, itemValue, unpack(THEME[_script.theme].secondary))
5295 panel.items[j].vpointer = pointer
5296 hudPointers[#hudPointers+1] = pointer
5297 end
5298 -- Update item value position
5299 else
5300 xeno.HUDUpdateLocation(item.vpointer, currentAxisX + 110, currentAxisY)
5301 end
5302
5303 -- Each items adds 25px to the y-axis
5304 currentAxisY = currentAxisY + 15
5305
5306 -- Test if we need to wrap the column
5307 if not wrappedColumn and currentAxisY >= maxAxisY then
5308 -- Reset y-axis (from panel title)
5309 currentAxisY = rightcolumn.y + 20
5310 -- Wrap to next "column"
5311 currentAxisX = currentAxisX + rightcolumn.w
5312 -- Flag as we wrapped once, do not show anymore panels
5313 wrappedPanel = true
5314 end
5315 end
5316
5317 -- Wrapped items in the panel, flag the column as wrapped
5318 if wrappedPanel then
5319 wrappedColumn = true
5320 end
5321
5322 -- Add padding to bottom if there were items
5323 if itemCount > 0 then
5324 currentAxisY = currentAxisY + 15
5325 end
5326
5327 -- Wrapped the last panel (hide the rest)
5328 if wrappedColumn then
5329 currentAxisX = currentAxisX - (rightcolumn.w * 2)
5330 end
5331 end
5332 end
5333
5334 local function hudPanelCreate(title, column, skipUpdate)
5335 local panel = {
5336 title = title,
5337 items = {}
5338 }
5339 table.insert(_hud[column].panels, panel)
5340 if not skipUpdate then
5341 hudUpdatePositions()
5342 end
5343 _hud.index[title] = panel
5344 return panel
5345 end
5346
5347 local function hudItemCreate(panel, title, value, skipUpdate)
5348 local panel = _hud.index[panel]
5349 if not panel then
5350 return false
5351 end
5352
5353 local item = {
5354 title = title,
5355 value = value
5356 }
5357 table.insert(panel.items, item)
5358 if not skipUpdate then
5359 hudUpdatePositions()
5360 end
5361 _hud.index[panel.title][title] = item
5362 return item
5363 end
5364
5365 local function hudItemUpdate(panel, title, value, skipUpdate)
5366 local panel = _hud.index[panel]
5367 if panel then
5368 local item = panel[title]
5369 if item and value ~= item.value then
5370 item.value = value
5371 local itemValue = value
5372 if type(value) == 'number' then
5373 itemValue = formatNumber(value) .. ' gp'
5374 end
5375 xeno.HUDUpdateTextText(item.vpointer, itemValue)
5376 if not skipUpdate then
5377 hudUpdatePositions()
5378 end
5379 end
5380 end
5381 end
5382
5383 local function getItemValue(lootID)
5384 return _config['Prices'][lootID] or xeno.getItemValue(lootID)
5385 end
5386
5387 local function hudTrack(type, itemid, amount, skipUpdate)
5388 -- Loot / Supplies
5389 local hudItem = _hud.index[type][itemid]
5390 if not hudItem then
5391 hudItemCreate(type, itemid, 0, false)
5392 hudItem = _hud.index[type][itemid]
5393 end
5394
5395 -- TODO: finish duration
5396 -- Check if this item has a duration
5397 local duration = ITEM_LIST_DURATIONS[itemid]
5398 --local hudValue = values and formatNumber((itemValue / (values.d * 60)) * count) .. ' gp' or '--'
5399
5400 -- Get current count and values
5401 local calculateValue = type == 'Supplies' and xeno.getItemCost or getItemValue
5402 local itemValue = calculateValue(itemid)
5403
5404 local hudCount = (hudItem.rawCount or 0) + amount
5405 local hudValue = (hudItem.rawValue or 0) + (itemValue * amount)
5406
5407 -- Save raw values
5408 hudItem.rawCount = hudCount
5409 hudItem.rawValue = hudValue
5410
5411 -- Timestamp duration item
5412 if duration then
5413 hudItem._lastCheck = os.time()
5414 end
5415
5416 -- Update HUD
5417 local text = ''
5418 if ITEM_LIST_MONEY[itemid] then
5419 text = formatNumber(hudValue) .. ' gp'
5420 else
5421 text = formatNumber(hudCount) .. ' (' .. formatNumber(hudValue) .. ' gp)'
5422 end
5423 hudItemUpdate(type, itemid, text, skipUpdate)
5424 end
5425
5426 -- polling
5427 -- supplies decrementing
5428 -- loot added
5429 -- corpse looting
5430 -- skinned items
5431 -- eq tracking (ammo, distance weapons, amulets)
5432 -- ammo tracking (ammo slot)
5433 -- time tracking (softboots, rings)
5434 --
5435
5436 local function hudQueryLootChanges()
5437 -- TODO: support fishing/skinning
5438 -- TODO: decrement used loot (food, pots, etc)
5439
5440 -- Total value from this query
5441 local totalQueryValue = 0
5442
5443 local function queryBackpackChanges(type, filter)
5444 -- Check looted items (if not main bp)
5445 if _backpacks[type] and _backpacks[type] > 0 then
5446 -- Create snapshot if doesn't exist
5447 if not _hud.lootSnapshots[type] then
5448 _hud.lootSnapshots[type] = {}
5449 end
5450
5451 -- Count the items in the backpack
5452 local newCounts = {}
5453 for spot = 0, xeno.getContainerItemCount(_backpacks[type]) - 1 do
5454 local item = xeno.getContainerSpotData(_backpacks[type], spot)
5455 -- Only look at items in list
5456 if not filter or filter[item.id] then
5457 -- Initialize the count if needed
5458 if not newCounts[item.id] then
5459 newCounts[item.id] = 0
5460 end
5461 -- Increment the count
5462 newCounts[item.id] = newCounts[item.id] + math.max(1, item.count)
5463 end
5464 end
5465
5466 -- Find out which items went missing (none found and were there before)
5467 for lootID, lootCount in pairs(_hud.lootSnapshots[type]) do
5468 if not newCounts[lootID] then
5469 _hud.lootSnapshots[type][lootID] = 0
5470 end
5471 end
5472
5473 -- Loop through all itemids with new counts
5474 for lootID, lootCount in pairs(newCounts) do
5475 -- No snapshot, set to zero
5476 if not _hud.lootSnapshots[type][lootID] then
5477 _hud.lootSnapshots[type][lootID] = 0
5478 end
5479 -- Get difference of snapshot and new count
5480 local difference = lootCount - _hud.lootSnapshots[type][lootID]
5481 -- Snapshot current count (even without corpse)
5482 _hud.lootSnapshots[type][lootID] = lootCount
5483 -- If difference is positive and corpse open, add to totals
5484 if difference > 0 and isCorpseOpen() then
5485 -- Add to overall total
5486 local value = (getItemValue(lootID) * difference)
5487 totalQueryValue = totalQueryValue + value
5488 hudTrack('Loot', lootID, difference)
5489 end
5490 end
5491 end
5492 end
5493
5494 queryBackpackChanges('Gold', ITEM_LIST_MONEY)
5495 queryBackpackChanges('Loot')
5496
5497 if totalQueryValue > 0 then
5498 -- Update looted total
5499 local totalLooted = _hud.index['Statistics']['Looted'].value
5500 local totalWaste = _hud.index['Statistics']['Wasted'].value
5501 hudItemUpdate('Statistics', 'Looted', totalLooted + totalQueryValue, true)
5502 -- Update profit
5503 local gain = (totalLooted + totalQueryValue) - totalWaste
5504 local timediff = os.time() - _script.start
5505 local staminadiff = _script.startStamina - xeno.getSelfStamina()
5506 staminadiff = (staminadiff > 0 and staminadiff or 1) * 60
5507 local useStaminaMeasure = _config['HUD']['Per-Stamina-Measurement']
5508 local diff = useStaminaMeasure and staminadiff or timediff
5509 local hourlygain = tonumber(math.floor(gain / (diff / 3600))) or 0
5510 local hourPostfix = useStaminaMeasure and ' gp/sh' or ' gp/h'
5511 hudItemUpdate('Statistics', 'Hourly Profit', formatNumber(hourlygain) .. hourPostfix, false)
5512 -- Update HUD
5513 hudUpdatePositions()
5514 end
5515 end
5516
5517 local function hudQuerySupplyChanges()
5518 if _script.depotOpen then
5519 return
5520 end
5521
5522 -- Total value from this query
5523 local totalQueryValue = 0
5524
5525 local function queryBackpackChanges(type, filter)
5526 -- Check supply items, skip if closed
5527 local backpack = _backpacks[type]
5528 if backpack and xeno.getContainerOpen(backpack) then
5529 -- Create snapshot if doesn't exist
5530 if not _hud.supplySnapshots[type] then
5531 _hud.supplySnapshots[type] = {}
5532 end
5533
5534 -- Count the items in the backpack
5535 local newCounts = {}
5536 for spot = 0, xeno.getContainerItemCount(backpack) - 1 do
5537 local item = xeno.getContainerSpotData(backpack, spot)
5538 -- Only look at items in list
5539 if not filter or filter[item.id] then
5540 -- Initialize the count if needed
5541 if not newCounts[item.id] then
5542 newCounts[item.id] = 0
5543 end
5544 -- Increment the count
5545 newCounts[item.id] = newCounts[item.id] + math.max(1, item.count)
5546 end
5547 end
5548
5549 -- Find out which items went missing (none found and were there before)
5550 for supplyID, supplyCount in pairs(_hud.supplySnapshots[type]) do
5551 if not newCounts[supplyID] then
5552 -- This differs from the loot tracking,
5553 -- instead of clearing the snapshot, we set the newCount to 0, so we get a negative
5554 newCounts[supplyID] = 0
5555 end
5556 end
5557
5558 -- Loop through all itemids with new counts
5559 for supplyID, supplyCount in pairs(newCounts) do
5560 -- No snapshot, set to zero
5561 if not _hud.supplySnapshots[type][supplyID] then
5562 _hud.supplySnapshots[type][supplyID] = 0
5563 end
5564 -- Get difference of snapshot and new count
5565 local difference = supplyCount - _hud.supplySnapshots[type][supplyID]
5566 -- Snapshot current count (even when gaining supplies)
5567 _hud.supplySnapshots[type][supplyID] = supplyCount
5568 -- If difference is negative AND depot is not open, add to totals
5569 if difference < 0 then
5570 -- Only the absolute value matters at this point
5571 difference = math.abs(difference)
5572 -- Threshold failsafe (impossible to use this many supplies within 200ms)
5573 if difference < SUPPLY_CHECK_THRESHOLD then
5574 local value = xeno.getItemCost(supplyID) * difference
5575 -- Add to overall total
5576 totalQueryValue = totalQueryValue + value
5577 hudTrack('Supplies', supplyID, difference)
5578 end
5579 end
5580 end
5581 end
5582
5583 -- TODO: Look for items in the equipment slot that decreased from the container
5584 -- decrease the missing count by the slot count
5585 end
5586
5587 local function queryEquipmentChanges(type, filter)
5588 -- Slots: ring, ammo, amulet, weapon
5589 local slotFunc = {
5590 ['Amulet'] = xeno.getAmuletSlotData,
5591 ['Ammo'] = xeno.getAmmoSlotData,
5592 ['Distance'] = xeno.getWeaponSlotData,
5593 ['Feet'] = xeno.getFeetSlotData,
5594 ['Ring'] = xeno.getRingSlotData
5595 }
5596
5597 local slot = slotFunc[type]
5598 if not slot then return end
5599 slot = slot()
5600
5601 local isSlotEmpty = slot.id == 0
5602
5603 -- Ignore if item is not in our supply list
5604 if not isSlotEmpty and not filter[slot.id] then
5605 return
5606 end
5607
5608 -- Previous state of this slot
5609 local lastSlot = _hud.supplySnapshots[type]
5610 if lastSlot and lastSlot.id == 0 then
5611 lastSlot = nil
5612 end
5613
5614 -- This is a new item (we do not have a snapshot for it)
5615 -- record state and do work next time :)
5616 if not isSlotEmpty and (not lastSlot or lastSlot.id ~= slot.id) then
5617 _hud.supplySnapshots[type] = slot
5618 return
5619 end
5620
5621 -- If slot is empty, make sure we didn't just dequip the item
5622 if isSlotEmpty and lastSlot and _script.equipped[type:lower()] then
5623 _hud.supplySnapshots[type] = slot
5624 return
5625 end
5626
5627 -- Get difference of counts, if slot is empty, the difference is the snapshot count
5628 if lastSlot then
5629 local difference = isSlotEmpty and lastSlot.count or lastSlot.count - slot.count
5630 local itemid = isSlotEmpty and lastSlot.id or slot.id
5631 if difference > 0 and difference < SUPPLY_CHECK_THRESHOLD then
5632 -- Add to overall total
5633 local value = xeno.getItemCost(itemid) * difference
5634 totalQueryValue = totalQueryValue + value
5635 -- Update HUD
5636 hudTrack('Supplies', itemid, difference)
5637 end
5638 end
5639
5640 -- No matter what, always update your snapshot
5641 _hud.supplySnapshots[type] = slot
5642 end
5643
5644 -- Populate backpack and item lists to check changes
5645 local supplyLists = {}
5646 for itemid, supply in pairs(_supplies) do
5647 -- Amulets and Rings always go in the Supplies backpack
5648 local name = supply.group
5649 -- Item is a distance weapon, doesn't go to ammo slot
5650 if DISTANCE_WEAPONS[itemid] then
5651 name = 'Distance'
5652 end
5653 -- Init table if it doesn't exist
5654 if not supplyLists[name] then
5655 supplyLists[name] = {}
5656 end
5657 -- Add supply item to the list
5658 supplyLists[name][itemid] = true
5659 end
5660
5661 queryBackpackChanges('Feet', {[ITEMID.SOFTBOOTS_ACTIVE] = true})
5662
5663 for backpack, filter in pairs(supplyLists) do
5664 if backpack == 'Amulet'
5665 or backpack == 'Ring'
5666 or backpack == 'Distance'
5667 or backpack == 'Ammo' then
5668 queryEquipmentChanges(backpack, filter)
5669 else
5670 queryBackpackChanges(backpack, filter)
5671 end
5672 end
5673
5674 if totalQueryValue > 0 then
5675 -- Update supply total
5676 local totalWaste = _hud.index['Statistics']['Wasted'].value
5677 local totalLooted = _hud.index['Statistics']['Looted'].value
5678 hudItemUpdate('Statistics', 'Wasted', totalWaste + totalQueryValue, true)
5679 -- Update profit
5680 local gain = totalLooted - (totalWaste + totalQueryValue)
5681 local timediff = os.time() - _script.start
5682 local staminadiff = _script.startStamina - xeno.getSelfStamina()
5683 staminadiff = (staminadiff > 0 and staminadiff or 1) * 60
5684 local useStaminaMeasure = _config['HUD']['Per-Stamina-Measurement']
5685 local diff = useStaminaMeasure and staminadiff or timediff
5686 local hourlygain = tonumber(math.floor(gain / (diff / 3600))) or 0
5687 local hourPostfix = useStaminaMeasure and ' gp/sh' or ' gp/h'
5688 hudItemUpdate('Statistics', 'Hourly Profit', formatNumber(hourlygain) .. hourPostfix, false)
5689
5690 end
5691 end
5692
5693 local function hudUpdate()
5694 -- TODO: categorize based on hud color, update theme colors
5695 for i = 1, #hudPointers do
5696 local pointer = hudPointers[i]
5697 if pointer then
5698 -- Change color
5699 end
5700 end
5701 end
5702
5703 local function hudInit()
5704
5705 hudPanelCreate('General', 'leftcolumn', true)
5706 hudPanelCreate('Statistics', 'leftcolumn', true)
5707 hudPanelCreate('Script', 'leftcolumn', true)
5708
5709 hudPanelCreate('Supplies', 'rightcolumn', true)
5710 hudPanelCreate('Loot', 'rightcolumn', true)
5711
5712 hudItemCreate('General', 'XenoBot', _script.xenoversion, true)
5713 hudItemCreate('General', 'Online Time', '--', true)
5714 hudItemCreate('General', 'Time Remaining', '--', true)
5715 hudItemCreate('General', 'Server Save', '--', true)
5716 hudItemCreate('General', 'Stamina', '--', true)
5717 hudItemCreate('General', 'Latency', '--', true)
5718 hudItemCreate('General', 'Balance', '--', true)
5719
5720 hudItemCreate('Script', 'Version', LIB_REVISION, true)
5721 hudItemCreate('Script', 'Round', tostring(_script.round), true)
5722 hudItemCreate('Script', 'State', 'Setting up backpacks', true)
5723 hudItemCreate('Script', 'Route', '--', true)
5724 hudItemCreate('Script', 'Walker', '--', true)
5725 hudItemCreate('Script', 'Targeter', '--', true)
5726 --hudItemCreate('Script', 'Avg. Resupply', '--', true)
5727 --hudItemCreate('Script', 'Avg. Round', '--', true)
5728
5729 hudItemCreate('Statistics', 'Experience', '--', true)
5730 hudItemCreate('Statistics', 'Profit', '--', true)
5731 hudItemCreate('Statistics', 'Hourly Exp', '--', true)
5732 hudItemCreate('Statistics', 'Hourly Profit', '--', true)
5733 hudItemCreate('Statistics', 'Looted', 0, true)
5734 hudItemCreate('Statistics', 'Wasted', 0, true)
5735 hudItemCreate('Statistics', 'Exp to Level', '--', true)
5736 hudItemCreate('Statistics', 'Time to Level', '--', true)
5737
5738 local containerColor = THEME[_script.theme].title
5739 for i = 0, 15 do
5740 containerHud[i+1] = xeno.HUDCreateText(-100, -100, '--', unpack(containerColor))
5741 end
5742
5743 hudUpdatePositions()
5744 end
5745
5746 -- Export global functions
5747 return {
5748 hudUpdateDimensions = hudUpdateDimensions,
5749 hudUpdatePositions = hudUpdatePositions,
5750 hudItemUpdate = hudItemUpdate,
5751 hudQueryLootChanges = hudQueryLootChanges,
5752 hudQuerySupplyChanges = hudQuerySupplyChanges,
5753 hudInit = hudInit
5754 }
5755end)()
5756Ini = (function()
5757 -- Imports
5758 local split = Core.split
5759 local trim = Core.trim
5760 local countPairs = Core.countPairs
5761 local indexTable = Core.indexTable
5762 local setTimeout = Core.setTimeout
5763 local getSelfName = Core.getSelfName
5764 local debug = Core.debug
5765 local error = Console.error
5766 local log = Console.log
5767 local prompt = Console.prompt
5768
5769 local function loadIniFile(file)
5770 -- Could not load config
5771 if not file then
5772 error('Could not load the config.')
5773 end
5774
5775 local tbl = {}
5776 local section
5777 for line in file:lines() do
5778 local s = string.match(line, "^%[([^%]]+)%]$")
5779 if s then
5780 section = s
5781 tbl[section] = tbl[section] or {}
5782 end
5783
5784 local key, value = string.match(line, "^([^%s]+)%s+=%s+([^;]+)")
5785
5786 -- If the first try didnt work, check for a multi-word key
5787 if not key then
5788 key, value = string.match(line, '"(.+)"%s+=%s+([^;]*)')
5789 end
5790 if key and value then
5791 -- Type casting
5792 if tonumber(value) ~= nil then
5793 value = tonumber(value)
5794 else
5795 value = string.gsub(value, "^%s*(.-)%s*$", "%1")
5796 if value == "true" then
5797 value = true
5798 elseif value == "false" then
5799 value = false
5800 -- Transform comma-delimited string to table
5801 elseif string.find(value, ',') then
5802 value = split(value, ',')
5803 for i = 1, #value do
5804 value[i] = trim(value[i])
5805 end
5806 end
5807 end
5808 if section then
5809 if not tbl[section] then
5810 tbl[section] = {}
5811 end
5812 tbl[section][key] = value
5813 end
5814 end
5815 end
5816
5817 file:close()
5818 return tbl
5819 end
5820
5821 local function loadMarketPrices()
5822 local file = io.open(PRICES_CONFIG_PATH, 'r')
5823
5824 -- Found config, compare config version against embedded config
5825 if file then
5826 local match = false
5827 for line in file:lines() do
5828 if string.match(line, '^; ::' .. _script.pricesConfigHash .. '$') then
5829 match = true
5830 break
5831 end
5832 end
5833 if not match then
5834 log('Updating script config file...')
5835 file:close()
5836 file = nil
5837 end
5838 end
5839 -- Could not find a config anywhere (or we wanted to update)
5840 if not file then
5841 -- Write the embedded config to disk
5842 local defaultConfig = io.open(PRICES_CONFIG_PATH, 'w+')
5843 if defaultConfig then
5844 defaultConfig:write(LIB_PRICES_CONFIG)
5845 defaultConfig:close()
5846 else
5847 error('Could not write default config file.')
5848 end
5849
5850 -- Try again
5851 file = io.open(PRICES_CONFIG_PATH, 'r')
5852 end
5853
5854 local priceConfig = loadIniFile(file)
5855 local prices = {}
5856
5857 -- Load default prices
5858 if priceConfig['Default'] then
5859 for name, price in pairs(priceConfig['Default']) do
5860 local id = xeno.getItemIDByName(name)
5861 prices[id] = price
5862 end
5863 end
5864
5865 -- Load and overwrite with character specific config
5866 if priceConfig[getSelfName()] then
5867 for name, price in pairs(priceConfig[getSelfName()]) do
5868 local id = xeno.getItemIDByName(name)
5869 prices[id] = price
5870 end
5871 end
5872
5873 return prices
5874 end
5875
5876
5877 local function updateSupplyConfig()
5878 local function loadBlockSection(sectionName, extras)
5879 local section = _config[sectionName]
5880 if section then
5881 for key, value in pairs(section) do
5882 local start, stop = string.find(key, 'Name')
5883 if start and value then
5884 -- Base name of the key (ex: 'Mana' of 'ManaName')
5885 local name = string.sub(key, 1, start-1)
5886 local enabled = section[name .. 'Enabled']
5887 -- Supply is enabled
5888 if enabled ~= false then
5889 -- TODO: validate user submitted name/id
5890 local id = nil
5891 if sectionName == 'Spells' then
5892 id = string.lower(value)
5893 else
5894 id = type(value) == 'string' and xeno.getItemIDByName(value) or tonumber(value)
5895 end
5896
5897 local min = section[name .. 'Min'] or 0
5898 local max = section[name .. 'Max'] or 0
5899
5900 -- Noob proofing
5901 if min > max then
5902 error('Config error. The "' .. name .. 'Min" value is higher than the "' .. name .. 'Max" value.')
5903 end
5904
5905 local alarm = section[name .. 'Alarm'] or 0
5906 local options = nil
5907
5908 -- Extra fields
5909 if extras then
5910 options = {}
5911 for i = 1, #extras do
5912 local extraKey = extras[i]
5913 local extraValue = section[name .. extraKey]
5914 if extraValue ~= nil then
5915 if extraKey == 'Creatures' then
5916 options[extraKey] = indexTable(extraValue, true)
5917 else
5918 options[extraKey] = extraValue
5919 end
5920 end
5921 end
5922 end
5923
5924 -- Add item to supplies list
5925 local index = sectionName == 'Spells' and name or id
5926 _supplies[index] = {
5927 name = name,
5928 id = id,
5929 group = sectionName,
5930 count = 0,
5931 needed = 0,
5932 min = min,
5933 max = max,
5934 alarm = alarm,
5935 options = options
5936 }
5937 end
5938 end
5939 end
5940 end
5941 end
5942
5943 loadBlockSection('Potions')
5944 loadBlockSection('Ammo')
5945 loadBlockSection('Food')
5946 loadBlockSection('Supplies')
5947 loadBlockSection('Runes', {'Priority', 'TargetMin', 'Targets', 'MaxHP', 'MinHP'})
5948 loadBlockSection('Spells', {'Priority', 'TargetMin', 'Targets', 'Utito', 'MaxHP', 'MinHP'})
5949 loadBlockSection('Ring', {'Creatures', 'CreatureCount', 'MinHP', 'MinMP'})
5950 loadBlockSection('Amulet', {'Creatures', 'CreatureCount', 'MinHP', 'MinMP'})
5951 end
5952
5953 local function loadMasterConfig()
5954 local file = io.open(MASTER_CONFIG_PATH, 'r')
5955 if not file then
5956 return nil
5957 end
5958 local config = loadIniFile(file)
5959
5960 local groups = {}
5961 -- Multi Script #1
5962 for name, rules in pairs(config) do
5963 local priority = tonumber(name:match('Multi Script #(%d)'))
5964 if rules.Enabled then
5965 rules.priority = priority
5966 groups[#groups+1] = rules
5967 end
5968 end
5969
5970 table.sort(groups, function(a, b)
5971 return a.priority < b.priority
5972 end)
5973
5974 return groups
5975 end
5976
5977 local function loadConfigFile(callback, isReload)
5978 local configName = '[' .. getSelfName() .. '] ' .. _script.name .. '.ini'
5979 local configPath = FOLDER_CONFIG_PATH .. configName
5980
5981 local function parse(file)
5982 local tbl = loadIniFile(file)
5983
5984 -- Convert anti-lure creatures to key,value table
5985 if tbl['Anti Lure'] then
5986 local lureCreatures = tbl['Anti Lure']['Creatures']
5987 if lureCreatures then
5988 local lureTbl = {}
5989 if type(lureCreatures) == 'table' then
5990 for i = 1, #lureCreatures do
5991 lureTbl[string.lower(lureCreatures[i])] = true
5992 end
5993 tbl['Anti Lure']['Creatures'] = lureTbl
5994 else
5995 tbl['Anti Lure']['Creatures'] = {
5996 [string.lower(lureCreatures)] = true
5997 }
5998 end
5999 end
6000 end
6001
6002 -- Update start config values
6003 if not isReload and tbl['HUD'] then
6004 _script.theme = tbl['HUD']['Theme'] or 'light'
6005 end
6006
6007 _config = tbl
6008 _config['Prices'] = loadMarketPrices()
6009 updateSupplyConfig()
6010 callback()
6011 end
6012
6013 local function promptConfig()
6014 local message = 'A new config file was generated, please reconfigure before proceeding. Type "ok" to continue.'
6015 -- Print in server console (this is not a debug, do not remove)
6016 print(message)
6017 prompt(message, function(response)
6018 if string.find(string.lower(response), 'ok') then
6019 local newFile = io.open(configPath, 'r')
6020 parse(newFile)
6021 else
6022 promptConfig()
6023 end
6024 end)
6025 end
6026
6027 -- Open config file
6028 local file = io.open(configPath, 'r')
6029
6030 -- Found config, compare config version against embedded config
6031 if file then
6032 local match = false
6033 for line in file:lines() do
6034 if string.match(line, '^; ::' .. _script.configHash .. '$') then
6035 match = true
6036 break
6037 end
6038 end
6039 if not match then
6040 log('Updating script config file...')
6041 file:close()
6042 file = nil
6043 end
6044 end
6045
6046 -- Could not find a config anywhere (or we wanted to update)
6047 if not file then
6048 -- Write the embedded config to disk
6049 local defaultConfig = io.open(configPath, 'w+')
6050 if defaultConfig then
6051 defaultConfig:write(LIB_CONFIG)
6052 defaultConfig:close()
6053 promptConfig()
6054 else
6055 error('Could not write default config file.')
6056 end
6057 return
6058 end
6059
6060 -- Using existing config
6061 parse(file)
6062 end
6063
6064 local function parseRange(value)
6065 local range = type(value) == 'number' and {value, value} or split(value, '-')
6066 return tonumber(range[1]), tonumber(range[2])
6067 end
6068
6069 local function checkChainRules(groups, initCheck)
6070 local switchScript = nil
6071 -- TODO: balance
6072 local ruleChecks = {
6073 -- Randomly choose between the range, always trigger if reached or above range
6074 ['Rounds'] = function(req)
6075 if req == 0 then return false end
6076 local min, max = parseRange(req)
6077 local target = math.random(min, max)
6078 local round = _script.round - 1
6079 local status = round == target or round >= max
6080 debug(('Rounds rule: %s [%d = %d]'):format(tostring(status), round, target))
6081 return status
6082 end,
6083 -- Randomly choose between the range, always trigger if reached or above range
6084 ['Level'] = function(req)
6085 if req == 0 then return false end
6086 local min, max = parseRange(req)
6087 local target = math.random(min, max)
6088 local level = xeno.getSelfLevel()
6089 local status = level == target or level >= max
6090 debug(('Level rule: %s [%d = %d]'):format(tostring(status), level, target))
6091 return status
6092 end,
6093 -- Trigger within the range
6094 ['Experience'] = function(req)
6095 if req == 0 then return false end
6096 local min, max = parseRange(req)
6097 local timediff = os.time() - _script.start
6098 local gain = xeno.getSelfExperience() - _script.baseExp
6099 local hourlyexp = tonumber(math.floor(gain / (timediff / 3600))) or 0
6100 local status = hourlyexp >= min and hourlyexp <= max
6101 debug(('Hourly exp rule: %s [%d, %s]'):format(tostring(status), hourlyexp, req))
6102 return status
6103 end,
6104 -- Trigger within the range
6105 ['Profit'] = function(req)
6106 if req == 0 then return false end
6107 local min, max = parseRange(req)
6108 local timediff = os.time() - _script.start
6109 local totalLooted = _hud.index['Statistics']['Looted'].value
6110 local totalWaste = _hud.index['Statistics']['Wasted'].value
6111 local profit = totalLooted - totalWaste
6112 local hourlygain = tonumber(math.floor(profit / (timediff / 3600))) or 0
6113 local status = hourlygain >= min and hourlygain <= max
6114 debug(('Hourly profit rule: %s [%d, %s]'):format(tostring(status), hourlygain, req))
6115 return status
6116 end,
6117 -- Randomly choose between the range, always trigger if reached or above range
6118 ['Time'] = function(req)
6119 if req == 0 then return false end
6120 local min, max = parseRange(req)
6121 local target = math.random(min, max)
6122 local hours = math.floor((os.time() - _script.start) / 3600)
6123 local status = hours == target or hours >= max
6124 debug(('Time rule: %s [%d = %d]'):format(tostring(status), hours, target))
6125 return status
6126 end,
6127 -- Randomly choose between the range, always trigger if reached or above range
6128 ['Strangers'] = function(req)
6129 if req == 0 then return false end
6130 local min, max = parseRange(req)
6131 local target = math.random(min, max)
6132 local strangers = _script.strangers
6133 local status = strangers == target or strangers >= max
6134 debug(('Crowded rule: %s [%d = %d]'):format(tostring(status), strangers, target))
6135 return status
6136 end
6137 }
6138
6139 for i = 1, #groups do
6140 local properties = groups[i]
6141 -- Character is in group or no character list supplied
6142 local players = indexTable(properties.Characters, true)
6143 if not players or players[getSelfName():lower()] then
6144 local scriptList = indexTable(properties.From, true)
6145 local currentScript = _script.slug:gsub('.xbst', '')
6146 if not scriptList or scriptList[currentScript:lower()] then
6147
6148 local rules = {}
6149 -- Get rule list from properties
6150 for property, value in pairs(properties) do
6151 if ruleChecks[property] then
6152 rules[property] = value
6153 end
6154 end
6155
6156 -- How many rules it takes to trigger a switch for this group of rules
6157 local ruleLimit = properties.Check == 'all' and countPairs(rules) or properties.Check
6158 local failedRules = 0
6159
6160 -- Start checking rules
6161 for rule, requirement in pairs(rules) do
6162 if not initCheck or (initCheck and (rule == 'Level')) then
6163 local status = ruleChecks[rule](requirement)
6164 if status then
6165 failedRules = failedRules + 1
6166 -- Found a script, stop checking rules in this group
6167 if failedRules >= ruleLimit then
6168 local scripts = properties.Goto
6169 local tries = 20
6170 local function getTargetScript()
6171 local target = type(scripts) == 'string' and scripts or scripts[math.random(1, #scripts)]
6172 if target:lower() == currentScript:lower() then
6173 if type(scripts) == 'string' then
6174 return nil
6175 end
6176 tries = tries - 1
6177 if tries <= 0 then
6178 return nil
6179 end
6180 return getTargetScript()
6181 end
6182 return target
6183 end
6184 local targetScript = getTargetScript()
6185 if targetScript then
6186 switchScript = targetScript
6187 break
6188 end
6189 end
6190 end
6191 end
6192 end
6193
6194 debug(('Rule Group #%d: %d/%d'):format(i, failedRules, ruleLimit))
6195
6196 -- Found a script, stop checking groups of rules
6197 if switchScript then
6198 debug('Chaining to script: ' .. switchScript)
6199 xeno.loadSettings(switchScript, 'All')
6200 break
6201 end
6202 end
6203 end
6204 end
6205
6206 return switchScript ~= nil
6207 end
6208
6209 -- Export global functions
6210 return {
6211 loadConfigFile = loadConfigFile,
6212 loadMasterConfig = loadMasterConfig,
6213 checkChainRules = checkChainRules
6214 }
6215end)()
6216Npc = (function()
6217
6218 -- Imports
6219 local pingDelay = Core.pingDelay
6220 local setTimeout = Core.setTimeout
6221 local talk = Core.talk
6222 local checkSoftBoots = Core.checkSoftBoots
6223 local getXenoVersion = Core.getXenoVersion
6224 local debug = Core.debug
6225 local error = Console.error
6226 local getTotalItemCount = Container.getTotalItemCount
6227 local containerMoveItems = Container.containerMoveItems
6228 local getMoney = Container.getMoney
6229 local getFlasks = Container.getFlasks
6230
6231 local function moveTransactionGoldChange(container, callback)
6232 if container == _backpacks['Gold'] then
6233 callback()
6234 return
6235 end
6236 -- Move loose gold change from container to gold backpack
6237 -- uses main bp if gold backpack isn't assigned.
6238 debug('moveTransactionGoldChange: ' .. container .. ', ' .. _backpacks['Gold'])
6239 containerMoveItems({
6240 src = container,
6241 dest = _backpacks['Gold'],
6242 items = {
6243 [3031] = true,
6244 [3035] = true
6245 },
6246 disableSourceCascade = true,
6247 openwindow = false
6248 }, function(success)
6249 callback()
6250 end)
6251 end
6252
6253 local function bankDepositGold(callback)
6254 -- Deposit everything (skip deposit if no money)
6255 talk(getMoney() <= 0 and {'hi'} or {'hi', 'deposit all', 'yes'}, function(responses)
6256 -- TODO: verify funds deposited
6257 callback()
6258 end)
6259 end
6260
6261 local function bankGetBalance(callback, nodialog)
6262 local dialog = not nodialog and {'hi', 'balance'} or {'balance'}
6263 talk(dialog, function(responses)
6264 -- Search for balance in dialog
6265 if responses then
6266 for i = 1, #responses do
6267 local response = responses[i]
6268 if response then
6269 local balanceText = response:gsub(',', '')
6270 local balance = balanceText:match('account balance is (%d+)')
6271 if balance then
6272 _script.balance = tonumber(balance) or 0
6273 -- Callback
6274 callback()
6275 return true
6276 end
6277 end
6278 end
6279 end
6280 -- Failure callback
6281 callback(false)
6282 end)
6283 end
6284
6285 local function bankWithdrawGold(amount, callback, nodialog)
6286 local dialog = not nodialog and {'hi', 'withdraw', amount, 'yes'} or {'withdraw', amount, 'yes'}
6287 local prevMoney = getMoney()
6288 local tries = 3
6289 local function interact()
6290 -- TODO: use npc proxy to verify withdraw
6291 setTimeout(function()
6292 if getMoney() > prevMoney then
6293 callback()
6294 else
6295 tries = tries - 1
6296 if tries <= 0 then
6297 error('Unable to withdraw ' .. amount .. ' gold. Make sure you have sufficient funds.')
6298 else
6299 talk(dialog, interact)
6300 end
6301 end
6302 end, pingDelay(DELAY.RANGE_TALK))
6303 end
6304
6305 talk(dialog, interact)
6306 end
6307
6308 local function shopSellableCount(itemid, includeEq)
6309 local countWithEq = xeno.shopGetItemSaleCountByID(itemid)
6310 if includeEq then
6311 return countWithEq
6312 end
6313
6314 local slots = {"getHeadSlotData", "getArmorSlotData", "getLegsSlotData", "getFeetSlotData", "getAmuletSlotData", "getWeaponSlotData",
6315 "getRingSlotData", "getBackpackSlotData", "getShieldSlotData", "getAmmoSlotData"}
6316
6317 local count = countWithEq
6318 for _, slot in ipairs(slots) do
6319 local itemInSlot = xeno[slot]()
6320 if itemInSlot.id == itemid then
6321 count = count - math.max(itemInSlot.count, 1)
6322 end
6323 end
6324
6325 return count
6326 end
6327
6328 local function shopSellItem(itemid, callback, neededCount, tries)
6329 tries = tries or 10
6330
6331 -- Sell specific amount or "all"
6332 local remaining = neededCount or 100000
6333
6334 local function sellItem()
6335 -- Item doesn't exist, ignore
6336 local amount = shopSellableCount(itemid)
6337
6338 -- No more to sell
6339 if amount <= 0 then
6340 debug('shopSellItem: sellItem -> callback()')
6341 callback()
6342 return
6343 end
6344
6345 -- Sell 100x at a time
6346 local neededStackCount = math.min(remaining, amount)
6347
6348 -- Successfully sold the stack
6349 if xeno.shopSellItemByID(itemid, neededStackCount) > 0 then
6350 -- Reduce remaining by sold stack count, reset tries
6351 remaining = remaining - neededStackCount
6352 -- TODO: add to HUD looted (itemid:neededStackCount)
6353 setTimeout(function()
6354 -- Remaining count to sell, recurse
6355 if remaining > 0 then
6356 debug('shopSellItem: sellItem()')
6357 sellItem()
6358 -- Sold all items, callback
6359 else
6360 debug('shopSellItem: callback()')
6361 callback()
6362 end
6363 end, pingDelay(DELAY.TRADE_TRANSACTION))
6364 -- Failed to sell, retrying
6365 elseif tries > 0 then
6366 debug('shopSellItem: retry('.. tries-1 ..')')
6367 shopSellItem(itemid, callback, remaining, tries-1)
6368 -- Out of tries. Failed to sell stack.
6369 else
6370 error('Failed to sell ' .. xeno.getItemNameByID(itemid) .. ' (' .. neededStackCount .. 'x).')
6371 end
6372 return
6373 end
6374 -- Start recursive selling
6375 debug('shopSellItem: sellItem() [start]')
6376 sellItem()
6377 end
6378
6379 local function shopSellLoot(sellList, callback, nodialog)
6380 -- Add key, value array to flat list
6381 local itemlist = {}
6382 for itemid, _ in pairs(sellList) do
6383 itemlist[#itemlist+1] = itemid
6384 end
6385 local itemcount = #itemlist
6386 debug('shopSellLoot: itemcount = '.. itemcount ..'')
6387
6388 function sell(index)
6389 local itemid = itemlist[index]
6390
6391 -- No more items, finish
6392 if not itemid then
6393 -- Move change to gold
6394 debug('shopSellLoot: moveTransactionGoldChange()')
6395 moveTransactionGoldChange(0, function()
6396 debug('shopSellLoot: callback()')
6397 callback()
6398 end)
6399 return
6400 end
6401
6402 local amount = shopSellableCount(itemid)
6403 debug('shopSellLoot: shopSellableCount('.. itemid ..') = ' .. amount)
6404
6405 if amount > 0 then
6406 shopSellItem(itemid, function()
6407 debug('shopSellLoot: shopSellItem('.. itemid ..')')
6408 -- Recurse to next item in list
6409 setTimeout(function()
6410 sell(index + 1)
6411 debug('shopSellLoot: sell('.. index + 1 ..')')
6412 end, pingDelay(DELAY.TRADE_TRANSACTION))
6413 end)
6414 else
6415 -- If we don't have the item, recurse without a wait.
6416 sell(index + 1)
6417 debug('shopSellLoot: sell('.. index + 1 ..') [no item]')
6418 end
6419 end
6420
6421 if nodialog then
6422 sell(1)
6423 debug('shopSellLoot: sell(1)')
6424 return
6425 end
6426
6427 talk({'hi', 'trade'}, function()
6428 -- Todo: use NPC proxy to verify trade window
6429 setTimeout(function()
6430 sell(1)
6431 debug('shopSellLoot: sell(1) [greet]')
6432 end, pingDelay(DELAY.RANGE_TALK))
6433 end)
6434 end
6435
6436 local function shopSellFlasks(callback)
6437 if getXenoVersion() <= 1092 then
6438 callback()
6439 return
6440 end
6441 debug('shopSellFlasks: shopSellLoot()')
6442 shopSellLoot(ITEM_LIST_FLASKS, callback, true)
6443 end
6444
6445 local function shopRefillSoftboots(callback)
6446 local tries = 10
6447 function repair()
6448 talk({'soft boots', 'yes'}, function()
6449 -- Wait for this bitch to shine our boots
6450 setTimeout(function()
6451 -- Move change to gold
6452 moveTransactionGoldChange(0, function()
6453 -- No more boots, or failed too much
6454 if getTotalItemCount(ITEMID.SOFTBOOTS_WORN) <= 0 or tries <= 0 then
6455 -- Equip softboots if needed
6456 checkSoftBoots()
6457 callback()
6458 else
6459 tries = tries - 1
6460 repair()
6461 end
6462 end)
6463 end, pingDelay(DELAY.TRADE_TRANSACTION))
6464 end)
6465 end
6466
6467 talk({'hi'}, function()
6468 repair()
6469 end)
6470 end
6471
6472 local function shopBuyItemUpToCount(itemid, neededCount, destination, callback, tries)
6473 destination = destination or 0
6474 tries = tries or 10
6475 local remaining = neededCount
6476
6477 local function buyItem()
6478 -- Item doesn't exist, ignore
6479 local mainbp = _backpacks['Main']
6480 local price = xeno.shopGetItemBuyPriceByID(itemid)
6481 local neededStackCount = math.min(remaining, 100)
6482
6483 -- Price not found
6484 if price <= 0 then
6485 debug('shopBuyItemUpToCount: moveTransactionGoldChange()')
6486 moveTransactionGoldChange(0, function()
6487 debug('shopBuyItemUpToCount: callback')
6488 callback()
6489 end)
6490 return
6491 end
6492
6493 -- Successfully bought stack
6494 if xeno.shopBuyItemByID(itemid, neededStackCount) > 0 then
6495 -- Reduce remaining by bought stack count, reset tries
6496 remaining = remaining - neededStackCount
6497
6498 local function buyAgain()
6499 -- Remaining count to buy, continue
6500 if remaining > 0 then
6501 buyItem()
6502 debug('shopBuyItemUpToCount -> buyAgain: buyItem()')
6503 -- Bought all items, destination is not main, callback
6504 elseif destination > 0 then
6505 -- Final cleanup
6506 debug('shopBuyItemUpToCount -> buyAgain: containerMoveItems()')
6507 containerMoveItems({
6508 src = mainbp,
6509 dest = destination,
6510 items = {[itemid] = true},
6511 disableSourceCascade = true,
6512 openwindow = false
6513 }, function(success)
6514 debug('shopBuyItemUpToCount -> buyAgain: callback() [move]')
6515 callback()
6516 end)
6517 -- Bought all items, destination is main
6518 else
6519 debug('shopBuyItemUpToCount -> buyAgain: callback()')
6520 callback()
6521 end
6522 end
6523
6524 -- TODO: add to log (itemid:neededStackCount)
6525 setTimeout(function()
6526 -- Only move if intended destination isn't main backpack
6527 -- and we have less than 3 free slots
6528 local freeSlots = xeno.getContainerItemCapacity(mainbp) - xeno.getContainerItemCount(mainbp)
6529 if destination > 0 and freeSlots < 4 then
6530 debug('shopBuyItemUpToCount: containerMoveItems()')
6531 -- Move to destination after buying stack
6532 containerMoveItems({
6533 src = mainbp,
6534 dest = destination,
6535 items = {[itemid] = true},
6536 disableSourceCascade = true,
6537 openwindow = false
6538 }, function(success)
6539 buyAgain()
6540 debug('shopBuyItemUpToCount: buyAgain() [retry]')
6541 end)
6542 else
6543 buyAgain()
6544 debug('shopBuyItemUpToCount: buyAgain()')
6545 end
6546 end, pingDelay(DELAY.TRADE_TRANSACTION))
6547 -- Failed to buy, retrying
6548 elseif tries > 0 then
6549 shopBuyItemUpToCount(itemid, remaining, destination, callback, tries-1)
6550 debug('shopBuyItemUpToCount: retry(' .. tries - 1 .. ')')
6551 -- Out of tries. Failed to buy stack.
6552 else
6553 error('Failed to buy ' .. xeno.getItemNameByID(itemid) .. ' (' .. neededStackCount .. 'x). ' .. 'Make sure you have enough capacity and gold.')
6554 end
6555 return
6556 end
6557 -- Start recursive buying
6558 buyItem()
6559 end
6560
6561 local function shopBuySupplies(group, callback)
6562 local items = {}
6563 local backpack = _backpacks[group] and _backpacks[group] or nil
6564 local function buyListItem(index)
6565 -- Reached end of list, callback
6566
6567 if index > #items then
6568 debug('shopBuySupplies: callback')
6569 callback()
6570 return
6571 end
6572
6573 -- Lookup current item
6574 local item = items[index]
6575
6576 -- Item doesn't exist or not needed
6577 if not item or not item.needed or item.needed < 1 then
6578 debug('shopBuySupplies: buyListItem(' .. index + 1 .. ') [skip]')
6579 buyListItem(index + 1)
6580 return
6581 end
6582
6583 -- Buy item
6584 shopBuyItemUpToCount(item.id, item.needed, backpack, function()
6585 debug('shopBuySupplies: buyListItem(' .. index + 1 .. ')')
6586 buyListItem(index + 1)
6587 end)
6588 end
6589
6590 -- Whether we need to greet npc
6591 local greetNPC = true
6592
6593 -- Populate items
6594 for itemid, supply in pairs(_supplies) do
6595 -- Belongs to the correct group
6596 if supply.group == group then
6597 -- Minimum is expected to be checked and is below expected
6598 if supply.needed and supply.needed > 0 then
6599 items[#items+1] = supply
6600 -- Check if item is in trade window (if open)
6601 if xeno.shopGetItemBuyPriceByID(itemid) > 0 then
6602 greetNPC = false
6603 end
6604 end
6605 end
6606 end
6607
6608 if greetNPC then
6609 talk({'hi'}, function()
6610 -- Try to sell flasks if we may be at the magic shop
6611 if group == 'Potions' then
6612 talk({'trade'}, function()
6613 shopSellFlasks(function()
6614 debug('shopBuySupplies: buyListItem(1) [greet, potions]')
6615 buyListItem(1)
6616 end)
6617 end)
6618 else
6619 talk({'trade'}, function()
6620 debug('shopBuySupplies: buyListItem(1) [greet]')
6621 buyListItem(1)
6622 end)
6623 end
6624 end)
6625 else
6626 -- Try to sell flasks if we may be at the magic shop
6627 if group == 'Potions' then
6628 shopSellFlasks(function()
6629 debug('shopBuySupplies: buyListItem(1) [potions]')
6630 buyListItem(1)
6631 end)
6632 else
6633 debug('shopBuySupplies: buyListItem(1)')
6634 buyListItem(1)
6635 end
6636 end
6637 end
6638
6639 local function shopBuyBackpacks(count, callback)
6640 talk({'hi', 'trade'}, function()
6641 setTimeout(function()
6642 local itemid = nil
6643 -- Find a backpack to buy from the NPC
6644 for id, _ in pairs(ITEM_LIST_BACKPACKS) do
6645 local price = xeno.shopGetItemBuyPriceByID(id)
6646 if price > 0 then
6647 itemid = id
6648 break
6649 end
6650 end
6651
6652 if not itemid then
6653 error('Unable to find a backpack to purchase. Please contact support.')
6654 return
6655 end
6656
6657 shopBuyItemUpToCount(itemid, count, 0, function()
6658 callback()
6659 end)
6660 end, pingDelay(DELAY.RANGE_TALK))
6661 end)
6662 end
6663
6664 -- Export global functions
6665 return {
6666 bankDepositGold = bankDepositGold,
6667 bankGetBalance = bankGetBalance,
6668 bankWithdrawGold = bankWithdrawGold,
6669 shopSellLoot = shopSellLoot,
6670 shopRefillSoftboots = shopRefillSoftboots,
6671 shopBuySupplies = shopBuySupplies,
6672 shopBuyBackpacks = shopBuyBackpacks
6673 }
6674end)()
6675Walker = (function()
6676
6677 -- Imports
6678 local debug = Core.debug
6679 local pingDelay = Core.pingDelay
6680 local setTimeout = Core.setTimeout
6681 local when = Core.when
6682 local split = Core.split
6683 local getPosFromString = Core.getPosFromString
6684 local cleanLabel = Core.cleanLabel
6685 local delayWalker = Core.delayWalker
6686 local resumeWalker = Core.resumeWalker
6687 local sortPositionsByDistance = Core.sortPositionsByDistance
6688 local getPositionFromDirection = Core.getPositionFromDirection
6689 local getDistanceBetween = Core.getDistanceBetween
6690 local getWalkableTiles = Core.getWalkableTiles
6691 local talk = Core.talk
6692 local log = Console.log
6693 local warn = Console.warn
6694 local error = Console.error
6695 local prompt = Console.prompt
6696 local bankWithdrawGold = Npc.bankWithdrawGold
6697 local getMoney = Container.getMoney
6698 local getTotalItemCount = Container.getTotalItemCount
6699
6700 local function walkerLabelExists(label)
6701 local waypoints = _settings['Walker']['WaypointList']
6702 for i = 1, #waypoints do
6703 if cleanLabel(waypoints[i].text) == label then
6704 return true
6705 end
6706 end
6707 return false
6708 end
6709
6710 local function walkerGetTownExit()
6711 if _script.townexit then
6712 return _script.townexit
6713 end
6714 local waypoints = _settings['Walker']['WaypointList']
6715 for i = 1, #waypoints do
6716 if string.find(waypoints[i].text, '~spawn:') then
6717 local label = cleanLabel(waypoints[i].text)
6718 _script.townexit = split(split(label, '|')[2], '~')[1]
6719 return
6720 end
6721 end
6722 end
6723
6724 local function walkerGetTownEntrance()
6725 if _script.townentrance then
6726 return _script.townentrance
6727 end
6728 local waypoints = _settings['Walker']['WaypointList']
6729 for i = 1, #waypoints do
6730 if string.find(waypoints[i].text, 'spawn~') then
6731 local label = cleanLabel(waypoints[i].text)
6732 _script.townentrance = split(split(label, '|')[2], '~')[2]
6733 return
6734 end
6735 end
6736 end
6737
6738 local function walkerStartPath(town, source, destination, callback)
6739 -- DISABLED, we should have depot~depot and similar paths.
6740 -- Already at destination, return success
6741 --if source == destination then
6742 -- callback()
6743 -- return
6744 --end
6745
6746 local function walkTo(label, walkCallback)
6747 xeno.gotoLabel(label)
6748 resumeWalker()
6749 when(EVENT_PATH_END, nil, function()
6750 debug('Reached destination: ' .. destination)
6751 _script.lastDestination = {name=destination, town=town}
6752 walkCallback()
6753 end)
6754 end
6755
6756 -- Label doesn't exist, try walking to depot, then destination from there
6757 local label = string.lower(town) .. '|' .. source .. '~' .. destination
6758 if not walkerLabelExists(label) then
6759 local depotLabel = string.lower(town) .. '|' .. source .. '~depot'
6760
6761 -- Depot doesn't exist, error out, this should never happen (path missing)
6762 if not walkerLabelExists(depotLabel) then
6763 -- Check if we're starting the script in spawn
6764 if source == 'spawn' then
6765 error('Unable to walk to ' .. destination .. ' from here. Please start the script near ' .. town .. '.')
6766 else
6767 error('Missing route: ' .. town .. ' ' .. source .. ' to ' .. destination .. '. Please contact support.')
6768 end
6769 return
6770 end
6771
6772 -- Depot path exist, take detour
6773 walkTo(depotLabel, function()
6774 -- Walk from depot to destination
6775 walkTo(string.lower(town) .. '|depot~' .. destination, callback)
6776 end)
6777 return
6778 end
6779
6780 -- Path exists, take direct path
6781 walkTo(label, callback)
6782 end
6783
6784 local function walkerGetPosAfterLabel(label, index)
6785 local waypoints = _settings['Walker']['WaypointList']
6786 local foundLabel = index and true or false
6787 local startIndex = index or 1
6788 for i = startIndex, #waypoints do
6789 waypoint = waypoints[i]
6790 -- Find the starting position if we haven't already
6791 if not foundLabel and cleanLabel(waypoint.text) == label then
6792 foundLabel = true
6793 end
6794 -- Find the related stand position after we find the label
6795 if foundLabel and tonumber(waypoint.tag) < 10 then
6796 local pos = getPosFromString(waypoint.text)
6797 return pos.x > 0 and pos or false
6798 end
6799 end
6800 return false
6801 end
6802
6803 local function walkerGetClosestLabel(multiFloor, prefix)
6804 local waypoints = _settings['Walker']['WaypointList']
6805 local positions = {}
6806 local selfPos = xeno.getSelfPosition()
6807 for i = 1, #waypoints do
6808 if waypoints[i].tag == '255' then
6809 local label = cleanLabel(waypoints[i].text)
6810 if not string.find(label, 'spawn~') then
6811 -- Prefix filter
6812 if not prefix or string.lower(split(label, '|')[1]) == string.lower(prefix) then
6813 -- Floor filter
6814 local pos = walkerGetPosAfterLabel(nil, i)
6815 if pos and (multiFloor or selfPos.z == pos.z) then
6816 pos.name = label
6817 table.insert(positions, pos)
6818 end
6819 end
6820 end
6821 end
6822 end
6823 positions = sortPositionsByDistance(selfPos, positions, 10)
6824 for i = 1, 5 do
6825 local spos = positions[i]
6826 if not spos then
6827 break
6828 end
6829 debug('Closest label #' .. i .. ' :' .. table.serialize(positions[i]))
6830 end
6831 return positions[1], positions
6832 end
6833
6834 local function walkerVerifyPosition(label, option)
6835 local pos = walkerGetPosAfterLabel(label)
6836 local selfPos = xeno.getSelfPosition()
6837 local distance = getDistanceBetween(selfPos, pos)
6838 if pos then
6839 -- no option specified, compare exact position
6840 if not option and distance < 1 and selfPos.z == pos.z then
6841 return true
6842 -- option (=) specified, check for higher z axis.
6843 elseif option == '=' and selfPos.z == pos.z then
6844 return true
6845 -- option (-) specified, check for higher z axis.
6846 elseif option == '-' and selfPos.z > pos.z then
6847 return true
6848 -- option (+) specified, check for lower z axis.
6849 elseif option == '+' and selfPos.z < pos.z then
6850 return true
6851 -- other long option specified, probably range.
6852 elseif option and #option > 1 then
6853 local operator = string.sub(option, 1, 1)
6854 local value = tonumber(string.sub(option, 2))
6855 -- if value is a number
6856 if value then
6857 local meetsRange = false
6858 if operator == '>' then
6859 meetsRange = (distance > value)
6860 elseif operator == '<' then
6861 meetsRange = (distance < value)
6862 elseif operator == '=' then
6863 meetsRange = (distance == value)
6864 end
6865 if meetsRange then
6866 return true
6867 end
6868 end
6869 end
6870 end
6871
6872 warn('Failed label ' .. label .. ', trying again.')
6873 xeno.gotoLabel(label)
6874 return false
6875 end
6876
6877 local function walkerGetNeededTools()
6878 local tools = {pick=false, shovel=false, rope=false}
6879 local needsTools = false
6880 local waypoints = _settings['Walker']['WaypointList']
6881 for i = 1, #waypoints do
6882 if not tools.rope and waypoints[i].tag == '3' then
6883 tools.rope = true
6884 needsTools = true
6885 elseif not tools.shovel and waypoints[i].tag == '4' then
6886 tools.shovel = true
6887 needsTools = true
6888 elseif not tools.pick and waypoints[i].tag == '255' and waypoints[i].tag == '255' and split(waypoints[i].text, '|')[1] == 'pick' then
6889 tools.pick = true
6890 needsTools = true
6891 end
6892 end
6893 return needsTools and tools or false
6894 end
6895
6896 local function walkToPos(x, y, z)
6897 _script.unsafeQueue = 0
6898 xeno.doSelfWalkTo(x, y, z)
6899 end
6900
6901 local function walkerReachNPC(name, callback, tries)
6902 tries = tries ~= nil and tries or 5
6903 local targetIndex = nil
6904 local targetPos = nil
6905 local selfPos = xeno.getSelfPosition()
6906
6907 -- Loop through battle list
6908 xeno.enterCriticalMode()
6909 for i = 0, 1300 do
6910 local creaturePos = xeno.getCreaturePosition(i)
6911 local distance = getDistanceBetween(selfPos, creaturePos)
6912 -- NPC we are looking for
6913 if string.lower(xeno.getCreatureName(i)) == string.lower(name) and distance < 7 then
6914 -- We need to walk to it
6915 if distance > 2 then
6916 targetIndex = i
6917 targetPos = creaturePos
6918 break
6919 -- We are close enough
6920 else
6921 xeno.exitCriticalMode()
6922 callback()
6923 return
6924 end
6925 end
6926 end
6927 xeno.exitCriticalMode()
6928
6929 -- Failed to find NPC
6930 if not targetPos then
6931 error('Unable to find ' .. name .. '. Please contact support.')
6932 return
6933 end
6934
6935 -- Walk to NPC
6936 local tiles = getWalkableTiles(targetPos, 2)
6937
6938 -- Failed to find walkable tile
6939 if not tiles then
6940 error('Unable to reach ' .. name .. '. Please contact support.')
6941 return
6942 end
6943
6944 -- Find closest tile
6945 positions = sortPositionsByDistance(selfPos, tiles, 10)
6946
6947 -- Walk to the closest tile
6948 local destination = positions[1]
6949 walkToPos(destination.x, destination.y, destination.z)
6950
6951 -- Wait and see if we reached the NPC
6952 setTimeout(function()
6953 -- In range, finish with callback
6954 if getDistanceBetween(xeno.getSelfPosition(), xeno.getCreaturePosition(targetIndex)) <= 2 then
6955 callback()
6956 elseif tries <= 0 then
6957 error('Unable to reach ' .. name .. '. Please contact support.')
6958 else
6959 walkerReachNPC(name, callback, tries - 1)
6960 end
6961 end, pingDelay(DELAY.FOLLOW_WAIT))
6962 end
6963
6964 local function walkerTravel(route, callback)
6965 local travelInfo = TRAVEL_ROUTES[route]
6966 local travelCost = travelInfo.cost
6967 local travelMethod = travelInfo.route or 'boat'
6968 -- Loop through all spectators on screen
6969 local spectators = {xeno.getCreatureSpectators(0)}
6970 -- Break when/if we find an npc with a transcript
6971 for _, listIndex in ipairs(spectators) do
6972 local name = xeno.getCreatureName(listIndex)
6973 local transcript = travelInfo.transcript[name]
6974 -- Found transcript
6975 if transcript then
6976 -- Follow NPC
6977 walkerReachNPC(name, function()
6978 -- Start talking
6979 talk(transcript, function()
6980 callback()
6981 end)
6982 end)
6983 break
6984 end
6985 end
6986 end
6987
6988 local function walkerGotoTown(targetTown, callback)
6989 local townPositions = sortPositionsByDistance(xeno.getSelfPosition(), TOWN_POSITIONS)
6990 local town = townPositions[1].name:lower()
6991 targetTown = targetTown:lower()
6992
6993 local selfPos = xeno.getSelfPosition()
6994 local lastDest = _script.lastDestination
6995 local lastDestLabel = lastDest and ('%s|%s~depot'):format(lastDest.town, lastDest.name)
6996 local lastDestPos = lastDestLabel and walkerGetPosAfterLabel(lastDestLabel)
6997 local labelList = nil
6998 local closestLabel = nil
6999
7000 -- Close to last known destination
7001 if lastDestPos and getDistanceBetween(selfPos, lastDestPos) <= 30 then
7002 closestLabel = lastDestPos
7003 closestLabel.name = lastDestLabel
7004 -- Find out where we are in town
7005 else
7006 -- Update label, override if we find depot is close
7007 closestLabel, labelList = walkerGetClosestLabel(true, town, 10)
7008 for i = 1, #labelList do
7009 local tmpLabel = labelList[i]
7010 if tmpLabel.name == ('%s|depot~depot'):format(town) then
7011 if getDistanceBetween(selfPos, tmpLabel) <= 20 then
7012 closestLabel = tmpLabel
7013 break
7014 end
7015 end
7016 end
7017 end
7018
7019 -- In town, return success
7020 if (town == targetTown) then
7021 callback(closestLabel)
7022 return
7023 end
7024
7025 -- Out of town, detect travel costs
7026 local route = town .. '~' .. targetTown
7027 local travelInfo = TRAVEL_ROUTES[route]
7028
7029 -- Travel path doesn't exist, prompt user to walk manually
7030 if not travelInfo then
7031 local function travelPrompt()
7032 prompt('Unable to travel to ' .. _script.town .. '. When you are in town type "retry" to continue.', function(response)
7033 if string.find(response, 'retry') then
7034 walkerGotoTown(targetTown, callback)
7035 else
7036 travelPrompt()
7037 end
7038 end)
7039 end
7040 travelPrompt()
7041 return
7042 end
7043
7044 local travelCost = travelInfo.cost
7045 local travelMethod = travelInfo.route or 'boat'
7046
7047 -- When we have travel money
7048 local function gotoBoat(startLocation)
7049 -- Walk to boat
7050 -- Wait for us to get to the boat
7051 walkerStartPath(town, startLocation, travelMethod, function(path)
7052 walkerTravel(route, function()
7053 -- Get closest town, fire function again if we're still not in the correct town
7054 -- some towns take multiple routes to reach
7055 local newTownPositions = sortPositionsByDistance(xeno.getSelfPosition(), TOWN_POSITIONS)
7056 local newTown = newTownPositions[1].name:lower()
7057 -- Not in the new town yet, recurse
7058 if newTown ~= targetTown then
7059 walkerGotoTown(targetTown, callback)
7060 -- Didn't leave the last town
7061 elseif newTown == town then
7062 error('Failed to travel to ' .. targetTown .. ' from ' .. newTown .. '.')
7063 return
7064 -- Reached the target town
7065 else
7066 callback({name=('%s|%s~depot'):format(town, travelMethod)})
7067 end
7068 end)
7069 end)
7070 end
7071
7072 -- Close label not found or too far away
7073 if not closestLabel or getDistanceBetween(selfPos, closestLabel) > 30 then
7074 error('Too far from any start point. Restart the script closer to a town.')
7075 return
7076 end
7077
7078 local closestRoute = split(closestLabel.name, '|')
7079 local closestPath = split(closestRoute[2], '~')
7080 local location = closestPath[1]
7081 local destination = closestPath[2]
7082
7083 debug('Closest location: ' .. location)
7084 debug('Recent location: ' .. tostring(lastDestLabel))
7085
7086 -- Tell the user what we're doing
7087 local locationName = location:gsub('%.', ' ');
7088 log('You are near the ' .. town .. ' ' .. locationName .. ', traveling to ' .. targetTown:lower() .. '.')
7089
7090 -- Needs gold to travel, go to bank from here
7091 local travelCostDifference = travelCost - getMoney()
7092 if travelCostDifference > 0 then
7093 -- Tell the user we need to go to the bank
7094 local bankMessage = 'Traveling to ' .. targetTown:lower() .. ' requires an additional ' .. travelCostDifference .. ' gold.'
7095 if location ~= 'bank' then
7096 bankMessage = bankMessage .. ' Heading to the ' .. town .. ' bank.'
7097 end
7098 log(bankMessage)
7099
7100 -- Go to local bank
7101 -- Wait for us to get to the bank
7102 walkerStartPath(town, location, 'bank', function()
7103 -- Withdraw travel funds (round up to nearest hundred)
7104 local roundedCost = math.ceil(travelCostDifference / 100) * 100
7105 bankWithdrawGold(roundedCost, function()
7106 gotoBoat('bank')
7107 end)
7108 end)
7109 return
7110 end
7111
7112 -- Has travel money, head to boat
7113 -- Go to travel label once at boat
7114 -- If no path from here to boat / bank, go to depot first
7115 gotoBoat(location)
7116 end
7117
7118 local function walkerGotoLocation(town, destination, callback)
7119 -- Make sure we're in the right town
7120 walkerGotoTown(town, function(closestLabel)
7121 local closestRoute = split(closestLabel.name, '|')
7122 local closestPath = split(closestRoute[2], '~')
7123 local start = closestPath[1]
7124 -- Walk to path
7125 walkerStartPath(town, start, destination, function(path)
7126 callback(path)
7127 end)
7128 end)
7129 end
7130
7131 local function walkerGotoDepot(callback)
7132 -- Go to the first town depot
7133 xeno.gotoLabel('depot|' .. _script.town .. '|1')
7134 -- Tell if-stuck monitor to try next depot if we get stuck
7135 _script.findingDepot = 1
7136 resumeWalker()
7137 when(EVENT_DEPOT_END, nil, function()
7138 -- Do not try anymore depots if-stuck
7139 _script.findingDepot = nil
7140 callback()
7141 end)
7142 end
7143
7144 local function walkerGetDoorDetails(doorID)
7145 -- Cached details
7146 if _script.doors[doorID] then
7147 return _script.doors[doorID]
7148 end
7149
7150 -- Determine door details
7151 -- get position of the stand before the label
7152 local doorPos = nil
7153 local posLabel = nil
7154 local direction = nil
7155 local startLabel = 'door|'..doorID..'|START'
7156 local standPos = walkerGetPosAfterLabel(startLabel)
7157 local directions = {['NORTH']=NORTH, ['SOUTH']=SOUTH, ['EAST']=EAST, ['WEST']=WEST}
7158 for dirName, dirCode in pairs(directions) do
7159 -- Look for the corresponding door label
7160 posLabel = 'door|'..doorID..'|'..dirName
7161 if walkerLabelExists(posLabel) then
7162 -- Determine position of the door
7163 doorPos = getPositionFromDirection(standPos, dirCode, 1)
7164 direction = dirCode
7165 break
7166 end
7167 end
7168
7169 if not doorPos then
7170 return error('Missing door position for Door #' .. doorID ..'. Please contact support.')
7171 end
7172
7173 _script.doors[doorID] = {
7174 doorPos = doorPos,
7175 standPos = standPos,
7176 posLabel = posLabel,
7177 startLabel = startLabel,
7178 direction = direction
7179 }
7180
7181 return _script.doors[doorID]
7182 end
7183
7184 local function walkerUseTrainer()
7185 local skill = _config['Logout']['Train-Skill']
7186 local statues = {
7187 sword = 16198,
7188 axe = 16199,
7189 club = 16200,
7190 spear = 16201,
7191 magic = 16202
7192 }
7193
7194 local itemid = statues[skill]
7195
7196 -- Skill statue does not exist
7197 if not itemid then
7198 error('Invalid skill type, valid options: sword, axe, club, spear, magic.')
7199 return
7200 end
7201
7202 -- Find statue on map
7203 local pos = xeno.getSelfPosition()
7204 for x = -7, 7 do
7205 for y = -7, 7 do
7206 local posX = pos.x + x
7207 local posY = pos.y + y
7208 if xeno.getTileUseID(posX, posY, pos.z).id == itemid then
7209 xeno.selfUseItemFromGround(posX, posY, pos.z)
7210 return
7211 end
7212 end
7213 end
7214
7215 error('Unable to locate the "'.. skill ..'" training statue.')
7216 end
7217
7218 local function walkerUseClosestItem(itemIdList)
7219 local pos = xeno.getSelfPosition()
7220 local tiles = {}
7221 for x = -7, 7 do
7222 for y = -7, 7 do
7223 local posX = pos.x + x
7224 local posY = pos.y + y
7225 local item = xeno.getTileUseID(posX, posY, pos.z)
7226 if item and itemIdList[item.id] then
7227 tiles[#tiles+1] = {x=posX, y=posY, z=pos.z}
7228 end
7229 end
7230 end
7231
7232 if #tiles == 0 then
7233 return nil
7234 end
7235
7236 local items = sortPositionsByDistance(pos, tiles)
7237 local target = items[1]
7238 return xeno.selfUseItemFromGround(target.x, target.y, target.z), target
7239 end
7240
7241 local function walkerCapacityDrop()
7242 if not _config['Capacity'] then
7243 return
7244 end
7245 local flaskDropCap = _config['Capacity']['Drop-Flasks']
7246 local goldDropCap = _config['Capacity']['Drop-Gold']
7247 if (goldDropCap and goldDropCap > 0) or (flaskDropCap and flaskDropCap > 0) then
7248 local cap = xeno.getSelfCap()
7249 local pos = xeno.getSelfPosition()
7250
7251 -- Get random position
7252 local tiles = getWalkableTiles(pos, 3)
7253 if not tiles or #tiles < 1 then
7254 tiles = {pos}
7255 end
7256
7257 local destination = tiles[math.random(1, #tiles)]
7258
7259 -- Check if we need to drop flasks
7260 if flaskDropCap and flaskDropCap > 0 and cap <= flaskDropCap then
7261 local flasks = ITEM_LIST_FLASKS
7262 -- Potion Backpack
7263 for spot = 0, xeno.getContainerItemCount(_backpacks['Potions']) - 1 do
7264 local item = xeno.getContainerSpotData(_backpacks['Potions'], spot)
7265 -- Item is a flask
7266 if flasks[item.id] then
7267 -- Drop this stack of flasks
7268 xeno.containerMoveItemToGround(_backpacks['Potions'], spot, destination.x, destination.y, destination.z, -1)
7269 setTimeout(function()
7270 walkerCapacityDrop()
7271 end, pingDelay(DELAY.CONTAINER_MOVE_ITEM))
7272 return
7273 end
7274 end
7275 -- Main Backpack (if different than Potion)
7276 if _backpacks['Potions'] ~= _backpacks['Main'] then
7277 for spot = 0, xeno.getContainerItemCount(_backpacks['Main']) - 1 do
7278 local item = xeno.getContainerSpotData(_backpacks['Main'], spot)
7279 -- Item is a flask
7280 if flasks[item.id] then
7281 -- Drop this stack of flasks
7282 xeno.containerMoveItemToGround(_backpacks['Main'], spot, destination.x, destination.y, destination.z, -1)
7283 setTimeout(function()
7284 walkerCapacityDrop()
7285 end, pingDelay(DELAY.CONTAINER_MOVE_ITEM))
7286 return
7287 end
7288 end
7289 end
7290 end
7291
7292 -- Check if we need to drop gold
7293 if goldDropCap and goldDropCap > 0 and cap <= goldDropCap then
7294 for spot = 0, xeno.getContainerItemCount(_backpacks['Gold']) - 1 do
7295 local item = xeno.getContainerSpotData(_backpacks['Gold'], spot)
7296 -- Item is gold
7297 if item.id == 3031 then
7298 -- Drop this stack of gold
7299 xeno.containerMoveItemToGround(_backpacks['Gold'], spot, destination.x, destination.y, destination.z, -1)
7300 setTimeout(function()
7301 walkerCapacityDrop()
7302 end, pingDelay(DELAY.CONTAINER_MOVE_ITEM))
7303 return
7304 end
7305 end
7306 -- Main Backpack (if different than Gold)
7307 if _backpacks['Gold'] ~= _backpacks['Main'] then
7308 for spot = 0, xeno.getContainerItemCount(_backpacks['Main']) - 1 do
7309 local item = xeno.getContainerSpotData(_backpacks['Main'], spot)
7310 -- Item is gold
7311 if item.id == 3031 then
7312 -- Drop this stack of gold
7313 xeno.containerMoveItemToGround(_backpacks['Main'], spot, destination.x, destination.y, destination.z, -1)
7314 setTimeout(function()
7315 walkerCapacityDrop()
7316 end, pingDelay(DELAY.CONTAINER_MOVE_ITEM))
7317 return
7318 end
7319 end
7320 end
7321 end
7322 end
7323 end
7324
7325 local function walkerRestoreMana(potionid, manaRestorePercent, callback)
7326 local selfid = xeno.getSelfID()
7327 local function pump()
7328 if getTotalItemCount(potionid) == 0 then
7329 return
7330 end
7331
7332 -- Stop if creature onscreen
7333 local pos = xeno.getSelfPosition()
7334 for i = CREATURES_LOW, CREATURES_HIGH do
7335 local cpos = xeno.getCreaturePosition(i)
7336 if cpos and pos.z == cpos.z and getDistanceBetween(pos, cpos) <= _config['Mana Restorer']['Range'] then
7337 if xeno.getCreatureVisible(i) and xeno.getCreatureHealthPercent(i) > 0 and xeno.isCreatureMonster(i) then
7338 callback()
7339 return
7340 end
7341 end
7342 end
7343 -- Stop if we're above threshold
7344 if math.abs((xeno.getSelfMana() / xeno.getSelfMaxMana()) * 100) >= manaRestorePercent then
7345 callback()
7346 return
7347 end
7348 -- Use potion
7349 xeno.selfUseItemWithCreature(potionid, selfid)
7350 -- Recurse
7351 setTimeout(function()
7352 pump()
7353 end, 100)
7354 end
7355 pump()
7356 end
7357
7358 -- Export global functions
7359 return {
7360 walkerLabelExists = walkerLabelExists,
7361 walkerGetTownExit = walkerGetTownExit,
7362 walkerGetTownEntrance = walkerGetTownEntrance,
7363 walkerStartPath = walkerStartPath,
7364 walkerGetPosAfterLabel = walkerGetPosAfterLabel,
7365 walkerGetClosestLabel = walkerGetClosestLabel,
7366 walkerGetNeededTools = walkerGetNeededTools,
7367 walkerReachNPC = walkerReachNPC,
7368 walkerGotoLocation = walkerGotoLocation,
7369 walkerGotoDepot = walkerGotoDepot,
7370 walkerGetDoorDetails = walkerGetDoorDetails,
7371 walkerUseTrainer = walkerUseTrainer,
7372 walkerUseClosestItem = walkerUseClosestItem,
7373 walkerCapacityDrop = walkerCapacityDrop,
7374 walkerRestoreMana = walkerRestoreMana,
7375 walkerTravel = walkerTravel
7376 }
7377end)()
7378Depot = (function()
7379
7380 -- Imports
7381 local pingDelay = Core.pingDelay
7382 local setTimeout = Core.setTimeout
7383 local getWalkableTiles = Core.getWalkableTiles
7384 local getDirectionTo = Core.getDirectionTo
7385 local getSelfLookPosition = Core.getSelfLookPosition
7386 local getPositionFromDirection = Core.getPositionFromDirection
7387 local formatList = Core.formatList
7388 local flattenItemCounts = Core.flattenItemCounts
7389 local warn = Console.warn
7390 local error = Console.error
7391 local log = Console.log
7392 local getLastContainer = Container.getLastContainer
7393 local getContainerByName = Container.getContainerByName
7394 local whenContainerUpdates = Container.whenContainerUpdates
7395 local containerMoveItems = Container.containerMoveItems
7396 local resetContainers = Container.resetContainers
7397 local walkerGotoDepot = Walker.walkerGotoDepot
7398 local walkerStartPath = Walker.walkerStartPath
7399 local bankWithdrawGold = Npc.bankWithdrawGold
7400 local shopBuyBackpacks = Npc.shopBuyBackpacks
7401
7402 local function openLocker(callback)
7403 -- Immediately callback if Locker is already open
7404 local locker = getContainerByName(CONT_NAME_LOCKER)
7405 if locker then
7406 callback(locker)
7407 return
7408 end
7409
7410 -- Depot position (defaults to look pos)
7411 local depot = getSelfLookPosition(1)
7412
7413 -- Detect entry, find depot tile from opposite direction
7414 local pos = xeno.getSelfPosition()
7415 local tiles = getWalkableTiles(pos, 1)
7416 if tiles and tiles[1] then
7417 local entryPos = tiles[1]
7418 depot = getPositionFromDirection({x=pos.x, y=pos.y}, getDirectionTo(entryPos, pos), 1)
7419 end
7420 -- Browse locker field
7421 xeno.selfBrowseField(depot.x, depot.y, pos.z)
7422 -- Wait for Browsefield window
7423 setTimeout(function()
7424 -- Browsefield window
7425 local browsefield = getLastContainer()
7426 -- Open Locker in same window (first slot)
7427 xeno.containerUseItem(browsefield, 0, true, true)
7428 -- Wait for Locker window
7429 whenContainerUpdates(browsefield, function(success)
7430 if not success then
7431 openLocker(callback)
7432 return
7433 end
7434
7435 -- Callback with container list index
7436 locker = getContainerByName(CONT_NAME_LOCKER)
7437 callback(locker)
7438 end)
7439 end, pingDelay(DELAY.BROWSE_FIELD))
7440 end
7441
7442 local function openDepot(callback)
7443 -- Immediately callback if Depot is already open
7444 local depot = getContainerByName(CONT_NAME_DEPOT)
7445 if depot then
7446 callback(depot)
7447 return
7448 end
7449
7450 -- Open Locker first
7451 openLocker(function(locker)
7452 -- Unable to open locker
7453 if not locker then
7454 callback(false)
7455 return
7456 end
7457
7458 -- Open Depot in same window (first slot)
7459 xeno.containerUseItem(locker, 0, true, true)
7460 -- Wait for Depot window
7461 whenContainerUpdates(locker, function(success)
7462 if not success then
7463 openDepot(callback)
7464 return
7465 end
7466
7467 -- Callback with container list index
7468 depot = getContainerByName(CONT_NAME_DEPOT)
7469 callback(depot)
7470 end)
7471 end)
7472 end
7473
7474 local function transferToDepot(depot, slot, callback)
7475 containerMoveItems({
7476 src = _backpacks['Loot'],
7477 dest = depot,
7478 slot = slot,
7479 openwindow = false,
7480 ignore = {
7481 -- Flasks
7482 [283] = true,
7483 [284] = true,
7484 [285] = true,
7485 -- Money
7486 [3031] = true,
7487 [3035] = true,
7488 [3043] = true,
7489 -- Tools
7490 [ITEMID.OBSIDIAN_KNIFE] = true,
7491 [ITEMID.BLESSED_STAKE] = true
7492 },
7493 filter = function(item)
7494 local supplyItem = _supplies[item.id]
7495 if slot == DEPOT.SLOT_STACK and xeno.isItemStackable(item.id) and not supplyItem then
7496 return true
7497 elseif slot == DEPOT.SLOT_NONSTACK and not xeno.isItemStackable(item.id) and not supplyItem then
7498 return true
7499 elseif slot == DEPOT.SLOT_SUPPLY and supplyItem then
7500 return true
7501 end
7502 return false
7503 end
7504 }, function(success, moveCounts)
7505 local totalCount = 0
7506 if moveCounts then
7507 for itemid, count in pairs(moveCounts) do
7508 -- TODO: add to HUD looted
7509 totalCount = totalCount + count
7510 end
7511 end
7512 if totalCount > 0 then
7513 local moveList = formatList(flattenItemCounts(moveCounts), ', ', '')
7514 local lootType = slot == DEPOT.SLOT_STACK and 'stackables' or 'items'
7515 log('Deposited ' .. lootType .. ': ' .. moveList .. '.')
7516 end
7517 -- Warn player not all their loot was moved
7518 if not success then
7519 warn('Some loot was unable to be deposited. Make sure you have enough free slots in your depot backpacks.')
7520 end
7521 -- Return
7522 callback()
7523 end)
7524 end
7525
7526 local function transferFromDepot(depot, neededSupplies, callback)
7527 -- Detect items we need to withdraw, and backpacks to withdraw to
7528 local groups = {}
7529 local groupIndex = {}
7530 local mainBP = _backpacks['Main']
7531 local potionsBP = _backpacks['Potions']
7532 local runesBP = _backpacks['Runes']
7533 local ammoBP = _backpacks['Ammo']
7534 local suppliesBP = _backpacks['Supplies']
7535 local groupTransfers = 0
7536
7537 local suppliesUsed = false
7538 for itemid, supply in pairs(_supplies) do
7539 if supply.group == 'Ring'or supply.group == 'Amulet' or supply.group == 'Supplies' then
7540 if supply.max > 0 then
7541 suppliesUsed = true
7542 break
7543 end
7544 end
7545 end
7546
7547 for itemid, supply in pairs(neededSupplies) do
7548 if supply.group == 'Food' then
7549 if not groups[mainBP] then groups[mainBP] = {} end
7550 groups[mainBP][itemid] = supply.needed
7551 elseif supply.group == 'Potions' then
7552 if not groups[potionsBP] then groups[potionsBP] = {} end
7553 groups[potionsBP][itemid] = supply.needed
7554 elseif supply.group == 'Runes' then
7555 if not groups[runesBP] then groups[runesBP] = {} end
7556 groups[runesBP][itemid] = supply.needed
7557 elseif supply.group == 'Ammo' then
7558 if not groups[ammoBP] then groups[ammoBP] = {} end
7559 groups[ammoBP][itemid] = supply.needed
7560 elseif supply.group == 'Ring' or supply.group == 'Amulet' or supply.group == 'Supplies' then
7561 if not groups[suppliesBP] then groups[suppliesBP] = {} end
7562 groups[suppliesBP][itemid] = supply.needed
7563 end
7564 end
7565
7566 -- Turn associate list into something we can iterate easily
7567 for bp, _ in pairs(groups) do
7568 groupIndex[#groupIndex+1] = bp
7569 end
7570
7571 -- Move each group
7572 local function transferGroup(index)
7573 -- Lookup group backpack
7574 local backpack = groupIndex[index]
7575 -- No more groups, finished
7576 if not backpack then
7577 if not suppliesUsed and groupTransfers == 0 then
7578 _script.disableWithdraw = true
7579 log('No supplies found in depot (3rd slot). Disabling future withdrawal attempts.')
7580 end
7581 return callback()
7582 end
7583 -- Look group items
7584 local items = groups[backpack]
7585 -- Move group
7586 containerMoveItems({
7587 src = depot,
7588 dest = backpack,
7589 openwindow = false,
7590 items = items,
7591 }, function(success, moveCounts)
7592 -- If we didn't withdraw anything, disable withdrawing supplies in the future
7593 local totalCount = 0
7594 if moveCounts then
7595 for itemid, count in pairs(moveCounts) do
7596 -- TODO: add to HUD supplies
7597 totalCount = totalCount + count
7598 end
7599 end
7600 if totalCount > 0 then
7601 local moveList = formatList(flattenItemCounts(moveCounts), ', ', '')
7602 log('Withdrew ' .. moveList .. '.')
7603 groupTransfers = groupTransfers + 1
7604 end
7605 -- Warn player not all their loot was moved
7606 if not success then
7607 warn('Unable to withdraw all supplies. Make sure your character has slots and capacity.')
7608 end
7609 -- Next group, after short delay
7610 setTimeout(function()
7611 transferGroup(index+1)
7612 end, pingDelay(DELAY.CONTAINER_MOVE_ITEM))
7613 end)
7614 end
7615
7616 -- Open supply slot in depot
7617 xeno.containerUseItem(depot, DEPOT.SLOT_SUPPLY, true, true)
7618 setTimeout(function()
7619 -- Start transfer
7620 transferGroup(1)
7621 end, pingDelay(DELAY.CONTAINER_MOVE_ITEM))
7622 end
7623
7624 local function depositBackpacks(locker, count, callback)
7625 local slots = {}
7626 -- Find the slots to move from
7627 for spot = 0, xeno.getContainerItemCount(0) - 1 do
7628 local item = xeno.getContainerSpotData(0, spot)
7629 if xeno.isItemContainer(item.id) then
7630 local newIndex = #slots+1
7631 slots[newIndex] = spot
7632 if newIndex >= count then
7633 break
7634 end
7635 end
7636 end
7637
7638
7639 -- Sort greatest to least so we don't shift source slots
7640 table.sort(slots, function(a,b) return a > b end)
7641
7642 local function moveBp(index)
7643 local fromSlot = slots[index]
7644 -- No more containers to deposit, return
7645 if not fromSlot then
7646 callback()
7647 return
7648 end
7649
7650 -- Move backpack to depot slot in locker
7651 xeno.containerMoveItemToContainer(0, fromSlot, locker, 0, 1)
7652
7653 -- Delay and continue
7654 setTimeout(function()
7655 moveBp(index+1)
7656 end, pingDelay(DELAY.CONTAINER_MOVE_ITEM))
7657 end
7658
7659 moveBp(1)
7660 end
7661
7662 local function depotTransfer(depot, needDeposit, neededSupplies, callback)
7663
7664 -- Minimize depot container
7665 xeno.minimizeContainer(depot)
7666
7667 -- Set depot state
7668 _script.depotOpen = true
7669
7670 -- Check slots
7671 local missingBps = 0
7672 for spot = 0, 2 do
7673 local item = xeno.getContainerSpotData(depot, spot)
7674 if not item or not xeno.isItemContainer(item.id) then
7675 missingBps = missingBps + 1
7676 end
7677 end
7678
7679 -- Stackable and nonstackables required
7680 if missingBps > 0 then
7681 warn('Missing loot and/or stackable loot container in depot. Walking to the equipment shop.')
7682 local bpPrice = missingBps * PRICE.BACKPACK
7683 -- Walk to bank
7684 walkerStartPath(_script.town, 'depot', 'bank', function()
7685 -- Withdraw gold
7686 bankWithdrawGold(bpPrice, function()
7687 -- Walk to tool NPC
7688 walkerStartPath(_script.town, 'bank', 'tools', function()
7689 -- Buy backpacks
7690 shopBuyBackpacks(missingBps, function()
7691 -- Walk back to depot
7692 walkerStartPath(_script.town, 'tools', 'depot', function()
7693 walkerGotoDepot(function()
7694 openLocker(function(locker)
7695 depositBackpacks(locker, missingBps, function()
7696 -- Recurse this insane callstack
7697 openDepot(function(newDepot)
7698 depotTransfer(newDepot, needDeposit, neededSupplies, callback)
7699 end)
7700 end)
7701 end)
7702 end)
7703 end)
7704 end)
7705 end)
7706 end)
7707 end)
7708 return
7709 end
7710
7711 -- We need to deposit
7712 if needDeposit then
7713 -- Deposit stackables
7714 transferToDepot(depot, DEPOT.SLOT_STACK, function()
7715 -- Delay
7716 setTimeout(function()
7717 -- Deposit non-stackables
7718 transferToDepot(depot, DEPOT.SLOT_NONSTACK, function()
7719 -- Deposit supplies
7720 transferToDepot(depot, DEPOT.SLOT_SUPPLY, function()
7721 -- We need to withdraw, delay and withdraw
7722 if neededSupplies then
7723 setTimeout(function()
7724 -- Withdraw supplies
7725 transferFromDepot(depot, neededSupplies, function()
7726 -- Set depot state
7727 _script.depotOpen = false
7728 callback()
7729 end)
7730 end, DELAY.CONTAINER_MOVE_ITEM)
7731 -- Nothing left
7732 else
7733 -- Set depot state
7734 _script.depotOpen = false
7735 callback()
7736 end
7737 end)
7738 end)
7739 end, DELAY.CONTAINER_MOVE_ITEM)
7740 end)
7741 -- Only withdraw
7742 elseif neededSupplies then
7743 -- Withdraw supplies
7744 transferFromDepot(depot, neededSupplies, function()
7745 -- Set depot state
7746 _script.depotOpen = false
7747 callback()
7748 end)
7749 -- Nothing?
7750 else
7751 -- Set depot state
7752 _script.depotOpen = false
7753 callback()
7754 end
7755 end
7756
7757 local function startDepotTransfer(needDeposit, neededSupplies, callback)
7758 -- Walk to nearest depot
7759 walkerGotoDepot(function()
7760 resetContainers(function()
7761 -- Open Depot
7762 openDepot(function(depot)
7763 -- Depot not opened, keep looking
7764 if not depot then
7765 startDepotTransfer(needDeposit, neededSupplies, callback)
7766 return
7767 end
7768 depotTransfer(depot, needDeposit, neededSupplies, callback)
7769 end)
7770 end)
7771 end)
7772 end
7773
7774 -- Export global functions
7775 return {
7776 startDepotTransfer = startDepotTransfer
7777 }
7778end)()
7779Settings = (function()
7780
7781 -- Imports
7782 local titlecase = Core.titlecase
7783 local formatList = Core.formatList
7784 local getSelfName = Core.getSelfName
7785 local split = Core.split
7786 local trim = Core.trim
7787 local setTimeout = Core.setTimeout
7788 local warn = Console.warn
7789 local error = Console.error
7790 local prompt = Console.prompt
7791 local walkerGetNeededTools = Walker.walkerGetNeededTools
7792
7793 local function loadSettingsFile(callback)
7794 local file = io.input(FOLDER_SETTINGS_PATH .. _script.slug)
7795 local message = 'Make sure "'.._script.slug..'" is in the "Documents/XenoBot/Settings" folder. Type "retry" to continue.'
7796 local function promptXBST()
7797 prompt(message, function(response)
7798 if string.find(string.lower(response), 'retry') then
7799 loadSettingsFile(callback)
7800 else
7801 promptXBST()
7802 end
7803 end)
7804 end
7805
7806 -- File does not exist
7807 if not file then
7808 promptXBST()
7809 return
7810 end
7811
7812 -- Read whole file
7813 local xml = io.read("*all")
7814 io.close(file)
7815
7816 -- Could not read
7817 if not xml then
7818 promptXBST()
7819 return
7820 end
7821
7822 local tbl = {}
7823
7824 local parseAttributes = function(str)
7825 local attr = {}
7826 string.gsub(str, PATTERN.XML_ATTR, function (w, _, a)
7827 attr[w] = a
7828 end)
7829 return attr
7830 end
7831
7832 local index = 1
7833 local current = {panel=nil, control=nil}
7834 while true do
7835 local begin, final, closed, label, str, empty = string.find(xml, PATTERN.XML_TAG, index)
7836 if not begin then
7837 break
7838 end
7839
7840 local attr = parseAttributes(str)
7841
7842 -- Contains no inner value: <item name="ex" />
7843 if empty == '/' and tbl[current.panel] then
7844 local attr = parseAttributes(str)
7845 local target = (current.control ~= nil) and tbl[current.panel][current.control] or tbl[current.panel]
7846 if attr.name then
7847 target[attr.name] = attr.value
7848 else
7849 table.insert(target, attr)
7850 end
7851
7852 -- New element start with possible children
7853 elseif closed == '' and attr.name then
7854 if label == 'panel' then
7855 tbl[attr.name] = {}
7856 elseif label == 'control' and tbl[current.panel] then
7857 tbl[current.panel][attr.name] = {}
7858 end
7859 current[label] = attr.name
7860 else -- close tag: </end>
7861 current[label] = nil
7862 end
7863 index = final + 1
7864 end
7865
7866 _settings = tbl
7867
7868 callback()
7869
7870 return true
7871 end
7872
7873 local function setNeededTools(callback)
7874 local tools = walkerGetNeededTools()
7875 if tools then
7876 -- Loop through main backpack
7877 local mainbp = _backpacks['Main']
7878 for spot = 0, xeno.getContainerItemCount(mainbp) - 1 do
7879 local item = xeno.getContainerSpotData(mainbp, spot)
7880 -- Rope
7881 if tools.rope and ROPE_TOOLS[item.id] then
7882 -- Save tool id
7883 _script.rope = item.id
7884 _script.ropeCode = ROPE_TOOLS[item.id]
7885 -- Stay on the same spot if its a whacky tool
7886 if item.id == 9594 or item.id == 9596 or item.id == 9598 then
7887 _script.shovel = item.id
7888 _script.pick = item.id
7889 _script.shovelCode = SHOVEL_TOOLS[item.id]
7890 end
7891 -- No other tools needed to detect, break search
7892 if not tools.shovel and not tools.pick then
7893 break
7894 end
7895 -- Shovel
7896 elseif tools.shovel and SHOVEL_TOOLS[item.id] then
7897 -- Save tool id
7898 _script.shovel = item.id
7899 _script.shovelCode = SHOVEL_TOOLS[item.id]
7900 -- Stay on the same spot if its a whacky tool
7901 if item.id == 9594 or item.id == 9596 or item.id == 9598 then
7902 _script.shovel = item.id
7903 _script.pick = item.id
7904 _script.shovelCode = SHOVEL_TOOLS[item.id]
7905 end
7906 -- No other tools needed to detect, break search
7907 if not tools.rope and not tools.pick then
7908 break
7909 end
7910 -- Pick
7911 elseif tools.pick and PICK_TOOLS[item.id] then
7912 -- Save tool id
7913 _script.pick = item.id
7914 -- No other tools needed to detect, break search
7915 if not tools.rope and not tools.shovel then
7916 break
7917 end
7918 end
7919 end
7920
7921 -- Error if we didn't find a tool we needed
7922 local missingTools = {}
7923 for tool, needed in pairs(tools) do
7924 if needed then
7925 if (tool == 'shovel' and not _script.shovel) or
7926 (tool == 'rope' and not _script.rope) or
7927 (tool == 'pick' and not _script.pick) then
7928 -- Add tool to missing list
7929 missingTools[#missingTools+1] = tool
7930 end
7931 end
7932 end
7933 if #missingTools > 0 then
7934 local message = 'You need a ' .. formatList(missingTools) .. ' in your main backpack. Type "retry" to continue.'
7935 local function promptTools()
7936 prompt(message, function(response)
7937 if string.find(string.lower(response), 'retry') then
7938 setNeededTools(callback)
7939 else
7940 promptTools()
7941 end
7942 end)
7943 end
7944 promptTools()
7945 return
7946 end
7947 end
7948 callback(tools)
7949 end
7950
7951 local lureTemplate = '<panel name="Dynamic Lure"><control name="LureList"><item count="%d" prioMax="9" prioMin="%d" prioRaw="%d" until="%d" enabled="1" /></control><control name="AttackWhileLuring" value="%d"/></panel>'
7952 local function getDynamicLureXBST()
7953 local amount = _config['Lure']['Amount']
7954 local priority = _config['Lure']['MinPriority']
7955 local untilCount = _config['Lure']['Until'] or 0
7956 local attackWhileLure = _config['Lure']['AttackWhileLure'] and 1 or 0
7957 -- Validate config
7958 if not priority or priority > 9 or priority < 0 then
7959 error('[Config Error] Dynamic lure minimum priority is missing or invalid [0-9]!')
7960 return
7961 end
7962 -- Validate amounts
7963 if untilCount >= amount then
7964 error('[Config Error] Dynamic lure "until" cannot be greater than "amount"!')
7965 return
7966 end
7967
7968 local rawPrio = 100 + priority
7969 return lureTemplate:format(amount, priority, rawPrio, untilCount, attackWhileLure)
7970 end
7971
7972 local function getShooterXBST()
7973 local xbst = nil
7974 local shooterList = {}
7975 local maxMana = xeno.getSelfMaxMana()
7976
7977 -- Loop through supplies, add Runes and Spells to the list.
7978 for _, supply in pairs(_supplies) do
7979 -- Belongs to the correct section, and has shooter settings
7980 if supply.group == 'Spells' or supply.group == 'Runes' and supply.options then
7981 local supplyid = supply.id
7982
7983 -- Titlecase monsters incase the user didn't
7984 local targets = supply.options['Targets']
7985 if type(targets) == 'table' then
7986 for i = 1, #targets do
7987 if targets[i] then
7988 targets[i] = titlecase(targets[i])
7989 end
7990 end
7991 targets = table.concat(targets, ',')
7992 elseif targets then
7993 targets = titlecase(targets)
7994 end
7995
7996 -- Look up item details
7997 local details = MAGIC_SHOOTER_ITEM[supplyid]
7998 if details then
7999 if supply.group == 'Runes' or maxMana >= details.mana then
8000 -- Add new item
8001 shooterList[#shooterList+1] = {
8002 spell = supply.group == 'Spells' and supplyid or '',
8003 rune = supply.group == 'Runes' and supplyid or 0,
8004 srange = details.srange,
8005 type = details.type,
8006 mana = details.mana and math.ceil((details.mana / xeno.getSelfMaxMana()) * 100) or 0,
8007 creature = targets,
8008 minhp = supply.options['MinHP'],
8009 maxhp = supply.options['MaxHP'],
8010 count = supply.options['TargetMin'],
8011 utito = supply.options['Utito'],
8012 priority = supply.options['Priority']
8013 }
8014 -- If utito is enabled, make a shooter entry (copy spell conditions)
8015 if supply.options['Utito'] then
8016 shooterList[#shooterList+1] = {
8017 spell = 'utito tempo',
8018 rune = 0,
8019 srange = details.srange,
8020 type = 4,
8021 mana = 50,
8022 --mana = details.mana and math.ceil((details.mana / xeno.getSelfMaxMana()) * 100) or 0,
8023 creature = targets,
8024 minhp = supply.options['MinHP'],
8025 maxhp = supply.options['MaxHP'],
8026 count = supply.options['TargetMin'],
8027 priority = 0
8028 }
8029 end
8030 else
8031 warn('Could not add "'..supplyid..'" to the shooter. You do not have enough mana to cast this spell.')
8032 end
8033 else
8034 if supply.group == 'Runes' then
8035 warn('Could not add "'..xeno.getItemNameByID(supplyid)..'" to the shooter. If this is a valid attack rune, please contact support.')
8036 else
8037 warn('Could not add "'..supplyid..'" to the shooter. If this is a valid attack spell, please contact support.')
8038 end
8039 end
8040 end
8041 end
8042
8043 if #shooterList > 0 then
8044 xbst = '<panel name="Spell Shooter"><control name="shooterList">'
8045 -- Sort shooter list by priority
8046 table.sort(shooterList, function(a, b)
8047 return a.priority < b.priority
8048 end)
8049 -- Add each list item
8050 for i = 1, #shooterList do
8051 local item = shooterList[i]
8052 xbst = xbst .. string.format(
8053 '<item spell="%s" rune="%d" srange="%s" type="%s" reason="0" minhp="%d" maxhp="%d" mana="%d" count="%d" creature="%s" danger="1" targ="1" enabled="1"/>',
8054 item.spell, item.rune, item.srange, item.type, item.minhp or 0, item.maxhp or 0, item.mana or 0, item.count or 0, item.creature
8055 )
8056 end
8057 xbst = xbst .. '</control></panel>'
8058 end
8059 return xbst
8060 end
8061
8062 local function setDynamicSettings(callback)
8063 -- Detect needed tools
8064 setNeededTools(function(tools)
8065 local xbstContents = ''
8066
8067 -- Walker Options
8068 if _script.ropeCode > 0 or _script.shovelCode > 0 then
8069 xbstContents = string.format([[<panel name="Walker Options"><control name="ropeOption" value="%d"/><control name="shovelOption" value="%d"/></panel>]], _script.ropeCode, _script.shovelCode)
8070 end
8071
8072 -- Detect ammunition or distance weapons
8073 local disWeaponID = 0
8074 local disAmmoID = 0
8075 for itemid, supply in pairs(_supplies) do
8076 if supply.group == 'Ammo' then
8077 if DISTANCE_WEAPONS[itemid] then
8078 disWeaponID = itemid
8079 else
8080 disAmmoID = itemid
8081 end
8082 end
8083 end
8084
8085 -- Equipment Manager
8086 if disWeaponID > 0 or disAmmoID > 0 then
8087 if not xbstContents then
8088 xbstContents = ''
8089 end
8090 xbstContents = xbstContents .. string.format([[<panel name="Equipment Manager"><control name="AmmoRefillID" value="%d"/><control name="AmmoRefillEnable" value="%d"/><control name="WeaponRefillID" value="%d"/><control name="WeaponRefillEnable" value="%d"/></panel>]], disAmmoID, disAmmoID > 0 and 1 or 0, disWeaponID, disWeaponID > 0 and 1 or 0)
8091 end
8092
8093 -- Looter
8094 local lootStyle = _config['Loot']['Loot-Style'] == 'first' and '0' or '1'
8095 local unlisted = _script.name:find('Thais Paw Collector') and '0' or '1'
8096 local lootXML = '<panel name="Looter"><control name="LootList" mode="' .. lootStyle .. '" minimum="0" maximum="0" skinner="2" unlisted="' .. unlisted .. '">'
8097
8098 -- Demonic Blood use/loot
8099 lootXML = lootXML .. '<item ID="6558" action="20"/><item ID="237" action="1"/><item ID="236" action="1"/>'
8100
8101 -- Only add gold if enabled
8102 if _config['Loot']['Loot-Gold'] then
8103 lootXML = lootXML .. '<item ID="3031" action="' .. _backpacks['Gold'] .. '"/>'
8104 end
8105
8106 -- Always add platinum coins to gold backpack (even if gold bp==main)
8107 lootXML = lootXML .. '<item ID="3035" action="' .. _backpacks['Gold'] .. '"/>'
8108
8109 -- Loop through targetlist, get items to add to looter
8110 local targeting = _settings['Targeting']
8111 if targeting then
8112 local targets = targeting['TargetingList']
8113 if targets then
8114 local lootList = {}
8115 for i = 1, #targets do
8116 local creatures = split(targets[i].type, ',')
8117 for k = 1, #creatures do
8118 local creature = trim(creatures[k])
8119 local monsterLoot = MONSTER_LOOT[creature]
8120 if monsterLoot then
8121 for j = 1, #monsterLoot do
8122 lootList[monsterLoot[j]] = true
8123 end
8124 end
8125 end
8126 end
8127 -- Add loot items to looter
8128 local minValue = _config['Loot']['Loot-Min-Value']
8129 local maxWeight = _config['Loot']['Loot-Max-Weight']
8130 local whiteList = _config['Loot']['Loot-WhiteList']
8131 local blackList = _config['Loot']['Loot-BlackList']
8132
8133 -- Convert strings to tables if needed
8134 if type(whiteList) ~= 'table' then
8135 whiteList = {whiteList}
8136 end
8137 if type(blackList) ~= 'table' then
8138 blackList = {blackList}
8139 end
8140
8141 -- Sort loot by value
8142 local lootKeys = {}
8143 for itemid, _ in pairs(lootList) do
8144 lootKeys[#lootKeys+1] = itemid
8145 end
8146 table.sort(lootKeys, function(a, b)
8147 return xeno.getItemValue(a) > xeno.getItemValue(b)
8148 end)
8149
8150 -- Iterate sorted keys
8151 for i = 1, #lootKeys do
8152 local itemid = lootKeys[i]
8153 local name = string.lower(xeno.getItemNameByID(itemid))
8154 local blackListed = blackList and #blackList > 0 and table.find(blackList, name, true)
8155 if not blackListed then
8156 -- POTIONS
8157 local whiteListed = whiteList and #whiteList > 0 and table.find(whiteList, name, true)
8158 -- Meets min value condition
8159 if whiteListed or ITEM_LIST_POTIONS[itemid] or ITEM_LIST_RUSTYARMORS[itemid] or (not minValue or minValue <= 0 or xeno.getItemValue(itemid) >= minValue) then
8160 -- Meets max weight condition
8161 if whiteListed or ITEM_LIST_POTIONS[itemid] or ITEM_LIST_RUSTYARMORS[itemid] or (not maxWeight or maxWeight <= 0 or xeno.getItemWeight(itemid) <= maxWeight) then
8162 lootXML = lootXML .. '<item ID="' .. itemid .. '" action="' .. _backpacks['Loot'] .. '"/>'
8163 end
8164 end
8165 end
8166 end
8167 end
8168 end
8169
8170 -- End of list
8171 xbstContents = xbstContents .. lootXML .. '</control></panel>'
8172
8173 -- Magic Shooter
8174 if not _config['General']['Manual-Shooter'] then
8175 local shooter = getShooterXBST()
8176 if shooter then
8177 if not xbstContents then
8178 xbstContents = ''
8179 end
8180 xbstContents = xbstContents .. shooter
8181 end
8182 end
8183
8184 -- Dynamic Lure
8185 if _config['Lure'] and _config['Lure']['Amount'] and _config['Lure']['Amount'] > 0 then
8186 local lurexml = getDynamicLureXBST()
8187 if lurexml then
8188 if not xbstContents then
8189 xbstContents = ''
8190 end
8191 xbstContents = xbstContents .. lurexml
8192 end
8193 end
8194
8195 -- Save XBST if needed
8196 if xbstContents then
8197 local filename = 'tmp.' .. string.gsub(getSelfName(), ".", string.byte)
8198 local file = io.open(FOLDER_SETTINGS_PATH .. filename .. '.xbst', 'w+')
8199 if file then
8200 file:write(xbstContents)
8201 file:close()
8202 xeno.loadSettings(FOLDER_SETTINGS_PATH .. filename, 'All')
8203 setTimeout(function()
8204 os.remove(FOLDER_SETTINGS_PATH .. filename .. '.xbst')
8205 end, 500)
8206 end
8207 end
8208 callback()
8209 end)
8210 end
8211
8212 -- Export global functions
8213 return {
8214 loadSettingsFile = loadSettingsFile,
8215 setDynamicSettings = setDynamicSettings
8216 }
8217end)()
8218Supply = (function()
8219
8220 -- Imports
8221 local formatList = Core.formatList
8222 local setInterval = Core.setInterval
8223 local clearTimeout = Core.clearTimeout
8224 local delayWalker = Core.delayWalker
8225 local resumeWalker = Core.resumeWalker
8226 local getDistanceBetween = Core.getDistanceBetween
8227 local log = Console.log
8228 local warn = Console.warn
8229 local prompt = Console.prompt
8230 local error = Console.error
8231 local getContainerItemCounts = Container.getContainerItemCounts
8232 local cleanContainers = Container.cleanContainers
8233 local getTotalItemCount = Container.getTotalItemCount
8234 local getMoney = Container.getMoney
8235 local getFlaskWeight = Container.getFlaskWeight
8236 local hudItemUpdate = Hud.hudItemUpdate
8237 local bankDepositGold = Npc.bankDepositGold
8238 local bankGetBalance = Npc.bankGetBalance
8239 local bankWithdrawGold = Npc.bankWithdrawGold
8240 local shopSellLoot = Npc.shopSellLoot
8241 local shopRefillSoftboots = Npc.shopRefillSoftboots
8242 local shopBuySupplies = Npc.shopBuySupplies
8243 local walkerLabelExists = Walker.walkerLabelExists
8244 local walkerStartPath = Walker.walkerStartPath
8245 local walkerGetPosAfterLabel = Walker.walkerGetPosAfterLabel
8246 local walkerGotoLocation = Walker.walkerGotoLocation
8247 local startDepotTransfer = Depot.startDepotTransfer
8248 local checkChainRules = Ini.checkChainRules
8249
8250 local function updateSupplyCount(supplyTypes, itemListFilter)
8251 local function checkBackpack(group)
8252 -- Count the backpack assigned to this supply group
8253 -- Some supplies share the same backpack (Amulets & Rings)
8254 local backpackName = group
8255 if group == 'Amulet' or group == 'Ring' then
8256 backpackName = 'Supplies'
8257 end
8258
8259 -- If dedicated backpack doesn't exist, check main backpack
8260 -- Supplies actually use the Main container, not Supplies
8261 local backpack = nil
8262 if group ~= 'Supplies' and _backpacks[backpackName] then
8263 backpack = _backpacks[backpackName]
8264 else
8265 backpack = _backpacks['Main']
8266 end
8267
8268 local sessionCount = {}
8269
8270 -- Count slots
8271 local slotItems = {}
8272
8273 slotItems[1] = xeno.getAmmoSlotData()
8274 slotItems[2] = xeno.getHeadSlotData()
8275 slotItems[3] = xeno.getArmorSlotData()
8276 slotItems[4] = xeno.getLegsSlotData()
8277 slotItems[5] = xeno.getFeetSlotData()
8278 slotItems[6] = xeno.getAmuletSlotData()
8279 slotItems[7] = xeno.getWeaponSlotData()
8280 slotItems[8] = xeno.getRingSlotData()
8281 slotItems[9] = xeno.getShieldSlotData()
8282
8283 -- Count supplies in slots
8284 for i = 1, 9 do
8285 local slot = slotItems[i]
8286 if slot and slot.id > 0 then
8287 if not itemListFilter or itemListFilter[slot.id] then
8288 local supply = _supplies[slot.id]
8289 -- This item is a supply and the correct group
8290 if supply and supply.group == group then
8291 -- Create supply count session if needed
8292 if not sessionCount[slot.id] then
8293 sessionCount[slot.id] = 0
8294 end
8295
8296 -- Update supply count
8297 local count = sessionCount[slot.id] + math.max(slot.count, 1)
8298 sessionCount[slot.id] = count
8299 _supplies[slot.id].count = count
8300 end
8301 end
8302 end
8303 end
8304
8305 -- Count supplies in the backpack determined above
8306 for spot = 0, xeno.getContainerItemCount(backpack) - 1 do
8307 local item = xeno.getContainerSpotData(backpack, spot)
8308 if not itemListFilter or itemListFilter[item.id] then
8309 local supply = _supplies[item.id]
8310 -- This item is a supply and the correct group
8311 if supply and supply.group == group then
8312 -- Create supply count session if needed
8313 if not sessionCount[item.id] then
8314 sessionCount[item.id] = 0
8315 end
8316
8317 -- Update supply count
8318 local count = sessionCount[item.id] + math.max(item.count, 1)
8319 sessionCount[item.id] = count
8320 _supplies[item.id].count = count
8321 end
8322 end
8323 end
8324
8325 -- Set supplies not found to zero
8326 for itemid, supply in pairs(_supplies) do
8327 if supply.group == group then
8328 if not sessionCount[itemid] then
8329 _supplies[itemid].count = 0
8330 end
8331 end
8332 end
8333 end
8334
8335 -- If not supply types specified, default to all
8336 if not supplyTypes then
8337 supplyTypes = {'Runes', 'Potions', 'Ammo', 'Food', 'Supplies', 'Ring', 'Amulet'}
8338 -- Wrap in table (passed one)
8339 elseif type(supplyTypes) ~= 'table' then
8340 supplyTypes = {supplyTypes}
8341 end
8342
8343 for i = 1, #supplyTypes do
8344 checkBackpack(supplyTypes[i])
8345 end
8346 end
8347
8348 local disableAlarm = function()
8349 prompt('To disable the alarm, enter any text to stop the alarm.', function(response)
8350 if _script.alarmInterval then
8351 clearTimeout(_script.alarmInterval)
8352 _script.alarmInterval = nil
8353 warn('Alarm disabled.')
8354 end
8355 end)
8356 end
8357
8358 local function checkAllSupplyThresholds(updatedNeededCount)
8359 -- Update all supply counts before checking
8360 updateSupplyCount()
8361
8362 -- Thresholds
8363 local minThresholds = false
8364 local maxThresholds = false
8365 local alarmThresholds = false
8366
8367 -- Loop through supplies, find the differences for each setting
8368 for itemid, supply in pairs(_supplies) do
8369 -- Minimum is expected to be checked and is below expected
8370 if supply.min > 0 and supply.count < supply.min then
8371 if not minThresholds then
8372 minThresholds = {}
8373 end
8374 minThresholds[itemid] = supply
8375 end
8376 -- Maximum is expected to be checked and is below expected
8377 local maxThreshold = supply.max >= 200 and supply.max - 20 or supply.max
8378 if supply.max > 0 and supply.count < maxThreshold then
8379 if not maxThresholds then
8380 maxThresholds = {}
8381 end
8382 maxThresholds[itemid] = supply
8383 -- Lock the buy amount
8384 if updatedNeededCount then
8385 supply.needed = supply.max - supply.count
8386 end
8387 -- Count is above max, always clear needed
8388 else
8389 supply.needed = 0
8390 end
8391 -- Alarm is expected to be checked and is below expected
8392 if supply.alarm > 0 and supply.count < supply.alarm then
8393 if not alarmThresholds then
8394 alarmThresholds = {}
8395 end
8396 alarmThresholds[itemid] = supply
8397 end
8398 end
8399
8400 -- Log items below minimum & max
8401 local itemNames = {}
8402 local itemsAdded = {}
8403 local itemLists = {minThresholds}
8404
8405 -- Town check, print max items too
8406 if not _script.inSpawn then
8407 itemLists[#itemLists + 1] = maxThresholds
8408 end
8409
8410 -- Add items from min and max tables
8411 for i = 1, #itemLists do
8412 local list = itemLists[i]
8413 if list then
8414 for itemid, _ in pairs(list) do
8415 -- Make sure we don't add duplicates
8416 if not itemsAdded[itemid] then
8417 -- Make name plural
8418 local name = xeno.getItemNameByID(itemid)
8419 local suffix = 's'
8420 -- Name ends with 's', make suffix 'es'
8421 if string.sub(name, -1) == 's' then
8422 suffix = 'es'
8423 end
8424 -- Check if alarm for this item is triggered
8425 local important = ''
8426 if _script.inSpawn and (alarmThresholds and alarmThresholds[itemid]) then
8427 important = '!!!'
8428 end
8429 -- Add to log
8430 itemNames[#itemNames+1] = name .. suffix .. important
8431 -- Flag as added to log
8432 itemsAdded[itemid] = true
8433 end
8434 end
8435 end
8436 end
8437
8438 -- Print the min and max items
8439 if #itemNames > 0 then
8440 warn('Low supply count for ' .. formatList(itemNames) .. '.')
8441 end
8442
8443 -- If any item was added to the alarm threshold, start alarm
8444 if _script.inSpawn and alarmThresholds and not _script.alarmInterval then
8445 -- Sound alarm until disabled
8446 _script.alarmInterval = setInterval(function()
8447 xeno.alert()
8448 end, DELAY.AlARM_INTERVAL)
8449
8450 -- Send alarm immediately
8451 xeno.alert()
8452
8453 -- Send stop alarm prompt to user
8454 disableAlarm()
8455 end
8456
8457 -- Return all supplies below thresholds
8458 return {
8459 min = minThresholds,
8460 max = maxThresholds,
8461 alarm = alarmThresholds
8462 }
8463 end
8464
8465 local function getResupplyDetails()
8466 -- Only run this after selling loot, depositing loot gold, and withdrawing supplies
8467 -- Needed count for items will be locked after running this function
8468 -- Counts are decremented per item purchase
8469 -- count can decrease after selling loot
8470 -- count can increase after depositing loot gold
8471 -- count can decrease after withdrawing supplies
8472 local totalCost = 0
8473 local totalWeight = 0
8474 local refillSupplyGroups = {}
8475 local refillSoftboots = false
8476 local venoreTravel = false
8477 local edronTravel = false
8478 local runeTravel = nil
8479 local sourceTown = string.lower(_script.town)
8480
8481 -- Spawn traveling, lookup by script name, apply a flat fee
8482 local spawnTravelFee = SPAWN_TRAVELLING[string.lower(_script.name)]
8483 if spawnTravelFee then
8484 totalCost = spawnTravelFee
8485 end
8486
8487 -- Softboots refill (+ travel costs if not in venore and not already)
8488 if _config['Soft Boots']['Mana-Percent'] > 0 then
8489 local wornSoftBootCount = getTotalItemCount(ITEMID.SOFTBOOTS_WORN)
8490 -- Worn softboots on us, refill
8491 if wornSoftBootCount > 0 then
8492 refillSoftboots = true
8493 -- Not in Venore, we need to travel
8494 if sourceTown ~= 'venore' then
8495 venoreTravel = true
8496 end
8497 totalCost = totalCost + math.max(0, wornSoftBootCount) * PRICE.SOFTBOOTS_REFILL
8498 end
8499 end
8500
8501 -- Supply refill cost (+ travel costs for exotic runes)
8502 local thresholds = checkAllSupplyThresholds(true)
8503 if thresholds.max then
8504 for itemid, supply in pairs(thresholds.max) do
8505 if supply.group ~= 'Amulet' and supply.group ~= 'Ring' then
8506 -- Runes can be domestic or foreign
8507 if supply.group == 'Runes' then
8508 -- We need to travel to Edron to get premium runes
8509 if RUNES_EXOTIC[itemid] and not EXOTIC_RUNE_TOWNS[sourceTown] then
8510 edronTravel = true
8511 runeTravel = 'Edron'
8512 -- We are in Edron and we need to travel to get free runes
8513 elseif RUNES_NORMAL[itemid] and sourceTown == 'edron' then
8514 venoreTravel = true
8515 runeTravel = 'Venore'
8516 -- We can buy these runes in town
8517 else
8518 refillSupplyGroups[supply.group] = true
8519 end
8520 -- Other supplies are all domestic
8521 elseif supply.group == 'Potions' or supply.group == 'Ammo' or supply.group == 'Food' then
8522 refillSupplyGroups[supply.group] = true
8523 end
8524 totalCost = totalCost + supply.needed * xeno.getItemCost(itemid)
8525 totalWeight = totalWeight + supply.needed * xeno.getItemWeight(itemid)
8526 end
8527 end
8528 end
8529
8530 -- Add all travel costs to total
8531 if venoreTravel then
8532 local travelRoute = TRAVEL_ROUTES[sourceTown .. '~venore']
8533 local returnRoute = TRAVEL_ROUTES['venore~' .. sourceTown]
8534 if not travelRoute then
8535 error('Missing travel route from ' .. sourceTown .. ' to venore. Please contact support.')
8536 end
8537 totalCost = totalCost + travelRoute.cost + returnRoute.cost
8538 end
8539 if edronTravel and sourceTown ~= 'edron' then
8540 local returnRoute = TRAVEL_ROUTES['edron~' .. sourceTown]
8541 if venoreTravel then
8542 -- Since we're returning directly from Edron we don't need the cash for traveling back from Venore anymore.
8543 local notNeededVenoreReturnRoute = TRAVEL_ROUTES['venore~' .. sourceTown]
8544 totalCost = totalCost - notNeededVenoreReturnRoute.cost
8545 -- We already had to go to venore, head from venore to edron
8546 sourceTown = 'venore'
8547 end
8548 local travelRoute = TRAVEL_ROUTES[sourceTown .. '~edron']
8549 if not travelRoute then
8550 error('Missing travel route from ' .. sourceTown .. ' to edron. Please contact support.')
8551 end
8552 totalCost = totalCost + travelRoute.cost + returnRoute.cost
8553 end
8554
8555 return {
8556 withdrawGold = math.max(0, totalCost),
8557 capNeeded = math.max(0, totalWeight),
8558 refillSupplyGroups = refillSupplyGroups,
8559 refillSoftboots = refillSoftboots,
8560 foreignRuneTown = runeTravel
8561 }
8562 end
8563
8564 local function resupply(callback, step, loot, details)
8565 --[[
8566 - Check if we need to go to loot seller
8567 - Sell loot (if any)
8568 - Check if we need to go to bank
8569 - Deposit gold (if any)
8570 IF NOT WITHDRAWING SUPPPLIES:
8571 - Withdraw gold
8572 - Check if we need to go to depot
8573 - Deposit loot (if any)
8574 - Withdraw supplies (if any)
8575 - IF HASN'T WITHDREW SUPPLY GOLD Check if we need to go to bank
8576 - Withdraw gold
8577 - Check travel-prone supplies first
8578 - Softboot refill (travel venore)
8579 - In edron and need "normal" runes (travel venore)
8580 - Not in edron, gray island, rathleton and need "exotic" runes (travel edron)
8581 - If already traveling to venore, travel directly from venore to edron
8582 - Check regular supplies
8583 - Runes not needing travel, Potions, Ammo, and Food
8584 - Order is based on current distance
8585 ]]
8586
8587 -- Update HUD and script state
8588 _script.state = 'Resupplying';
8589
8590 step = step or 0
8591
8592 -- Step 1) Detect loot to sell
8593 if step < 1 then
8594 -- Deep search of loot backpack.
8595 delayWalker()
8596 getContainerItemCounts(_backpacks['Loot'], function(items)
8597 resumeWalker()
8598
8599 -- No items at all, skip step
8600 if not items then
8601 resupply(callback, 1, items)
8602 return
8603 end
8604
8605 -- NPC Sell disabled (still pass items for deposit detection)
8606 if not _config['Loot']['Npc-Sell'] then
8607 resupply(callback, 1, items)
8608 return
8609 end
8610
8611 -- List of sellable loot in this town
8612 local townLoot = SELLABLE_LOOT[string.lower(_script.town)]
8613
8614 -- Nothing sellable in town, skip step
8615 if not townLoot then
8616 resupply(callback, 1, items)
8617 return
8618 end
8619
8620 -- Path chosen (closest to us)
8621 local closestPath = nil
8622 local closestDistance = nil
8623 local supplyTown = string.lower(_script.town)
8624 local selfPos = xeno.getSelfPosition()
8625
8626 -- Determine if we can sell each loot item
8627 local lootPaths = {}
8628 local lootPathCount = 0
8629 for itemid, itemcount in pairs(items) do
8630 -- We can sell this item
8631 local lootPath = townLoot[itemid]
8632 if lootPath then
8633 -- First type found is boolean, only one loot seller for town
8634 if type(lootPath) == 'boolean' then
8635 lootPaths['loot'] = true
8636 break
8637 -- Insert loot path as an option if not already
8638 elseif not lootPaths['loot.' .. lootPath] then
8639 lootPaths['loot.' .. lootPath] = true
8640 lootPathCount = lootPathCount + 1
8641 end
8642 end
8643 end
8644
8645 -- Find the closest loot path to take
8646 for pathName, _ in pairs(lootPaths) do
8647 local label = supplyTown .. '|' .. pathName .. '~depot'
8648 -- Path exists
8649 if walkerLabelExists(label) then
8650 local pos = walkerGetPosAfterLabel(label)
8651 local distance = getDistanceBetween(selfPos, pos)
8652 -- Closest label so far
8653 if closestDistance == nil or distance < closestDistance then
8654 closestDistance = distance
8655 closestPath = pathName
8656 end
8657 end
8658 end
8659
8660 -- We have loot to deposit
8661 if closestPath then
8662 log('Walking to the depot to deposit loot.')
8663 walkerGotoLocation(_script.town, closestPath, function()
8664 -- Arrived at loot shop
8665 shopSellLoot(townLoot, function()
8666 -- Repeat step if more loot paths
8667 if lootPathCount > 1 then
8668 resupply(callback, 0, items)
8669 -- Skip to next step
8670 else
8671 resupply(callback, 1, items)
8672 end
8673 end)
8674 end)
8675 -- No loot, go to next step
8676 else
8677 resupply(callback, 1, items)
8678 end
8679 end, true)
8680 return
8681 end
8682
8683 -- Step 2) Detected money to deposit AND we need to do step 3 OTHERWISE skip to step 4
8684 if step < 2 then
8685 -- Loot remaining in loot backpack
8686 local depositLoot = false
8687 if loot then
8688 for itemid, _ in pairs(loot) do
8689 if itemid ~= ITEMID.OBSIDIAN_KNIFE and itemid ~= ITEMID.BLESSED_STAKE then
8690 depositLoot = true
8691 break
8692 end
8693 end
8694 end
8695
8696 -- We need to withdraw supplies
8697 local withdrawSupplies = not _script.disableWithdraw and checkAllSupplyThresholds().max
8698
8699 -- We can skip step 3, so we can also skip this step, go past 3.
8700 if not depositLoot and not withdrawSupplies then
8701 resupply(callback, 3, loot)
8702 return
8703 end
8704
8705 local gold = getMoney()
8706 if gold > 500 then
8707 walkerGotoLocation(_script.town, 'bank', function(path)
8708 -- Arrived at bank
8709 bankDepositGold(function()
8710 -- Balance
8711 bankGetBalance(function()
8712 -- Deposited gold, continue resupply
8713 resupply(callback, 2, loot)
8714 end, true)
8715 end)
8716 end)
8717 else
8718 -- Skip to next step
8719 resupply(callback, 2, loot)
8720 end
8721 return
8722 end
8723
8724 -- Step 3) Detected loot to deposit or supplies to withdraw
8725 if step < 3 then
8726 -- Loot remaining in loot backpack
8727 local depositLoot = false
8728 if loot then
8729 for itemid, _ in pairs(loot) do
8730 if itemid ~= ITEMID.OBSIDIAN_KNIFE
8731 and itemid ~= ITEMID.MECHANICAL_ROD
8732 and itemid ~= ITEMID.FISHING_ROD
8733 and itemid ~= ITEMID.BLESSED_STAKE then
8734 depositLoot = true
8735 break
8736 end
8737 end
8738 end
8739
8740 -- We need to withdraw supplies
8741 local neededSupplies = false
8742 if not _script.disableWithdraw then
8743 neededSupplies = checkAllSupplyThresholds(true).max
8744 end
8745
8746 if depositLoot and neededSupplies then
8747 log('Walking to the depot to withdraw supplies and deposit loot.')
8748 elseif depositLoot then
8749 log('Walking to the depot to deposit loot.')
8750 elseif neededSupplies then
8751 log('Walking to the depot to withdraw supplies.')
8752 end
8753
8754 -- Do we need to head to the depot (deposit or withdraw)
8755 if depositLoot or neededSupplies then
8756 walkerGotoLocation(_script.town, 'depot', function()
8757 -- Arrived at depot
8758 startDepotTransfer(depositLoot, neededSupplies, function()
8759 if _script.chainConfig then
8760 if checkChainRules(_script.chainConfig) then
8761 return
8762 end
8763 end
8764 -- Finished deposit & withdraw, continue resupply
8765 resupply(callback, 3, loot)
8766 end)
8767 end)
8768 return
8769 end
8770 end
8771
8772 -- SHIT GETS REAL NOW. From this point on we need to look into the future
8773 -- Includes gold cost, softboot refill, and foreign rune town
8774 -- Do NOT call more than once, as it resets the future (resets needed values)!!!
8775 if not details then
8776 details = getResupplyDetails()
8777 end
8778
8779 -- Step 4) Once this is called the amount we will buy is locked
8780 if step < 4 then
8781 if details.withdrawGold > 0 then
8782 -- Check capacity
8783 if details.capNeeded > 0 then
8784 -- Deduct weight of supplies we sell before buying (flasks)
8785 --[[local estimatedCap = xeno.getSelfCap() - getFlaskWeight()
8786 -- Not enough capacity for supplies error
8787 if details.capNeeded > estimatedCap then
8788 error('Not enough capacity for supplies. Please lower config values or increase your capacity.')
8789 return
8790 end]]
8791 end
8792 walkerGotoLocation(_script.town, 'bank', function()
8793 -- Arrived at bank
8794 bankDepositGold(function()
8795 -- Under 10k rounds to nearest hundredths, otherwise thousandths
8796 local roundTo = details.withdrawGold < 10000 and 100 or 1000
8797 local roundedCost = math.ceil(details.withdrawGold / roundTo) * roundTo
8798 -- Withdraw travel funds
8799 bankWithdrawGold(roundedCost, function()
8800 -- Balance
8801 bankGetBalance(function()
8802 -- Finished bank withdrawal, continue resupply
8803 resupply(callback, 4, loot, details)
8804 end, true)
8805 end, true)
8806 end)
8807 end)
8808 return
8809 end
8810 end
8811
8812 -- Step 5) Detected softboots to refill
8813 if step < 5 then
8814 if details.refillSoftboots then
8815 walkerGotoLocation('Venore', 'soft.boots', function()
8816 -- Arrived at softboots
8817 shopRefillSoftboots(function()
8818 -- Refilled boots, continue resupply
8819 resupply(callback, 5, loot, details)
8820 end)
8821 end)
8822 return
8823 end
8824 end
8825
8826 -- Step 6) Detected foreign runes to buy
8827 if step < 6 then
8828 if details.foreignRuneTown then
8829 walkerGotoLocation(details.foreignRuneTown, 'runes', function()
8830 -- Arrived at softboots
8831 shopBuySupplies('Runes', function()
8832 -- Refilled boots, continue resupply
8833 resupply(callback, 6, loot, details)
8834 end)
8835 end)
8836 return
8837 end
8838 end
8839
8840 -- Step 7) Detected remaining supplies to buy (runes, potions, ammo, food)
8841 if step < 7 then
8842 -- Loop through all supplies needed to refill
8843 -- go to the closest supply location
8844 -- remove the group from the refill table
8845 -- repeat until none are left
8846 -- when none are left set step to 7
8847 local selfPos = xeno.getSelfPosition()
8848 local supplyTown = string.lower(_script.town)
8849 local supplyChosen = nil
8850 local supplyDistance = nil
8851
8852 for supply, status in pairs(details.refillSupplyGroups) do
8853 if status then
8854 -- Find a label that starts at the destination (so we can determine distance)
8855 local label = supplyTown .. '|' .. string.lower(supply) .. '~depot'
8856 -- Route to supplies has to exist
8857 if walkerLabelExists(label) then
8858 local pos = walkerGetPosAfterLabel(label)
8859 local distance = getDistanceBetween(selfPos, pos)
8860 -- Closest label so far
8861 if supplyDistance == nil or distance < supplyDistance then
8862 supplyDistance = distance
8863 supplyChosen = supply
8864 end
8865 end
8866 end
8867 end
8868
8869
8870 -- No supply chose, this step is complete
8871 if not supplyChosen then
8872 resupply(callback, 7, loot, details)
8873 return
8874 end
8875
8876 -- Walk to closest shop
8877 walkerGotoLocation(_script.town, string.lower(supplyChosen), function()
8878 -- Arrived at supplier
8879 shopBuySupplies(supplyChosen, function()
8880 -- Clear this supply
8881 details.refillSupplyGroups[supplyChosen] = nil
8882 -- Resupplied, trigger same step until we're out of resupplies
8883 resupply(callback, 6, loot, details)
8884 end)
8885 end)
8886
8887 return
8888 end
8889
8890 -- Done, go to town exit
8891 log('Ready to hunt. Walking to the spawn.')
8892
8893 -- Update script state
8894 _script.state = 'Walking to town exit';
8895
8896 walkerGotoLocation(_script.town, _script.townexit, function()
8897
8898 -- Update script state
8899 _script.state = 'Walking to spawn';
8900
8901 -- Exited town, go to spawn
8902 walkerStartPath(_script.town, _script.townexit, 'spawn', function()
8903 -- Enter spawn
8904 xeno.gotoLabel('enterspawn')
8905 resumeWalker()
8906 xeno.setTargetingEnabled(true)
8907 xeno.setLooterEnabled(true)
8908 -- Reached spawn
8909 if callback then
8910 callback()
8911 -- We finished our first resupply
8912 _script.firstResupply = false
8913 end
8914 end)
8915 end)
8916 end
8917
8918 -- Export global functions
8919 return {
8920 checkSoftBoots = checkSoftBoots,
8921 checkAllSupplyThresholds = checkAllSupplyThresholds,
8922 resupply = resupply
8923 }
8924end)()
8925Targeter = (function()
8926
8927 -- Imports
8928 local clearTimeout = Core.clearTimeout
8929 local setInterval = Core.setInterval
8930 local getDistanceBetween = Core.getDistanceBetween
8931
8932 local function targetingGetCreatureThreshold(list, range, amount, multifloor, floor)
8933 local targets = {}
8934 local count = 0
8935 local pos = xeno.getSelfPosition()
8936 for i = CREATURES_LOW, CREATURES_HIGH do
8937 local name = xeno.getCreatureName(i)
8938 -- Is this creature invited to die?
8939 if list[string.lower(name)] then
8940 -- Position & Distance
8941 local cpos = xeno.getCreaturePosition(i)
8942 local distance = getDistanceBetween(pos, cpos)
8943 if ((pos.z + (floor or 0)) == cpos.z or multifloor) and distance <= range then
8944 -- Normal creature checks
8945 if xeno.getCreatureVisible(i) and xeno.getCreatureHealthPercent(i) > 0 and xeno.isCreatureMonster(i) then
8946 targets[#targets+1] = xeno.getCreatureIDFromIndex(i)
8947 count = count + 1
8948 end
8949 end
8950 end
8951 end
8952 return {count >= amount, targets}
8953 end
8954
8955 -- Export global functions
8956 return {
8957 targetingGetCreatureThreshold = targetingGetCreatureThreshold
8958 }
8959end)()
8960do
8961 -- Imports
8962 local debug = Core.debug
8963 local formatNumber = Core.formatNumber
8964 local formatTime = Core.formatTime
8965 local freeMemory = Core.freeMemory
8966 local throttle = Core.throttle
8967 local titlecase = Core.titlecase
8968 local talk = Core.talk
8969 local clearTimeout = Core.clearTimeout
8970 local setTimeout = Core.setTimeout
8971 local setInterval = Core.setInterval
8972 local checkTimers = Core.checkTimers
8973 local checkEvents = Core.checkEvents
8974 local delayWalker = Core.delayWalker
8975 local resumeWalker = Core.resumeWalker
8976 local indexTable = Core.indexTable
8977 local split = Core.split
8978 local pingDelay = Core.pingDelay
8979 local getTimeUntilServerSave = Core.getTimeUntilServerSave
8980 local clearWalkerPath = Core.clearWalkerPath
8981 local getPositionFromDirection = Core.getPositionFromDirection
8982 local getDistanceBetween = Core.getDistanceBetween
8983 local getSelfName = Core.getSelfName
8984 local sortPositionsByDistance = Core.sortPositionsByDistance
8985 local cast = Core.cast
8986 local isSpell = Core.isSpell
8987 local cureConditions = Core.cureConditions
8988 local checkSoftBoots = Core.checkSoftBoots
8989 local log = Console.log
8990 local warn = Console.warn
8991 local openConsole = Console.openConsole
8992 local openDebugChannel = Console.openDebugChannel
8993 local openPrivateMessageConsole = Console.openPrivateMessageConsole
8994 local cleanContainers = Container.cleanContainers
8995 local resetContainers = Container.resetContainers
8996 local getTotalItemCount = Container.getTotalItemCount
8997 local setupContainers = Container.setupContainers
8998 local whenContainerUpdates = Container.whenContainerUpdates
8999 local unrustLoot = Container.unrustLoot
9000 local hudUpdateDimensions = Hud.hudUpdateDimensions
9001 local hudUpdatePositions = Hud.hudUpdatePositions
9002 local hudItemUpdate = Hud.hudItemUpdate
9003 local hudQueryLootChanges = Hud.hudQueryLootChanges
9004 local hudQuerySupplyChanges = Hud.hudQuerySupplyChanges
9005 local loadConfigFile = Ini.loadConfigFile
9006 local shopSellLoot = Npc.shopSellLoot
9007 local walkerLabelExists = Walker.walkerLabelExists
9008 local walkerStartPath = Walker.walkerStartPath
9009 local walkerGetClosestLabel = Walker.walkerGetClosestLabel
9010 local walkerReachNPC = Walker.walkerReachNPC
9011 local walkerGetDoorDetails = Walker.walkerGetDoorDetails
9012 local walkerUseTrainer = Walker.walkerUseTrainer
9013 local walkerUseClosestItem = Walker.walkerUseClosestItem
9014 local walkerCapacityDrop = Walker.walkerCapacityDrop
9015 local walkerRestoreMana = Walker.walkerRestoreMana
9016 local walkerGotoLocation = Walker.walkerGotoLocation
9017 local walkerTravel = Walker.walkerTravel
9018 local setDynamicSettings = Settings.setDynamicSettings
9019 local checkAllSupplyThresholds = Supply.checkAllSupplyThresholds
9020 local resupply = Supply.resupply
9021 local targetingGetCreatureThreshold = Targeter.targetingGetCreatureThreshold
9022
9023 local LABEL_ACTIONS = {
9024 -- Player & Supply Check [huntcheck|id]
9025 ['huntcheck'] = function(group, id, failLabel, skipSupplyCheck, skipState)
9026 skipState = skipState == 'true'
9027 skipSupplyCheck = skipSupplyCheck == 'true'
9028 local state = 'Hunting'
9029 local route = '--'
9030
9031 -- Pause walker
9032 delayWalker()
9033
9034 -- Drop vials / gold
9035 walkerCapacityDrop()
9036
9037 -- Check logout conditions
9038 local logoutReason = nil
9039 local logoutConfig = _config['Logout']
9040
9041 -- Condition: Forced
9042 if _script.forceLogoutQueued then
9043 logoutReason = 'Requested by user.'
9044 -- Condition: Low Stamina
9045 elseif logoutConfig['Stamina'] and logoutConfig['Stamina'] > 0 and (xeno.getSelfStamina() / 60) < logoutConfig['Stamina'] then
9046 logoutReason = 'Low stamina.'
9047 -- Condition: Reached Target level
9048 elseif logoutConfig['Level'] and logoutConfig['Level'] > 0 and xeno.getSelfLevel() >= logoutConfig['Level'] then
9049 logoutReason = 'Desired level reached.'
9050 -- Condition: near Server Save
9051 elseif logoutConfig['Server-Save'] and logoutConfig['Server-Save'] > 0 and getTimeUntilServerSave() < logoutConfig['Server-Save'] then
9052 logoutReason = 'Close to server save.'
9053 -- Condition: Reached Time limit
9054 elseif logoutConfig['Time-Limit'] and logoutConfig['Time-Limit'] > 0 and ((os.time() - _script.start) / 3600) >= logoutConfig['Time-Limit'] then
9055 logoutReason = 'Time limit reached.'
9056 end
9057
9058 -- Found a reason to logout
9059 if logoutReason then
9060 state = 'Exiting'
9061 -- Train or logout
9062 local action = 'logout'
9063 if logoutConfig['Train-Skill'] and logoutConfig['Train-Skill'] ~= 'none' then
9064 action = 'train'
9065 _script.trainingQueued = true
9066 else
9067 _script.logoutQueued = true
9068 end
9069 log('Returning to ' .._script.town.. ' to ' .. action .. '. ' .. logoutReason)
9070 -- Clean backpacks
9071 cleanContainers(_backpacks['Loot'], ITEM_LIST_SKINNABLE_LOOT, nil, true)
9072 -- Route system
9073 if failLabel then
9074 xeno.gotoLabel(failLabel, true)
9075 -- Regular system
9076 else
9077 xeno.gotoLabel('huntexit|' .. id)
9078 end
9079 -- Continue hunting
9080 else
9081 -- Check our state
9082 local supplies = not skipSupplyCheck and checkAllSupplyThresholds() or {}
9083 local lowSupplies = supplies.min
9084 local lowCap = not skipSupplyCheck and (xeno.getSelfCap() < _config['Capacity']['Hunt-Minimum']) or false
9085 local needSoftbootRepair = not skipSupplyCheck and (_config['Soft Boots']['Mana-Percent'] > 0 and getTotalItemCount(ITEMID.SOFTBOOTS_WORN) > 0) or false
9086 -- Resupply
9087 if _script.returnQueued or supplies.min or lowCap or needSoftbootRepair then
9088 if _script.state ~= 'Walking to exit' then
9089 state = 'Walking to exit'
9090 log('Returning to ' .. _script.town .. ' to ' .. (needSoftbootRepair and 'repair soft boots.' or 're-supply.') .. (_script.returnQueued and ' [forced]' or ''))
9091 end
9092 -- Clean backpacks
9093 cleanContainers(_backpacks['Loot'], ITEM_LIST_SKINNABLE_LOOT, nil, true)
9094 -- Route system
9095 if failLabel then
9096 xeno.gotoLabel(failLabel, true)
9097 -- Regular system
9098 else
9099 xeno.gotoLabel('huntexit|'..id)
9100 end
9101 -- Continue hunting
9102 else
9103 -- Route system
9104 if failLabel then
9105 -- Check route status (disable, enabled, random)
9106 -- random is a 50% chance to take the route
9107 local routeData = _config['Route']
9108 if routeData then
9109 local routeEnabled = routeData[id]
9110 if routeEnabled == 'random' then
9111 routeEnabled = math.random(1, 10) > 5 and true or false
9112 end
9113 if _script.returnQueued or not routeEnabled then
9114 xeno.gotoLabel(failLabel, true)
9115 -- Route enabled, update script state
9116 else
9117 route = id
9118 end
9119 end
9120 -- Regular system
9121 else
9122 xeno.gotoLabel('huntstart|'..id)
9123 end
9124 end
9125 end
9126 if not route then route = '--' end
9127 -- Update script state
9128 if not skipState and _config['HUD']['Enabled'] then
9129 _script.state = state;
9130 -- We don't currently have a route
9131 if _script.route == '--' then
9132 _script.route = route ~= '--' and route:gsub('-', ' ') or '--'
9133 end
9134 -- The current label is a route
9135 if route ~= '--' then
9136 -- Set the new route
9137 _script.route = route:gsub('-', ' ')
9138 end
9139 end
9140
9141 -- Resume walker
9142 resumeWalker()
9143 end,
9144
9145 -- Exit spawn and return to town
9146 ['spawnexit'] = function(group)
9147 -- Update state
9148 log('Exiting spawn.')
9149 _script.inSpawn = false
9150 _script.state = 'Walking to town'
9151
9152 -- Update round
9153 _script.round = _script.round + 1
9154
9155 -- Disable active alarm if running
9156 if _script.alarmInterval then
9157 clearTimeout(_script.alarmInterval)
9158 _script.alarmInterval = nil
9159 warn('Alarm disabled.')
9160 end
9161
9162 -- Clear return queued
9163 _script.returnQueued = false
9164
9165 -- Walk to town|spawn~townentrance
9166 walkerStartPath(_script.town, 'spawn', _script.townentrance or _script.townexit, function()
9167
9168 -- Cure Conditions
9169 if _config['General']['Cure-Conditions'] then
9170 cureConditions()
9171 end
9172
9173 -- Trainers
9174 if _script.trainingQueued then
9175 walkerStartPath(_script.town, _script.townentrance or _script.townexit, 'trainers', function()
9176 walkerUseTrainer()
9177 --assert(false, 'Script finished successfully.')
9178 end)
9179 -- Logout
9180 elseif _script.logoutQueued then
9181 walkerStartPath(_script.town, _script.townentrance or _script.townexit, 'depot', function()
9182 log('Hunting complete, logging out.')
9183 if not _config['Logout']['Exit-Log'] then
9184 xeno.doSelfLogout()
9185 assert(false, 'Script finished successfully.')
9186 else
9187 os.exit()
9188 end
9189 end)
9190 -- Start resupply process
9191 else
9192 resupply()
9193 end
9194 end)
9195 end,
9196
9197 -- Enter spawn
9198 ['enterspawn'] = function(group)
9199 log('Entering spawn.')
9200 _script.state = 'Hunting';
9201 _script.inSpawn = true;
9202 end,
9203
9204 -- Door System
9205 ['door'] = function(group, id, action)
9206 if action == 'START' then
9207 local door = walkerGetDoorDetails(id)
9208 -- Skip to corresponding door label
9209 if xeno.getTileIsWalkable(door.doorPos.x, door.doorPos.y, door.doorPos.z) then
9210 xeno.gotoLabel(door.posLabel)
9211 end
9212 -- Door position label (ex: door|100|SOUTH)
9213 else
9214 -- Door is walkable
9215 -- Use door in front, step forward until out of doorway
9216 -- Check if we're on other side of door, if not go back to door|id|START
9217 local door = walkerGetDoorDetails(id)
9218 -- Open door if closed
9219 if not xeno.getTileIsWalkable(door.doorPos.x, door.doorPos.y, door.doorPos.z) then
9220 xeno.selfUseItemFromGround(door.doorPos.x, door.doorPos.y, door.doorPos.z)
9221 end
9222 -- Rush through open door
9223 xeno.doSelfStep(door.direction)
9224 xeno.doSelfStep(door.direction)
9225 -- If walker gets stuck in the next 3 seconds, return to door
9226 _script.stuckReturnLabel = door.startLabel
9227 _script.stuckReturnTimer = setTimeout(function()
9228 _script.stuckReturnLabel = nil
9229 end, 3000)
9230 end
9231 end,
9232
9233 -- Pick System
9234 ['pick'] = function(group, direction)
9235 local directions = {['NORTH']=NORTH, ['SOUTH']=SOUTH, ['EAST']=EAST, ['WEST']=WEST}
9236 local pos = getPositionFromDirection(xeno.getSelfPosition(), directions[direction], 1)
9237 xeno.selfUseItemWithGround(_script.pick, pos.x, pos.y, pos.z)
9238 end,
9239
9240 -- Levitate System
9241 ['levitate'] = function(group, change, direction, nostep)
9242 local prevFloor = xeno.getSelfPosition().z
9243 local directions = {['NORTH']=NORTH, ['SOUTH']=SOUTH, ['EAST']=EAST, ['WEST']=WEST}
9244 local dir = directions[direction]
9245
9246 -- Pause walker & looter
9247 delayWalker()
9248 xeno.setLooterEnabled(false)
9249 xeno.doSelfTurn(dir)
9250 xeno.doSelfTurn(dir)
9251
9252 if not nostep then
9253 xeno.doSelfStep(dir)
9254 end
9255
9256 setTimeout(function()
9257 if xeno.getSelfPosition().z == prevFloor then
9258 cast('exani hur ' .. (change == '+' and 'up' or 'down'))
9259 end
9260 xeno.setLooterEnabled(true)
9261 resumeWalker()
9262 end, 1000)
9263 end,
9264
9265 -- Anti-Lure System
9266 ['run'] = function(group, name, id, waitTime)
9267 -- Reached the end, wait
9268 if id == 'end' then
9269 local setIgnore = setTargetingIgnoreEnabled
9270 -- Pause walker
9271 delayWalker()
9272 -- Disable ignore mode
9273 setIgnore(false)
9274 -- Enable the walker when there's no more threats
9275 _script.antiLureCheckInterval = setInterval(function()
9276 -- Check if we can continue
9277 local threshold = targetingGetCreatureThreshold(
9278 _config['Anti Lure']['Creatures'],
9279 _config['Anti Lure']['Range'],
9280 _config['Anti Lure']['Amount'],
9281 false)
9282 -- Threshold met, stop timer and start walker
9283 if not threshold[1] then
9284 if _script.antiLureCheckInterval then
9285 clearTimeout(_script.antiLureCheckInterval)
9286 _script.antiLureCheckInterval = nil
9287 end
9288 resumeWalker()
9289 end
9290 end, waitTime and tonumber(waitTime) or 30000)
9291 end
9292 end,
9293
9294 -- Anti-Lure Spy System
9295 ['spy'] = function(group, floor, label, range, amount, list)
9296 -- Pause walker
9297 delayWalker()
9298
9299 local creatures = list and indexTable(split(list, ','), true) or _config['Anti Lure']['Creatures']
9300 if not creatures then
9301 resumeWalker()
9302 end
9303
9304 local firstCheck = true
9305 local function checkFloor()
9306 local threshold = targetingGetCreatureThreshold(
9307 creatures,
9308 range and tonumber(range) or _config['Anti Lure']['Range'],
9309 amount and tonumber(amount) or _config['Anti Lure']['Amount'],
9310 false,
9311 tonumber(floor)
9312 )
9313
9314 -- Safe
9315 if not threshold[1] then
9316 if not firstCheck then
9317 setTimeout(function()
9318 resumeWalker()
9319 end, pingDelay(DELAY.ANTILURE_DELAY))
9320 else
9321 resumeWalker()
9322 end
9323
9324 -- Not safe, bitch label found, run
9325 elseif label and label ~= 'nil' then
9326 xeno.gotoLabel(label)
9327 resumeWalker()
9328 -- Not safe, wait until it is
9329 else
9330 firstCheck = false
9331 setTimeout(checkFloor, 200)
9332 end
9333 end
9334
9335 checkFloor()
9336 end,
9337
9338 -- Traveling (travel|ankrahmun~svargrond)
9339 ['travel'] = function(group, route)
9340 delayWalker()
9341 walkerTravel(route, function()
9342 resumeWalker()
9343 end, true)
9344 end,
9345
9346 -- Edge cases
9347 ['special'] = function(group, param1, param2)
9348 -- Rafzan Shop (in-spawn shop)
9349 if param1 == 'RafzanTrade' then
9350 delayWalker()
9351 walkerReachNPC('Rafzan', function()
9352 shopSellLoot({
9353 [17846] = true,
9354 [17813] = true,
9355 [17812] = true,
9356 [17810] = true,
9357 [17859] = true
9358 }, function()
9359 resumeWalker()
9360 end)
9361 end)
9362 -- Elemental Soils Shop (in-spawn shop)
9363 elseif param1 == 'SoilsTrade' then
9364 delayWalker()
9365 walkerReachNPC('Arkulius', function()
9366 shopSellLoot({
9367 [940] = true, -- natural soil
9368 [941] = true, -- glimmering soil
9369 [942] = true, -- flawless ice crystal
9370 [944] = true, -- iced soil
9371 [945] = true, -- energy soil
9372 [946] = true, -- eternal flames
9373 [947] = true, -- mother soil
9374 [948] = true, -- pure energy
9375 [954] = true -- neutral matter
9376 }, function()
9377 resumeWalker()
9378 end)
9379 end)
9380 elseif param1 == 'SpheresMachine' then
9381 delayWalker()
9382 local gemItemId = tonumber(param2) or 678
9383 local tries = 0
9384 local depositInterval = nil
9385 local function depositGem()
9386 -- Drop gem in machine
9387 xeno.selfUseItemWithGround(gemItemId, 33269, 31830, 10)
9388 local pos = xeno.getSelfPosition()
9389 setTimeout(function()
9390 -- Attempt to use machine
9391 xeno.selfUseItemFromGround(33269, 31830, 10)
9392 -- Teleported
9393 if getDistanceBetween(xeno.getSelfPosition(), pos) > 10 then
9394 -- Stop gem dropper & resume bot
9395 clearTimeout(depositInterval)
9396 resumeWalker()
9397 end
9398 end, DELAY.CONTAINER_USE_ITEM)
9399 end
9400 depositInterval = setInterval(depositGem, DELAY.CONTAINER_USE_ITEM)
9401 -- Use nearest Yalahar gate mechanism, verify position change
9402 elseif param1 == 'YalaharMech' then
9403 delayWalker()
9404 local pos = xeno.getSelfPosition()
9405 local function useMech()
9406 walkerUseClosestItem({[8615]=true, [8618]=true})
9407 setTimeout(function()
9408 if getDistanceBetween(xeno.getSelfPosition(), pos) > 3 then
9409 resumeWalker()
9410 else
9411 useMech()
9412 end
9413 end, pingDelay(DELAY.MAP_USE_ITEM))
9414 end
9415 useMech()
9416 elseif param1 == 'PythiusMug' then
9417 delayWalker()
9418 local mugs = getTotalItemCount(ITEMID.GOLDEN_MUG)
9419 local dialog = {'hi', 'offer', 'yes', 'golden mug'}
9420 walkerReachNPC('Pythius the Rotten', function()
9421 talk(dialog, function()
9422 setTimeout(function()
9423 resumeWalker()
9424 if getTotalItemCount(ITEMID.GOLDEN_MUG) >= mugs then
9425 xeno.gotoLabel('special|PrePythiusMug')
9426 end
9427 end, pingDelay(DELAY.RANGE_TALK))
9428 end)
9429 end)
9430 end
9431 end
9432 }
9433
9434 local _huntingForDepot = false
9435 local _snapbacks = 0
9436 local _lastsnapback = 0
9437 local _lastposition = xeno.getSelfPosition()
9438 local _walkerStuckScreenshotInterval = nil
9439
9440 function onTick()
9441 if not _script.ready then return end
9442
9443 -- Backpacks were closed, we just logged in.
9444 if not _script.openingContainers and not xeno.getContainerOpen(0) then
9445 local inProtectionZone = xeno.getSelfFlag('inpz')
9446 -- Disable walker, looter, and targeter
9447 delayWalker()
9448 xeno.setLooterEnabled(false)
9449 xeno.setTargetingEnabled(false)
9450 -- If in PZ, we enable after we setup containers
9451 resetContainers(function()
9452 if inProtectionZone then
9453 resumeWalker()
9454 xeno.setLooterEnabled(true)
9455 xeno.setTargetingEnabled(true)
9456 end
9457 end)
9458 -- Otherwise, we wait 10 seconds
9459 if not inProtectionZone then
9460 setTimeout(function()
9461 resumeWalker()
9462 xeno.setLooterEnabled(true)
9463 xeno.setTargetingEnabled(true)
9464 end, 10000)
9465 end
9466 end
9467
9468 -- Check and execute timers
9469 checkTimers()
9470
9471 -- Update conditions (that need polling)
9472 _script.stuck = xeno.getWalkerStuck()
9473 _script.ignoring = xeno.getTargetingIgnoring()
9474
9475 -- We're stuck, take a screenshot in 10 seconds if we are still stuck then
9476 if _script.stuck and not _walkerStuckScreenshotInterval then
9477 _walkerStuckScreenshotInterval = setTimeout(function()
9478 xeno.setDiagnosticsEnabled(1)
9479 local pos = xeno.getSelfPosition()
9480 setTimeout(function()
9481 xeno.screenshot(('stuck-%d-%d-%d'):format(pos.x, pos.y, pos.z))
9482 xeno.setDiagnosticsEnabled(0)
9483 end, 1000)
9484 _walkerStuckScreenshotInterval = nil
9485 end, 10 * 1000)
9486
9487 -- No longer stuck, cancel the screenshot
9488 elseif not _script.stuck and _walkerStuckScreenshotInterval then
9489 clearTimeout(_walkerStuckScreenshotInterval)
9490 _walkerStuckScreenshotInterval = nil
9491 end
9492
9493 -- Anti-lure checks
9494 if _config['Anti Lure'] and _config['Anti Lure']['Amount'] and _config['Anti Lure']['Amount'] > 0 then
9495 -- Not already running away like a little bitch
9496 if not _script.ignoring then
9497 local setIgnore = setTargetingIgnoreEnabled
9498 local threshold = targetingGetCreatureThreshold(
9499 _config['Anti Lure']['Creatures'],
9500 _config['Anti Lure']['Range'],
9501 _config['Anti Lure']['Amount'],
9502 false)
9503 -- Threshold met, goto run path
9504 if threshold[1] then
9505 setIgnore(true)
9506 xeno.attackCreature(0)
9507 log('Anti-lure triggered. Returning to safety.')
9508 local closestPath = walkerGetClosestLabel(false, 'run')
9509 if closestPath then
9510 xeno.gotoLabel(closestPath.name)
9511 end
9512 end
9513 end
9514 end
9515
9516 -- Initiate path clearing process
9517 if _script.stuck then
9518 -- Finding a depot, try next one
9519 if _script.findingDepot then
9520 -- Not already searching
9521 if not _huntingForDepot then
9522 _script.findingDepot = _script.findingDepot + 1
9523 local label = 'depot|' .. string.lower(_script.town) .. '|' .. _script.findingDepot
9524 -- Depot exists, try walking to it
9525 if walkerLabelExists(label) then
9526 xeno.gotoLabel(label)
9527 -- Prevent stuck event from firing this same action immediately
9528 _huntingForDepot = true
9529 setTimeout(function()
9530 _huntingForDepot = false
9531 end, 3000)
9532 -- No more depots, go to a depotend, most likely to fail
9533 else
9534 xeno.gotoLabel('depotend')
9535 end
9536 end
9537 -- Go to return label if set
9538 elseif _script.stuckReturnLabel then
9539 xeno.gotoLabel(_script.stuckReturnLabel)
9540 _script.stuckReturnLabel = nil
9541 if _script.stuckReturnTimer then
9542 clearTimeout(_script.stuckReturnTimer)
9543 end
9544 end
9545 throttle(THROTTLE_CLEAR_PATH, clearWalkerPath)
9546 end
9547
9548 if _config['HUD'] and _config['HUD']['Enabled'] then
9549 -- Time related statistics
9550 local timediff = os.time() - _script.start
9551 local serverSave = getTimeUntilServerSave() * 3600
9552 local playerStamina = xeno.getSelfStamina() * 60
9553 local logoutConfig = _config['Logout']
9554 local simpleTime = _config['HUD'] and _config['HUD']['Simple-Time-Format']
9555
9556 hudItemUpdate('General', 'Balance', _script.balance, true)
9557 hudItemUpdate('General', 'Online Time', formatTime(timediff, simpleTime), true)
9558
9559 local timeLimit = logoutConfig['Time-Limit'] or 0
9560 local ssLimit = logoutConfig['Server-Save'] or 0
9561 local stamLimit = logoutConfig['Stamina'] or 0
9562 local remaining = {}
9563
9564 if timeLimit > 0 then
9565 remaining[#remaining+1] = (timeLimit * 3600) - (os.time() - _script.start)
9566 end
9567
9568 if serverSave > 0 then
9569 remaining[#remaining+1] = serverSave - (ssLimit * 3600)
9570 end
9571
9572 if stamLimit > 0 then
9573 remaining[#remaining+1] = playerStamina - (stamLimit * 3600)
9574 end
9575
9576 -- Sort remaining times (lowest first)
9577 table.sort(remaining)
9578
9579 -- Choose lowest remaining time to display
9580 local remainingTime = remaining[1]
9581 if remainingTime then
9582 hudItemUpdate('General', 'Time Remaining', formatTime(remainingTime, simpleTime), true)
9583 end
9584
9585 hudItemUpdate('General', 'Server Save', formatTime(serverSave, simpleTime), true)
9586 hudItemUpdate('General', 'Stamina', formatTime(playerStamina, simpleTime), true)
9587
9588 local ping = xeno.ping()
9589 if ping ~= _script.pingLast then
9590 _script.pingSum = _script.pingSum + ping
9591 _script.pingEntries = _script.pingEntries + 1
9592 _script.pingLast = ping
9593 end
9594
9595 if _script.pingEntries > 0 then
9596 hudItemUpdate('General', 'Latency', ping .. ' ms (avg: ' .. math.floor((_script.pingSum / _script.pingEntries) + 0.5) .. ')', true)
9597 end
9598
9599 -- Update experience gain
9600 local gain = xeno.getSelfExperience() - _script.baseExp
9601 local staminadiff = _script.startStamina - xeno.getSelfStamina()
9602 staminadiff = (staminadiff > 0 and staminadiff or 1) * 60
9603 local useStaminaMeasure = _config['HUD']['Per-Stamina-Measurement']
9604 local diff = useStaminaMeasure and staminadiff or timediff
9605 local hourlyexp = tonumber(math.floor(gain / (diff / 3600))) or 0
9606 local hourPostfix = useStaminaMeasure and ' xp/sh' or ' xp/h'
9607 hudItemUpdate('Statistics', 'Experience', formatNumber(gain) .. ' xp', true)
9608 hudItemUpdate('Statistics', 'Hourly Exp', formatNumber(hourlyexp) .. hourPostfix, true)
9609
9610 -- Update profit
9611 local totalLooted = _hud.index['Statistics']['Looted'].value
9612 local totalWaste = _hud.index['Statistics']['Wasted'].value
9613 local profit = totalLooted - totalWaste
9614 local hourlygain = tonumber(math.floor(profit / (diff / 3600))) or 0
9615 local hourPostfix = useStaminaMeasure and ' gp/sh' or ' gp/h'
9616 hudItemUpdate('Statistics', 'Profit', formatNumber(profit) .. ' gp', true)
9617 hudItemUpdate('Statistics', 'Hourly Profit', formatNumber(hourlygain) .. hourPostfix, true)
9618
9619 -- Update level stats
9620 local playerLevel = xeno.getSelfLevel()
9621 local targetLevel = playerLevel + 1
9622 local expLeft = ((50/3) * (targetLevel^3) - (100 * targetLevel^2) + ((850/3) * targetLevel) - 200) - xeno.getSelfExperience()
9623 hudItemUpdate('Statistics', 'Exp to Level', formatNumber(expLeft) .. ' xp', true)
9624 hudItemUpdate('Statistics', 'Time to Level', hourlyexp > 0 and formatTime(expLeft / (gain / timediff), simpleTime) or formatTime(0, simpleTime), true)
9625
9626 -- Update state
9627 hudItemUpdate('Script', 'State', _script.state, true)
9628 hudItemUpdate('Script', 'Reason', _script.reason, true)
9629 hudItemUpdate('Script', 'Route', _script.route, true)
9630 hudItemUpdate('Script', 'Round', tostring(_script.round), true)
9631
9632 local targetState = 'Disabled'
9633 if _script.ignoring then
9634 targetState = 'Ignoring'
9635 elseif xeno.getXenoBotStatus()["targeting"] then
9636 targetState = 'Enabled'
9637 end
9638
9639 hudItemUpdate('Script', 'Targeter', targetState, true)
9640
9641 local walkerState = 'Disabled'
9642 if _script.stuck then
9643 walkerState = 'Stuck'
9644 elseif xeno.getWalkerLuring() then
9645 walkerState = 'Luring'
9646 elseif xeno.getXenoBotStatus()["walker"] then
9647 walkerState = 'Enabled'
9648 end
9649
9650 hudItemUpdate('Script', 'Walker', walkerState, true)
9651
9652 -- Loot & Supply Polling
9653 hudQueryLootChanges()
9654 hudQuerySupplyChanges()
9655
9656 hudUpdateDimensions()
9657 hudUpdatePositions()
9658 end
9659
9660 --[[ Change gold (only on Open Tibia)
9661 if xeno.isRealTibia() ~= 1 then
9662 local mainbp = _backpacks['Main']
9663 local goldbp = _backpacks['Gold']
9664 local backpacks = {goldbp}
9665 if mainbp ~= goldbp then
9666 backpacks[#backpacks+1] = mainbp
9667 end
9668 for i = 1, #backpacks do
9669 local bp = backpacks[i]
9670 local used = false
9671 for spot = 0, xeno.getContainerItemCount(bp) - 1 do
9672 local item = xeno.getContainerSpotData(bp, spot)
9673 if item.count >= 100 and ITEM_LIST_MONEY[item.id] and item.id ~= 3043 then
9674 -- Use stack to change gold
9675 xeno.containerUseItem(bp, spot)
9676 -- Stop, we'll get the next one next tick
9677 used = true
9678 break
9679 end
9680 if used then break end
9681 end
9682 end
9683 end]]
9684
9685 -- Unruster
9686 unrustLoot()
9687
9688 -- Script crashed, safely walk to exit
9689 if _script.crashed then
9690 _script.crashed = nil
9691 local msg = 'Walking to depot to stop script.'
9692 local townPositions = sortPositionsByDistance(xeno.getSelfPosition(), TOWN_POSITIONS)
9693 local town = townPositions[1].name:lower()
9694 log(msg)
9695 print(msg)
9696 xeno.setWalkerEnabled(true)
9697 walkerGotoLocation(town, 'depot', function()
9698 assert(false, msg)
9699 end)
9700 end
9701 end
9702
9703 function onLabel(name)
9704 if not _script.ready then return end
9705
9706
9707 -- Path end event
9708 if name == 'end' then
9709 delayWalker()
9710 checkEvents(EVENT_PATH_END, _path)
9711 _path = {town = nil, route = nil, town = nil, from = nil, to = nil}
9712
9713 return
9714 end
9715
9716 -- Depot end event
9717 if name == 'depotend' then
9718 delayWalker()
9719 checkEvents(EVENT_DEPOT_END, 'depotend')
9720
9721 return
9722 end
9723
9724 -- Split label
9725 local label = split(name, '|')
9726 local group = label and label[1]
9727
9728 -- Path start, parse
9729 if group ~= 'travel' and string.find(name, '~') then
9730 local pathData = split(name, '|')
9731 local town = pathData[1]
9732 local route = pathData[2]
9733
9734 local routeData = split(route, '~')
9735 local from = routeData[1]
9736 local to = routeData[2]
9737
9738 -- Update path
9739 _path = {town = town, route = route, town = town, from = from, to = to}
9740
9741 return
9742 end
9743
9744 -- Reached the end of a route
9745 if group:sub(1,3) == 'No-' then
9746 -- False route matches current route
9747 if group:sub(4):lower() == _script.route:lower() then
9748 _script.route = '--';
9749 end
9750 end
9751
9752 -- Label events
9753 local event = LABEL_ACTIONS[group] or _events[EVENT_LABEL][group]
9754 if event then
9755 event(unpack(label))
9756 end
9757
9758 end
9759
9760 function onErrorMessage(message)
9761 if not _script.ready then return end
9762
9763
9764 checkEvents(EVENT_ERROR, message)
9765
9766 -- Snap back
9767 if message == ERROR_NOT_POSSIBLE then
9768 -- Only trigger in spawn
9769 if _script.inSpawn then
9770 local time = os.clock() * 1000
9771 local pos = xeno.getSelfPosition()
9772 -- It's been longer than 2 seconds, reset snapbacks
9773 if time - _lastsnapback > 2000 then
9774 _snapbacks = 0
9775 -- Moved since the last snapback
9776 elseif getDistanceBetween(_lastposition, pos) > 0 then
9777 _snapbacks = 0
9778 end
9779
9780 -- Save time and increment snapbacks by one
9781 _lastsnapback = time
9782 _lastposition = pos
9783 _snapbacks = _snapbacks + 1
9784 -- If snapbacks are at 5 or more, assume an invisible monster is blocking
9785 if _snapbacks >= 5 then
9786 if _config['General']['Invis-Anti-Stuck'] then
9787 local pvpsafe = true
9788 for i = CREATURES_LOW, CREATURES_HIGH do
9789 local cpos = xeno.getCreaturePosition(i)
9790 local distance = getDistanceBetween(pos, cpos)
9791 -- Same or 1 floor away, 2sqms away
9792 if math.abs(pos.z - cpos.z) <= 1 and distance <= 2 then
9793 -- Normal creature checks
9794 if xeno.getCreatureVisible(i) and xeno.getCreatureHealthPercent(i) > 0 and xeno.isCreaturePlayer(i) then
9795 pvpsafe = false
9796 end
9797 end
9798 end
9799
9800 -- TODO: free account alternative (wait for "You must learn this spell first" or "You need a premium account" error message)
9801 if pvpsafe then
9802 if _script.vocation == 'Paladin' then
9803 cast('exori san')
9804 elseif _script.vocation == 'Knight' then
9805 cast('exori')
9806 else
9807 cast('exori frigo')
9808 end
9809 end
9810 end
9811 _snapbacks = 0
9812 end
9813
9814 end
9815 end
9816 end
9817
9818 function onLootMessage(message)
9819 if not _script.ready then return end
9820
9821
9822 -- Check if we need to pump mana
9823 local manaRestorePercent = _config['Mana Restorer']['Restore-Percent']
9824 local potionName = _config['Potions']['ManaName']
9825 local playerPos = xeno.getSelfPosition()
9826 if manaRestorePercent and manaRestorePercent > 0 and potionName then
9827 -- Mana below threshold
9828 local playerMana = math.abs((xeno.getSelfMana() / xeno.getSelfMaxMana()) * 100)
9829 if playerMana <= manaRestorePercent then
9830 -- Check for monsters
9831 local potionid = type(potionName) == 'string' and xeno.getItemIDByName(potionName) or potionName
9832 local monsterOnScreen = false
9833 for i = CREATURES_LOW, CREATURES_HIGH do
9834 local cpos = xeno.getCreaturePosition(i)
9835 if cpos then
9836 local distance = getDistanceBetween(playerPos, cpos)
9837 if playerPos.z == cpos.z and distance <= _config['Mana Restorer']['Range'] then
9838 if xeno.getCreatureVisible(i) and xeno.getCreatureHealthPercent(i) > 0 and xeno.isCreatureMonster(i) then
9839 monsterOnScreen = true
9840 break
9841 end
9842 end
9843 end
9844 end
9845 -- No monsters on screen
9846 if not monsterOnScreen then
9847 -- Stop walker to pump
9848 delayWalker()
9849 walkerRestoreMana(potionid, manaRestorePercent, function()
9850 -- Mana restored, continue walking
9851 resumeWalker()
9852 end)
9853 end
9854 end
9855 end
9856
9857 -- Check if we need to drop anything
9858 throttle(THROTTLE_CAP_DROP, walkerCapacityDrop)
9859
9860 checkEvents(EVENT_LOOT, message)
9861
9862 end
9863
9864 local _battleMsgRunning = false
9865 function onBattleMessage(message)
9866 if _battleMsgRunning then return end
9867 if not _script.ready then return end
9868
9869 _battleMsgRunning = true
9870
9871 -- What should we equip/un-equip (slot=itemid)
9872 local equip = {}
9873 local unequip = {}
9874
9875 local playerPos = xeno.getSelfPosition()
9876 local playerRing = xeno.getRingSlotData().id
9877 local playerAmulet = xeno.getAmuletSlotData().id
9878 local playerHealth = math.abs((xeno.getSelfHealth() / xeno.getSelfMaxHealth()) * 100)
9879 local playerMana = math.abs((xeno.getSelfMana() / xeno.getSelfMaxMana()) * 100)
9880
9881 -- Collect all valid creatures
9882 local validCreaturesIndexes = {}
9883 for i = CREATURES_LOW, CREATURES_HIGH do
9884 -- Position & Distance
9885 local cpos = xeno.getCreaturePosition(i)
9886 local distance = getDistanceBetween(playerPos, cpos)
9887 if playerPos.z == cpos.z and distance <= 7 then
9888 -- Normal creature checks
9889 if xeno.getCreatureVisible(i) and xeno.getCreatureHealthPercent(i) > 0 and xeno.isCreatureMonster(i) then
9890 table.insert(validCreaturesIndexes, i)
9891 end
9892 end
9893 end
9894
9895 for itemid, supply in pairs(_supplies) do
9896 -- Check rings & amulets
9897 if supply.group == 'Ring' or supply.group == 'Amulet' and supply.options then
9898
9899 -- Thresholds
9900 local triggered = false
9901 local count = supply.options['CreatureCount'] or 0
9902 local health = supply.options['MinHP'] or 0
9903 local mana = supply.options['MinMP'] or 0
9904 local ignore = count == 0 and health == 0 and mana == 0
9905
9906 if not ignore then
9907
9908 local targets = supply.options['Creatures'] or {}
9909
9910 -- Count only monsters in the config
9911 local creatures = 0 -- Moved into the loops to fix a bug. Remember to remove the earlier definition.
9912 if count > 0 then
9913 for _, i in ipairs(validCreaturesIndexes) do
9914 local name = xeno.getCreatureName(i)
9915 debug('ring equipper - name: ' .. name)
9916 if targets[name:lower()] then
9917 creatures = creatures + 1
9918 debug('ring equipper - creature #: ' .. creatures)
9919 end
9920 end
9921 end
9922
9923 local slotItem = supply.group == 'Ring' and playerRing or playerAmulet
9924 -- We need to equip the ring
9925 if (count > 0 and creatures >= count) or (health > 0 and playerHealth <= health) or (mana > 0 and playerMana <= mana) then
9926 -- Only equip if we don't have it on already
9927 if slotItem ~= itemid and slotItem ~= ITEM_LIST_ACTIVE_RINGS[itemid] then
9928 equip[supply.group] = itemid
9929 end
9930 -- We need to un-equip the ring
9931 elseif slotItem == itemid or slotItem == ITEM_LIST_ACTIVE_RINGS[itemid] then
9932 unequip[supply.group] = itemid
9933 end
9934 end
9935 end
9936 end
9937
9938 -- Equip all queued supplies
9939 for group, itemid in pairs(equip) do
9940 -- Search for the item to equip
9941 local backpack = _backpacks['Supplies']
9942 local slot = group == 'Ring' and "ring" or "amulet"
9943 for spot = 0, xeno.getContainerItemCount(backpack) - 1 do
9944 local item = xeno.getContainerSpotData(backpack, spot)
9945 -- Equip item
9946 if item.id == itemid then
9947 xeno.containerMoveItemToSlot(backpack, spot, slot, -1)
9948 _script.equipped[slot] = true;
9949 -- If this group exists in unequip, remove it, as it will be removed on equip.
9950 if unequip[group] then
9951 unequip[group] = nil
9952 end
9953 break
9954 end
9955 end
9956 end
9957
9958 -- Un-equip all queued supplies
9959 for group, itemid in pairs(unequip) do
9960 local slot = group == 'Ring' and "ring" or "amulet"
9961 xeno.slotMoveItemToContainer(slot, _backpacks['Supplies'], 0)
9962 _script.equipped[slot] = false;
9963 end
9964
9965 checkSoftBoots()
9966 checkEvents(EVENT_BATTLE, message)
9967
9968 _battleMsgRunning = false
9969 end
9970
9971 function onChannelSpeak(channel, message)
9972 -- Do not check if the script is event ready
9973 -- prompts are needed beforehand
9974
9975 -- First character is slash, command is expected
9976 if string.sub(message, 1, 1) == '/' then
9977 -- Clear last console message, so we can repeat ourselves
9978 _lastConsoleMessage = nil
9979 local params = split(string.sub(message, 2), ' ')
9980 local command = params and params[1]
9981 -- Help command
9982 if command == 'help' then
9983 log([[Available commands:
9984 /resupply = Forces the script to return to town after the current round.
9985 /logout = Forces the script to return to town and logout after the current round.
9986 /config = Opens the config file for this script.
9987 /resethud = Reset the session start time.
9988 /history = Opens a channel to monitor received private messages.
9989 /debug = Opens a debug channel with more verbose logging.]])
9990 -- Clear memory
9991 elseif command == 'freemem' then
9992 local bytes = freeMemory()
9993 log('Released ' .. bytes .. ' bytes of allocated RAM.')
9994 -- Open config
9995 elseif command == '' then
9996 -- Open debug channel
9997 elseif command == 'debug' then
9998 openDebugChannel()
9999 -- Open private message history channel
10000 elseif command == 'history' then
10001 openPrivateMessageConsole()
10002 --[[elseif command == 'theme' then
10003 local themeName = params[2]
10004 local theme = themeName and THEME[themeName]
10005 if theme then
10006 _script.theme = themeName
10007 log('HUD theme set to ' .. themeName .. '.')
10008 else
10009 log('Invalid HUD theme name. [light, dark]')
10010 end]]
10011 -- Reset session time
10012 elseif command == 'resethud' then
10013 _script.start = os.time()
10014 log('Reset script start time.')
10015 elseif command == 'config' then
10016 local configName = '[' .. getSelfName() .. '] ' .. _script.name
10017 xeno.showConfigEditor(configName)
10018 -- Force logout
10019 elseif command == 'logout' then
10020 _script.forceLogoutQueued = true
10021 log('Returning to town to logout after the current round.')
10022 -- Force resupply
10023 elseif command == 'resupply' then
10024 _script.returnQueued = true
10025 log('Returning to town after the current round.')
10026 elseif command == 'refill' then
10027 _script.returnQueued = true
10028 log('Returning to town after the current round.')
10029 -- TODO: implement a more modular reload system in RC2
10030 -- Reload config
10031 --[[elseif command == 'reload' then
10032 _config = {}
10033 _supplies = {}
10034 loadConfigFile(function()
10035 setupContainers(function()
10036 setDynamicSettings(function()
10037 log('Reloaded the config.')
10038 -- If reloaded in town, trigger resupply
10039 if _script.state == 'Resupplying' or _script.state == 'Starting' then
10040 log('Triggering resupply incase supply counts may have changed.')
10041 resupply()
10042 end
10043 end)
10044 end)
10045 end, true)--]]
10046 end
10047 -- Spell, forward to default channel
10048 elseif isSpell(message:lower()) then
10049 xeno.selfSay(message)
10050 -- Not command, handle as usual
10051 else
10052 checkEvents(EVENT_COMMAND, message)
10053 end
10054
10055 end
10056
10057 function onLogoutEvent()
10058 if not _script.ready then return end
10059 _script.forceLogoutQueued = true
10060 log('Returning to town to logout after the current round. [Xeno Monitor]')
10061 end
10062
10063 function onChannelClose(channel)
10064 if not _script.ready then return end
10065
10066 if _script.channel and tonumber(_script.channel) == tonumber(channel) then
10067 openConsole()
10068 elseif _script.historyChannel and tonumber(_script.historyChannel) == tonumber(channel) then
10069 _script.historyChannel = nil
10070 end
10071
10072 end
10073
10074 function onPrivateMessage(name, level, message)
10075 if not _script.ready then return end
10076
10077
10078 -- TODO: filter spam
10079 -- TODO: alarm and red text for configurable words
10080 if _script.historyChannel then
10081 xeno.luaSendChannelMessage(_script.historyChannel, CHANNEL_ORANGE, name .. ' ['..level..']', message)
10082 end
10083
10084 end
10085
10086 function onNpcMessage(name, message)
10087 if not _script.ready then return end
10088
10089 checkEvents(EVENT_NPC, message, name)
10090
10091 end
10092
10093 function onContainerChange(index, title, id)
10094 if not _script.ready then return end
10095 -- Trigger on the next tick cycle
10096 -- we need to give the client time to update
10097 -- on RL Tibia we artificially delay this time for safety reasons
10098 local delay = xeno.isRealTibia() == 1 and pingDelay(DELAY.CONTAINER_WAIT) or 0
10099 setTimeout(function()
10100
10101 checkEvents(EVENT_CONTAINER, index, title, id)
10102
10103 end, delay)
10104 end
10105
10106 xeno.registerNativeEventListener(TIMER_TICK, 'onTick')
10107 xeno.registerNativeEventListener(WALKER_SELECTLABEL, 'onLabel')
10108 xeno.registerNativeEventListener(ERROR_MESSAGE, 'onErrorMessage')
10109 xeno.registerNativeEventListener(LOOT_MESSAGE, 'onLootMessage')
10110 xeno.registerNativeEventListener(BATTLE_MESSAGE, 'onBattleMessage')
10111 xeno.registerNativeEventListener(EVENT_SELF_CHANNELSPEECH, 'onChannelSpeak')
10112 xeno.registerNativeEventListener(LOGOUT_COMMAND, 'onLogoutEvent')
10113 xeno.registerNativeEventListener(EVENT_SELF_CHANNELCLOSE, 'onChannelClose')
10114 xeno.registerNativeEventListener(PRIVATE_MESSAGE, 'onPrivateMessage')
10115 xeno.registerNativeEventListener(NPC_MESSAGE, 'onNpcMessage')
10116 xeno.registerNativeEventListener(CONTAINER_OPEN, 'onContainerChange')
10117end
10118--local global = _G
10119--local env = {}
10120--setfenv(1, env)
10121
10122local function init()
10123 xeno.setWalkerEnabled(false)
10124
10125 -- Made in Texas with love <3
10126 xeno.HUDCreateText(65, 33, 'X E N O B O T', 0, 0, 0)
10127 xeno.HUDCreateText(66, 32, 'X E N O B O T', 255, 255, 255)
10128
10129 -- Imports
10130 local loadMasterConfig = Ini.loadMasterConfig
10131 local loadConfigFile = Ini.loadConfigFile
10132 local checkChainRules = Ini.checkChainRules
10133 local hudInit = Hud.hudInit
10134 local checkSoftBoots = Core.checkSoftBoots
10135 local isXenoBotBinary = Core.isXenoBotBinary
10136 local getXenoVersion = Core.getXenoVersion
10137 local openConsole = Console.openConsole
10138 local openDebugChannel = Console.openDebugChannel
10139 local sortPositionsByDistance = Core.sortPositionsByDistance
10140 local log = Console.log
10141 local setupContainers = Container.setupContainers
10142 local walkerGetTownExit = Walker.walkerGetTownExit
10143 local walkerGetTownEntrance = Walker.walkerGetTownEntrance
10144 local loadSettingsFile = Settings.loadSettingsFile
10145 local setDynamicSettings = Settings.setDynamicSettings
10146 local resupply = Supply.resupply
10147 local walkerGetClosestLabel = Walker.walkerGetClosestLabel
10148
10149 _script.chainConfig = loadMasterConfig()
10150
10151 -- Immediately check chaining rules to see if we
10152 if _script.chainConfig then
10153 if checkChainRules(_script.chainConfig, true) then
10154 return
10155 end
10156 end
10157
10158 -- Only allow XenoBot Binary
10159 if getXenoVersion() < MINIMUM_XENO_VERSION then
10160 print('You are using an older version of XenoBot. Update to the latest version of XenoBot to run this script.')
10161 return
10162 end
10163
10164 -- Create channel
10165 openConsole()
10166
10167 if DEBUG_ACCOUNTS[xeno.getUserName():lower()] then
10168 openDebugChannel()
10169 end
10170
10171 -- Load config.ini
10172 loadConfigFile(function()
10173 -- Grab screen dimensions
10174 if _config['HUD']['Enabled'] then
10175 hudInit()
10176 end
10177 -- Ready for events
10178 _script.ready = true
10179 -- Only continue after containers are setup
10180 setupContainers(function()
10181 -- Check EQ related stuff
10182 checkSoftBoots()
10183
10184 -- Load XBST
10185 loadSettingsFile(function()
10186 -- Modify XBST
10187 xeno.setWalkerEnabled(false)
10188 setDynamicSettings(function()
10189 xeno.setWalkerEnabled(true)
10190 -- Detect town exit
10191 walkerGetTownExit()
10192 walkerGetTownEntrance()
10193
10194 -- Check if we're in the spawn or in town
10195 local position = xeno.getSelfPosition()
10196 local townPositions = sortPositionsByDistance(xeno.getSelfPosition(), TOWN_POSITIONS)
10197 local town = townPositions[1].name
10198 local startLabel = walkerGetClosestLabel(true, town:lower())
10199 local huntStart = false
10200 if not startLabel or getDistanceBetween(position, startLabel) > 30 then
10201 startLabel = walkerGetClosestLabel(false, 'huntstart')
10202 huntStart = true
10203 if not startLabel or getDistanceBetween(position, startLabel) > 30 then
10204 error('Too far from any start point. Restart the script closer to a town.')
10205 return
10206 end
10207 end
10208
10209 -- Start the walker in the spawn or in town
10210 if huntStart then
10211 xeno.gotoLabel(startLabel.name)
10212 _script.state = 'Hunting';
10213 _script.inSpawn = true;
10214 else
10215 resupply()
10216 end
10217 end)
10218 end)
10219 end)
10220 end)
10221end
10222
10223init()