local root=(arg[1] or '.')..'/' local function read(path)local f=assert(io.open(root..path));local s=f:read('*a');f:close();return s end local function Check(native) local env=setmetatable({},{__index=getfenv(0)});env._G=env local frames,modules,timers,queue={},{},{},{} local now,cast,channel,selfTarget,queries=10,nil,nil,false,0 local keys,remote=false,nil local methods={} local function object(kind,name,parent) local f=setmetatable({kind=kind,name=name,parent=parent,scripts={},events={},shown=true,width=140,alpha=1}, {__index=function(_,key)return methods[key] or (string.find(key,'^[A-Z]') and function()end or nil)end}) table.insert(frames,f);if name then env[name]=f end;return f end function methods:SetScript(k,v)self.scripts[k]=v end function methods:GetScript(k)return self.scripts[k]end function methods:RegisterEvent(k)self.events[k]=true end function methods:Hide()self.shown=false end function methods:Show()self.shown=true end function methods:IsShown()return self.shown end function methods:SetPoint(...)self.point=arg end function methods:ClearAllPoints()self.point=nil end function methods:GetFont()return 'font',12,'' end function methods:GetAlpha()return self.alpha end function methods:SetAlpha(a)self.alpha=a end function methods:SetWidth(w)self.width=w end function methods:GetWidth()return self.width end function methods:GetHeight()return 10 end function methods:GetEffectiveScale()return 1 end function methods:SetTexture(t)self.path=t end function methods:SetText(t)self.text=t end function methods:SetVertexColor(...)self.tint=arg end function methods:SetBackdropBorderColor(...)self.rim=arg end function methods:SetMinMaxValues(a,b)self.low,self.high=a,b;self.timer=nil end function methods:SetValue(v)self.value=v;self.writes=(self.writes or 0)+1 end function methods:SetTimerDuration(d,_,direction)self.timer,self.direction=d,direction end function methods:GetStatusBarTexture()if not self.fill then self.fill=object('Texture')end;return self.fill end function methods:CreateTexture()return object('Texture')end function methods:CreateFontString()return object('FontString')end env.CreateFrame=object env.UIParent=object('Frame');env.TargetFrame=object('Frame');env.TargetofTargetFrame=object('Frame') env.TargetFrameBuff1=object('Texture');env.TargetFrameBuff1:Hide() env.TargetofTargetFrame:Hide() env.CastingBarFrame=object('StatusBar');env.CastingBarText=object('FontString') env.GetTime=function()return now end env.UnitExists=function()return true end env.UnitIsUnit=function(a,b)return a==b or selfTarget and ((a=='target' and b=='player') or (b=='target' and a=='player'))end env.Enum={StatusBarInterpolation={Immediate=0},StatusBarTimerDirection={ElapsedTime=0,RemainingTime=1}} env.C_EventUtils={IsEventValid=function()return true end} env.C_Timer={NewTimer=function(delay,callback) local t={at=now+delay,callback=callback};function t:Cancel()self.cancelled=true end table.insert(timers,t);return t end} env.C_DurationUtil={CreateDuration=function() local d={} function d:SetTimeSpan(a,b)self.start,self.finish=a,b end function d:GetStartTime()return self.start end function d:GetEndTime()return self.finish end return d end,CreateDurationTextBinding=function() local b={};function b:SetFontString(v)self.text=v end function b:SetFormatter(v)self.formatter=v end function b:SetExpiredText(v)self.expired=v end function b:SetDuration(v)self.duration=v end function b:Enable()self.enabled=true end function b:Disable()self.enabled=false end return b end} env.C_StringUtil={CreateNumericRuleFormatter=function()return {AddBreakpoint=function(self,v)self.rule=v end}end} local suppliedDuration env.UnitCastingDuration=function()return suppliedDuration end env.UnitChannelDuration=env.UnitCastingDuration env.ShaguTweaks={T=setmetatable({},{__index=function(_,k)return k end}),API={durationtimers=native,casts=true,eventutils=true,timer=true}, libcast={db={}},GetGlobalEnv=function()return env end} env.ShaguTweaks.UnitCastingInfo=function() if remote then return 'Remote','','','remote',remote.start*1000,remote.finish*1000 end end function env.ShaguTweaks:register(m)modules[m.title]=m;return m end env.ShaguTweaks.HookScript=function(frame,name,callback) local original=frame.scripts[name];frame.scripts[name]=function()if original then original()end;callback()end end env.ShaguTweaks.QueueFunction=function(callback)table.insert(queue,callback)end local API=env.ShaguTweaks.API API.Defer=env.ShaguTweaks.QueueFunction API.RegisterUnitEvent=function(f,e,a,b)f.events[e]={a,b}end API.AreMoveModifiersDown=function()return keys end API.UnitGUID=function()return 'guid' end API.GetCastInfo=function()queries=queries+1;if cast then return cast.name or 'Cast','','','icon',cast.start*1000,cast.finish*1000 end end API.GetChannelInfo=function()if channel then return 'Channel','','','icon',channel.start*1000,channel.finish*1000 end end local src=read('api.lua');local a=assert(string.find(src,'API.GetCastDuration = function',1,true));local b=assert(string.find(src,'API.IsSpellInRange =',a,true)) local helper=assert(loadstring('local API=ShaguTweaks.API;local _G=ShaguTweaks.GetGlobalEnv();'..string.sub(src,a,b-1)));setfenv(helper,env);helper() local function load(path)local fn=assert(loadfile(root..path));setfenv(fn,env);fn()end load('mods/target-castbar.lua');modules['Enemy Castbars']:enable() load('mods/improved-castbar.lua');modules['Improved Castbar']:enable() local target,player=env.ShaguTargetCastbar,env.STImprovedCastbar local function emit(name,unit) env.event,env.arg1=name,unit for _,frame in ipairs(frames)do if frame.events[name] and frame.scripts.OnEvent then env.this=frame;frame.scripts.OnEvent()end end env.this=nil end local function runQueue()local work=queue;queue={};for _,fn in ipairs(work)do fn()end end cast={start=9,finish=15} suppliedDuration=env.C_DurationUtil.CreateDuration();suppliedDuration:SetTimeSpan(9,15) emit('UNIT_SPELLCAST_START','target');emit('UNIT_SPELLCAST_START','player') assert(target.shown and player.shown) if native then assert(target.timer==suppliedDuration and target.direction==0 and not target.scripts.OnUpdate) assert(target.spark.point[2]==target:GetStatusBarTexture()) assert(player.timerBinding.enabled and player.timerBinding.duration==suppliedDuration and not player.scripts.OnUpdate) assert(player.timerBinding.formatter.rule.format=='%.1fs') assert(env.CastingBarFrame.timer==nil,'player native fill/fade must remain untouched') local old=target.expiry -- Pushback replaces the snapshot and cancels the previous expiry. cast.finish=17;emit('UNIT_SPELLCAST_DELAYED','target');emit('UNIT_SPELLCAST_DELAYED','player') assert(old.cancelled and target.timer.finish==17 and player.timerBinding.duration.finish==17) assert(suppliedDuration.finish==15,'a mismatched engine snapshot must not be mutated') local fallback=target.fallbackDuration cast=nil;suppliedDuration=nil;channel={start=10,finish=18};emit('UNIT_SPELLCAST_CHANNEL_START','target');emit('UNIT_SPELLCAST_CHANNEL_START','player') assert(target.direction==1 and target.fallbackDuration==fallback and player.timerBinding.enabled) channel.finish=16;emit('UNIT_SPELLCAST_CHANNEL_UPDATE','target');assert(target.timer.finish==16) -- Aura layout work coalesces; animation does not query casts or write values. emit('UNIT_AURA','target');emit('UNIT_AURA','target');assert(table.getn(queue)==1);runQueue() env.TargetofTargetFrame:Show();env.TargetofTargetFrame.scripts.OnShow();runQueue();assert(target.positionY==-24) local activeTimer=target.timer env.ShaguTweaks.UITint={r=1,g=.78,b=.4,a=1};env.ShaguTweaks.UIBorder={r=.8,g=.62,b=.32,a=1} keys=true;emit('MODIFIER_STATE_CHANGED');assert(env.ShaguTweaksTargetCastBarMover.shown and target.timer==activeTimer) assert(env.ShaguTweaksTargetCastBarMover.border.tint[2]==.78 and env.ShaguTweaksTargetCastBarMover.icon.rim[1]==.8) keys=false;emit('MODIFIER_STATE_CHANGED');assert(not env.ShaguTweaksTargetCastBarMover.shown) local before=queries;now=11;assert(queries==before and not target.writes) -- Cancel/stop clears target immediately, retaining player text for native fade. channel=nil;emit('UNIT_SPELLCAST_INTERRUPTED','target');emit('UNIT_SPELLCAST_STOP','player') assert(not target.shown and not target.timer and not player.timerBinding.enabled and player.shown) env.CastingBarFrame.scripts.OnHide();assert(not player.shown) -- Missing STOP still expires; replacement casts cannot be hidden by old timers. cast={start=11,finish=15};emit('UNIT_SPELLCAST_START','target');local expiry=target.expiry cast.finish=19;emit('UNIT_SPELLCAST_DELAYED','target');assert(expiry.cancelled) now=19;target.expiry.callback();assert(not target.shown and not target.timer) -- Player-only events refresh a self-target; unrelated player casts do not. now=20;cast={start=20,finish=25};selfTarget=true;emit('UNIT_SPELLCAST_START','player');assert(target.shown) cast=nil;emit('UNIT_SPELLCAST_STOP','player');assert(not target.shown) selfTarget=false;cast={start=20,finish=25};emit('UNIT_SPELLCAST_START','player');assert(not target.shown) cast=nil;emit('PLAYER_TARGET_CHANGED');assert(not target.shown) -- Complete remote timing still uses the optional GUID cache complement. env.ShaguTweaks.superwow_active=true;env.ShaguTweaks.libcast.db.guid={cast='Remote'} remote={start=20,finish=26};env.ShaguTweaks.libcast.OnRemoteChanged('guid') assert(target.shown and target.timer.finish==26 and target.text.text=='Remote') remote=nil;env.ShaguTweaks.libcast.db.guid=nil;env.ShaguTweaks.libcast.OnRemoteChanged('guid') assert(not target.shown) else assert(target.scripts.OnUpdate and player.scripts.OnUpdate and not player.timerBinding) env.this=target;target.scripts.OnUpdate();assert(target.value==1) env.this=player;env.arg1=.1;player.scripts.OnUpdate();assert(player.timerText.text=='5.0s') end end Check(true);Check(false) print('Castbars: native snapshot/fallback durations, fill/spark/text ownership, delay/channel replacement, cancellation/expiry, player fade, self-target routing and older API animation passed')