forked from Ops226/nnRunner
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaddon.lua
1 lines (1 loc) · 75.1 KB
/
addon.lua
1
Class={}function Class:init(...)end function Class:extend(obj,class_name)local obj=obj or{}obj.__class__=class_name and class_name or "class";local function copyTable(table,destination)local table=table or{}local result=destination or{}for k,v in pairs(table)do if not result[k]then if type(v)=="table" and k~="__index" and k~="__newindex" then result[k]=copyTable(v)else result[k]=v end end end return result end copyTable(self,obj)obj._=obj._ or{}local mt={}mt.__call=function(self,...)return self:new(...)end mt.__index=function(table,key)local val=rawget(table._,key)if val and type(val)=="table" and(val.get~=nil or val.value~=nil)then if val.get then if type(val.get)=="function" then return val.get(table,val.value)else return val.get end elseif val.value then return val.value end else return val end end mt.__newindex=function(table,key,value)local val=rawget(table._,key)if val and type(val)=="table" and((val.set~=nil and val._==nil)or val.value~=nil)then local v=value if val.set then if type(val.set)=="function" then v=val.set(table,value,val.value)else v=val.set end end val.value=v if val and val.afterSet then val.afterSet(table,v)end else table._[key]=value end end setmetatable(obj,mt)return obj end function Class:set(prop,value)if not value and type(prop)=="table" then for k,v in pairs(prop)do rawset(self._,k,v)end else rawset(self._,prop,value)end end function Class:new(...)local obj=self:extend({})if obj.init then obj:init(...)end return obj end function class(attr,class_name)attr=attr and attr or{}attr.__class__=class_name and class_name or "class";return Class:extend(attr)end function dump_docs(class)end function typeof(obj)if obj==nil then return "nil";end if obj.__class__ then return obj.__class__ else return type(obj)end end function settype(obj,class)obj.__class__=class end;local LIBSTUB_MAJOR,LIBSTUB_MINOR="LibStub",2 local LibStub=_G[LIBSTUB_MAJOR]if not LibStub or LibStub.minor<LIBSTUB_MINOR then LibStub=LibStub or{libs={},minors={}}_G[LIBSTUB_MAJOR]=LibStub LibStub.minor=LIBSTUB_MINOR function LibStub:NewLibrary(major,minor)assert(type(major)=="string","Bad argument #2 to `NewLibrary' (string expected)")minor=assert(tonumber(strmatch(minor,"%d+")),"Minor version must either be a number or contain a number.")local oldminor=self.minors[major]if oldminor and oldminor>=minor then return nil end self.minors[major],self.libs[major]=minor,self.libs[major]or{}return self.libs[major],oldminor end function LibStub:GetLibrary(major,silent)if not self.libs[major]and not silent then error(("Cannot find a library instance of %q."):format(tostring(major)),2)end return self.libs[major],self.minors[major]end function LibStub:IterateLibraries()return pairs(self.libs)end setmetatable(LibStub,{__call=LibStub.GetLibrary})end;function __DUMPMACRO(obj_to_dump)print(LibJSON.Serialize(obj_to_dump))end local MAJOR,MINOR="ScrollingTable",tonumber("@project-timestamp@")or 40300;local lib,oldminor=LibStub:NewLibrary(MAJOR,MINOR);if not lib then return;end do lib.SORT_ASC=1;lib.SORT_DSC=2;local defaultcolor={["r"]=1.0,["g"]=1.0,["b"]=1.0,["a"]=1.0};local defaulthighlight={["r"]=1.0,["g"]=0.9,["b"]=0.0,["a"]=0.5};local defaulthighlightblank={["r"]=0.0,["g"]=0.0,["b"]=0.0,["a"]=0.0};local lrpadding=2.5;local ScrollPaneBackdrop={bgFile="Interface\\ChatFrame\\ChatFrameBackground",edgeFile="Interface\\Tooltips\\UI-Tooltip-Border",tile=true,tileSize=16,edgeSize=16,insets={left=3,right=3,top=5,bottom=3}};local SetHeight=function(self)self.frame:SetHeight((self.displayRows*self.rowHeight)+10);self:Refresh();end local SetWidth=function(self)local width=13;for num,col in pairs(self.cols)do width=width+(col.width and col.width or 10);end self.frame:SetWidth(width+20);self:Refresh();end local function SetHighLightColor(self,frame,color)if not frame.highlight then frame.highlight=frame:CreateTexture(nil,"OVERLAY");frame.highlight:SetAllPoints(frame);end frame.highlight:SetColorTexture(color.r,color.g,color.b,color.a);end local FireUserEvent=function(self,frame,event,handler,...)if not handler(...)then if self.DefaultEvents[event]then self.DefaultEvents[event](...);end end end local function RegisterEvents(self,events,fRemoveOldEvents)local table=self;for i,row in ipairs(self.rows)do for j,col in ipairs(row.cols)do if fRemoveOldEvents and self.events then for event,handler in pairs(self.events)do col:SetScript(event,nil);end end for event,handler in pairs(events)do col:SetScript(event,function(cellFrame,...)local realindex=table.filtered[i+table.offset];table:FireUserEvent(col,event,handler,row,cellFrame,table.data,table.cols,i,realindex,j,table,...);end);end end end for j,col in ipairs(self.head.cols)do if fRemoveOldEvents and self.events then for event,handler in pairs(self.events)do col:SetScript(event,nil);end end for event,handler in pairs(events)do col:SetScript(event,function(cellFrame,...)table:FireUserEvent(col,event,handler,self.head,cellFrame,table.data,table.cols,nil,nil,j,table,...);end);end end self.events=events;end local function SetDisplayRows(self,num,rowHeight)local table=self;self.displayRows=num;self.rowHeight=rowHeight;if not self.rows then self.rows={};end for i=1,num do local row=self.rows[i];if not row then row=CreateFrame("Button",self.frame:GetName().."Row"..i,self.frame);self.rows[i]=row;if i>1 then row:SetPoint("TOPLEFT",self.rows[i-1],"BOTTOMLEFT",0,0);row:SetPoint("TOPRIGHT",self.rows[i-1],"BOTTOMRIGHT",0,0);else row:SetPoint("TOPLEFT",self.frame,"TOPLEFT",4,-5);row:SetPoint("TOPRIGHT",self.frame,"TOPRIGHT",-4,-5);end row:SetHeight(rowHeight);end if not row.cols then row.cols={};end for j=1,#self.cols do local col=row.cols[j];if not col then col=CreateFrame("Button",row:GetName().."col"..j,row);col.text=row:CreateFontString(col:GetName().."text","OVERLAY","GameFontHighlightSmall");row.cols[j]=col;local align=self.cols[j].align or "LEFT";col.text:SetJustifyH(align);col:EnableMouse(true);col:RegisterForClicks("AnyUp");if self.events then for event,handler in pairs(self.events)do col:SetScript(event,function(cellFrame,...)if table.offset then local realindex=table.filtered[i+table.offset];table:FireUserEvent(col,event,handler,row,cellFrame,table.data,table.cols,i,realindex,j,table,...);end end);end end end if j>1 then col:SetPoint("LEFT",row.cols[j-1],"RIGHT",0,0);else col:SetPoint("LEFT",row,"LEFT",2,0);end col:SetHeight(rowHeight);col:SetWidth(self.cols[j].width);col.text:SetPoint("TOP",col,"TOP",0,0);col.text:SetPoint("BOTTOM",col,"BOTTOM",0,0);col.text:SetWidth(self.cols[j].width-2*lrpadding);end j=#self.cols+1;col=row.cols[j];while col do col:Hide();j=j+1;col=row.cols[j];end end for i=num+1,#self.rows do self.rows[i]:Hide();end self:SetHeight();end local function SetDisplayCols(self,cols)local table=self;self.cols=cols;local row=self.head if not row then row=CreateFrame("Frame",self.frame:GetName().."Head",self.frame);row:SetPoint("BOTTOMLEFT",self.frame,"TOPLEFT",4,0);row:SetPoint("BOTTOMRIGHT",self.frame,"TOPRIGHT",-4,0);row:SetHeight(self.rowHeight);row.cols={};self.head=row;end for i=1,#cols do if type(cols[i].defaultsort)=="string" then if cols[i].defaultsort:lower()=="asc" then cols[i].defaultsort=lib.SORT_DSC;elseif cols[i].defaultsort:lower()=="dsc" then cols[i].defaultsort=lib.SORT_ASC;else cols[i].defaultsort=nil;end end if type(cols[i].sort)=="string" then if cols[i].sort:lower()=="asc" then cols[i].sort=lib.SORT_DSC;elseif cols[i].sort:lower()=="dsc" then cols[i].sort=lib.SORT_ASC;else cols[i].sort=nil;end end local colFrameName=row:GetName().."Col"..i;local col=getglobal(colFrameName);if not col then col=CreateFrame("Button",colFrameName,row);col:RegisterForClicks("AnyUp");if self.events then for event,handler in pairs(self.events)do col:SetScript(event,function(cellFrame,...)table:FireUserEvent(col,event,handler,row,cellFrame,table.data,table.cols,nil,nil,i,table,...);end);end end end row.cols[i]=col;local fs=col:GetFontString()or col:CreateFontString(col:GetName().."fs","OVERLAY","GameFontHighlightSmall");fs:SetAllPoints(col);fs:SetPoint("LEFT",col,"LEFT",lrpadding,0);fs:SetPoint("RIGHT",col,"RIGHT",-lrpadding,0);local align=cols[i].align or "LEFT";fs:SetJustifyH(align);col:SetFontString(fs);fs:SetText(cols[i].name);fs:SetTextColor(1.0,1.0,1.0,1.0);col:SetPushedTextOffset(0,0);if i>1 then col:SetPoint("LEFT",row.cols[i-1],"RIGHT",0,0);else col:SetPoint("LEFT",row,"LEFT",2,0);end col:SetHeight(self.rowHeight);col:SetWidth(cols[i].width);local color=cols[i].bgcolor;if(color)then local colibg="col"..i.."bg";local bg=self.frame[colibg];if not bg then bg=self.frame:CreateTexture(nil,"OVERLAY");self.frame[colibg]=bg;end bg:SetPoint("BOTTOM",self.frame,"BOTTOM",0,4);bg:SetPoint("TOPLEFT",col,"BOTTOMLEFT",0,-4);bg:SetPoint("TOPRIGHT",col,"BOTTOMRIGHT",0,-4);bg:SetColorTexture(color.r,color.g,color.b,color.a);end end self:SetDisplayRows(self.displayRows,self.rowHeight);self:SetWidth();end local function Show(self)self.frame:Show();self.scrollframe:Show();self.showing=true;end local function Hide(self)self.frame:Hide();self.showing=false;end local function SortData(self)if not(self.sorttable)or(#self.sorttable~=#self.data)then self.sorttable={};end if#self.sorttable~=#self.data then for i=1,#self.data do self.sorttable[i]=i;end end local i,sortby=1,nil;while i<=#self.cols and not sortby do if self.cols[i].sort then sortby=i;end i=i+1;end if sortby then table.sort(self.sorttable,function(rowa,rowb)local column=self.cols[sortby];if column.comparesort then return column.comparesort(self,rowa,rowb,sortby);else return self:CompareSort(rowa,rowb,sortby);end end);end self.filtered=self:DoFilter();self:Refresh();end local StringToNumber=function(str)if str=="" then return 0;else return tonumber(str)end end local function CompareSort(self,rowa,rowb,sortbycol)local cella,cellb=self:GetCell(rowa,sortbycol),self:GetCell(rowb,sortbycol);local a1,b1=cella,cellb;if type(a1)=='table' then a1=a1.value;end if type(b1)=='table' then b1=b1.value;end local column=self.cols[sortbycol];if type(a1)=="function" then if(cella.args)then a1=a1(unpack(cella.args))else a1=a1(self.data,self.cols,rowa,sortbycol,self);end end if type(b1)=="function" then if(cellb.args)then b1=b1(unpack(cellb.args))else b1=b1(self.data,self.cols,rowb,sortbycol,self);end end if type(a1)~=type(b1)then local typea,typeb=type(a1),type(b1);if typea=="number" and typeb=="string" then if tonumber(b1)then b1=StringToNumber(b1);else a1=tostring(a1);end elseif typea=="string" and typeb=="number" then if tonumber(a1)then a1=StringToNumber(a1);else b1=tostring(b1);end end end if a1==b1 then if column.sortnext then local nextcol=self.cols[column.sortnext];if not(nextcol.sort)then if nextcol.comparesort then return nextcol.comparesort(self,rowa,rowb,column.sortnext);else return self:CompareSort(rowa,rowb,column.sortnext);end else return false;end else return false;end else local direction=column.sort or column.defaultsort or lib.SORT_DSC;if direction==lib.SORT_ASC then return a1<b1;else return a1>b1;end end end local Filter=function(self,rowdata)return true;end local function SetFilter(self,Filter)self.Filter=Filter;self:SortData();end local DoFilter=function(self)local result={};for row=1,#self.data do local realrow=self.sorttable[row];local rowData=self:GetRow(realrow);if self:Filter(rowData)then table.insert(result,realrow);end end return result;end function GetDefaultHighlightBlank(self)return self.defaulthighlightblank;end function SetDefaultHighlightBlank(self,red,green,blue,alpha)if not self.defaulthighlightblank then self.defaulthighlightblank=defaulthighlightblank;end if red then self.defaulthighlightblank["r"]=red;end if green then self.defaulthighlightblank["g"]=green;end if blue then self.defaulthighlightblank["b"]=blue;end if alpha then self.defaulthighlightblank["a"]=alpha;end end function GetDefaultHighlight(self)return self.defaulthighlight;end function SetDefaultHighlight(self,red,green,blue,alpha)if not self.defaulthighlight then self.defaulthighlight=defaulthighlight;end if red then self.defaulthighlight["r"]=red;end if green then self.defaulthighlight["g"]=green;end if blue then self.defaulthighlight["b"]=blue;end if alpha then self.defaulthighlight["a"]=alpha;end end local function EnableSelection(self,flag)self.fSelect=flag;end local function ClearSelection(self)self:SetSelection(nil);end local function SetSelection(self,realrow)self.selected=realrow;self:Refresh();end local function GetSelection(self)return self.selected;end local function DoCellUpdate(rowFrame,cellFrame,data,cols,row,realrow,column,fShow,table,...)if fShow then local rowdata=table:GetRow(realrow);local celldata=table:GetCell(rowdata,column);local cellvalue=celldata;if type(celldata)=="table" then cellvalue=celldata.value;end if type(cellvalue)=="function" then if celldata.args then cellFrame.text:SetText(cellvalue(unpack(celldata.args)));else cellFrame.text:SetText(cellvalue(data,cols,realrow,column,table));end else cellFrame.text:SetText(cellvalue);end local color=nil;if type(celldata)=="table" then color=celldata.color;end local colorargs=nil;if not color then color=cols[column].color;if not color then color=rowdata.color;if not color then color=defaultcolor;else colorargs=rowdata.colorargs;end else colorargs=cols[column].colorargs;end else colorargs=celldata.colorargs;end if type(color)=="function" then if colorargs then color=color(unpack(colorargs));else color=color(data,cols,realrow,column,table);end end cellFrame.text:SetTextColor(color.r,color.g,color.b,color.a);local highlight=nil;if type(celldata)=="table" then highlight=celldata.highlight;end if table.fSelect then if table.selected==realrow then table:SetHighLightColor(rowFrame,highlight or cols[column].highlight or rowdata.highlight or table:GetDefaultHighlight());else table:SetHighLightColor(rowFrame,table:GetDefaultHighlightBlank());end end else cellFrame.text:SetText("");end end local function SetData(self,data,isMinimalDataformat)self.isMinimalDataformat=isMinimalDataformat;self.data=data;self:SortData();end local function GetRow(self,realrow)return self.data[realrow];end local function GetCell(self,row,col)local rowdata=row;if type(row)=="number" then rowdata=self:GetRow(row);end if self.isMinimalDataformat then return rowdata[col];else return rowdata.cols[col];end end local function IsRowVisible(self,realrow)local firstVisibleIdx=1+(self.offset or 0);local lastVisibleIdx=(firstVisibleIdx+self.displayRows)-1;for i=firstVisibleIdx,lastVisibleIdx do if self.filtered[i]==realrow and realrow~=nil then return true;end end return false;end function lib:CreateST(cols,numRows,rowHeight,highlight,parent)local st={};self.framecount=self.framecount or 1;local f=CreateFrame("Frame","ScrollTable"..self.framecount,parent or UIParent,BackdropTemplateMixin and "BackdropTemplate");self.framecount=self.framecount+1;st.showing=true;st.frame=f;st.Show=Show;st.Hide=Hide;st.SetDisplayRows=SetDisplayRows;st.SetRowHeight=SetRowHeight;st.SetHeight=SetHeight;st.SetWidth=SetWidth;st.SetDisplayCols=SetDisplayCols;st.SetData=SetData;st.SortData=SortData;st.CompareSort=CompareSort;st.RegisterEvents=RegisterEvents;st.FireUserEvent=FireUserEvent;st.SetDefaultHighlightBlank=SetDefaultHighlightBlank;st.SetDefaultHighlight=SetDefaultHighlight;st.GetDefaultHighlightBlank=GetDefaultHighlightBlank;st.GetDefaultHighlight=GetDefaultHighlight;st.EnableSelection=EnableSelection;st.SetHighLightColor=SetHighLightColor;st.ClearSelection=ClearSelection;st.SetSelection=SetSelection;st.GetSelection=GetSelection;st.GetCell=GetCell;st.GetRow=GetRow;st.DoCellUpdate=DoCellUpdate;st.IsRowVisible=IsRowVisible;st.RowIsVisible=IsRowVisible;st.SetFilter=SetFilter;st.DoFilter=DoFilter;highlight=highlight or{};st:SetDefaultHighlight(highlight["r"],highlight["g"],highlight["b"],highlight["a"]);st:SetDefaultHighlightBlank();st.displayRows=numRows or 12;st.rowHeight=rowHeight or 15;st.cols=cols;st.DefaultEvents={["OnEnter"]=function(rowFrame,cellFrame,data,cols,row,realrow,column,table,...)if row and realrow then local rowdata=table:GetRow(realrow);local celldata=table:GetCell(rowdata,column);local highlight=nil;if type(celldata)=="table" then highlight=celldata.highlight;end table:SetHighLightColor(rowFrame,highlight or cols[column].highlight or rowdata.highlight or table:GetDefaultHighlight());end return true;end,["OnLeave"]=function(rowFrame,cellFrame,data,cols,row,realrow,column,table,...)if row and realrow then local rowdata=table:GetRow(realrow);local celldata=table:GetCell(rowdata,column);if realrow~=table.selected or not table.fSelect then table:SetHighLightColor(rowFrame,table:GetDefaultHighlightBlank());end end return true;end,["OnClick"]=function(rowFrame,cellFrame,data,cols,row,realrow,column,table,button,...)if button=="LeftButton" then if not(row or realrow)then for i,col in ipairs(st.cols)do if i~=column then cols[i].sort=nil;end end local sortorder=lib.SORT_DSC;if not cols[column].sort and cols[column].defaultsort then sortorder=cols[column].defaultsort;elseif cols[column].sort and cols[column].sort==lib.SORT_DSC then sortorder=lib.SORT_ASC;end cols[column].sort=sortorder;table:SortData();else if table:GetSelection()==realrow then table:ClearSelection();else table:SetSelection(realrow);end end return true;end end,};st.data={};f:SetBackdrop(ScrollPaneBackdrop);f:SetBackdropColor(0.1,0.1,0.1);f:SetPoint("CENTER",parent or UIParent,"CENTER",0,0);local scrollframe=CreateFrame("ScrollFrame",f:GetName().."ScrollFrame",f,"FauxScrollFrameTemplate");st.scrollframe=scrollframe;scrollframe:Show();scrollframe:SetScript("OnHide",function(self,...)self:Show();end);scrollframe:SetPoint("TOPLEFT",f,"TOPLEFT",0,-4);scrollframe:SetPoint("BOTTOMRIGHT",f,"BOTTOMRIGHT",-26,3);local scrolltrough=CreateFrame("Frame",f:GetName().."ScrollTrough",scrollframe);scrolltrough:SetWidth(17);scrolltrough:SetPoint("TOPRIGHT",f,"TOPRIGHT",-4,-3);scrolltrough:SetPoint("BOTTOMRIGHT",f,"BOTTOMRIGHT",-4,4);scrolltrough.background=scrolltrough:CreateTexture(nil,"BACKGROUND");scrolltrough.background:SetAllPoints(scrolltrough);scrolltrough.background:SetColorTexture(0.05,0.05,0.05,1.0);local scrolltroughborder=CreateFrame("Frame",f:GetName().."ScrollTroughBorder",scrollframe);scrolltroughborder:SetWidth(1);scrolltroughborder:SetPoint("TOPRIGHT",scrolltrough,"TOPLEFT");scrolltroughborder:SetPoint("BOTTOMRIGHT",scrolltrough,"BOTTOMLEFT");scrolltroughborder.background=scrolltrough:CreateTexture(nil,"BACKGROUND");scrolltroughborder.background:SetAllPoints(scrolltroughborder);scrolltroughborder.background:SetColorTexture(0.5,0.5,0.5,1.0);st.Refresh=function(self)FauxScrollFrame_Update(scrollframe,#st.filtered,st.displayRows,st.rowHeight);local o=FauxScrollFrame_GetOffset(scrollframe);st.offset=o;for i=1,st.displayRows do local row=i+o;if st.rows then local rowFrame=st.rows[i];local realrow=st.filtered[row];local rowData=st:GetRow(realrow);local fShow=true;for col=1,#st.cols do local cellFrame=rowFrame.cols[col];local fnDoCellUpdate=st.DoCellUpdate;if rowData then st.rows[i]:Show();local cellData=st:GetCell(rowData,col);if type(cellData)=="table" and cellData.DoCellUpdate then fnDoCellUpdate=cellData.DoCellUpdate;elseif st.cols[col].DoCellUpdate then fnDoCellUpdate=st.cols[col].DoCellUpdate;elseif rowData.DoCellUpdate then fnDoCellUpdate=rowData.DoCellUpdate;end else st.rows[i]:Hide();fShow=false;end fnDoCellUpdate(rowFrame,cellFrame,st.data,st.cols,row,st.filtered[row],col,fShow,st);end end end end scrollframe:SetScript("OnVerticalScroll",function(self,offset)FauxScrollFrame_OnVerticalScroll(self,offset,st.rowHeight,function()st:Refresh()end);end);st:SetFilter(Filter);st:SetDisplayCols(st.cols);st:RegisterEvents(st.DefaultEvents);return st;end end;local nn=...runner={}runner.nn=nn runner.Rotations={}runner.Engine={}runner.Classes={}runner.Routines={}runner.Behaviors={}runner.UI={}runner.Mechanics={}runner.UI.menuFrame={}runner.LocalPlayer=nil runner.rotations={}runner.rotation=nil runner.routines={}runner.routine=nil runner.behaviors={}runner.AceGUI=LibStub("AceGUI-3.0")runner.ScrollingTable=LibStub("ScrollingTable")runner.localPlayer=nil runner.Draw=nil runner.running=true runner.lastTick=0 runner.lastMount=0 runner.lastDebug=0 runner.lastAFK=0 runner.lastEnter=0 runner.profiles={}runner.mechanics={}runner.waypoints={}runner.mountedWhore=false runner.frame=CreateFrame("Frame")_G.runner=runner function registerMechanic(name,mechanic)local mech=mechanic()runner.mechanics[name:lower()]=mech end function registerRotation(rotation)local rot=rotation()runner.rotations[rot.Name:lower()]=rot end function registerRoutine(routine)local rot=routine()runner.routines[rot.Name:lower()]=rot end function registerProfile(profile)table.insert(runner.profiles,profile)end function registerBehavior(name,behavior)runner.behaviors[name:lower()]=behavior end function deep_copy(original,copies)if type(original)~='table' then return original end copies=copies or{}if copies[original]then return copies[original]end local copy={}copies[original]=copy for key,value in pairs(original)do local dc_key,dc_value=deep_copy(key,copies),deep_copy(value,copies)copy[dc_key]=dc_value end setmetatable(copy,deep_copy(getmetatable(original),copies))return copy end runner.frame:RegisterEvent("PLAYER_ENTERING_WORLD")runner.frame:SetScript("OnUpdate",function(self,elapsed)if GetTime()-runner.lastTick>.15 then if GetTime()-runner.lastAFK>60 then LastHardwareAction(GetTime()*1000)runner.lastAFK=GetTime()end runner.lastTick=GetTime()if not runner.LocalPlayer then runner.LocalPlayer=runner.Classes.MultiboxPlayer:new("player")else runner.LocalPlayer:Update()end runner.Engine.ObjectManager:Update()if runner.UI.ObjectViewer2 then runner.UI.ObjectViewer2:Update()end if runner.UI.ObjectViewer then runner.UI.ObjectViewer:Update()end if not runner.Draw then runner.Draw=nn.Utils.Draw:New()end if runner.Draw then runner.Draw:ClearCanvas()end if not runner.running then return end runner.InteractionManager:HandleAll()if not runner.routine then runner.routine=runner.routines["rotationroutine"]end if not runner.rotation then runner.rotation=runner.rotations[select(1,UnitClass("player")):lower()]end runner.UI.menuFrame:UpdateMenu()runner:DrawNearestDisturbedEarth()if runner.routine then runner.routine:Run()end end end)runner.frame:SetScript("OnKeyDown",function(self,key)if key=="`" then print("Hotkey toggling bot "..(runner.running and "off" or "on"))runner.running=not runner.running end if key=="2" then local target=UnitTarget("player")print("Target: "..(target or "none"))if target then for i=0,300,4 do local f=runner.nn.ObjectField(target,i*4,1)print("Field "..i..": "..(f or "nil"))end print("============================")end end Unlock(runner.frame.SetPropagateKeyboardInput,runner.frame,true)end)function runner:DrawNearestDisturbedEarth()local nearest=nil local nearestDistance=9999 for k,v in pairs(runner.Engine.ObjectManager.gameobjects)do if v.Name=="Disturbed Earth" then local distance=v:DistanceFromPlayer()if distance<nearestDistance then nearest=v nearestDistance=distance end end end if nearest then local x,y,z=ObjectPosition(nearest.pointer)local px,py,pz=ObjectPosition("player")runner.Draw:SetColor(255,0,0,255)runner.Draw:Line(px,py,pz,x,y,z,1,0,0,1)end end function tableCount(t)local count=0 if not t then return 0 end for _ in pairs(t)do count=count+1 end return count end function mysplit(inputstr,sep)if sep==nil then sep="%s";end local t={}for str in string.gmatch(inputstr,"([^"..sep.."]+)")do table.insert(t,str)end return t end function runner:randomBetween(min,max)return math.random()*(max-min)+min end function runner:randomTable(table)local randomIndex=math.random(1,#table)return table[randomIndex]end;runner.Classes.GameObject=class({},"GameObject")local GameObject=runner.Classes.GameObject runner.Classes.GameObject=GameObject runner.GameObjectViewColumns={"Name","Pointer","Distance","Type","Lootable";}print("hi")function GameObject:init(pointer)self.pointer=pointer self.Pointer=pointer self.Name=ObjectName(self.pointer)self.Type=runner.nn.ObjectType(self.pointer)self.GameObjectType=runner.nn.GameObjectType(self.pointer)self.x,self.y,self.z=ObjectPosition(self.pointer)self.X,self.Y,self.Z=self.x,self.y,self.z self.Facing=ObjectFacing(self.pointer)self.BoundingRadius=ObjectBoundingRadius(self.pointer)self.Height=ObjectHeight(self.pointer)self.Distance=99999 self.PathDistance=0 self.CanLoot=false self.Lootable=false self.ObjectType=runner.nn.GameObjectType(self.pointer)self.CanGather=self.ObjectType==50 self.CanAttack=false self.UpdateRate=0 self.IsQuestGiver=runner.nn.ObjectField(self.pointer,16*4,1)==67 or runner.nn.ObjectField(v,16*4,1)==1 self.IsQuestTurnin=runner.nn.ObjectField(self.pointer,16*4,1)==68 or runner.nn.ObjectField(v,16*4,1)==6 self.IsQuestObjective=self:isQuestObjective()self.ObjectiveFor={}self.objectCount=tableCount(runner.Engine.ObjectManager.gameobjects)+tableCount(runner.Engine.ObjectManager.units)+tableCount(runner.Engine.ObjectManager.players)self.NextUpdate=GetTime()+runner:randomBetween(self.objectCount,(self.objectCount*3.5))/1000 end function GameObject:Update()if GetTime()<self.NextUpdate then return end self.Name=ObjectName(self.pointer)self.Type=runner.nn.GameObjectType(self.pointer)self.x,self.y,self.z=ObjectPosition(self.pointer)self.Distance=self:DistanceFromPlayer()self.Facing=ObjectFacing(self.pointer)self.Lootable=runner.nn.ObjectLootable(self.pointer)self.CanLoot=runner.nn.ObjectLootable(self.pointer)self.ObjectType=runner.nn.GameObjectType(self.pointer)self.CanGather=self.ObjectType==50 self.CanAttack=Unlock(UnitCanAttack,"player",self.pointer)self.IsQuestGiver=runner.nn.ObjectField(self.pointer,16*4,1)==67 or runner.nn.ObjectField(self.pointer,16*4,1)==1 self.IsQuestTurnin=runner.nn.ObjectField(self.pointer,16*4,1)==68 or runner.nn.ObjectField(self.pointer,16*4,1)==6 self.IsQuestObjective=self:isQuestObjective()self.ObjectiveFor=self:GetObjectiveFor()self.DynamicFlags=runner.nn.DynamicFlags(self.pointer)self.objectCount=tableCount(runner.Engine.ObjectManager.gameobjects)+tableCount(runner.Engine.ObjectManager.units)+tableCount(runner.Engine.ObjectManager.players)self.UpdateRate=self:GetUpdateRate()self.NextUpdate=GetTime()+self.UpdateRate end function GameObject:GetUpdateRate()local multiplier=self.objectCount/1350 or 1 if self.Distance<10 then return.15*multiplier end if self.Distance<50 then return.75*multiplier end if self.Distance<100 then return 1.5*multiplier end if self.Distance<200 then return 3*multiplier end return 3*multiplier end function GameObject:LOS()local x1,y1,z1=runner.nn.ObjectPosition('player')local x2,y2,z2=runner.nn.ObjectPosition(self.pointer)local playerHeight=runner.nn.ObjectHeight('player')local unitHeight=self.Height local checkHeights={{playerHeight,unitHeight},{playerHeight/2,unitHeight/2}}for _,heights in ipairs(checkHeights)do local hitX,hitY,hitZ=TraceLine(x1,y1,z1+heights[1],x2,y2,z2+heights[2],0x100111)if not hitX then return true end end return false end function GameObject:isQuestObjective()runner.nn.SetMouseover(self.pointer)local tooltip=Unlock(C_TooltipInfo.GetUnit,"mouseover")if tooltip then for k,v in pairs(tooltip.lines)do if v.type==8 or v.type==17 then local texthex=v.leftColor:GenerateHexColor()if texthex=="ffffffff" then return true end end end else local dynamicFlags=runner.nn.DynamicFlags(self.pointer)if dynamicFlags then if string.find(dynamicFlags,"49")then return true end end end return false end function GameObject:GetObjectiveFor()local objectives={}runner.nn.SetMouseover(self.pointer)local tooltip=Unlock(C_TooltipInfo.GetUnit,"mouseover")if tooltip then for k,v in pairs(tooltip.lines)do if v.type==17 then table.insert(objectives,v.id)end end end return objectives end function GameObject:Debug()local px,py,pz=ObjectPosition("player")local x,y,z=ObjectPosition(self.pointer)runner.Draw:Line(px,py,pz,x,y,z,1,0,0,1)runner.Draw:Circle(x,y,z,8)end function GameObject:ToViewerRow()return{self.Name,self.pointer,string.format("%.2f",self.Distance),self.Type,self.CanLoot and "Yes" or "No";}end function GameObject:NavigationDistance()return runner.Engine.Navigation:PathDistance(self.pointer)end function GameObject:DistanceFromPlayer()local x1,y1,z1=self.x,self.y,self.z local x2,y2,z2=ObjectPosition("player")if not x2 then return 99999 end local dist=math.sqrt((x2-x1)^2+(y2-y1)^2+(z2-z1)^2)return dist end function GameObject:DistanceFrom(unit)local x1,y1,z1=self.x,self.y,self.z local x2,y2,z2=unit.x,unit.y,unit.z return math.sqrt((x2-x1)^2+(y2-y1)^2+(z2-z1)^2)end function GameObject:DistanceFromPoint(x,y,z)z=z or self.z if not x then return 99999 end local x1,y1,z1=self.x,self.y,self.z if not x1 then return 99999 end return math.sqrt((x-x1)^2+(y-y1)^2+(z-z1)^2)or 99999 end function GameObject:DistanceFromPlayer2D()local x1,y1=self.x,self.y local x2,y2=ObjectPosition("player")if not x2 then return 99999 end return math.sqrt((x2-x1)^2+(y2-y1)^2)end function GameObject:UnitsInRange(range)local units=0 for k,v in pairs(runner.Engine.ObjectManager.units)do if v.Distance<=range and not v.IsDead then units=units+1 end end return units end function GameObject:PlayersInRange(range)local units=0 for k,v in pairs(runner.Engine.ObjectManager.players)do if v.Distance<=range and not v.IsDead then units=units+1 end end return units end function GameObject:EnemiesInRange(range)local units=0 for k,v in pairs(runner.Engine.ObjectManager.units)do if self:DistanceFrom(v)<=range and v.Reaction and v.Reaction<=4 and not v.isDead and v.CanAttack then units=units+1 end end return units end function GameObject:FriendsInRange(range)local units=0 for k,v in pairs(runner.Engine.ObjectManager.units)do if v.Distance<=range and v.Reaction>4 and not v.IsDead then units=units+1 end end return units end;runner.Classes.AreaTrigger=runner.Classes.GameObject:extend()local AreaTrigger=runner.Classes.AreaTrigger runner.Classes.AreaTrigger=AreaTrigger runner.AreaTriggerViewColumns={"x","y","z","radius";}function AreaTrigger:init(pointer)self.pointer=pointer self.radius=0 self.spellID=0 end function AreaTrigger:Update()self.radius=runner.nn.ObjectField(self.pointer,280*4,4)self.creatorID=runner.nn.ObjectField(self.pointer,356*4,5)self.creatorName=runner.nn.ObjectName(self.creatorID)if self.creatorID then self.Reaction=Unlock(UnitReaction,self.creatorID,"player")else self.Reaction=8 end self.x,self.y,self.z=ObjectPosition(self.pointer)self.sx,self.sy,self.sz=self.x,self.y,self.z self.Distance=self:DistanceFromPlayer()self.PlayerInside=self.Distance<self.radius end function AreaTrigger:ToViewerRow()return{self.x,self.y,self.z,self.Creator }end;runner.Classes.Unit=runner.Classes.GameObject:extend()local Unit=runner.Classes.Unit runner.Classes.Unit=Unit runner.UnitViewColumns={"Name","Pointer","Distance","Reaction","Level","IsCasting","HP","Height","BoundingRadius","Lootable","Dead";}function Unit:init(pointer)runner.Classes.GameObject.init(self,pointer)self.Reaction=Unlock(UnitReaction,self.pointer,"player")self.Level=Unlock(UnitLevel,self.pointer)self.IsCasting=false self.HP=Unlock(UnitHealth,self.pointer)/Unlock(UnitHealthMax,self.pointer)*100 self.InCombat=Unlock(UnitAffectingCombat,self.pointer)self.IsFocus=false self.Dispellable=false self.DeEnrage=false self.isDead=Unlock(UnitIsDeadOrGhost,self.pointer)self.CanAttack=Unlock(UnitCanAttack,"player",self.pointer)self.SoulFragments=0 self.Threat=0 self.ThreatPercent=0 self.IsPlayer=false self.Role=UnitGroupRolesAssigned(self.pointer)self.Target=nil end function Unit:Update()runner.Classes.GameObject.Update(self)self.Reaction=Unlock(UnitReaction,self.pointer,"player")self.Level=Unlock(UnitLevel,self.pointer)self.IsCasting=Unlock(UnitCastingInfo,self.pointer)~=nil or Unlock(UnitChannelInfo,self.pointer)~=nil self.HP=Unlock(UnitHealth,self.pointer)/Unlock(UnitHealthMax,self.pointer)*100 self.InCombat=Unlock(UnitAffectingCombat,self.pointer)self.IsFocus=self:IsPlayerFocus()self.Dispellable=self:ShouldDispell()self.DeEnrage=self:ShouldDeEnrage()self.isDead=Unlock(UnitIsDeadOrGhost,self.pointer)self.IsPlayer=Unlock(UnitIsPlayer,self.pointer)self.CanAttack=Unlock(UnitCanAttack,"player",self.pointer)self.SoulFragments=self:GetAuraCount("Soul Fragments","HELPFUL")self.Threat=select(5,Unlock(UnitDetailedThreatSituation,"player",self.pointer))self.ThreatPercent=select(3,Unlock(UnitDetailedThreatSituation,"player",self.pointer))self.Role=UnitGroupRolesAssigned(self.pointer)self.Target=Unlock(UnitTarget,self.pointer)self.Trivial=Unlock(UnitIsTrivial,self.pointer)self.score=self:GetScore()local fields={}for i=0,250,4 do local t=runner.nn.ObjectField(self.pointer,i*4,1)table.insert(fields,i.." with "..t)end self.Fields=fields self:Debug()end function Unit:Debug()runner.Draw:Text(self.score,"GAMEFONTNORMAL",self.X,self.Y,self.Z+2,1,1,1,1)end function Unit:ScanAuras()for i=1,40 do local buffInfo=Unlock(C_UnitAuras.GetAuraDataByIndex,self.pointer,i,"HELPFUL")if buffInfo then runner.UI.ObjectViewer:AddAura(buffInfo,self.Name)end end for i=1,40 do local buffInfo=Unlock(C_UnitAuras.GetAuraDataByIndex,self.pointer,i,"HARMFUL")if buffInfo then runner.UI.ObjectViewer:AddAura(buffInfo,self.Name)end end end function Unit:GetScore()local score=0 if self.Trivial then return-10000 end local distance=self:DistanceFromPlayer()local HP=self.HP score=score+(800-(distance*5))score=score+((100-HP)*3)if self.InCombat then score=score+300 end if self.Threat and runner.LocalPlayer.Role=="TANK" then score=score+((4-self.Threat)*30)end if not self:LOS()then score=score-400 end if self.isDead then score=score-100000 end return score end function Unit:CastingSpellByName(name)local spellName,_,_,_,_,_,_,_,_,spellId=Unlock(UnitCastingInfo,self.pointer)if not spellName then spellName,_,_,_,_,_,_,_,_,spellId=Unlock(UnitChannelInfo,self.pointer)end return spellName==name end function Unit:GetAuraCount(name,filter)local auraName,icon,auraCount,dispelType,duration,expirationTime,source,isStealable,nameplateShowPersonal,spellId,canApplyAura,isBossDebuff,castByPlayer,nameplateShowAll,timeMod=AuraUtil.FindAuraByName(name,self.pointer,filter)if not auraName then return 0 end return auraCount end function Unit:ShouldDeEnrage()local deEnrage=false for i=1,40 do local name,_,_,_,_,_,_,_,_,spellId=Unlock(UnitBuff,self.pointer,i)if not name then break end local dispelType=select(5,GetSpellInfo(spellId))if dispelType=="Enrage" or dispelType=="Magic" then deEnrage=true break end end return deEnrage end function Unit:ShouldDispell()local dispellable=false for i=1,40 do local name,_,_,_,_,_,_,_,_,spellId=Unlock(UnitDebuff,self.pointer,i)if not name then break end local dispelType=select(5,GetSpellInfo(spellId))if dispelType=="Magic" or dispelType=="Curse" or dispelType=="Disease" or dispelType=="Poison" then dispellable=true break end end return dispellable end function Unit:HasStealable()for i=1,40 do local name,_,_,_,_,_,_,_,_,spellId=Unlock(UnitBuff,self.pointer,i)if not name then break end local dispelType=select(5,GetSpellInfo(spellId))if dispelType=="Magic" then return true end end return false end function Unit:IsPlayerFocus()return self.pointer==runner.nn:GetFocus()end function Unit:ToViewerRow()return{self.Name,self.pointer,string.format("%.2f",self.Distance),self.Reaction,self.Level,self.IsCasting and "Yes" or "No",string.format("%.2f",self.HP),string.format("%.2f",self.Height),string.format("%.2f",self.BoundingRadius),self.CanLoot and "Yes" or "No",self.isDead and "Yes" or "No";}end function Unit:HasAura(name,filter)local aname=AuraUtil.FindAuraByName(name,self.pointer,filter)return aname~=nil end function Unit:ShouldInterruptCasting()local name,text,texture,startTimeMS,endTimeMS,isTradeSkill,castID,notInterruptible,spellId=UnitCastingInfo(self.pointer)if not name then name,text,texture,startTimeMS,endTimeMS,isTradeSkill,notInterruptible,spellId=UnitChannelInfo(self.pointer)end if not name then return false end local elapsed=GetTime()-(startTimeMS/1000)return name and not notInterruptible and elapsed>0.2 end;runner.Classes.Player=runner.Classes.Unit:extend()local Player=runner.Classes.Player runner.Classes.Player=Player runner.PlayerViewColumns={"Name","Pointer","Distance","Reaction","Level","IsCasting","HP","Focus","Height","BoundingRadius";}function Player:init(pointer)runner.Classes.Unit.init(self,pointer)self.Race=select(2,UnitRace(self.pointer))self.Class=UnitClassBase(self.pointer)self.Focus=Unlock(UnitPower,self.pointer,2)self.Mana=Unlock(UnitPower,self.pointer,0)/Unlock(UnitPowerMax,self.pointer,0)*100 self.Energy=Unlock(UnitPower,self.pointer,3)/Unlock(UnitPowerMax,self.pointer,3)*100 self.Rage=Unlock(UnitPower,self.pointer,1)/Unlock(UnitPowerMax,self.pointer,1)*100 self.ComboPoints=Unlock(UnitPower,self.pointer,4)self.ArcaneCharges=Unlock(UnitPower,self.pointer,16)self.HolyPower=Unlock(UnitPower,self.pointer,9)self.SoulShards=Unlock(UnitPower,self.pointer,7)self.LunarPower=Unlock(UnitPower,self.pointer,8)self.Maelstrom=Unlock(UnitPower,self.pointer,11)self.Insanity=Unlock(UnitPower,self.pointer,13)self.Fury=Unlock(UnitPower,self.pointer,17)self.Role=UnitGroupRolesAssigned(self.pointer)self.Vigor=Unlock(UnitPower,self.pointer,25)end function Player:Update()runner.Classes.Unit.Update(self)self.Mana=Unlock(UnitPower,self.pointer,0)/Unlock(UnitPowerMax,self.pointer,0)*100 self.Energy=Unlock(UnitPower,self.pointer,3)/Unlock(UnitPowerMax,self.pointer,3)*100 self.Rage=Unlock(UnitPower,self.pointer,1)/Unlock(UnitPowerMax,self.pointer,1)*100 self.ComboPoints=Unlock(UnitPower,self.pointer,4)self.ArcaneCharges=Unlock(UnitPower,self.pointer,16)self.HolyPower=Unlock(UnitPower,self.pointer,9)self.SoulShards=Unlock(UnitPower,self.pointer,7)self.LunarPower=Unlock(UnitPower,self.pointer,8)self.Maelstrom=Unlock(UnitPower,self.pointer,11)self.Insanity=Unlock(UnitPower,self.pointer,13)self.Fury=Unlock(UnitPower,self.pointer,17)self.Role=UnitGroupRolesAssigned(self.pointer)self.Vigor=Unlock(UnitPower,self.pointer,25)end function Player:ToViewerRow()return{self.Name,self.pointer,string.format("%.2f",self.Distance),self.Reaction,self.Level,self.IsCasting and "Yes" or "No",string.format("%.2f",self.HP),self.IsFocus and "yes" or "no",string.format("%.2f",self.Height),string.format("%.2f",self.BoundingRadius)}end;runner.Classes.LocalPlayer=runner.Classes.Player:extend()local LocalPlayer=runner.Classes.LocalPlayer runner.Classes.LocalPlayer=LocalPlayer local fields=nil function LocalPlayer:init(pointer)runner.Classes.Player.init(self,pointer)self.spec=GetSpecialization()self.specName=select(2,GetSpecializationInfo(self.spec))self.Pitch=0 end function LocalPlayer:Update()local lastZ=self.z runner.Classes.Player.Update(self)if self.z and lastZ then self.ZDelta=self.z-lastZ else self.ZDelta=0 end self.spec=GetSpecialization()self.specName=select(2,GetSpecializationInfo(self.spec))self.Rotation=runner.nn.ObjectRotation(self.pointer)self.ForwardSpeed=select(3,C_PlayerInfo.GetGlidingInfo())self.Gliding=select(1,C_PlayerInfo.GetGlidingInfo())self.Pitch=runner.nn.ObjectField(self.pointer,280*4,4)end function LocalPlayer:EquipUpgrades()if StaticPopup1Button1~=nil and StaticPopup1Button1:IsVisible()then print("Accepting upgrade")Unlock(RunMacroText,"/click StaticPopup1Button1")end for i=0,NUM_BAG_SLOTS do for j=1,C_Container.GetContainerNumSlots(i)do local item=C_Container.GetContainerItemInfo(i,j)if item then local itemLoc=ItemLocation:CreateFromBagAndSlot(i,j)local itemLink=C_Item.GetItemLink(itemLoc)local isUpgrade=PawnIsItemDefinitivelyAnUpgrade(itemLink,true)if isUpgrade then C_Container.UseContainerItem(i,j)if StaticPopup1Button1~=nil and StaticPopup1Button1:IsVisible()then print("Accepting upgrade")Unlock(RunMacroText,"/click StaticPopup1Button1")end end end end end end;runner.Classes.MultiboxPlayer=runner.Classes.LocalPlayer:extend()local MultiboxPlayer=runner.Classes.MultiboxPlayer runner.Classes.MultiboxPlayer=MultiboxPlayer function MultiboxPlayer:init(pointer)runner.Classes.LocalPlayer.init(self,pointer)self.isMultiboxEnabled=false self.isMaster=false self.forcedFollow=false self.masterGUID=nil self.masterName=nil self.masterObject=nil self.FOLLOW_MIN_DISTANCE=1 self.FOLLOW_MAX_DISTANCE=5 self.MELEE_RANGE=5 self.RANGED_COMBAT_RANGE=25 self.followPosition=nil self.followAngle=nil self.lastMasterFacing=nil self.lastMasterMoving=nil self.lastPositionUpdate=0 self.POSITION_UPDATE_COOLDOWN=0.2 self.DIRECTION_CHANGE_THRESHOLD=math.rad(45)end function MultiboxPlayer:Update()runner.Classes.LocalPlayer.Update(self)if self.masterGUID and not self.masterObject then for _,player in pairs(runner.Engine.ObjectManager.players)do if UnitGUID(player.pointer)==self.masterGUID then self.masterObject=player break end end end end function MultiboxPlayer:GetMasterTarget()if not self.masterObject then return nil end return runner.Engine.ObjectManager:GetByPointer(UnitTarget(self.masterObject.pointer))end function MultiboxPlayer:IsPlayerMeleeSpec()local specID=GetSpecialization()if not specID then return false end local meleeSpecs={[250]=true,[251]=true,[252]=true,[577]=true,[581]=true,[103]=true,[104]=true,[268]=true,[269]=true,[270]=true,[66]=true,[70]=true,[65]=true,[259]=true,[260]=true,[261]=true,[263]=true,[71]=true,[72]=true,[73]=true }return meleeSpecs[specID]or false end function MultiboxPlayer:IsInMeleePosition(target)if not target then return false end local px,py,pz=ObjectPosition(self.pointer)local tx,ty,tz=ObjectPosition(target.pointer)if not px or not tx then return false end local distance=math.sqrt((px-tx)^2+(py-ty)^2)local desiredRange=self.MELEE_RANGE+target.BoundingRadius if math.abs(distance-desiredRange)>1 then return false end local targetFacing=ObjectFacing(target.pointer)local angleToPlayer=math.atan2(py-ty,px-tx)local behindAngle=(targetFacing+math.pi)%(2*math.pi)local angleDiff=math.abs(angleToPlayer-behindAngle)if angleDiff>math.pi then angleDiff=2*math.pi-angleDiff end return angleDiff<=math.pi/2 end function MultiboxPlayer:IsInRangedPosition(target)if not target then return false end local px,py,pz=ObjectPosition(self.pointer)local tx,ty,tz=ObjectPosition(target.pointer)if not px or not tx then return false end local distance=math.sqrt((px-tx)^2+(py-ty)^2)local desiredRange=self.RANGED_COMBAT_RANGE+target.BoundingRadius if math.abs(distance-desiredRange)>2 then return false end local hitX,hitY,hitZ=TraceLine(px,py,pz+2,tx,ty,tz+2,0x100111)return not hitX end function MultiboxPlayer:IsInTankPosition(target)if not target then return false end local distance=self:DistanceFrom(target)local desiredRange=target.BoundingRadius*0.5 return distance<=desiredRange+1 end function MultiboxPlayer:NeedsRepositioning(target)if not target then return false end local isTank=UnitGroupRolesAssigned(self.pointer)=="TANK";local isRanged=UnitGroupRolesAssigned(self.pointer)=="RANGED";if isRanged then return not self:IsInRangedPosition(target)elseif isTank then return not self:IsInTankPosition(target)else return not self:IsInMeleePosition(target)end end function MultiboxPlayer:CalculateRangedPosition(target)local px,py,pz=ObjectPosition(self.pointer)local tx,ty,tz=ObjectPosition(target.pointer)if not px or not tx then return nil end local dx,dy=px-tx,py-ty local dist=math.sqrt(dx*dx+dy*dy)if dist==0 then return nil end local desiredDistance=self.RANGED_COMBAT_RANGE+target.BoundingRadius local scale=desiredDistance/dist return{x=tx+dx*scale,y=ty+dy*scale,z=tz }end function MultiboxPlayer:CalculateMeleePosition(target)local targetFacing=ObjectFacing(target.pointer)local tx,ty,tz=ObjectPosition(target.pointer)if not targetFacing or not tx then return nil end local behindAngle=(targetFacing+math.pi)%(2*math.pi)local randomOffset=(math.random()-0.5)*math.pi/2 local finalAngle=(behindAngle+randomOffset)%(2*math.pi)local radius=self.MELEE_RANGE+target.BoundingRadius return{x=tx+math.cos(finalAngle)*radius,y=ty+math.sin(finalAngle)*radius,z=tz }end function MultiboxPlayer:GetCombatPosition(target)if not target then return nil end if not self:NeedsRepositioning(target)then return nil end local isTank=UnitGroupRolesAssigned(self.pointer)=="TANK";local isRanged=UnitGroupRolesAssigned(self.pointer)=="RANGED";if isRanged then return self:CalculateRangedPosition(target)elseif not isTank then return self:CalculateMeleePosition(target)else local tx,ty,tz=ObjectPosition(target.pointer)local offset=target.BoundingRadius*0.5 return{x=tx+offset,y=ty+offset,z=tz}end end function MultiboxPlayer:GetCombatRange()if UnitGroupRolesAssigned(self.pointer)=="RANGED" then return self.RANGED_COMBAT_RANGE end return self.MELEE_RANGE end function MultiboxPlayer:ShouldFollowMaster()if self.isMaster then return false end if not self.isMultiboxEnabled then return false end if not self.masterObject then return false end return self.forcedFollow or not UnitAffectingCombat(self.masterObject.pointer)end function MultiboxPlayer:NeedsNewFollowPosition()if not self.followPosition then return true end if not self.masterObject then return false end local currentTime=GetTime()if currentTime-self.lastPositionUpdate>self.POSITION_UPDATE_COOLDOWN then return true end local distance=self:GetDistanceFromMaster()if distance>self.FOLLOW_MAX_DISTANCE*1.5 then return true end local masterMoving=GetUnitSpeed(self.masterObject.pointer)>0 if self.lastMasterMoving~=masterMoving then self.lastMasterMoving=masterMoving return true end if masterMoving then local masterFacing=ObjectFacing(self.masterObject.pointer)if self.lastMasterFacing then local facingDiff=math.abs(masterFacing-self.lastMasterFacing)if facingDiff>self.DIRECTION_CHANGE_THRESHOLD then return true end end end return false end function MultiboxPlayer:IsAtFollowPosition()if not self.followPosition then return false end local px,py,pz=ObjectPosition(self.pointer)if not px then return false end local distance=math.sqrt((px-self.followPosition.x)^2+(py-self.followPosition.y)^2 )if runner.Draw then runner.Draw:Circle(px,py,pz,0.5)runner.Draw:Text(string.format("Distance to target: %.1f",distance),"GAMEFONTNORMAL",px,py,pz+2)end return distance<=2 end function MultiboxPlayer:GetDistanceFromMaster()if not self.masterObject then return 999999 end return self:DistanceFrom(self.masterObject)end function MultiboxPlayer:IsSafeToAttack(targetObject)if not targetObject then return false end local isTank=UnitGroupRolesAssigned(self.pointer)=="TANK";if isTank then return true end return UnitAffectingCombat(targetObject.pointer)end function MultiboxPlayer:GetClosestLootable()local closestLootable=nil local closestDistance=9999 for _,unit in pairs(runner.Engine.ObjectManager.units)do if unit.Reaction and unit.Reaction<4 and unit.CanLoot then local distance=unit:DistanceFromPlayer()if distance<closestDistance then closestLootable=unit closestDistance=distance end end end return closestLootable end function MultiboxPlayer:ShouldLoot()return not UnitAffectingCombat(self.pointer)end function MultiboxPlayer:ToViewerRow()local baseRow=runner.Classes.Player.ToViewerRow(self)table.insert(baseRow,self.isMaster and "Master" or "Slave")table.insert(baseRow,self.masterName or "None")return baseRow end;runner.Classes.Point=class()local Point=runner.Classes.Point runner.Classes.Point=Point function Point:init(x,y,z)self.X=x self.Y=y self.Z=z end function Point:DistanceFromXYZ(x,y,z)return math.sqrt((self.X-x)^2+(self.Y-y)^2+(self.Z-z)^2)end function Point:DistanceFromPoint(point)return math.sqrt((self.X-point.X)^2+(self.Y-point.Y)^2+(self.Z-point.Z)^2)end function Point:DistanceFromPlayer()local px,py,pz=ObjectPosition("player")return math.sqrt((self.X-px)^2+(self.Y-py)^2+(self.Z-pz)^2)end function Point:DistanceFromUnit(unit)local x,y,z=ObjectPosition(unit.Pointer)return math.sqrt((self.X-x)^2+(self.Y-y)^2+(self.Z-z)^2)end function Point:ToString()return string.format("X: %f Y: %f Z: %f",self.X,self.Y,self.Z)end;local nn=...runner.Engine.ObjectManager={}local OM=runner.Engine.ObjectManager runner.Engine.ObjectManager=OM local gameobjects,units,players,party,items,areatrigger={},{},{},{},{},{}OM.gameobjects,OM.units,OM.players,OM.party,OM.items,OM.areatrigger=gameobjects,units,players,party,items,areatrigger function OM:Update()local gameObjects=nn.ObjectManager("GameObject" or 8)local Units=nn.ObjectManager("Unit" or 5)local Players=nn.ObjectManager("Player" or 6)local AreaTriggers=nn.ObjectManager("AreaTrigger" or 11)local DynamicObjects=nn.ObjectManager("DynamicObject" or 9)local justOb=nn.ObjectManager("Object" or 0)self.party={}for k,v in pairs(self.gameobjects)do if not runner.nn.ObjectExists(v.pointer)then self.gameobjects[k]=nil end end for k,v in pairs(self.units)do if not runner.nn.ObjectExists(v.pointer)then self.units[k]=nil end end for k,v in pairs(self.players)do if not runner.nn.ObjectExists(v.pointer)then self.players[k]=nil end end for k,v in pairs(self.areatrigger)do if not runner.nn.ObjectExists(v.pointer)or v.x==0 then self.areatrigger[k]=nil end end for index,pointer in pairs(gameObjects)do if not self.gameobjects[pointer]then self.gameobjects[pointer]=runner.Classes.GameObject:new(pointer)else self.gameobjects[pointer]:Update()end end for index,pointer in pairs(Units)do if not self.units[pointer]then self.units[pointer]=runner.Classes.Unit:new(pointer)else if Unlock(UnitInParty,pointer)then table.insert(self.party,self.units[pointer])end self.units[pointer]:Update()end end for index,pointer in pairs(Players)do if not self.players[pointer]then self.players[pointer]=runner.Classes.Player:new(pointer)else if Unlock(UnitInParty,pointer)then table.insert(self.party,self.units[pointer])end self.players[pointer]:Update()end end for index,pointer in pairs(AreaTriggers)do if not self.areatrigger[pointer]then self.areatrigger[pointer]=runner.Classes.AreaTrigger:new(pointer)else self.areatrigger[pointer]:Update()end end end function OM:GetClosestGatherable()local closest=nil local closestDistance=9999 for k,v in pairs(self.gameobjects)do if v.CanGather then local distance=v:DistanceFromPlayer()if distance<closestDistance then closest=v closestDistance=distance end end end return closest end function OM:GetByPointer(pointer)pointer=tonumber(pointer)if self.gameobjects[pointer]then return self.gameobjects[pointer]end if self.units[pointer]then return self.units[pointer]end if self.players[pointer]then return self.players[pointer]end if self.areatrigger[pointer]then return self.areatrigger[pointer]end return nil end function OM:GetByName(name)for k,v in pairs(self.gameobjects)do if v.Name==name then return v end end for k,v in pairs(self.units)do if v.Name==name then return v end end for k,v in pairs(self.players)do if v.Name==name then return v end end return nil end function OM:GetObjectsByName(name)local objects={}for k,v in pairs(self.gameobjects)do if v.Name==name then table.insert(objects,v)end end for k,v in pairs(self.units)do if v.Name==name then table.insert(objects,v)end end for k,v in pairs(self.players)do if v.Name==name then table.insert(objects,v)end end return objects end function OM:GetClosestByName(name)local closest=nil local closestDistance=9999 for k,v in pairs(self.gameobjects)do if v.Name==name then local distance=v:DistanceFromPlayer()if distance<closestDistance then closest=v closestDistance=distance end end end for k,v in pairs(self.units)do if v.Name==name then local distance=v:DistanceFromPlayer()if distance<closestDistance then closest=v closestDistance=distance end end end for k,v in pairs(self.players)do if v.Name==name then local distance=v:DistanceFromPlayer()if distance<closestDistance then closest=v closestDistance=distance end end end return closest end function OM:GetClosestQuestGiver()local closest=nil local closestDistance=9999 for k,v in pairs(self.gameobjects)do if v.IsQuestGiver then local distance=v:DistanceFromPlayer()if distance<closestDistance then closest=v closestDistance=distance end end end for k,v in pairs(self.units)do if v.IsQuestGiver then local distance=v:DistanceFromPlayer()if distance<closestDistance then closest=v closestDistance=distance end end end return closest end function OM:GetClosestQuestTurnin()local closest=nil local closestDistance=9999 for k,v in pairs(self.gameobjects)do if v.IsQuestTurnin then local distance=v:DistanceFromPlayer()if distance<closestDistance then closest=v closestDistance=distance end end end for k,v in pairs(self.units)do if v.IsQuestTurnin then local distance=v:DistanceFromPlayer()if distance<closestDistance then closest=v closestDistance=distance end end end return closest end function OM:GetClosestQuestObjective()local closest=nil local closestDistance=9999 for k,v in pairs(self.gameobjects)do if v.IsQuestObjective then local distance=v:DistanceFromPlayer()if distance<closestDistance then closest=v closestDistance=distance end end end for k,v in pairs(self.units)do if v.IsQuestObjective and not v.isDead then local distance=v:DistanceFromPlayer()if distance<closestDistance then closest=v closestDistance=distance end end end return closest end function OM:GetTank()for k,v in pairs(self.units)do if v.Role=="TANK" then return v end end return nil end function OM:GetHealer()for k,v in pairs(self.party)do if v.Role=="HEALER" then return v end end return nil end function OM:GetClosestLootable()local closest=nil local closestDistance=9999 for k,v in pairs(self.units)do if v.Lootable and v.isDead then local distance=v:DistanceFromPlayer()if distance<closestDistance then closest=v closestDistance=distance end end end return closest end function OM:GetClosestEnemy()local closest=nil local closestDistance=9999 for k,v in pairs(self.units)do if v.Reaction and v.Reaction<4 and not v.isDead and v.CanAttack then local distance=v:DistanceFromPlayer()if distance<closestDistance then closest=v closestDistance=distance end end end return closest end function OM:FindMobWithNameAndAura(name,aura)for k,v in pairs(self.units)do if v.Name==name then if v:HasAura(aura)then return v end end end return nil end;runner.InteractionManager={}local InteractionManager=runner.InteractionManager function InteractionManager:CancelCinematic()if InCinematic()or IsInCinematicScene()then StopCinematic()end if IsInCinematicScene()then CancelScene()end end function InteractionManager:SelectFirstGossip()if#C_GossipInfo.GetOptions()then for k,v in pairs(C_GossipInfo.GetOptions())do if v.status==0 then C_GossipInfo.SelectOption(v.gossipOptionID)end end end end function InteractionManager:AcceptAllQuests()if QuestFrame:IsVisible()or GossipFrame:IsVisible()then for i=1,GetNumAvailableQuests()do SelectAvailableQuest(i)end for k,v in pairs(C_GossipInfo.GetAvailableQuests())do C_GossipInfo.SelectAvailableQuest(v.questID)end end end function InteractionManager:QuestContinue()if QuestFrameAcceptButton:IsVisible()then QuestFrameAcceptButton:Click()end if QuestFrameCompleteButton:IsVisible()then QuestFrameCompleteButton:Click()end if QuestFrameCompleteQuestButton:IsVisible()then QuestFrameCompleteQuestButton:Click()end end function InteractionManager:CompleteQuest()if QuestFrame:IsVisible()then for i=1,GetNumActiveQuests()do SelectActiveQuest(i)end end if QuestFrameCompleteQuestButton:IsVisible()then CompleteQuest()end end function InteractionManager:BuyFirstVendorItem()if MerchantFrame:IsVisible()then MerchantSellAllJunkButton:Click()for i=1,GetMerchantNumItems()do if select(3,GetMerchantItemInfo(i))==0 then BuyMerchantItem(i)return end end end end function InteractionManager:StaticPopUp()if StaticPopup1Button1:IsVisible()then StaticPopup1Button1:Click()end end function InteractionManager:HandleAll()InteractionManager:CancelCinematic()InteractionManager:SelectFirstGossip()InteractionManager:AcceptAllQuests()InteractionManager:QuestContinue()InteractionManager:CompleteQuest()InteractionManager:BuyFirstVendorItem()InteractionManager:StaticPopUp()end;local FormationManager={}runner.Engine.FormationManager=FormationManager FormationManager.MIN_DISTANCE=2 FormationManager.MAX_DISTANCE=5 FormationManager.MIN_SPACING=1 FormationManager.ARC_WIDTH=math.pi*0.55 FormationManager.followers={}FormationManager.positions={}FormationManager.lastMasterPos=nil FormationManager.lastMasterFacing=nil function FormationManager:AddFollower(player)local guid=UnitGUID(player.pointer)for _,follower in ipairs(self.followers)do if follower.guid==guid then runner.Engine.DebugManager:Debug("FormationManager",string.format("Follower already exists: %s",UnitName(player.pointer)))return end end runner.Engine.DebugManager:Debug("FormationManager",string.format("Adding follower: GUID=%s, Name=%s",guid,UnitName(player.pointer)))table.insert(self.followers,{guid=guid,name=UnitName(player.pointer),player=player })end function FormationManager:RemoveFollower(guid)for i,follower in ipairs(self.followers)do if follower.guid==guid then runner.Engine.DebugManager:Debug("FormationManager",string.format("Removing follower: %s",follower.name ))table.remove(self.followers,i)self.positions[guid]=nil break end end end function FormationManager:CheckPosition(x,y,masterX,masterY)local masterDist=math.sqrt((x-masterX)^2+(y-masterY)^2)if masterDist<self.MIN_DISTANCE or masterDist>self.MAX_DISTANCE then runner.Engine.DebugManager:Debug("FormationManager",string.format("Position invalid - Master distance: %.2f (min: %d, max: %d)",masterDist,self.MIN_DISTANCE,self.MAX_DISTANCE ))return false end for guid,pos in pairs(self.positions)do local dist=math.sqrt((x-pos.x)^2+(y-pos.y)^2)if dist<self.MIN_SPACING then runner.Engine.DebugManager:Debug("FormationManager",string.format("Position invalid - Too close to %s (%.2f < %d)",self:GetFollowerName(guid),dist,self.MIN_SPACING ))return false end end return true end function FormationManager:GetFollowerName(guid)for _,follower in ipairs(self.followers)do if follower.guid==guid then return follower.name end end return "Unknown";end function FormationManager:AssignPositions(masterX,masterY,masterZ,masterFacing)self.positions={}local numFollowers=#self.followers if numFollowers==0 then return end local arcStep=self.ARC_WIDTH/(numFollowers+1)local baseDistance=(self.MIN_DISTANCE+self.MAX_DISTANCE)/2 runner.Engine.DebugManager:Debug("FormationManager",string.format("Assigning positions for %d followers",numFollowers ))for i,follower in ipairs(self.followers)do local angle=masterFacing+math.pi-(self.ARC_WIDTH/2)+(i*arcStep)local distance=baseDistance local x=masterX+math.cos(angle)*distance local y=masterY+math.sin(angle)*distance self.positions[follower.guid]={x=x,y=y,z=masterZ,angle=angle-masterFacing-math.pi,distance=distance }runner.Engine.DebugManager:Debug("FormationManager",string.format("Assigned position for %s: angle=%.2f, distance=%.2f",follower.name,angle-masterFacing-math.pi,distance ))end end function FormationManager:ShouldUpdatePositions(masterX,masterY,masterFacing)if not self.lastMasterPos then self.lastMasterPos={x=masterX,y=masterY}self.lastMasterFacing=masterFacing return true end local distMoved=math.sqrt((masterX-self.lastMasterPos.x)^2+(masterY-self.lastMasterPos.y)^2 )if distMoved>1 then self.lastMasterPos={x=masterX,y=masterY}self.lastMasterFacing=masterFacing return true end return false end function FormationManager:DumpDebugState()runner.Engine.DebugManager:Debug("FormationManager","=== Formation State Dump ===")runner.Engine.DebugManager:Debug("FormationManager",string.format("Followers: %d",#self.followers))for i,follower in ipairs(self.followers)do runner.Engine.DebugManager:Debug("FormationManager",string.format("Follower %d: GUID=%s, Name=%s",i,follower.guid,follower.name ))end local posCount=0 for guid,pos in pairs(self.positions)do posCount=posCount+1 runner.Engine.DebugManager:Debug("FormationManager",string.format("Position for %s: x=%.2f, y=%.2f, z=%.2f",self:GetFollowerName(guid),pos.x,pos.y,pos.z ))end runner.Engine.DebugManager:Debug("FormationManager",string.format("Total Positions: %d",posCount))runner.Engine.DebugManager:Debug("FormationManager","========================")end function FormationManager:BroadcastPositions()if not IsInGroup()then return end local positionData={}for guid,pos in pairs(self.positions)do table.insert(positionData,string.format("%s:%.2f:%.2f:%.2f",guid,pos.x,pos.y,pos.z))end local message="FORMATION:"..table.concat(positionData,"|")runner.Engine.DebugManager:Debug("FormationManager",string.format("Broadcasting formation data (%d positions)",#positionData ))if IsInRaid()then C_ChatInfo.SendAddonMessage("MBXR",message,"RAID")elseif IsInGroup()then C_ChatInfo.SendAddonMessage("MBXR",message,"PARTY")end end function FormationManager:GetFollowerPosition(guid)return self.positions[guid]end function FormationManager:DrawDebug()if not runner.Draw then return end local master for _,follower in ipairs(self.followers)do if follower.player.masterObject then master=follower.player.masterObject break end end if master then local mx,my,mz=ObjectPosition(master.pointer)local mf=ObjectFacing(master.pointer)if mx then runner.Draw:SetColor(255,0,0,255)runner.Draw:Circle(mx,my,mz,1)local fx=mx+math.cos(mf)*3 local fy=my+math.sin(mf)*3 runner.Draw:Line(mx,my,mz,fx,fy,mz)local leftAngle=mf+math.pi-self.ARC_WIDTH/2 local rightAngle=mf+math.pi+self.ARC_WIDTH/2 local lx=mx+math.cos(leftAngle)*self.MAX_DISTANCE local ly=my+math.sin(leftAngle)*self.MAX_DISTANCE local rx=mx+math.cos(rightAngle)*self.MAX_DISTANCE local ry=my+math.sin(rightAngle)*self.MAX_DISTANCE runner.Draw:SetColor(0,255,255,128)runner.Draw:Line(mx,my,mz,lx,ly,mz)runner.Draw:Line(mx,my,mz,rx,ry,mz)runner.Draw:SetColor(0,255,0,255)for guid,pos in pairs(self.positions)do runner.Draw:Circle(pos.x,pos.y,pos.z,0.5)local followerName=self:GetFollowerName(guid)runner.Draw:Text(followerName,"GAMEFONTNORMAL",pos.x,pos.y,pos.z+2)end runner.Draw:SetColor(255,255,0,255)for _,follower in ipairs(self.followers)do local px,py,pz=ObjectPosition(follower.player.pointer)if px then runner.Draw:Circle(px,py,pz,0.5)local pos=self.positions[follower.guid]if pos then runner.Draw:SetColor(0,255,0,255)runner.Draw:Line(px,py,pz,pos.x,pos.y,pos.z)end end end end end end;runner.Engine.Navigation={}local Navigation=runner.Engine.Navigation runner.Engine.Navigation=Navigation local lastPOSCheck=0 local lastPOSX,lastPOSY,lastPOSZ=0,0,0 local currentPath=nil local currentPathIndex=nil local lastSurge=0 local function Distance3D(x1,y1,z1,x2,y2,z2)if not x1 or not y1 or not z1 or not x2 or not y2 or not z2 then return 999999 end local dx=x2-x1 local dy=y2-y1 local dz=z2-z1 return math.sqrt(dx*dx+dy*dy+dz*dz)end local function GenerateStraightLinePath(startX,startY,startZ,endX,endY,endZ)runner.Engine.DebugManager:Debug("Navigation","Generating straight line path")local path={}local totalDistance=Distance3D(startX,startY,startZ,endX,endY,endZ)local maxGap=2 local steps=math.ceil(totalDistance/maxGap)steps=math.max(2,steps)runner.Engine.DebugManager:Debug("Navigation",string.format("Path generation:\n".."- Total distance: %.2f yards\n".."- Maximum allowed gap: %d yards\n".."- Required steps: %d\n".."- Actual gap between points: %.2f yards",totalDistance,maxGap,steps,totalDistance/steps ))for i=0,steps do local t=i/steps local x=startX+(endX-startX)*t local y=startY+(endY-startY)*t local z=startZ+(endZ-startZ)*t table.insert(path,{x=x,y=y,z=z})if i>0 then local lastPoint=path[#path-1]local pointDistance=Distance3D(lastPoint.x,lastPoint.y,lastPoint.z,x,y,z)runner.Engine.DebugManager:Debug("Navigation",string.format("Point %d/%d - Distance from previous: %.2f yards",i,steps,pointDistance ))end end return path end function Navigation:FacePoint(x,y,z)local px,py,pz=runner.LocalPlayer.x,runner.LocalPlayer.y,runner.LocalPlayer.z if not px or not py or not pz then return end z=z or pz local dx,dy,dz=px-x,py-y,pz-z local radians=math.atan2(-dy,-dx)if radians<0 then radians=radians+math.pi*2 end runner.nn.SetPlayerFacing(radians)end local function GeneratePath(startX,startY,startZ,endX,endY,endZ)local _,_,_,_,_,_,_,mapId=GetInstanceInfo()local path=runner.nn.GenerateLocalPath(mapId,startX,startY,startZ,endX,endY,endZ)runner.Engine.DebugManager:Debug("Navigation",string.format("Attempting path generation from (%.2f, %.2f, %.2f) to (%.2f, %.2f, %.2f)",startX,startY,startZ,endX,endY,endZ ))if not path or#path<=1 then runner.Engine.DebugManager:Warning("Navigation","Map-based path generation failed")path=GenerateStraightLinePath(startX,startY,startZ,endX,endY,endZ)else local firstPoint=path[1]if firstPoint.x==0 and firstPoint.y==0 and firstPoint.z==0 then runner.Engine.DebugManager:Warning("Navigation","Map returned invalid coordinates (0,0,0)")path=GenerateStraightLinePath(startX,startY,startZ,endX,endY,endZ)end end return path end function Navigation:waypointAwayFrom(x,y,z,distance)local possible={}for i=1,20 do table.insert(possible,runner.Classes.Point:new(x+math.random(-distance*1.2,distance*1.2),y+math.random(-distance*1.2,distance*1.2),z))end local best=nil for k,v in pairs(possible)do if Navigation:PointHasPath(v.X,v.Y,v.Z)then if not best then best=v else if best:DistanceFromXYZ(x,y,z)>distance*1 then if v:DistanceFromPlayer()<best:DistanceFromPlayer()then best=v end end end end end return best end function Navigation:IsBehindTarget(target)if not target then return false end local px,py,pz=ObjectPosition("player")local tx,ty,tz=ObjectPosition(target.pointer)if not px or not tx then return false end local distance=math.sqrt((px-tx)^2+(py-ty)^2)local targetFacing=ObjectFacing(target.pointer)local angleToPlayer=math.atan2(py-ty,px-tx)local behindAngle=(targetFacing+math.pi)%(2*math.pi)local angleDiff=math.abs(angleToPlayer-behindAngle)if angleDiff>math.pi then angleDiff=2*math.pi-angleDiff end return angleDiff<=math.pi/2 end function Navigation:MoveBehindUnit(target)local targetFacing=ObjectFacing(target.pointer)local tx,ty,tz=ObjectPosition(target.pointer)if not targetFacing or not tx then return nil end local behindAngle=(targetFacing+math.pi)%(2*math.pi)local randomOffset=(math.random()-0.5)*math.pi/2 local finalAngle=(behindAngle+randomOffset)%(2*math.pi)local radius=5+target.BoundingRadius return{x=tx+math.cos(finalAngle)*radius,y=ty+math.sin(finalAngle)*radius,z=tz }end function Navigation:PointHasPath(x,y,z)local _,_,_,_,_,_,_,mapId=GetInstanceInfo()local path=runner.nn.GenerateLocalPath(mapId,runner.LocalPlayer.x,runner.LocalPlayer.y,runner.LocalPlayer.z,x,y,z)return path and#path>1 end function Navigation:MoveTo(unit)if unit then local x,y,z=ObjectPosition(unit)local px,py,pz=ObjectPosition("player")local distance=Distance3D(px,py,pz,x,y,z)if distance>20 and runner.LocalPlayer.Class=="SHAMAN" then if not runner.LocalPlayer:HasAura("Ghost Wolf","HELPFUL")then Unlock(CastSpellByName,"Ghost Wolf")end end local path=GeneratePath(px,py,pz,x,y,z)currentPath=path currentPathIndex=2 if#path>1 then local pathIndex=2 local tx=tonumber(path[pathIndex].x)local ty=tonumber(path[pathIndex].y)local tz=tonumber(path[pathIndex].z)local distance=Distance3D(px,py,pz,tx,ty,tz)if distance<2 then pathIndex=pathIndex+1 currentPathIndex=pathIndex if pathIndex>#path then Unlock(MoveForwardStop)currentPath=nil currentPathIndex=nil return end end tx=tonumber(path[pathIndex].x)ty=tonumber(path[pathIndex].y)tz=tonumber(path[pathIndex].z)local dx,dy,dz=px-tx,py-ty,pz-tz local radians=math.atan2(-dy,-dx)if radians<0 then radians=radians+math.pi*2 end runner.nn.SetPlayerFacing(radians)Unlock(MoveForwardStart)if GetTime()-lastPOSCheck>2 then local playerX,playerY,playerZ=ObjectPosition("player")local stuckdist=Distance3D(playerX,playerY,playerZ,lastPOSX,lastPOSY,lastPOSZ)if stuckdist<1 then runner.Engine.DebugManager:Warning("Navigation","Detected stuck state, attempting jump")Unlock(JumpOrAscendStart)end lastPOSCheck=GetTime()lastPOSX,lastPOSY,lastPOSZ=ObjectPosition("player")end end end end function Navigation:FlyToPoint(x,y,z)if not IsFlyableArea()then return end if GetShapeshiftForm()~=3 then Unlock(CastSpellByName,"Travel Form")end runner.Engine.Navigation:FacePoint(x,y,z)local groundZ=select(3,runner.nn.TraceLine(runner.LocalPlayer.x,runner.LocalPlayer.y,10000,runner.LocalPlayer.x,runner.LocalPlayer.y,-10000,0x110))runner.Draw:Text("Distance left "..self:Distance2D(runner.LocalPlayer.x,runner.LocalPlayer.y,x,y),"GAMEFONTNORMAL",runner.LocalPlayer.x,runner.LocalPlayer.y,runner.LocalPlayer.z+3)if self:Distance2D(runner.LocalPlayer.x,runner.LocalPlayer.y,x,y)>20 then if runner.LocalPlayer.z-groundZ<90 then Unlock(CastSpellByName,"Skyward Ascent")return end if runner.LocalPlayer.Pitch<=.1 then if not IsMouselooking()then Unlock(MouselookStart)Unlock(MoveViewDownStart,2)Unlock(MouselookStop)end print("Moving pitch down "..tostring(IsMouselooking()))return end if runner.LocalPlayer.Pitch>.1 and runner.LocalPlayer.Pitch<.3 then print("Pitch is good")Unlock(MoveViewDownStop)Unlock(MouselookStop)return end if runner.LocalPlayer.Vigor<4 then Unlock(CastSpellByName,"Second Wind")end if not runner.LocalPlayer:HasAura("Ohn'ahra's Gusts","HELPFUL")and runner.LocalPlayer.z-groundZ>80 or not runner.LocalPlayer:HasAura("Thrill of the Sky","HELPFUL")and runner.LocalPlayer.Vigor>2 and GetTime()-lastSurge>4 then Unlock(CastSpellByName,"Surge Forward")lastSurge=GetTime()end else print("We need to land")if runner.LocalPlayer.z-groundZ>5 then Unlock(PitchDownStart)else Unlock(PitchDownStop)end end end function Navigation:Distance2D(x1,y1,x2,y2)if not x1 or not y1 or not x2 or not y2 then return 999999 end return sqrt((x2-x1)^2+(y2-y1)^2)end function Navigation:MoveToPoint(x,y,z)local px,py,pz=ObjectPosition("player")if IsFlyableArea()then local distance=Navigation:Distance2D(px,py,pz,x,y,z)if distance>200 then Navigation:FlyToPoint(x,y,z)return end end local distance=Distance3D(px,py,pz,x,y,z)if distance>20 and runner.LocalPlayer.Class=="SHAMAN" then if not runner.LocalPlayer:HasAura("Ghost Wolf","HELPFUL")then Unlock(CastSpellByName,"Ghost Wolf")end end local path=GeneratePath(px,py,pz,x,y,z)currentPath=path currentPathIndex=2 if#path>1 then self:Debug(path)local pathIndex=2 local tx=tonumber(path[pathIndex].x)local ty=tonumber(path[pathIndex].y)local tz=tonumber(path[pathIndex].z)local distance=Distance3D(px,py,pz,tx,ty,tz)if distance<2 then pathIndex=pathIndex+1 currentPathIndex=pathIndex if pathIndex>#path then Unlock(MoveForwardStop)currentPath=nil currentPathIndex=nil return end end tx=tonumber(path[pathIndex].x)ty=tonumber(path[pathIndex].y)tz=tonumber(path[pathIndex].z)local dx,dy,dz=px-tx,py-ty,pz-tz local radians=math.atan2(-dy,-dx)if radians<0 then radians=radians+math.pi*2 end runner.nn.SetPlayerFacing(radians)Unlock(MoveForwardStart)end end function Navigation:GetCurrentPath()return currentPath end function Navigation:GetCurrentPathIndex()return currentPathIndex end function Navigation:Debug(path)for i=1,#path-1 do local x1,y1,z1=tonumber(path[i].x),tonumber(path[i].y),tonumber(path[i].z)local x2,y2,z2=tonumber(path[i+1].x),tonumber(path[i+1].y),tonumber(path[i+1].z)runner.Draw:Line(x1,y1,z1,x2,y2,z2)end end function Navigation:FaceUnit(unit)if unit then local x,y,z=ObjectPosition(unit)local px,py,pz=ObjectPosition("player")local dx,dy,dz=px-x,py-y,pz-z local radians=math.atan2(-dy,-dx)if radians<0 then radians=radians+math.pi*2 end runner.nn.SetPlayerFacing(radians)end end function Navigation:PathDistance(unit)if unit then local x,y,z=ObjectPosition(unit)local px,py,pz=ObjectPosition("player")local _,_,_,_,_,_,_,mapId=GetInstanceInfo()local path=runner.nn.GenerateLocalPath(mapId,px,py,pz,x,y,z)local distance=0 for i=1,#path-1 do local x1,y1,z1=tonumber(path[i].x),tonumber(path[i].y),tonumber(path[i].z)local x2,y2,z2=tonumber(path[i+1].x),tonumber(path[i+1].y),tonumber(path[i+1].z)distance=distance+Distance3D(x1,y1,z1,x2,y2,z2)end runner.Engine.DebugManager:Debug("Navigation",string.format("Path distance to %s: %.2f yards",UnitName(unit),distance ))return distance end return 0 end;runner.UI.ObjectViewer2={}local OV=runner.UI.ObjectViewer2 runner.UI.ObjectViewer2=OV OV.SelectedPointer=nil local inlineGroup=nil local ScrollFrame=nil local sortKey="Distance";local searchText="";local green,red,blue,yellow="|cff00ff00","|cffff0000","|cff0000ff","|cffffff00";local mainFrame=runner.AceGUI:Create("Window","ObjectViewerFrame",UIParent)mainFrame:SetTitle("Object Manager")mainFrame:SetLayout("Flow")mainFrame:SetWidth(1000)mainFrame:SetHeight(800)mainFrame:Show()local sortDropdown=runner.AceGUI:Create("Dropdown")sortDropdown:SetList({["Distance"]="Distance",["Name"]="Name",["Reaction"]="Reaction";})sortDropdown:SetValue("Distance")sortDropdown:SetCallback("OnValueChanged",function(_,_,key)sortKey=key OV:Update()end)local searchBox=runner.AceGUI:Create("EditBox")searchBox:SetLabel("Search")searchBox:SetCallback("OnEnterPressed",function(_,_,text)searchText=text OV:Update()end)mainFrame:AddChild(searchBox)mainFrame:AddChild(sortDropdown)local tree=runner.AceGUI:Create("TreeGroup")tree:SetLayout("Fill")tree:SetFullWidth(true)tree:SetFullHeight(700)tree:SetTree({{value="Objects",text="Objects",children={{value="Objects",text="Objects"},{value="Players",text="Players"},{value="Units",text="Units"},{value="Area Triggers",text="Area Triggers"}}}})tree:SetCallback("OnGroupSelected",function(container,_,group)tree:ReleaseChildren()inlineGroup=runner.AceGUI:Create("InlineGroup")inlineGroup:SetFullWidth(true)inlineGroup:SetFullHeight(true)inlineGroup:SetLayout("Fill")tree:AddChild(inlineGroup)ScrollFrame=runner.AceGUI:Create("ScrollFrame")ScrollFrame:SetLayout("Flow")ScrollFrame:SetFullWidth(true)ScrollFrame:SetFullHeight(true)inlineGroup:AddChild(ScrollFrame)OV.ScrollFrame=ScrollFrame OV:BuildObjectList(ScrollFrame,group)end)mainFrame:AddChild(tree)function OV:SortBy(originalTable,key)local tempUnits={}local hasKey=false for k,v in pairs(originalTable)do if v[key]~=nil then hasKey=true end table.insert(tempUnits,v)end if hasKey then table.sort(tempUnits,function(a,b)return a[key]<b[key]end)end return tempUnits end function OV:Update()local gameobjectData,unitData,playerData,areaTriggerData={},{},{},{}for k,v in pairs(OV:SortBy(runner.Engine.ObjectManager.gameobjects,sortKey))do table.insert(gameobjectData,{value=v.pointer,text=v.Name })end for k,v in pairs(OV:SortBy(runner.Engine.ObjectManager.units,sortKey))do if v.Reaction<4 then textColor=red elseif v.Reaction==4 then textColor=yellow else textColor=green end table.insert(unitData,{value=v.pointer,text=textColor..v.Name })end for k,v in pairs(runner.Engine.ObjectManager.players)do table.insert(playerData,{value=v.pointer,text=textColor..v.Name })end table.insert(playerData,{value="player",text=green.."Local Player";})for k,v in pairs(runner.Engine.ObjectManager.areatrigger)do table.insert(areaTriggerData,{value=v.pointer,text="AreaTrigger";})end local fieldsData={}for i=0,2000,4 do local t=runner.nn.ObjectField("player",i*4,4)table.insert(fieldsData,{value=i,text=i.." with "..t })end local target=UnitTarget("player")or "no";local treeData={{value="Target",text="Target",icon="Interface\\Icons\\INV_Misc_QuestionMark",children={{value=target,text="Your Target"}}},{value="GameObjects",text="GameObjects",icon="Interface\\Icons\\INV_Drink_05",children=gameobjectData },{value="Units",text="Units",children=unitData },{value="Players",text="Players",children=playerData },{value="AreaTriggers",text="AreaTriggers",children=areaTriggerData },{value="Fields",text="Fields",children=fieldsData }}tree:SetTree(treeData)if OV.SelectedPointer then OV:BuildObjectList(OV.ScrollFrame,nil,OV.SelectedPointer)end end function OV:BuildObjectList(container,selected,SelectedPointer)if selected~=nil then local split={strsplit("\001",selected)}local pointer=split[2]OV.SelectedPointer=pointer else pointer=SelectedPointer end local object=runner.Engine.ObjectManager:GetByPointer(pointer)if pointer=="player" then object=runner.LocalPlayer end if not object then return end container:ReleaseChildren()object:Debug()for k,v in pairs(object._)do if type(v)=="string" or type(v)=="number" or type(v)=="boolean" then local label=runner.AceGUI:Create("Label")if searchText and searchText~=''and(string.find(tostring(v):lower(),searchText:lower())or string.find(k:lower(),searchText:lower()))then label:SetText(green..k..": "..tostring(v))else label:SetText(k..": "..tostring(v))end container:AddChild(label)end if type(v)=="table" then local splitlabel=runner.AceGUI:Create("Label")splitlabel:SetText(" ")splitlabel:SetFullWidth(true)container:AddChild(splitlabel)local label=runner.AceGUI:Create("Label")label:SetText(k..":")container:AddChild(label)for k2,v2 in pairs(v)do if type(v2)=="string" or type(v2)=="number" or type(v2)=="boolean" then local label=runner.AceGUI:Create("Label")if searchText and searchText~='' and(string.find(tostring(v2):lower(),searchText:lower())or string.find(k2:lower(),searchText:lower()))then label:SetText(green..k2..": "..tostring(v2))else label:SetText(k2..": "..tostring(v2))end container:AddChild(label)end end local splitlabel2=runner.AceGUI:Create("Label")splitlabel2:SetText(" ")splitlabel2:SetFullWidth(true)container:AddChild(splitlabel2)end end end function OV:Toggle()if mainFrame:IsShown()then mainFrame:Hide()else mainFrame:Show()end end;