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