-- ELORIA / ZEROBOT HUD 6.4 — MINING TELEPORT GUARD / GameData(7) -- Paste this entire file into one EloriaBot/ZeroBot scripting slot. -- Uses only the documented sandbox API; no client file modification. -- Boss Sequence requires the Game.bossSequenceStart extension in GD7. -- Native profit/loot/supply counters are NOT exported by this sandbox. -- Damage below is explicitly the sum of received DAMAGE_DEALT messages. local CONFIG={x=65,y=100,font=12,rowHeight=20,mainSprite=24875,tileSize=30,tileGap=22} local E = {version='6.4', enabled={}, fish={}, expanded='runners', widgets={}, timers={}, status='Gotowy', errors={}, due={}, visits={}, blocked={}, rockDone={}, session={}, boss={category=1,wave=5,stage='IDLE',statue={x=31595,y=17406,z=5},tried={}}, dungeon={index=1,stage='IDLE',statue={x=14232,y=19348,z=3},skipped=0}, bosstiary={index=1,room=1,stage='APPROACH'}, mining={stage='IDLE'}, alive=true} -- Drome Potions: standalone logic integrated into this HUD. E.drome={ ids={36740,36737,36724,36739,36736,36742,36741,36738}, masterIconId=36737, cooldown=60*60*1000, auto=false, nextUse={}, lastAction=0 } local C={text='#EEF4FF',muted='#8C96BE',on='#46FF9B',off='#FF4673',blue='#41DCFF',red='#FF4673'} local dirs={{0,-1,0},{1,0,1},{0,1,2},{-1,0,3},{1,-1,4},{1,1,5},{-1,1,6},{-1,-1,7}} local fishModes={ {name='Fish',ids={4602,4597,4599,4601,629,4600,622},sprite=3483}, {name='Shimmer',ids={12560},catchId=12557,sprite=12557}, {name='Sandfish',ids={13988},catchId=13992,sprite=13992}, {name='Old Nasty',ids={12560},catchId=21402,baitId=3492,sprite=21402}, {name='Rainbow',ids={7236},catchId=7158,sprite=7158}, {name='Northern',ids={7236},catchId=3580,sprite=3580} } -- Fishing: tylko jeden wybrany typ naraz. do local first=nil for i=1,#fishModes do if E.fish[i] and not first then first=i end end first=first or 1 for i=1,#fishModes do E.fish[i]=(i==first) end end local fishCursor=0 E.clock=os.time()*1000 local function now() return E.clock end local function pos() return Player.getPosition() end local function copy(p) return p and {x=p.x,y=p.y,z=p.z} end local function key(p) return p.x..':'..p.y..':'..p.z end local function dist(a,b) if not a or not b or a.z~=b.z then return 99999 end return math.max(math.abs(a.x-b.x),math.abs(a.y-b.y)) end local function same(a,b) return a and b and a.x==b.x and a.y==b.y and a.z==b.z end local function teleport(a,b) return a and b and (a.z~=b.z or dist(a,b)>4) end local function due(k,ms) local t=now(); if t<(E.due[k] or 0) then return false end E.due[k]=t+ms; return true end local function status(s) E.status=s end local function safe(tag,fn) if now()<(E.errors[tag..'retry'] or 0) then return end local ok,err=pcall(fn) if not ok then E.errors[tag]=(E.errors[tag] or 0)+1; E.errors[tag..'retry']=now()+5000 status(tag..': '..tostring(err):sub(1,110)) print('HUD '..tag..': '..tostring(err)) if E.errors[tag]>=3 then E.enabled[tag]=false end else E.errors[tag]=0 end end E.api={Game=Game,Map=Map} local function apiReady() return true end local function count(k,n) E.session[k]=(E.session[k] or 0)+(n or 1) end local function short(n) n=tonumber(n) or 0 if math.abs(n)>=1000000000 then return string.format('%.2fkkk',n/1000000000) end if math.abs(n)>=1000000 then return string.format('%.2fkk',n/1000000) end if math.abs(n)>=1000 then return string.format('%.1fk',n/1000) end return string.format('%.0f',n) end local function readExperience() if type(Player.getExperience)~='function' then return nil end local ok,value=pcall(Player.getExperience) if ok then return tonumber(value) end end function E.resetSession() E.session={start=now(),level=tonumber(Player.getLevel()) or 0, experienceStart=readExperience(),damage=0,damageSeen=false,dps=0, maxHit=0,target='-',peak=0,hits={},messages=0,damageEvents=0,samples={}} end local function sampleStats() local s=E.session;if not s.start then E.resetSession();s=E.session end local t=now();local sum=0 while s.hits[1] and s.hits[1].t<=t-10000 do table.remove(s.hits,1) end for _,hit in ipairs(s.hits) do sum=sum+hit.v end s.dps=sum/math.max(1,math.min(10,(t-s.start)/1000));s.peak=math.max(s.peak,s.dps) if Client.isConnected() then local xp=readExperience() if xp then if s.experienceStart==nil then s.experienceStart=xp end s.experience=xp-s.experienceStart s.experienceHour=s.experience*3600000/math.max(1000,t-s.start) end end end local function damageMessage(message) if type(message)~='table' then return end local s=E.session;s.messages=s.messages+1 local mode=tonumber(message.messageType) local text=tostring(message.text or '') -- Bounded diagnostic history, printed only on explicit HUD click. s.samples[#s.samples+1]={mode=tostring(message.messageType),text=text:sub(1,220)} if #s.samples>12 then table.remove(s.samples,1) end local types=Enums.MessageTypes or {} local dealt=mode==(tonumber(types.MESSAGE_DAMAGE_DEALT) or 23) if dealt then s.damageEvents=s.damageEvents+1 end local lower=text:lower() -- Accept other categories only when text explicitly identifies our attack. local own=lower:find('due to your attack',1,true)~=nil or lower:match('^you deal%s+%d')~=nil if not dealt and not own then return end if lower:match('^you lose') or lower:find('healed',1,true) then return end local amount=0 if dealt then amount=math.max(0,tonumber(message.messagePrimaryValue) or 0) +math.max(0,tonumber(message.messageSecondaryValue) or 0) end if amount<=0 then local value=lower:match('loses%s+([%d,]+)%s+hitpoints') or lower:match('loses%s+([%d,]+)%s+hit points') or lower:match('^you deal%s+([%d,]+)%s+damage') if value then amount=tonumber((value:gsub(',',''))) or 0 end end if amount<=0 then return end s.damageSeen=true;s.damage=s.damage+amount s.hits[#s.hits+1]={t=now(),v=amount} if amount>s.maxHit then s.maxHit=amount;s.target=text:match('^(.+)%s+loses%s+[%d,]+') or '-' end end local function stopMovement() E.pending=nil; E.nav=nil end -- Mining: confirmed teleport item IDs + dynamically learned teleport tiles. -- 22761 and 38935 are known transition/teleport objects used by this client. local MINING_TELEPORT_IDS={[22761]=true,[38935]=true} local miningTileForbidden local function step(direction) local p=pos(); if not p or not apiReady() then return false end local t=now();local pending=E.pending if pending then if not same(p,pending.from) then E.pending=nil elseif t-pending.at=(E.due.mapScan or 0) then E.due.mapScan=now()+600 E.tiles=E.api.Map.getTiles(); E.grid={} for _,tile in ipairs(E.tiles) do E.grid[key(tile.position)]=tile end end return E.tiles end miningTileForbidden=function(p) if not E.enabled.mining or not p then return false end local m=E.mining m.teleportTiles=m.teleportTiles or {} -- Position learned after an actual teleport is permanently avoided. if m.teleportTiles[key(p)] then return true end -- Known teleport object visible on this tile. local tile=E.grid and E.grid[key(p)] if tile then for _,thing in ipairs(tile.things or {}) do if MINING_TELEPORT_IDS[tonumber(thing.id)] then return true end end end return false end local function walkable(p) if not E.grid[key(p)] then return false end if miningTileForbidden(p) then return false end return E.api.Map.isTileWalkable(p.x,p.y,p.z,false,true,false,false) end -- Mining wall-safety: prefer open tiles and avoid hugging walls/corners. local function miningOpenSides(p) if not p then return 0 end local n=0 for i=1,4 do local d=dirs[i] local q={x=p.x+d[1],y=p.y+d[2],z=p.z} if walkable(q) then n=n+1 end end return n end local function miningWallPenalty(p) if not E.enabled.mining or not p then return 0 end local open=miningOpenSides(p) if open<=1 then return 120 end -- dead end / tight wall pocket if open==2 then return 45 end -- corner / narrow wall lane if open==3 then return 10 end return 0 end local function route(target,range) local origin=pos(); if not origin or not target or origin.z~=target.z then return nil end screen(); local q,seen={{p=origin}},{[key(origin)]=true};local head=1 while head<=#q and head<=700 do local node=q[head];head=head+1 if dist(node.p,target)<=range then return node.first end for _,d in ipairs(dirs) do local p={x=node.p.x+d[1],y=node.p.y+d[2],z=node.p.z};local k=key(p) local edge=key(node.p)..':'..d[3] if not seen[k] and now()>=(E.blocked[edge] or 0) and walkable(p) then local diagonal=d[1]~=0 and d[2]~=0 if not diagonal or (walkable({x=p.x,y=node.p.y,z=p.z}) and walkable({x=node.p.x,y=p.y,z=p.z})) then seen[k]=true;q[#q+1]={p=p,first=node.first or d[3]} end end end end end local function go(target,range) range=range or 1;local p=pos();if dist(p,target)<=range then return true end local d=route(target,range); if d~=nil then step(d) end return false end local function findItem(ids,near,radius) local best,bestDistance for _,t in ipairs(screen()) do if not near or dist(t.position,near)<=(radius or 99999) then for _,item in ipairs(t.things) do if ids[item.id] then local d=dist(pos(),t.position) if not bestDistance or d10000 then nextBoss();status('Boss Run: brak wejscia, kolejna kategoria') end return end if b.stage=='ENTRY' then if p.z~=b.entry.z or inLobby(b,p) then b.stage='IDLE';return end if p.y<=b.entryY then b.stage='FIGHT';status('Boss Run: walka / 4 kroki wykonane') else step(0);status('Boss Run: wejscie '..math.min(4,b.entry.y-p.y)..'/4 N') end return end if b.stage=='FIGHT' then if inLobby(b,p) then count('boss'..b.category);nextBoss() end return end if not inLobby(b,p) then status('Boss Run: podejdz do pokoju z posagiem');return end local found=findItem({[46087]=true},b.statue,12);if found then b.statue=found end if not go(b.statue,1) then status('Boss Run: podchodze do posagu');return end if type(Game.bossSequenceStart)~='function' then E.enabled.boss=false;status('Boss Run: klient nie udostepnia API');return end -- GD7 checks that the statue panel is live before sending this command. -- Category, full mode and wave size are encoded in that single API call. if due('bossPanel',650) and Game.bossSequenceStart(b.category,b.wave) then b.stage='WAIT_TELEPORT';b.origin=copy(p);b.startedAt=now() status('Boss Run: Full / '..b.wave..' - czekam na teleport') else useGround(b.statue,'bossStatue');status('Boss Run: otwieram panel') end end local function option(v) return v.id or v[1],tostring(v.text or v.name or v[2] or '') end local function button(modal,pattern) for _,b in ipairs(modal.buttons or {}) do local id,text=option(b);if text:lower():match(pattern) then return id end end end local function answer(m,buttonId,choiceId) if not buttonId or E.modal~=m or m.answered then return false end m.answered=true local accepted=Game.modalWindowAnswer(m.id,buttonId,choiceId or 0,true) if accepted==false then m.answered=false;return false end E.modal=nil;return true end local function cooldown(text) local lower=text:lower() if lower:find('%[cd:%s*0%s*%]') or lower:find('%[cd:%s*00:00%s*%]') or lower:find('cooldown:%s*none') or lower:find('cooldown:%s*0%s+seconds') or lower:find('cooldown:%s*00:00:00') then return false end return lower:find('cooldown') or lower:find('%[cd:') or lower:find('exhaust') or lower:find('you need to wait',1,true) or lower:find('you have to wait',1,true) end local function declaredButton(m,id) if id==nil then return nil end for _,v in ipairs(m.buttons) do local actual=option(v);if actual==id then return id end end end -- Dungeon module transplanted from the user-confirmed working Eloria Pack. local dungeonTick,dungeonModal,resetDungeon do local dungeonConfig={ statueId=63529,statue={x=14232,y=19348,z=3},spawn={x=14232,y=19349,z=3},chestId=24875, names={"EldKar","Elorveth","Dreloriax","Noxeloria","Eldens Bane", "The Elorian Scourge","Eldenborn Shade","Voice of Eloria","Ashen Eldress","Eloriak the Blighted"}} local function nowSec() return os.time() end local function copyPos(p) if not p then return nil end return {x=p.x,y=p.y,z=p.z} end local function cheb(a,b) if not a or not b or a.z~=b.z then return 99999 end return math.max(math.abs(a.x-b.x),math.abs(a.y-b.y)) end local function norm(s) return string.lower(tostring(s or "")):gsub("%s+"," ") end local function playerPos() local p=Player.getPosition() if p then return {x=p.x,y=p.y,z=p.z} end return Map.getCameraPosition() end local function online() local id=Player.getId() return id~=nil and id~=0 end local function tileWalkable(p) if not p then return false end return Map.isTileWalkable(p.x,p.y,p.z,false,false,false,false)==true end local function nearestWalkableAround(target,me) local offsets={{1,0},{-1,0},{0,1},{0,-1}} local best=nil local bd=99999 for _,o in ipairs(offsets) do local p={x=target.x+o[1],y=target.y+o[2],z=target.z} if tileWalkable(p) then local d=cheb(me,p) if d#dungeonConfig.names then E.enabled.dungeon=false D.stage="DONE" E.status="Dungeon list finished" else D.stage="NEXT: "..dungeonConfig.names[D.current] end end local function cooldownSeconds(text) text=tostring(text or "") local h,m,s=string.match(text,"%[CD:%s*(%d+):(%d+):(%d+)%]") if not h then h,m,s=string.match(string.lower(text),"cooldown%s+remaining:%s*(%d+):(%d+):(%d+)") end if not h then return 0 end return (tonumber(h) or 0)*3600+(tonumber(m) or 0)*60+(tonumber(s) or 0) end local function selectDungeonChoice(modal) for wanted=D.current,#dungeonConfig.names do local wantedName=norm(dungeonConfig.names[wanted]) for _,c in ipairs(modal.choices or {}) do local text=norm(c.text) if string.find(text,wantedName,1,true) and cooldownSeconds(c.text)==0 then return wanted,c.id end end end -- Fallback: pozycja na liscie, nadal z prawdziwym choice.id. local first=math.max(1,D.current or 1) for i=first,#(modal.choices or {}) do local c=modal.choices[i] if c and cooldownSeconds(c.text)==0 then return i,c.id end end return nil,nil end local function dungeonOnModal(modal) if not E.enabled.dungeon then return false end local title=norm(modal.title) local message=norm(modal.message) if title=="solo dungeon system" or string.find(title,"solo dungeon",1,true) then local wanted,choiceId=selectDungeonChoice(modal) if not wanted then D.stage="NO FREE DUNGEON" return true end local enter=findButton(modal,{"enter","select","start","continue"},modal.defaultEnterButton) if enter==nil then enter=3 end D.current=wanted D.stage="SELECT "..dungeonConfig.names[wanted] if Game.modalWindowAnswer(modal.id,enter,choiceId,true) then D.stage="WAIT CONFIRM" else D.stage="SELECT REJECTED" end return true end if title=="confirm entry" or string.find(title,"confirm entry",1,true) then local enter=findButton(modal,{"enter now","enter","confirm","yes","start","ok"},nil) local cancel=findButton(modal,{"cancel","close","back"},modal.defaultEscapeButton) local isCooldown=string.find(message,"cooldown",1,true)~=nil or (cancel~=nil and enter==nil) if isCooldown then if cancel~=nil then Game.modalWindowAnswer(modal.id,cancel,255,true) end nextDungeon("cooldown -> next") return true end if enter==nil then enter=modal.defaultEnterButton end if enter==nil then D.stage="NO CONFIRM BUTTON" return true end if Game.modalWindowAnswer(modal.id,enter,255,true) then D.awaiting=true D.confirmAt=nowSec() D.stage="WAIT TELEPORT" else D.stage="CONFIRM REJECTED" end return true end return false end local function findDungeonChest() local me=playerPos() if not me then return nil end local best=nil local bd=99999 for _,tile in ipairs(Map.getTiles() or {}) do local p=tile.position if p and p.z==me.z then for _,thing in ipairs(tile.things or {}) do if tonumber(thing.id)==dungeonConfig.chestId then local d=cheb(me,p) if d3 then D.awaiting=false D.inside=true D.enteredAt=now D.entryTarget=nil D.patrolCenter=copyPos(p) D.phase=1 D.stage="PATROL LEFT" D.lastWalk=0 return end if now-(D.confirmAt or now)>12 then nextDungeon("entry timeout") end return end if D.inside then if now-(D.enteredAt or now)>5 and cheb(p,dungeonConfig.spawn)<=2 then nextDungeon("completed") return end local chest=findDungeonChest() if chest then if cheb(p,chest)<=1 then if now-(D.chestLastUse or 0)>=2 then D.chestLastUse=now D.stage="USE CHEST 24875" if Map.useItem(chest.x,chest.y,chest.z) then D.chestClicked=true D.stage="CHEST CLICKED / WAIT TP" end end else local approach=nearestWalkableAround(chest,p) if approach and now-(D.lastWalk or 0)>=2 then D.lastWalk=now D.stage="GO CHEST 24875" Map.goTo(approach.x,approach.y,approach.z) end end return end if not D.patrolCenter then D.patrolCenter=copyPos(p) end local c=D.patrolCenter local targets={ {x=c.x-11,y=c.y,z=c.z,label="LEFT"}, {x=c.x,y=c.y,z=c.z,label="CENTER"}, {x=c.x+11,y=c.y,z=c.z,label="RIGHT"}, {x=c.x,y=c.y,z=c.z,label="CENTER"} } local target=targets[D.phase or 1] if cheb(p,target)==0 then D.phase=(D.phase or 1)+1 if D.phase>4 then D.phase=1 end target=targets[D.phase] end D.stage="PATROL "..target.label if now-(D.lastWalk or 0)>=2 then D.lastWalk=now Map.goTo(target.x,target.y,target.z) end return end if cheb(p,dungeonConfig.statue)<=10 and now-(D.lastUse or 0)>=2 then D.lastUse=now D.stage="OPEN MODAL" Map.useItem(dungeonConfig.statue.x,dungeonConfig.statue.y,dungeonConfig.statue.z) else D.stage="WAIT STATUE" end end dungeonModal=dungeonOnModal resetDungeon() end local BOSSTIARY_ENTRIES = { {index=1, x=15508,y=15267,z=0, name="bullwark"}, {index=2, x=15509,y=15265,z=0, name="deep terror"}, {index=3, x=15509,y=15269,z=0, name="latrivan"}, {index=4, x=15511,y=15265,z=0, name="professor maxxen"}, {index=5, x=15511,y=15269,z=0, name="hellgorak"}, {index=6, x=15513,y=15265,z=0, name="the lord of the lice"}, {index=7, x=15513,y=15269,z=0, name="madareth"}, {index=8, x=15515,y=15265,z=0, name="lisa"}, {index=9, x=15515,y=15269,z=0, name="annihilon"}, {index=10, x=15517,y=15265,z=0, name="evil mastermind"}, {index=11, x=15517,y=15269,z=0, name="golgordan"}, {index=12, x=15519,y=15265,z=0, name="heoni"}, {index=13, x=15519,y=15269,z=0, name="ushuriel"}, {index=14, x=15521,y=15265,z=0, name="mephiles"}, {index=15, x=15521,y=15269,z=0, name="zugurosh"}, {index=16, x=15523,y=15265,z=0, name="dazed leaf golem"}, {index=17, x=15523,y=15269,z=0, name="thalas"}, {index=18, x=15525,y=15265,z=0, name="owin"}, {index=19, x=15525,y=15269,z=0, name="vashresamun"}, {index=20, x=15527,y=15265,z=0, name="the imperor"}, {index=21, x=15527,y=15269,z=0, name="mahrdis"}, {index=22, x=15529,y=15265,z=0, name="mr. punish"}, {index=23, x=15529,y=15269,z=0, name="ashmunrah"}, {index=24, x=15531,y=15265,z=0, name="massacre"}, {index=25, x=15531,y=15269,z=0, name="morguthis"}, {index=26, x=15533,y=15265,z=0, name="grand canon dominus"}, {index=27, x=15533,y=15269,z=0, name="the ravager"}, {index=28, x=15535,y=15265,z=0, name="grand commander soeren"}, {index=29, x=15535,y=15269,z=0, name="dipthrah"}, {index=30, x=15537,y=15265,z=0, name="grand chaplain gaunder"}, {index=31, x=15537,y=15269,z=0, name="omruc"}, {index=32, x=15539,y=15265,z=0, name="preceptor lazare"}, {index=33, x=15539,y=15269,z=0, name="horestis"}, {index=34, x=15549,y=15265,z=0, name="bane lord"}, {index=35, x=15549,y=15269,z=0, name="rahemos"}, {index=36, x=15551,y=15265,z=0, name="fleshslicer"}, {index=37, x=15551,y=15269,z=0, name="glooth fairy"}, {index=38, x=15553,y=15265,z=0, name="guard captain quaid"}, {index=39, x=15553,y=15269,z=0, name="mawhawk"}, {index=40, x=15555,y=15265,z=0, name="gaffir"}, {index=41, x=15555,y=15269,z=0, name="dirtbeard"}, {index=42, x=15557,y=15265,z=0, name="custodian"}, {index=43, x=15557,y=15269,z=0, name="doctor perhaps"}, {index=44, x=15559,y=15265,z=0, name="ekatrix"}, {index=45, x=15559,y=15269,z=0, name="the welter"}, {index=46, x=15561,y=15265,z=0, name="grand mother foulscale"}, {index=47, x=15561,y=15269,z=0, name="monstor"}, {index=48, x=15563,y=15265,z=0, name="glitterscale"}, {index=49, x=15563,y=15269,z=0, name="boogey"}, {index=50, x=15565,y=15265,z=0, name="thawing dragon lord"}, {index=51, x=15565,y=15269,z=0, name="jailer"}, {index=52, x=15567,y=15265,z=0, name="dracola"}, {index=53, x=15567,y=15269,z=0, name="black knight"}, {index=54, x=15569,y=15265,z=0, name="tyrn"}, {index=55, x=15569,y=15269,z=0, name="raging mage"}, {index=56, x=15571,y=15265,z=0, name="chizzoron the distorter"}, {index=57, x=15571,y=15269,z=0, name="mad mage"}, {index=58, x=15573,y=15265,z=0, name="zorvorax"}, {index=59, x=15573,y=15269,z=0, name="zushuka"}, {index=60, x=15575,y=15265,z=0, name="gelidrazah the frozen"}, {index=61, x=15575,y=15269,z=0, name="diseased fred"}, {index=62, x=15577,y=15265,z=0, name="kalyassa"}, {index=63, x=15577,y=15269,z=0, name="diseased bill"}, {index=64, x=15579,y=15265,z=0, name="tazhadur"}, {index=65, x=15579,y=15269,z=0, name="diseased dan"}, {index=66, x=15581,y=15265,z=0, name="ancient spawn of morgathla"}, {index=67, x=15581,y=15269,z=0, name="mozradek"}, {index=68, x=15583,y=15265,z=0, name="the shatterer"}, {index=69, x=15583,y=15269,z=0, name="xogixath"}, {index=70, x=15585,y=15265,z=0, name="bragrumol"}, {index=71, x=15585,y=15267,z=0, name="zulazza the corruptor"}, {index=72, x=15585,y=15269,z=0, name="the handmaiden"}, {index=73, x=15546,y=15275,z=0, name="the lily of night"}, {index=74, x=15542,y=15275,z=0, name="the blazing rose"}, {index=75, x=15542,y=15280,z=0, name="the diamond blossom"}, {index=76, x=15546,y=15280,z=0, name="the winter bloom"}, {index=77, x=15537,y=15289,z=0, name="the plasmother"}, {index=78, x=15537,y=15293,z=0, name="countess sorrow"}, {index=79, x=15539,y=15289,z=0, name="the voice of ruin"}, {index=80, x=15539,y=15293,z=0, name="warlord ruzad"}, {index=81, x=15549,y=15289,z=0, name="the moonlight aster"}, {index=82, x=15549,y=15293,z=0, name="hairman the huge"}, {index=83, x=15551,y=15289,z=0, name="the flaming orchid"}, {index=84, x=15551,y=15293,z=0, name="foreman kneebiter"}, {index=85, x=15553,y=15289,z=0, name="lord of the elements"}, {index=86, x=15553,y=15293,z=0, name="jesse the wicked"}, {index=87, x=15555,y=15289,z=0, name="dreadmaw"}, {index=88, x=15555,y=15293,z=0, name="smuggler baron silvertoe"}, {index=89, x=15557,y=15289,z=0, name="zomba"}, {index=90, x=15557,y=15293,z=0, name="general murius"}, {index=91, x=15559,y=15289,z=0, name="fleabringer"}, {index=92, x=15559,y=15293,z=0, name="barbaria"}, {index=93, x=15561,y=15289,z=0, name="the abomination"}, {index=94, x=15561,y=15293,z=0, name="xenia"}, {index=95, x=15563,y=15289,z=0, name="the frog prince"}, {index=96, x=15563,y=15293,z=0, name="white pale"}, {index=97, x=15565,y=15289,z=0, name="rukor zad"}, {index=98, x=15565,y=15293,z=0, name="man in the cave"}, {index=99, x=15567,y=15289,z=0, name="groam"}, {index=100, x=15567,y=15293,z=0, name="dharalion"}, {index=101, x=15569,y=15289,z=0, name="elvira hammerthrust"}, {index=102, x=15569,y=15293,z=0, name="willi wasp"}, {index=103, x=15571,y=15289,z=0, name="rotworm queen"}, {index=104, x=15571,y=15293,z=0, name="the blightfather"}, {index=105, x=15573,y=15289,z=0, name="mornenion"}, {index=106, x=15573,y=15293,z=0, name="big boss trolliver"}, {index=107, x=15575,y=15289,z=0, name="gravelord oshuran"}, {index=108, x=15575,y=15293,z=0, name="robby the reckless"}, {index=109, x=15577,y=15289,z=0, name="the evil eye"}, {index=110, x=15577,y=15293,z=0, name="furyosa"}, {index=111, x=15579,y=15289,z=0, name="captain jones"}, {index=112, x=15579,y=15293,z=0, name="yakchal"}, {index=113, x=15581,y=15289,z=0, name="hirintror"}, {index=114, x=15581,y=15293,z=0, name="ocyakao"}, {index=115, x=15583,y=15289,z=0, name="the percht queen"}, {index=116, x=15583,y=15293,z=0, name="yaga the crone"}, {index=117, x=15585,y=15291,z=0, name="grorlam"}, {index=118, x=15585,y=15293,z=0, name="grandfather tridian"}, {index=119, x=15542,y=15299,z=0, name="zevelon duskbringer"}, {index=120, x=15542,y=15304,z=0, name="diblis the fair"}, {index=121, x=15544,y=15307,z=0, name="the pale count"}, {index=122, x=15546,y=15299,z=0, name="arachir the ancient one"}, {index=123, x=15546,y=15304,z=0, name="sir valorcrest"} } -- Bosstiary control flow adapted from the user's BOSSTIARY.lua. -- Uses the HUD timer and modal listener; no extra HUD, chat messages or blocking waits. local function advanceBosstiary() local b=E.bosstiary b.room=b.room+1 if b.room>5 then b.room=1;b.index=b.index+1 end if b.index>#BOSSTIARY_ENTRIES then b.index=1;b.cycles=(b.cycles or 0)+1 end b.stage='APPROACH';b.origin=nil;b.sentAt=nil;b.bossSeen=false b.lastMovePos=nil;b.stuck=0;E.pending=nil;E.tiles=nil status('Bosstiary: '..BOSSTIARY_ENTRIES[b.index].name..' / pokoj '..b.room) end local function bosstiaryWalkTo(target) local b,p=E.bosstiary,pos();if not p or p.z~=target.z then return end if not due('bosstiaryWalk',500) then return end if same(p,b.lastMovePos) then b.stuck=(b.stuck or 0)+1 else b.stuck=0 end b.lastMovePos=copy(p) if dist(p,target)<=45 and (b.stuck or 0)<3 then Map.goTo(target.x,target.y,target.z);return end -- Recalculate a reachable partial route when the destination is outside -- the autowalk range, including the final boss -> first boss leg. screen() local q={{p=p}};local seen={[key(p)]=true};local head=1 local best,score while head<=#q and head<=700 do local node=q[head];head=head+1 if node.first~=nil then local value=dist(node.p,target)*100+(E.bosstiary.routeVisits and E.bosstiary.routeVisits[key(node.p)] or 0)*5 if not score or value=(E.blocked[key(node.p)..':'..d[3]] or 0) and (same(n,target) or walkable(n)) then if d[1]==0 or d[2]==0 or (walkable({x=n.x,y=node.p.y,z=p.z}) and walkable({x=node.p.x,y=n.y,z=p.z})) then seen[k]=true;q[#q+1]={p=n,first=node.first or d[3]} end end end end b.routeVisits=b.routeVisits or {} b.routeVisits[key(p)]=(b.routeVisits[key(p)] or 0)+1 if best and best.first~=nil then step(best.first) else status('Bosstiary: brak dostepnej trasy do '..(target.name or 'wyjscia')) end end local function bosstiaryModal(m) local b=E.bosstiary if b.stage~='APPROACH' and b.stage~='MODAL' then return end if dist(pos(),BOSSTIARY_ENTRIES[b.index])>2 then return end local choice for _,v in ipairs(m.choices) do local id,label=option(v) if tonumber(label:lower():match('room%s*(%d+)'))==b.room then choice=id;break end end if choice==nil then return end if now()-m.at<500 then return end local enter=button(m,'enter') or button(m,'select') or button(m,'^ok$') or declaredButton(m,m.defaultEnter) if answer(m,enter,choice) then b.origin=copy(pos());b.sentAt=now();b.stage='WAIT_TELEPORT' b.lastMovePos=nil;b.stuck=0 end end local function bosstiaryTick() local b,p=E.bosstiary,pos();if not p then return end local target=BOSSTIARY_ENTRIES[b.index] if not target then b.index=1;b.room=1;target=BOSSTIARY_ENTRIES[1] end -- Handle an automatic return in BOTH room states, before monster/exit logic. if (b.stage=='FIGHT' or b.stage=='EXIT') and dist(p,target)<=5 then advanceBosstiary();return end if b.stage=='WAIT_TELEPORT' then if dist(p,target)>15 then b.stage='FIGHT';b.enteredAt=now();b.bossSeen=false;b.lastMovePos=nil;b.stuck=0 elseif now()-(b.sentAt or now())>6000 then b.stage='APPROACH' end return end if b.stage=='FIGHT' then if not due('bosstiaryMonsters',500) then return end local mobs=0 for _,id in ipairs(Map.getCreatureIds(true,false) or {}) do local creature=Creature(id) if creature and creature:getType()==Enums.CreatureTypes.CREATURETYPE_MONSTER and dist(p,creature:getPosition())<=10 then mobs=mobs+1 end end if mobs>0 then b.bossSeen=true;status('Bosstiary: '..target.name..' / walka') elseif b.bossSeen or now()-(b.enteredAt or now())>4000 then b.stage='EXIT';b.exitAt=now();b.lastMovePos=nil;b.stuck=0 end return end if b.stage=='EXIT' then local exit=findItem({[22761]=true},p,10) if exit then bosstiaryWalkTo(exit);status('Bosstiary: powrot przez wyjscie 22761') elseif now()-(b.exitAt or now())>3000 then -- Wait briefly for automatic return, then search reachable visible tiles. local searchTarget for _,tile in ipairs(screen()) do if tile.position.z==p.z and dist(p,tile.position)>=4 and walkable(tile.position) then if not searchTarget or (b.routeVisits and b.routeVisits[key(tile.position)] or 0)<(b.routeVisits and b.routeVisits[key(searchTarget)] or 0) then searchTarget=tile.position end end end if searchTarget then bosstiaryWalkTo(searchTarget) end status('Bosstiary: szukam wyjscia 22761') end return end if p.z~=target.z then status('Bosstiary: podejdz do sali wejsc na poziomie '..target.z);return end if dist(p,target)==0 then if b.stage~='MODAL' then b.openAt=now() end b.stage='MODAL';status('Bosstiary: '..target.name..' / czekam na pokoje') if now()-(b.openAt or now())>5000 then b.stage='APPROACH' end return end b.stage='APPROACH';bosstiaryWalkTo(target) status('Bosstiary: ide do '..target.name..' / pokoj '..b.room) end function E.onModal(id,title,message,buttons,defaultEnter,defaultEscape,choices,priority) if not E.alive then return end E.modal={id=id,title=tostring(title or ''),message=tostring(message or ''),buttons=buttons or {},choices=choices or {},at=now(),defaultEnter=defaultEnter,defaultEscape=defaultEscape} end local function explorationTarget(p) local m=E.mining m.seen=m.seen or {};m.failed=m.failed or {} for _,tile in ipairs(screen()) do if tile.position.z==p.z then m.seen[key(tile.position)]=true end end local q={{p=p,depth=0}};local seen={[key(p)]=true};local head=1 local best,score while head<=#q and head<=700 do local node=q[head];head=head+1 if node.depth>0 and now()>=(m.failed[key(node.p)] or 0) then local frontier=0 for i=1,4 do local d=dirs[i] if not m.seen[key({x=node.p.x+d[1],y=node.p.y+d[2],z=p.z})] then frontier=frontier+1 end end local visits=E.visits[key(node.p)] or 0 local momentum=m.heading and (node.p.x-p.x)*m.heading.x+(node.p.y-p.y)*m.heading.y or 0 local value=frontier*1000-visits*35+node.depth*2+momentum*3-miningWallPenalty(node.p) if not score or value>score then best=copy(node.p);score=value end end for _,d in ipairs(dirs) do local n={x=node.p.x+d[1],y=node.p.y+d[2],z=p.z};local k=key(n) if not seen[k] and now()>=(E.blocked[key(node.p)..':'..d[3]] or 0) and walkable(n) then if d[1]==0 or d[2]==0 or (walkable({x=n.x,y=node.p.y,z=p.z}) and walkable({x=node.p.x,y=n.y,z=p.z})) then seen[k]=true;q[#q+1]={p=n,depth=node.depth+1} end end end end return best end -- Normal stairs/ramps remain usable. Actual teleports are blocked by -- MINING_TELEPORT_IDS and by dynamically learned m.teleportTiles. local function miningVisibleTeleport(p) local best,bestDist=nil,nil for _,tile in ipairs(screen()) do if tile.position and tile.position.z==p.z then for _,thing in ipairs(tile.things or {}) do if MINING_TELEPORT_IDS[tonumber(thing.id)] then local d=dist(p,tile.position) if not bestDist or d4 else suddenTeleport=(dz>1 or math.max(dx,dy)>3) end end if previous and suddenTeleport and not m.returning then local trigger=miningLearnTeleportTrigger(m,previous) m.returnTarget=copy(m.lastSafeMine or m.mineAnchor or previous) m.returning=true m.returnStarted=now() m.exitLanding=copy(p) m.target=nil;m.targetAt=nil;m.used=0;m.lastUse=nil m.explore=nil;m.exploreAt=nil E.pending=nil;E.nav=nil;E.tiles=nil;E.grid={} E.due.mapScan=0;E.due.explore=0;E.due.step=0 if trigger then status('Mining: teleport wykryty i zablokowany / wracam do kopalni') else status('Mining: wyszedlem z kopalni / wracam') end end -- Recovery mode has priority over every mining action. if m.returning and m.returnTarget then local target=m.returnTarget if p.z==target.z then if dist(p,target)<=2 then m.returning=false m.returnStarted=nil m.exitLanding=nil m.lastSafeMine=copy(p) m.lastPosition=copy(p) E.pending=nil;E.tiles=nil;E.due.explore=0 status('Mining: wrocilem do kopalni') else go(target,1) status('Mining: wracam do kopalni') end return end -- Different floor: look for a visible return teleport and intentionally -- use it only for recovery. Normal mining never enters these tiles. local returnTeleport=miningVisibleTeleport(p) if returnTeleport then if due('miningReturnTeleport',1400) then Map.goTo(returnTeleport.x,returnTeleport.y,returnTeleport.z) end status('Mining: szukam powrotnego teleportu') else -- Stay near the landing area instead of wandering farther away. if m.exitLanding and p.z==m.exitLanding.z and dist(p,m.exitLanding)>2 and due('miningReturnLanding',1400) then Map.goTo(m.exitLanding.x,m.exitLanding.y,m.exitLanding.z) end status('Mining: poza kopalnia / czekam na droge powrotna') end return end -- Keep updating the last confirmed safe location only during normal mining. m.lastSafeMine=copy(p) -- Floor changes caused by normal stairs/ramps are allowed. They do not use -- the teleport IDs above and are handled as ordinary mine navigation. if m.floor~=p.z or (m.target and m.target.z~=p.z) or (m.explore and m.explore.z~=p.z) then m.floor=p.z m.target=nil;m.targetAt=nil;m.used=0;m.lastUse=nil m.explore=nil;m.exploreAt=nil;m.heading=nil m.progressAt=now();m.stuckSince=now();m.recoveries=0;m.transitionAt=now() E.pending=nil;E.nav=nil;E.tiles=nil;E.grid={} E.due.mapScan=0;E.due.explore=0;E.due.step=0;E.due.pickaxe=0 status('Mining: pietro '..p.z..' / kontynuuje kopalnie') end if not same(p,m.lastPosition) then if m.lastPosition and dist(p,m.lastPosition)<=1 then m.heading={x=p.x-m.lastPosition.x,y=p.y-m.lastPosition.y} end m.lastPosition=copy(p);m.progressAt=now() E.visits[key(p)]=(E.visits[key(p)] or 0)+1 E.tiles=nil m.stuckSince=now() end -- If position does not change while a route is active, abandon it quickly. local movingToRock=m.target and dist(p,m.target)>1 local movingExplore=m.explore and dist(p,m.explore)>0 if movingToRock or movingExplore then m.stuckSince=m.stuckSince or now() if now()-m.stuckSince>4000 then miningRecover(m,p,movingToRock and 'do skaly' or 'eksploracja') return end else m.stuckSince=now() end if E.api.Game.getItemCount(19249)<1 then status('Mining: brak kilofa 19249');return end if m.target then if now()<(E.rockDone[key(m.target)] or 0) then m.target=nil;return end if now()-(m.targetAt or now())>10000 and dist(p,m.target)>1 then E.rockDone[key(m.target)]=now()+45000;m.target=nil;m.targetAt=nil E.pending=nil;E.tiles=nil;E.due.mapScan=0 status('Mining: nie moge dojsc do skaly / pomijam');return end if go(m.target,1) and due('pickaxe',450) then local ok=E.api.Game.useItemOnGround(19249,m.target.x,m.target.y,m.target.z) if ok then m.used=(m.used or 0)+1;m.lastUse=now();count('miningUses') status('Mining: wykopuje kamien '..m.used..'x') else -- "Wykopac kamienia" is no longer accepted: only now release this rock. E.rockDone[key(m.target)]=now()+5000 m.target=nil;m.targetAt=nil;m.lastUse=nil;m.used=0 E.pending=nil;E.nav=nil;E.tiles=nil E.due.mapScan=0;E.due.explore=0;E.due.step=0;E.due.pickaxe=0 status('Mining: nie da sie dalej wykopac / nastepny kamien') end end return end local candidate,best for _,t in ipairs(screen()) do local blocked=E.rockDone[key(t.position)] if t.position.z==p.z and (not blocked or blocked1 then local bestOpen=0 for i=1,4 do local dd=dirs[i] local ap={x=t.position.x+dd[1],y=t.position.y+dd[2],z=t.position.z} if walkable(ap) then bestOpen=math.max(bestOpen,miningOpenSides(ap)) end end if bestOpen<=1 then approachPenalty=100 elseif bestOpen==2 then approachPenalty=12 end end local score=d*10+approachPenalty if (not best or score4500 then m.failed=m.failed or {};m.failed[key(m.explore)]=now()+45000 m.explore=nil;m.progressAt=now() end if not m.explore and due('explore',750) then m.explore=explorationTarget(p) m.exploreAt=now() end if m.explore then go(m.explore,0);status('Mining: szukam kolejnych skal') else status('Mining: brak dostepnej trasy') end end function E.onText(mode,text) local m=E.mining;text=tostring(text or ''):lower() if E.enabled.mining and m.target and m.lastUse and now()-m.lastUse<2500 then if text:find('depleted') then E.rockDone[key(m.target)]=math.huge;count('depleted');m.target=nil E.pending=nil;E.nav=nil;E.tiles=nil;E.due.mapScan=0 status('Mining: skala wyczerpana / nastepna') elseif text:find('cannot mine here') or text:find('no way') then local p=pos();if p then E.visits[key(p)]=(E.visits[key(p)] or 0)+3 end E.rockDone[key(m.target)]=now()+15000;m.target=nil end end end local function fishingTick() return end local FISHING_WORKING_GROUNDS={ ['Fish']={4602,4597,4599,4601,629,4600,622}, ['Shimmer']={12560}, ['Sandfish']={13988}, ['Old Nasty']={12560}, ['Rainbow']={7236}, ['Northern']={7236} } local function fishingTileHasId(p,wanted) if not p or not wanted then return false end local things=Map.getThings(p.x,p.y,p.z) if not things then return false end for _,thing in pairs(things) do if wanted[tostring(thing.id)] then return true end end return false end -- Osobny timer, tak jak w dzialajacym Ryby.lua. Timer.new('eloria_hud_fishing_exact',function() local player=Creature(Player.getId()) if not player then return end local pp=player:getPosition() if not pp then return end local enabledCount=0 local checked={} for modeIndex=1,#fishModes do local mode=fishModes[modeIndex] if E.fish[modeIndex] then enabledCount=enabledCount+1 local ids=FISHING_WORKING_GROUNDS[mode.name] if ids then local wanted={} for _,id in ipairs(ids) do wanted[tostring(id)]=true end local spots={} for dx=-7,7 do for dy=-5,5 do local x,y,z=pp.x+dx,pp.y+dy,pp.z local things=Map.getThings(x,y,z) if things then for _,thing in pairs(things) do if wanted[tostring(thing.id)] then spots[#spots+1]={x=x,y=y,z=z} break end end end end end checked[#checked+1]=mode.name..':'..#spots -- IMPORTANT: once a real fishing spot is found and used, -- stop here. Do not let later modes overwrite STATUS with -- "Northern | brak lowiska". if #spots>0 then local p=spots[math.random(#spots)] local result=Game.useItemOnGround(3483,p.x,p.y,p.z) count('fishingUses') status('Fishing [CAST]: '..mode.name.. ' | lowisk '..#spots.. ' | '..p.x..','..p.y..','..p.z.. ' | '..tostring(result)) return end end end end if enabledCount>0 then status('Fishing [SCAN]: '..table.concat(checked,' | ')) end end,100) function E.toggle(name) local value=not E.enabled[name] for _,n in ipairs({'boss','dungeon','bosstiary','mining'}) do E.enabled[n]=false end if value and name=='boss' and type(Game.bossSequenceStart)~='function' then status('Boss Run: brak API klienta');E.refresh();return end E.enabled[name]=value;stopMovement();E.modal=nil;E.tiles=nil if name=='boss' then E.boss.stage='IDLE';E.boss.tried={};E.boss.restUntil=0 end if name=='dungeon' then resetDungeon() end if name=='bosstiary' then E.bosstiary.stage='APPROACH' end if name=='mining' then E.mining.teleportTiles=E.mining.teleportTiles or {} E.mining.returning=false E.mining.returnTarget=nil E.mining.returnStarted=nil E.mining.exitLanding=nil E.mining.lastPosition=nil E.mining.lastSafeMine=value and copy(pos()) or nil E.mining.mineAnchor=value and copy(pos()) or nil E.mining.floor=value and (pos() and pos().z or nil) or nil end status(value and (name..': ON') or 'Automatyzacja zatrzymana');E.refresh() end E.x,E.y=CONFIG.x,CONFIG.y E.optionsX,E.optionsY=E.x,E.y+265 E.analyzerX,E.analyzerY=E.x,E.y+380 --====================================================================-- -- HUD - OLD ELORIA 6.67 VISUAL STYLE -- Same tile/category/card presentation; current gameplay logic remains above. --====================================================================-- E.x,E.y=CONFIG.x,CONFIG.y E.sections={runners=false,fishing=false} E.menuOpen=false E.activeCategory=nil E.optionDetailId=nil local colors={ white={255,255,255}, text={255,255,0}, muted={255,220,80}, on={40,255,60}, off={255,75,60}, blue={0,255,255}, red={255,40,100}, orange={255,165,65} } local function paint(h,color) local c=colors[color or 'text'] h:setColor(c[1],c[2],c[3]) end local function label(id,text,y,callback,color,x) local r=E.widgets[id] x=x or E.x if not r then local h=HUD.new(x,y,text,true) local shadow=HUD.new(x+1,y+1,text,true) h:setFontSize(CONFIG.font) shadow:setFontSize(CONFIG.font) shadow:setColor(0,0,0) shadow:setPhantom(true) if shadow.setZIndex then shadow:setZIndex(190) end if h.setZIndex then h:setZIndex(191) end h:setDraggable(false) if callback then h:setCallback(callback) end r={hud=h,shadow=shadow} E.widgets[id]=r end if r.text~=text then r.hud:setText(text) r.shadow:setText(text) r.text=text end if r.x~=x or r.y~=y then r.hud:setPos(x,y) r.shadow:setPos(x+1,y+1) r.x=x;r.y=y end paint(r.hud,color) r.keep=true if not r.shown then r.hud:show() r.shadow:show() r.shown=true end end local function boldLabel(id,text,y,callback,color,x) label(id,text,y,callback,color,x) local face=E.widgets[id] face.hud:setFontSize(10) face.shadow:setFontSize(10) for i,offset in ipairs({{1,0},{0,1},{1,1}}) do local copyId=id..'Bold'..i label(copyId,text,y+offset[2],nil,color,(x or E.x)+offset[1]) local copy=E.widgets[copyId] copy.hud:setFontSize(10) copy.shadow:setFontSize(10) copy.hud:setPhantom(true) copy.shadow:setPhantom(true) end face.hud:setPhantom(callback==nil) if callback then face.hud:setCallback(callback) end end local function tile(id,x,y,itemId,caption,callback,selected,draggable) local r=E.widgets[id] if not r then local sprite=HUD.new(x,y,itemId,true) sprite:setSize(CONFIG.tileSize,CONFIG.tileSize) if sprite.setZIndex then sprite:setZIndex(185) end sprite:setPhantom(false) sprite:setDraggable(draggable or false) if callback then sprite:setCallback(callback) end local empty=HUD.new(x,y,'',true) empty:setPhantom(true) r={hud=sprite,shadow=empty} E.widgets[id]=r end r.hud:setPos(x,y) r.shadow:setPos(x,y) r.keep=true if not r.shown then r.hud:show() r.shadow:show() r.shown=true end boldLabel(id..'Caption',caption,y+CONFIG.tileSize+5,callback,selected and 'on' or 'blue',x) end local function directRunnerIcon(id,x,y,itemId,callback,active) local r=E.widgets[id] if not r then local ok,sprite=pcall(function() return HUD.new(x,y,itemId,true) end) if not ok or not sprite then sprite=HUD.new(x,y,CONFIG.mainSprite,true) end sprite:setSize(CONFIG.tileSize,CONFIG.tileSize) sprite:setDraggable(false) sprite:setPhantom(false) if sprite.setZIndex then sprite:setZIndex(190) end if callback then sprite:setCallback(callback) end local empty=HUD.new(x,y,'',true) empty:setPhantom(true) r={hud=sprite,shadow=empty} E.widgets[id]=r end r.hud:setPos(x,y) r.shadow:setPos(x,y) r.hud:setSize(CONFIG.tileSize,CONFIG.tileSize) if r.hud.setOpacity then r.hud:setOpacity(active and 1.0 or 0.82) end if r.hud.setScale then r.hud:setScale(active and 1.05 or 1.0) end r.keep=true if not r.shown then r.hud:show() r.shadow:show() r.shown=true end return r end local function itemCount(itemId) if type(Game)=='table' and type(Game.getItemCount)=='function' then local ok,v=pcall(Game.getItemCount,itemId) if ok then return tonumber(v) or 0 end end return 0 end local function dromeRemaining(itemId) return math.max(0,(E.drome.nextUse[itemId] or 0)-now()) end local function dromeTimeText(ms) ms=tonumber(ms) or 0 if ms<=0 then return 'READY' end local sec=math.ceil(ms/1000) local min=math.floor(sec/60) if min>=60 then return string.format('%dh%02d',math.floor(min/60),min%60) end return string.format('%02d:%02d',min,sec%60) end local function dromeSummary() local total=0 local ready=false local shortest=nil for _,itemId in ipairs(E.drome.ids) do local c=itemCount(itemId) total=total+c if c>0 then local rem=dromeRemaining(itemId) if rem<=0 then ready=true elseif not shortest or rem0 then if manual then status('Drome '..itemId..': cooldown '..dromeTimeText(dromeRemaining(itemId))) end return false end if itemCount(itemId)<1 then if manual then status('Drome '..itemId..': brak potki') end return false end if type(Game)~='table' or type(Game.useItem)~='function' then if manual then status('Drome: Game.useItem API niedostepne') end return false end E.drome.lastAction=now() local ok,result=pcall(Game.useItem,itemId) if not ok or result~=true then if manual then status('Drome '..itemId..': use not confirmed') end return false end E.drome.nextUse[itemId]=now()+E.drome.cooldown status('Drome '..itemId..': used / cooldown 1h') return true end local function dromeAutoTick() if not E.drome.auto then return end if now()-(E.drome.lastAction or 0)<1500 then return end for _,itemId in ipairs(E.drome.ids) do if itemCount(itemId)>0 and dromeRemaining(itemId)<=0 then useDrome(itemId,false) return end end end local function toggleDrome() E.drome.auto=not E.drome.auto status('Drome Potions: '..(E.drome.auto and 'AUTO ON' or 'AUTO OFF')) E.refresh() end local function fishingOn() for i=1,#fishModes do if E.fish[i] then return true end end return false end local function selectedFishIndex() for i=1,#fishModes do if E.fish[i] then return i end end return 1 end local function setSingleFish(index) for i=1,#fishModes do E.fish[i]=(i==index) end local mode=fishModes[index] local modeName='Fish' if mode and mode.name then modeName=tostring(mode.name) end status(string.format('Fishing: %s',modeName)) end local function toggleFishing() if fishingOn() then for i=1,#fishModes do E.fish[i]=false end status('Fishing: OFF') else setSingleFish(selectedFishIndex()) end E.refresh() end local optionIcons={ drome={36737,'Drome'}, dromeState={36737,'Drome'}, dromeCount={36737,'Pots'}, boss={46087,'Boss Run'}, dungeon={63529,'Dungeon'}, bosstiary={24875,'Bosstiary'}, mining={19249,'Mining'}, category={46087,'Poziom'}, wave={46087,'Fale'}, room={63738,'Pokoj'}, bossIndex={63738,'Boss'}, fishing={3483,'Fishing'}, fishMode={3483,'Tryb'}, session={3031,'Sesja'}, level={3031,'Level'}, xp={3031,'XP'}, damage={19249,'DMG'}, maxHit={19249,'Max Hit'}, target={3031,'Target'}, reset={3031,'Reset'} } local function groupedRows(rows) local parent={ category='boss',wave='boss', room='bosstiary',bossIndex='bosstiary', xpHour='xp', dps='damage',peak='damage', levelHour='level', reset='session' } local groups,byId={},{} for _,row in ipairs(rows) do if row.id~='status' and row.id~='status2' and row.id~='status3' then local id=parent[row.id] or row.id if not byId[id] then byId[id]={id=id,rows={}} groups[#groups+1]=byId[id] end byId[id].rows[#byId[id].rows+1]=row end end return groups end local function compactOption(row) local shortNames={ boss='Boss Run', dungeon='Dungeon', bosstiary='Bosstiary', mining='Mining' } if shortNames[row.id] then return shortNames[row.id] end local value=tostring(row.text or '') if row.id=='category' then return ({'Easy','Medium','Hard'})[E.boss.category] end if row.id=='wave' then return 'Fale: '..E.boss.wave end if row.id=='room' then return 'Pokoj: '..E.bosstiary.room..'/5' end if row.id=='bossIndex' then return 'Boss: '..E.bosstiary.index..'/123' end if value=='[ ON ]' or value=='[ON]' then return '[ ON ]' end if value=='[ OFF ]' or value=='[OFF]' then return '[ OFF ]' end if #value>12 then return value:sub(1,10)..'..' end return value end local function groupCard(group,x,y) local definition=optionIcons[group.id] or {3031,group.id} local fishIndex=group.id:match('^fishMode(%d+)$') if fishIndex then local mode=fishModes[tonumber(fishIndex)] if mode then definition={mode.sprite or mode.catchId or 3483,mode.name} end end local key='group'..group.id local r=E.widgets[key] if not r then local ok,sprite=pcall(function() return HUD.new(x,y,definition[1],true) end) if not ok or not sprite then sprite=HUD.new(x,y,CONFIG.mainSprite,true) end sprite:setSize(CONFIG.tileSize,CONFIG.tileSize) if sprite.setZIndex then sprite:setZIndex(185) end sprite:setDraggable(false) sprite:setPhantom(false) local empty=HUD.new(x,y,'',true) empty:setPhantom(true) r={hud=sprite,shadow=empty} E.widgets[key]=r end r.hud:setPos(x,y) r.shadow:setPos(x,y) r.hud:setCallback(function() local first=group.rows[1] E.optionDetailId=first.id if first.callback then first.callback() end E.refresh() end) r.keep=true if not r.shown then r.hud:show() r.shadow:show() r.shown=true end for i,row in ipairs(group.rows) do local value=compactOption(row) local lineId=key..'Value'..row.id local action=function() E.optionDetailId=row.id if row.callback then row.callback() end E.refresh() end boldLabel(lineId,value,y+CONFIG.tileSize+12+(i-1)*12,action,row.color,x) end end local tileCategories={ {id='runners',name='Runners',sprite=24875}, {id='fishing',name='Fishing',sprite=3483} } local function addRow(rows,id,text,cb,color) rows[#rows+1]={id=id,text=text,callback=cb,color=color} end local function toggleFishMode(index) if E.fish[index] then for i=1,#fishModes do E.fish[i]=false end status('Fishing: OFF') else setSingleFish(index) end E.refresh() end -- Dynamic labels/cards are destroyed before each redraw. -- This avoids text remnants after closing a tab. local function hideDynamicPanelWidgets() for id,r in pairs(E.widgets) do if id:find('^group') or id:find('^option') or id:find('^runner') or id=='status' or id=='status2' or id=='status3' then if r.hud then pcall(function() r.hud:hide() end) end if r.shadow then pcall(function() r.shadow:hide() end) end r.shown=false r.keep=false end end end function E.refresh() hideDynamicPanelWidgets() for _,r in pairs(E.widgets) do r.keep=false end -- Main movable ELORIA tile. tile('title',E.x,E.y,CONFIG.mainSprite,'Eloria',function() E.menuOpen=not E.menuOpen if not E.menuOpen then E.activeCategory=nil end E.refresh() end,E.menuOpen,true) if E.menuOpen then local tileStep=CONFIG.tileSize+CONFIG.tileGap -- TWO tabs only: Runners + Fishing. for i,definition in ipairs(tileCategories) do local category=definition.id tile('tile'..category,E.x,E.y+i*tileStep, definition.sprite,definition.name,function() if E.activeCategory==category then E.activeCategory=nil else E.activeCategory=category end for key in pairs(E.sections) do E.sections[key]=(key==E.activeCategory) end E.refresh() end,E.activeCategory==category,false) end -- Standalone direct Mining button. local miningY=E.y+3*tileStep tile('standaloneMining',E.x,miningY,19249,'Mining',function() E.toggle('mining') E.refresh() end,E.enabled.mining==true,false) -- Standalone direct Drome Potions button. local dromeY=E.y+4*tileStep tile('standaloneDrome',E.x,dromeY,36737,'Drome Potions',function() toggleDrome() E.refresh() end,E.drome.auto==true,false) local dromeState,dromeColor=dromeSummary() label('standaloneDromeState', (E.drome.auto and 'ON ' or 'OFF ')..dromeState, dromeY+CONFIG.tileSize+18,nil,dromeColor,E.x) local rows={} local panelMode=E.activeCategory if E.activeCategory=='runners' then -- Runners are drawn directly below, without groupedRows/groupCard. elseif E.activeCategory=='fishing' then -- Fishing contains fish modes only. for i,mode in ipairs(fishModes) do local idx=i addRow(rows,'fishMode'..i, E.fish[i] and '[ ON ] '..mode.name or mode.name, function() toggleFishMode(idx) end, E.fish[i] and 'on' or 'blue') end end if panelMode then local baseX=E.x+CONFIG.tileSize+15 local startY=(panelMode=='runners') and (E.y+tileStep) or (E.y+2*tileStep) local cellW,cellH=74,88 local groups=groupedRows(rows) local renderedGroups=0 if panelMode=='runners' then -- Stable vertical Runners panel, completely independent from groupCard(). local runnerX=E.x+92 local runnerY=E.y+tileStep-4 local rowGap=62 local textX=runnerX+38 -- Stable positions for all three Runner entries. local dungeonY=runnerY local bossY=runnerY+rowGap local bosY=runnerY+rowGap*2 -- Create ALL three icons first. This prevents a later text/callback -- problem in Boss Run from stopping the Bosstiary icon from appearing. directRunnerIcon('runnerDungeonIcon',runnerX,dungeonY,63529,function() E.toggle('dungeon') E.refresh() end,E.enabled.dungeon==true) directRunnerIcon('runnerBossIcon',runnerX,bossY,46087,function() E.toggle('boss') E.refresh() end,E.enabled.boss==true) -- Real Bosstiary icon used by the working HUD sent today. directRunnerIcon('runnerBosstiaryIcon',runnerX,bosY,63738,function() E.toggle('bosstiary') E.refresh() end,E.enabled.bosstiary==true) -- 1. Dungeon label('runnerDungeonTitle','Dungeon',dungeonY+1,function() E.toggle('dungeon');E.refresh() end,'blue',textX) label('runnerDungeonState', E.enabled.dungeon and 'ON' or 'OFF', dungeonY+17,function() E.toggle('dungeon');E.refresh() end,E.enabled.dungeon and 'on' or 'off',textX) -- 3. Bosstiary is rendered BEFORE Boss Run settings on purpose. -- Its visual position is still below Boss Run. label('runnerBosstiaryTitle','Bosstiary',bosY+1,function() E.toggle('bosstiary');E.refresh() end,'blue',textX) label('runnerBosstiaryState', E.enabled.bosstiary and 'ON' or 'OFF', bosY+17,function() E.toggle('bosstiary');E.refresh() end,E.enabled.bosstiary and 'on' or 'off',textX) label('runnerBosstiaryRoom', 'Pokoj: '..tostring(E.bosstiary.room or 1)..'/5', bosY+31,nil,'white',textX) label('runnerBosstiaryBoss', 'Boss: '..tostring(E.bosstiary.index or 1)..'/123', bosY+45,nil,'muted',textX) -- 2. Boss Run label('runnerBossTitle','Boss Run',bossY+1,function() E.toggle('boss');E.refresh() end,'blue',textX) label('runnerBossState', E.enabled.boss and 'ON' or 'OFF', bossY+17,function() E.toggle('boss');E.refresh() end,E.enabled.boss and 'on' or 'off',textX) label('runnerBossCategory', ({'Easy','Medium','Hard'})[tonumber(E.boss.category) or 1], bossY+31,function() if E.enabled.boss then status('Wylacz Boss Run przed zmiana') else E.boss.category=((tonumber(E.boss.category) or 1)%3)+1 end E.refresh() end,'white',textX) label('runnerBossWave', 'Fale: '..tostring(E.boss.wave or 5), bossY+45,function() if E.enabled.boss then status('Wylacz Boss Run przed zmiana') else local current=tonumber(E.boss.wave) or 5 E.boss.wave=({[1]=3,[3]=5,[5]=1})[current] or 5 end E.refresh() end,'yellow',textX) renderedGroups=3 else for i,group in ipairs(groups) do local col=(i-1)%2 local row=math.floor((i-1)/2) groupCard(group,baseX+col*cellW,startY+row*cellH) renderedGroups=renderedGroups+1 end end local st=tostring(E.status or '') local rowsUsed if panelMode=='runners' then rowsUsed=3 else rowsUsed=math.ceil(math.max(1,renderedGroups)/2) end local statusY local statusX if panelMode=='runners' then statusX=E.x+92 statusY=E.y+tileStep-4+62*3+2 else statusX=baseX statusY=startY+rowsUsed*cellH end label('status',st:sub(1,34),statusY,nil,'muted',statusX) if #st>34 then label('status2',st:sub(35,68),statusY+14,nil,'muted',statusX) end end end for _,r in pairs(E.widgets) do if not r.keep and r.shown then r.hud:hide() r.shadow:hide() r.shown=false end end end local function destroyIconWidgets() -- Old-style HUD keeps all item sprites inside E.widgets. end local function online() return Client.isConnected() and pos()~=nil end local wasOnline=online() function E.tick() if not E.alive then return end E.clock=math.max(E.clock+100,os.time()*1000) safe('ui',function() local r=E.widgets.title if r then local p=r.hud:getPos() if p and (p.x~=E.x or p.y~=E.y) then E.x=math.max(0,p.x) E.y=math.max(0,p.y) E.refresh() end end end) local connected=online() if connected~=wasOnline then E.enabled={};E.fish={};E.drome.auto=false;E.pending=nil;E.modal=nil;E.tiles=nil if connected then E.resetSession();status('Zalogowano / automatyzacje OFF') else status('Offline / automatyzacje OFF') end wasOnline=connected end if connected then if E.modal and now()-E.modal.at>=150 then local m=E.modal safe('modal',function() if E.enabled.bosstiary then bosstiaryModal(m) end end) end if E.enabled.boss then safe('boss',bossTick) elseif E.enabled.dungeon then safe('dungeon',function() local wasInside=E.dungeon.inside local wasChest=E.dungeon.chestClicked dungeonTick() if wasInside and not E.dungeon.inside and E.status=='Dungeon: completed' then count('dungeons') end if not wasChest and E.dungeon.chestClicked then count('chests') end end) elseif E.enabled.bosstiary then safe('bosstiary',bosstiaryTick) elseif E.enabled.mining then safe('mining',mineTick) end safe('fishing',fishingTick) safe('drome',dromeAutoTick) end if due('refresh',1000) then safe('stats',sampleStats);safe('ui',E.refresh) end end E.callbacks={} local function register(id,fn) Game.registerEvent(id,fn);E.callbacks[#E.callbacks+1]={id=id,fn=fn} end function E.shutdown() E.alive=false;destroyTimer('eloria-zerobot-gd7-v2') for _,cb in ipairs(E.callbacks) do Game.unregisterEvent(cb.id,cb.fn) end for _,r in pairs(E.widgets) do r.hud:destroy();r.shadow:destroy() end destroyIconWidgets() end E.resetSession() register(Game.Events.MODAL_WINDOW,function(m) if E.enabled.dungeon then safe('dungeonModal',function() local previous=E.dungeon.current dungeonModal(m) if E.dungeon.current~=previous and E.status=='Dungeon: cooldown -> next' then count('cooldowns') end end) return end E.onModal(m.id,m.title,m.message,m.buttons,m.defaultEnterButton,m.defaultEscapeButton,m.choices,m.priority) end) register(Game.Events.TEXT_MESSAGE,function(m) if type(m)~='table' then return end safe('analyzerText',function() damageMessage(m) end) safe('text',function() E.onText(m.messageType,tostring(m.text or '')) end) end) E.refresh() Timer.new('eloria-zerobot-gd7-v2',E.tick,100,true) print('Eloria ZeroBot HUD 6.4: syntax compatibility + Mining teleport guard.')