Nenue@6: --- Turok Nenue@6: -- Castbar.lua Nenue@6: -- Created: 12/4/2015 11:17 PM Nenue@6: -- @file-author@ Nenue@6: -- @project-revision@ @project-hash@ Nenue@6: -- @file-revision@ @file-hash@ Nenue@6: -- Deals with casting events and works the castingbar interface Nenue@6: local print, tostring, tonumber, rep, pairs, min, format, unpack, select, strpad = print, tostring, tonumber, string.rep, pairs, math.min, string.format, unpack, select, string.rep Nenue@6: local GetTime, UnitCastingInfo, UnitChannelInfo, GetSpellInfo, GetSpellTabInfo = GetTime, UnitCastingInfo, UnitChannelInfo, GetSpellInfo, GetSpellTabInfo Nenue@6: local floor = math.floor Nenue@6: local T = Turok Nenue@6: local db Nenue@6: local PING_CEILING = 190 -- transpacific Nenue@6: local PING_MIDLINE = 100 Nenue@6: local PING_FLOOR = 10 -- transcontinental Nenue@6: --@debug Nenue@6: local cType, cText, cNum, cWord, cKey, cPink, cBool = cType, cText, cNum, cWord, cKey, cPink, cBool Nenue@6: local print = function(...) Nenue@6: if _G.Devian and _G.DevianDB.workspace ~= 1 then Nenue@6: _G.print('Tek', ...) Nenue@6: end Nenue@6: end Nenue@6: Nenue@6: local EVENT_COLOR = '|cFF44FF44' Nenue@6: local EVENT_COLOR2 = '|cFF88FF88' Nenue@6: local DATA_COLOR = '|cFFDD77DD' Nenue@6: --@end-debug@ Nenue@6: Nenue@6: local mod = T:NewModule("Tek") Nenue@6: function mod:OnInitialize() Nenue@6: self.db = T.db.tek Nenue@6: self.castbar = {} Nenue@6: self.UNIT_SPELLCAST_SENT = self.SpellCastRequest Nenue@6: self.UNIT_SPELLCAST_START = self.SpellCastEvent Nenue@6: self.UNIT_SPELLCAST_DELAYED = self.SpellCastEvent Nenue@6: self.UNIT_SPELLCAST_SUCCEEDED = self.SpellCastEvent Nenue@6: self.UNIT_SPELLCAST_STOP = self.SpellCastEvent Nenue@6: self.UNIT_SPELLCAST_FAILED = self.SpellCastEvent Nenue@6: self.UNIT_SPELLCAST_INTERRUPTED = self.SpellCastEvent Nenue@6: self.UNIT_SPELLCAST_CHANNEL_START = self.SpellCastEvent Nenue@6: self.UNIT_SPELLCAST_CHANNEL_STOP = self.SpellCastEvent Nenue@6: self.UNIT_SPELLCAST_CHANNEL_UPDATE = self.SpellCastEvent Nenue@6: self.UNIT_SPELLCAST_INTERRUPTIBLE = self.SpellCastEvent Nenue@6: self.UNIT_SPELLCAST_UNINTERRUPTIBLE = self.SpellCastEvent Nenue@6: end Nenue@6: Nenue@6: --- events & units Nenue@6: local TRACKED_UNITS = {'player', 'target', 'focus', 'pet'} Nenue@6: local FADE_OUT_TIME, FADE_IN_TIME = 1, .2 Nenue@6: Nenue@6: local TEXTURE_SUFFIX = { Nenue@6: ['CHANNEL_START'] = '_channeling', Nenue@6: ['CHANNEL_UPDATE'] = '_channeling', Nenue@6: ['CHANNEL_STOPPED'] = '_channeling', Nenue@6: ['START'] = '_casting', Nenue@6: ['INTERRUPTED'] = '_interrupted', Nenue@6: ['SUCCEEDED'] = '_finished', Nenue@6: } Nenue@6: Nenue@6: function mod:OnEnable() Nenue@6: db = self.db Nenue@6: -- setup castingbar frames Nenue@6: local c = self.castbar Nenue@6: for _, unit in pairs(TRACKED_UNITS) do Nenue@6: local cdb = db[unit] Nenue@6: --@debug@ Nenue@6: print(DATA_COLOR .. unit .. '|r castbar creation')--@end-debug@ Nenue@6: Nenue@6: -- Set frames Nenue@6: local fn = 'Tek' .. unit .. 'CastBar' Nenue@6: c[unit] = CreateFrame('Frame', fn, _G[cdb.parent], 'TurokCastingBar') Nenue@6: Nenue@6: local pc = c[unit] Nenue@6: T.SetFrameLayout(pc, cdb) Nenue@6: T.SetTextureLayout(pc.icon, cdb.icon or db.icon) Nenue@6: T.SetStatusTextures(pc, cdb.statusbar or db.statusbar) Nenue@6: Nenue@6: if unit == 'player' then Nenue@6: T.SetFontLayout(pc.ping, db.ping) Nenue@6: T.SetFontLayout(pc.downtime, db.downtime) Nenue@6: pc.pingbar:ClearAllPoints() Nenue@6: pc.pingbar:SetPoint('TOPRIGHT', pc.background, 'TOPRIGHT', 0, 0) Nenue@6: pc.pingbar:SetHeight(pc.background:GetHeight()) Nenue@6: else Nenue@6: if pc.interrupt then Nenue@6: pc.interrupt:SetSize(pc.icon.size*3, pc.icon.size* 3) Nenue@6: end Nenue@6: Nenue@6: end Nenue@6: Nenue@6: T.SetFontLayout(pc.casttime, cdb.casttime or db.casttime) Nenue@6: T.SetFontLayout(pc.spelltext, cdb.spelltext or db.spelltext) Nenue@6: Nenue@6: pc:SetAlpha(0) Nenue@6: pc.last = nil Nenue@6: pc.sent = {} Nenue@6: pc.unit = unit Nenue@6: pc.SetSpell = self.SetSpell Nenue@6: pc.SetState = self.SetState Nenue@6: pc.Update = self.Update Nenue@6: end Nenue@6: Nenue@6: Nenue@6: -- kill default casting bar Nenue@6: -- T.cbscripts = {CastingBarFrame:GetScript('OnUpdate'), CastingBarFrame:GetScript('OnEvent')} Nenue@6: CastingBarFrame:SetScript('OnUpdate', nil) Nenue@6: CastingBarFrame:SetScript('OnEvent', nil) Nenue@6: CastingBarFrame:Hide() Nenue@6: PetCastingBarFrame:SetScript('OnUpdate', nil) Nenue@6: PetCastingBarFrame:SetScript('OnEvent', nil) Nenue@6: PetCastingBarFrame:Hide() Nenue@6: end Nenue@6: Nenue@6: function mod:UpdateLocked() Nenue@6: for k, v in pairs(self.castbar) do Nenue@6: if T.unlocked then Nenue@6: local name, texture, offset, numSpells = GetSpellTabInfo(T.specPage) Nenue@6: v.value = offset Nenue@6: v.duration = numSpells Nenue@6: v.spelltext:SetText(k) Nenue@6: v.icon:SetTexture(texture) Nenue@6: v:SetAlpha(1) Nenue@6: v:Show() Nenue@6: v:EnableMouse(true) Nenue@6: v:SetMovable(true) Nenue@6: v:RegisterForDrag("LeftButton") Nenue@6: v:SetScript('OnDragStart', function(self) self:StartMoving() end) Nenue@6: v:SetScript('OnDragStop', function(self) self:StopMovingOrSizing() end) Nenue@6: else Nenue@6: Nenue@6: v:SetScript('OnDragStart', nil) Nenue@6: v:SetScript('OnDragStop', nil) Nenue@6: v:EnableMouse(false) Nenue@6: v:SetMovable(false) Nenue@6: Nenue@6: print('Saving bar coordinates post unlock') Nenue@6: Nenue@6: db[k].x = v:GetLeft() Nenue@6: db[k].y = v:GetTop() Nenue@6: db[k].anchor = 'BOTTOMLEFT' Nenue@6: db[k].anchorTo = 'BOTTOMLEFT' Nenue@6: db[k].parent = 'UIParent' Nenue@6: end Nenue@6: end Nenue@6: end Nenue@6: Nenue@6: --- Store spell requests for use in tracking lag time Nenue@6: function mod:SpellCastRequest(e, unit, spellName, rank, target, castID) Nenue@6: print('|cFFFF4400Request sent:|r ', spellName, castID, target) Nenue@6: self.castbar.player.sent[castID] = { Nenue@6: spellName = spellName, Nenue@6: castID = castID, Nenue@6: sendTime = GetTime()*1000, Nenue@6: } Nenue@6: end Nenue@6: Nenue@6: --- Handle events pertaining to a cast bar Nenue@6: function mod:SpellCastEvent(event, unit, ...) Nenue@6: if not self.castbar[unit] then Nenue@6: return Nenue@6: end Nenue@6: Nenue@6: local u = T.unit[unit] Nenue@6: local c = self.castbar[unit] Nenue@6: Nenue@6: --- doubling as an invocation source test Nenue@6: local spellName, rank, castID, spellID = ... Nenue@6: Nenue@6: local channelinfo = T.unit[unit].channeling Nenue@6: local castinginfo = T.unit[unit].casting Nenue@6: local sendq = c.sent Nenue@6: local castd = c.last Nenue@6: local timestamp = GetTime()*1000 Nenue@6: Nenue@6: --todo: remove truncation Nenue@6: local e = event:match("UNIT_SPELLCAST_([%a_]+)") Nenue@6: print(GetTime(), '|cFFFF8747'..e..(strpad(' ',12-#e))..'|r |cFFDDFF00#'..tostring(castID)..'|r', spellName, spellID) Nenue@6: Nenue@6: c.channeling = channelinfo and channelinfo[1] or nil Nenue@6: c.casting = castinginfo and castinginfo[1] or nil Nenue@6: if c.channeling then Nenue@6: -- {name, subText, text, texture, startTime, endTime, isTradeSkill, notInterruptible} Nenue@6: c.startTime = channelinfo[5] Nenue@6: c.endTime = channelinfo[6] Nenue@6: c.nonInterruptible = channelinfo[8] Nenue@6: elseif c.casting then Nenue@6: --name, subText, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible Nenue@6: c.startTime = castinginfo[5] Nenue@6: c.endTime = castinginfo[6] Nenue@6: c.castID = castinginfo[8] Nenue@6: c.nonInterruptible = castinginfo[9] Nenue@6: else Nenue@6: print(' |cFFFF7700internal data was nil|r') Nenue@6: end Nenue@6: Nenue@6: --- process the "event" Nenue@6: local setSpell, setState Nenue@6: if e == 'START' then Nenue@6: setSpell = true Nenue@6: setState = true Nenue@6: Nenue@6: -- player data calculations Nenue@6: if unit == 'player' then Nenue@6: if sendq[castID] and sendq[castID].spellName == spellName then Nenue@6: local sent = sendq[castID] Nenue@6: print(' |cFF00AAFFmatched:|r', sent.spellName, castID) Nenue@6: c.pingTime = c.startTime - sent.sendTime Nenue@6: print(' ping:', c.pingTime) Nenue@6: for cid, cdata in pairs(sendq) do Nenue@6: if cdata.sendTime <= sent.sendTime then Nenue@6: --print(' forgetting #'..cdata.sendTime, cid, cdata.spellName) Nenue@6: sendq[cid] = nil Nenue@6: end Nenue@6: end Nenue@6: Nenue@6: if castd ~= nil then Nenue@6: print(' downtime calc hit') Nenue@6: c.downTime = timestamp - castd.endTime Nenue@6: castd = nil Nenue@6: else Nenue@6: c.downTime = nil Nenue@6: end Nenue@6: c.castID = castID Nenue@6: end Nenue@6: end Nenue@6: elseif e == 'CHANNEL_START' or e == 'CHANNEL_UPDATE' then Nenue@6: setSpell = true Nenue@6: setState = true Nenue@6: c.event = e Nenue@6: Nenue@6: elseif e == 'STOP' then Nenue@6: setState = true Nenue@6: c.casting = nil Nenue@6: castd = { Nenue@6: spellName = spellName, Nenue@6: spellID = spellID, Nenue@6: castID = castID, Nenue@6: endTime = timestamp, Nenue@6: } Nenue@6: print(' |cFFFFFF00found', timestamp, spellName) Nenue@6: Nenue@6: elseif e == 'CHANNEL_STOP' then Nenue@6: setState = true Nenue@6: c.channeling = nil Nenue@6: castd = { Nenue@6: spellName = spellName, Nenue@6: spellID = spellID, Nenue@6: castID = castID, Nenue@6: endTime = timestamp, Nenue@6: } Nenue@6: print(' |cFFFF0088found', timestamp, spellName) Nenue@6: Nenue@6: elseif e == 'SUCCEEDED' then Nenue@6: if c.spellName == spellName and not c.channeling then Nenue@6: setState = true Nenue@6: print(' |cFFFF4400',c.spellName,'=',spellName,' pushing event', e) Nenue@6: end Nenue@6: -- if there are 'send' args when this fires, it means it's an instant cast Nenue@6: if sendq[castID] and sendq[castID].spellName == spellName then Nenue@6: castd = { Nenue@6: spellName = spellName, Nenue@6: spellID = spellID, Nenue@6: castID = castID, Nenue@6: endTime = timestamp, Nenue@6: } Nenue@6: print(' |cFFFFAA00found', timestamp, spellName) Nenue@6: end Nenue@6: Nenue@6: elseif e =='FAILED' then Nenue@6: if spellName == c.spellName and not c.channeling then Nenue@6: setState = true Nenue@6: end Nenue@6: Nenue@6: elseif e == 'INTERRUPTED' then Nenue@6: setState = true Nenue@6: Nenue@6: end Nenue@6: Nenue@6: Nenue@6: Nenue@6: Nenue@6: if setSpell then Nenue@6: print(' |cFF44FF00setting spell', spellName) Nenue@6: end Nenue@6: if setState then Nenue@6: print(' |cFFFF4400pushing event', e) Nenue@6: end Nenue@6: Nenue@6: if setSpell then Nenue@6: c:SetSpell(e) Nenue@6: end Nenue@6: if setState then Nenue@6: c:SetState(e) Nenue@6: end Nenue@6: end Nenue@6: Nenue@6: Nenue@6: --- Sets all the static elements of the casting bar, such as config values, icon texture, and name Nenue@6: function mod.SetSpell(c, event) Nenue@6: local _ Nenue@6: local time, alpha = GetTime(), c:GetAlpha() Nenue@6: local u, cdb = T.unit[c.unit], c.db Nenue@6: Nenue@6: c.stopped = nil Nenue@6: c.stopping = nil Nenue@6: print('New spell:', c.spellName) Nenue@6: --- use only internal info Nenue@6: local data = (event == 'CHANNEL_START' or event == 'CHANNEL_UPDATE' or event == 'CHANNEL_STOP') and u.channeling or u.casting Nenue@6: local spellName, rank, displayName, texture, startTime, endTime, isTradeSkill, castID, nonInterruptible = unpack(data) Nenue@6: Nenue@6: Nenue@6: if spellName == nil then Nenue@6: print(" Can't do arithmetic, no data.") Nenue@6: else Nenue@6: print(cText(" Arithmetic vars:"), cNum(startTime), cNum(endTime)) Nenue@6: end Nenue@6: Nenue@6: c.spellName = spellName Nenue@6: c.castID = castID or 0 Nenue@6: c.rank = rank Nenue@6: c.displayName = displayName Nenue@6: c.texture = texture Nenue@6: c.nonInterruptible = nonInterruptible Nenue@6: c.isTradeSkill = isTradeSkill Nenue@6: c.endTime = endTime or 0 Nenue@6: c.startTime = startTime or 0 Nenue@6: c.duration = c.endTime - c.startTime Nenue@6: Nenue@6: if c.unit == 'player' then Nenue@6: if c.pingTime then Nenue@6: print(' ping',c.pingTime, endTime, startTime, c.duration) Nenue@6: local draw_dist = (c.pingTime / c.duration) * (c.fill_width) Nenue@6: if draw_dist > c.fill_width then Nenue@6: draw_dist = c.fill_width Nenue@6: end Nenue@6: print('SET AND SHOW PING ', c.pingTime,'on', c.pingbar:GetName()) Nenue@6: c.pingbar:SetWidth(draw_dist) Nenue@6: c.pingbar:Show() Nenue@6: Nenue@6: local rv = c.pingTime - PING_FLOOR Nenue@6: local rr = PING_MIDLINE - PING_FLOOR Nenue@6: local gv = c.pingTime - PING_MIDLINE Nenue@6: local gr = PING_CEILING - PING_MIDLINE Nenue@6: local r = c.pingTime > PING_MIDLINE and 1 or (c.pingTime < PING_FLOOR and 0 or (rv / rr)) Nenue@6: local g = c.pingTime < PING_MIDLINE and 1 or (c.pingTime > PING_CEILING and 0 or 1-(gv / gr)) Nenue@6: print(c.pingTime, rv, '/', rr, '=', r) Nenue@6: print(c.pingTime, gv, '/', gr, '=', g) Nenue@6: Nenue@6: c.ping:SetText(floor(c.pingTime)) Nenue@6: c.ping:SetTextColor(r,g,0) Nenue@6: c.ping:Show() Nenue@6: else Nenue@6: c.pingbar:Hide() Nenue@6: c.ping:Hide() Nenue@6: end Nenue@6: if c.downTime then Nenue@6: c.downtime:Show() Nenue@6: if c.downTime > 1500 then Nenue@6: c.downtime:SetText(nil) Nenue@6: else Nenue@6: c.downtime:SetText(c.downTime) Nenue@6: end Nenue@6: else Nenue@6: c.downtime:Hide() Nenue@6: end Nenue@6: else Nenue@6: if c.nonInterruptible then Nenue@6: c.interrupt:Show() Nenue@6: else Nenue@6: c.interrupt:Hide() Nenue@6: end Nenue@6: end Nenue@6: Nenue@6: c.icon:SetTexture(texture) Nenue@6: c.spelltext:Show() Nenue@6: c.spelltext:SetText(spellName) Nenue@6: Nenue@6: c.fill_inverse = c.channeling Nenue@6: -- set timers Nenue@6: end Nenue@6: Nenue@6: --- Deals with failed/succeeded/interrupted visuals and fading cues Nenue@6: -- Fired by one of those events, or cast/channel info is returning nothing Nenue@6: function mod:SetState(event) Nenue@6: if T.unlocked then Nenue@6: return Nenue@6: end Nenue@6: Nenue@6: local time = GetTime() * 1000 Nenue@6: print(' ',self:GetName(), '|cFF44FF00event trigger:|r', event) Nenue@6: Nenue@6: -- We want these to be updating no matter what is happening Nenue@6: if TEXTURE_SUFFIX[event] then Nenue@6: local cdb = self.db Nenue@6: if cdb.foreground_texture then Nenue@6: print(' |cFF00AAFFevent|r '..event..', |cFF00AAFFtexture|r ', cdb.foreground_texture) Nenue@6: self.foreground:SetVertexColor(unpack(cdb['foreground' .. TEXTURE_SUFFIX[event]] and cdb['foreground' .. TEXTURE_SUFFIX[event]] or cdb.foreground_color)) Nenue@6: else Nenue@6: self.foreground:SetTexture(unpack(cdb['foreground' .. TEXTURE_SUFFIX[event]] and cdb['foreground' .. TEXTURE_SUFFIX[event]] or cdb.foreground_color)) Nenue@6: end Nenue@6: Nenue@6: if cdb.background_texture then Nenue@6: print(' texture=', cdb.background_texture) Nenue@6: self.background:SetVertexColor(unpack(cdb['background' .. TEXTURE_SUFFIX[event]] and cdb['background' .. TEXTURE_SUFFIX[event]] or cdb.background_color)) Nenue@6: else Nenue@6: self.background:SetTexture(unpack(cdb['background' .. TEXTURE_SUFFIX[event]] and cdb['background' .. TEXTURE_SUFFIX[event]] or cdb.background_color)) Nenue@6: end Nenue@6: end Nenue@6: Nenue@6: -- are we starting or stopping Nenue@6: if event == 'START' or event == 'CHANNEL_START' then Nenue@6: print(' |cFF00AAFFStarting display|r ', self.spellName) Nenue@6: self:Show() Nenue@6: if self.__fade:IsPlaying() then Nenue@6: self.__fade:Stop() Nenue@6: end Nenue@6: Nenue@6: self:Fade(FADE_IN_TIME, self.alpha) Nenue@6: else Nenue@6: if event == 'SUCCEEDED' and not self.channeling then Nenue@6: print(' |cFF00AAFFsuccess deduced values|r', self.value, 'to', self.duration) Nenue@6: self.percent = 1 Nenue@6: self.value = self.duration Nenue@6: end Nenue@6: Nenue@6: -- Actual fading out begins here, anything else is statistical sugar Nenue@6: if event == 'STOP' or event == 'CHANNEL_STOP' then Nenue@6: print('yeah we done') Nenue@6: if self.__fade:IsPlaying() then Nenue@6: self.__fade:Stop() Nenue@6: end Nenue@6: self:Fade(FADE_OUT_TIME, 0) Nenue@6: end Nenue@6: end Nenue@6: end Nenue@6: Nenue@6: --- Animation loop Nenue@6: function mod:Update() Nenue@6: local time = GetTime() * 1000 Nenue@6: local u = self.unit Nenue@6: local s = self Nenue@6: Nenue@6: -- update vals Nenue@6: if s.casting or s.channeling then Nenue@6: self.value = s.casting and (time - s.startTime) or (s.endTime - time) Nenue@6: self.percent = (time - s.startTime) / self.duration Nenue@6: if time > s.endTime then Nenue@6: self.value = self.fill_inverse and 0 or s.duration Nenue@6: self.percent = self.fill_inverse and 0 or 1 Nenue@6: self.elapsed = time - s.endTime Nenue@6: end Nenue@6: --_G.print('Update',' ',self.unit, self.value, self.percent) Nenue@6: else Nenue@6: self.value = 1000 Nenue@6: self.percent = 1 Nenue@6: end Nenue@6: self.casttime:SetText(format("%.2f", self.value / 1000)) Nenue@6: Nenue@6: self:SetProgress(self.percent) Nenue@6: end Nenue@6: Nenue@6: --- Refresh the CastingBar for things like target change, pet despawn, etc. Nenue@6: -- Will attempt to flush out any existing cast action data, then re-invoke SpellCastEvent() with as much data can be acquired. Nenue@6: -- There is no event data such as spellID directly available, so this is essentially the biggest constraint we have on what Nenue@6: -- the castingbar can depend on. Nenue@6: function mod:UpdateUnit(unit) Nenue@6: local print = function(...) _G.print('Update', ...) end Nenue@6: print(cText 'GUID changed:|cFFFFFF99', unit) Nenue@6: local c = mod.castbar[unit] Nenue@6: if c.channeling or c.casting then Nenue@6: print(cText ' was casting a spell, run STOP event and hide completely') Nenue@6: c:SetState(c.casting and 'STOP' or 'CHANNEL_STOP') Nenue@6: c.channeling = nil Nenue@6: c.casting = nil Nenue@6: c:Hide() Nenue@6: end Nenue@6: Nenue@6: if T.unit[unit].casting then Nenue@6: print(cText(' new target is CASTING')) Nenue@6: mod:SpellCastEvent('UNIT_SPELLCAST_START', unit) Nenue@6: end Nenue@6: if T.unit[unit].channeling then Nenue@6: print(cText(' new target is CHANNELING')) Nenue@6: mod:SpellCastEvent('UNIT_SPELLCAST_CHANNEL_START', unit) Nenue@6: end Nenue@6: Nenue@6: T:UNIT_SPELLCAST(unit)-- name, subText, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible Nenue@6: T:UNIT_CHANNEL(unit) -- name, subText, text, texture, startTime, endTime, isTradeSkill, notInterruptible Nenue@6: Nenue@6: end Nenue@6: Nenue@6: function mod:UpdateInterrupt(e, unit, ...) Nenue@6: mod.castbar[unit].nonInterruptible = (e == 'UNIT_SPELLCAST_UNINTERRUPTIBLE') and true or false Nenue@6: if unit ~= 'player' and unit ~='pet' then Nenue@6: Nenue@6: end Nenue@6: end Nenue@6: Nenue@6: mod.UNIT_PET = function(self, e, unit) Nenue@6: if unit == 'player' then Nenue@6: self:UpdateUnit('pet') Nenue@6: end Nenue@6: end Nenue@6: mod.PLAYER_TARGET_CHANGED = function(self, ...) Nenue@6: _G.print('Main', cPink(self),...) Nenue@6: self:UpdateUnit('target') Nenue@6: end Nenue@6: mod.PLAYER_FOCUS_CHANGED = function(self) Nenue@6: self:UpdateUnit('focus') Nenue@6: end