Files
moon-well-ui/data/addon/ForeverUI/CastBar.lua
T
Adrestie 115de7ed41 Add mod-forever-ui: the modern interface rebuilt for a 3.3.5a client
The addon and the art it needs, read from a modern client and served to
3.3.5 through texture coordinates, since that client has no atlas.

What is in place: player frame with its states, death knight runes, target
frame and its classification rings, cast bar, action bar with its page block
and end caps, micro menu, bags bar, experience and reputation bars. Every
piece registers itself as a movable system.

This folder is the workspace. The addon in a client and the art in patch-Z
are copies tools/deployer.py makes, and what sits under Interface\ForeverUI
in the patch is exactly what data/art holds -- the deployer removes from the
archive anything this folder no longer carries. Each .blp comes with the .png
that shows what it holds.

docs/AMELIORATIONS.md lists what is known to be improvable: where the
reproduction departs from the reference and why, what the 2010 client cannot
do, and the debts left in the tooling.

The repository README also gains the row for mod-limit-break, which was
written earlier and had not been committed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 20:12:09 +02:00

200 lines
6.0 KiB
Lua

-- ForeverUI : la barre d'incantation du joueur.
--
-- RELEVE DES SOURCES
-- art interface/castingbar/uicastingbar.blp : ui-castingbar-frame
-- (214x16) pose par-dessus, ui-castingbar-background (209x11) en
-- fond, et trois remplissages de meme taille --
-- filling-standard, filling-channel, uninterruptable -- plus
-- ui-castingbar-interrupted pour l'echec et ui-castingbar-shield
-- (54x64) pour ce qui ne peut pas etre interrompu.
-- donnees 3.3.5 CastingBarFrame.lua : UnitCastingInfo et UnitChannelInfo
-- rendent nom, texture, debut et fin en millisecondes, et le
-- drapeau "non interruptible". Les evenements sont les
-- UNIT_SPELLCAST_*.
-- sens une incantation se remplit, un canalisation se vide : c'est la
-- seule difference de calcul entre les deux.
--
-- POSITION : systeme a part entiere, comme dans l'editeur de camelot ou la
-- barre d'incantation se deplace independamment des cadres d'unite.
local BAR_WIDTH, BAR_HEIGHT = 214, 16
local FILL_WIDTH, FILL_HEIGHT = 209, 11
local FILL = {
standard = "ui-castingbar-filling-standard",
channel = "ui-castingbar-filling-channel",
uninterruptible = "ui-castingbar-uninterruptable",
interrupted = "ui-castingbar-interrupted",
}
local HOLD_AFTER_END = 1.0
local frame = CreateFrame("Frame", "ForeverUICastBar", UIParent)
frame:SetWidth(BAR_WIDTH)
frame:SetHeight(BAR_HEIGHT)
frame:SetFrameStrata("MEDIUM")
frame:Hide()
local background = frame:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(background, "ui-castingbar-background")
background:SetPoint("CENTER")
local fill = frame:CreateTexture(nil, "BORDER")
fill:SetPoint("LEFT", background, "LEFT", 0, 0)
local border = frame:CreateTexture(nil, "ARTWORK")
ForeverUI.SetAtlas(border, "ui-castingbar-frame")
border:SetPoint("CENTER")
local shield = frame:CreateTexture(nil, "OVERLAY")
ForeverUI.SetAtlas(shield, "ui-castingbar-shield")
shield:SetPoint("CENTER", frame, "LEFT", 2, -1)
shield:Hide()
local spellText = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
spellText:SetPoint("CENTER", frame, "CENTER", 0, 0)
local timeText = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
timeText:SetPoint("RIGHT", frame, "RIGHT", -6, 0)
frame.background = background
frame.fill = fill
frame.border = border
frame.shield = shield
frame.spellText = spellText
frame.timeText = timeText
local function stopBar()
frame.casting = false
frame.channeling = false
frame.holdUntil = nil
frame:Hide()
end
local function showFailure(atlasKey, message)
frame.casting = false
frame.channeling = false
frame.holdUntil = GetTime() + HOLD_AFTER_END
ForeverUI.SetAtlasFill(fill, FILL[atlasKey], 1, FILL_WIDTH)
spellText:SetText(message or "")
timeText:SetText("")
shield:Hide()
frame:Show()
end
local function startCast(channel)
local name, _subtext, text, texture, startTime, endTime, _isTrade, _castID, notInterruptible
if channel then
name, _subtext, text, texture, startTime, endTime, _isTrade, notInterruptible = UnitChannelInfo("player")
else
name, _subtext, text, texture, startTime, endTime, _isTrade, _castID, notInterruptible = UnitCastingInfo("player")
end
if not name then
stopBar()
return
end
frame.casting = not channel
frame.channeling = channel and true or false
frame.startTime = startTime / 1000
frame.endTime = endTime / 1000
frame.holdUntil = nil
frame.fillKey = notInterruptible and "uninterruptible" or (channel and "channel" or "standard")
spellText:SetText(text or name)
if notInterruptible then
shield:Show()
else
shield:Hide()
end
frame:Show()
end
frame:SetScript("OnEvent", function(_self, event, unit)
if event == "PLAYER_ENTERING_WORLD" then
ForeverUI.Suppress(CastingBarFrame)
stopBar()
return
end
if unit ~= "player" then
return
end
if event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_DELAYED" then
startCast(false)
elseif event == "UNIT_SPELLCAST_CHANNEL_START" or event == "UNIT_SPELLCAST_CHANNEL_UPDATE" then
startCast(true)
elseif event == "UNIT_SPELLCAST_STOP" or event == "UNIT_SPELLCAST_CHANNEL_STOP" then
stopBar()
elseif event == "UNIT_SPELLCAST_FAILED" then
showFailure("interrupted", FAILED or "Echec")
elseif event == "UNIT_SPELLCAST_INTERRUPTED" then
showFailure("interrupted", INTERRUPTED or "Interrompu")
elseif event == "UNIT_SPELLCAST_INTERRUPTIBLE" then
shield:Hide()
frame.fillKey = frame.channeling and "channel" or "standard"
elseif event == "UNIT_SPELLCAST_NOT_INTERRUPTIBLE" then
shield:Show()
frame.fillKey = "uninterruptible"
end
end)
frame:SetScript("OnUpdate", function(self)
if self.holdUntil then
if GetTime() > self.holdUntil then
stopBar()
end
return
end
if not (self.casting or self.channeling) then
return
end
local now = GetTime()
local total = self.endTime - self.startTime
if total <= 0 then
stopBar()
return
end
local elapsed = now - self.startTime
if elapsed < 0 then
elapsed = 0
end
local fraction = elapsed / total
if fraction > 1 then
fraction = 1
end
-- Une incantation se remplit, une canalisation se vide.
if self.channeling then
fraction = 1 - fraction
end
ForeverUI.SetAtlasFill(fill, FILL[self.fillKey or "standard"], fraction, FILL_WIDTH)
timeText:SetText(string.format("%.1f", math.max(0, self.endTime - now)))
if now >= self.endTime then
stopBar()
end
end)
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("UNIT_SPELLCAST_START")
frame:RegisterEvent("UNIT_SPELLCAST_STOP")
frame:RegisterEvent("UNIT_SPELLCAST_DELAYED")
frame:RegisterEvent("UNIT_SPELLCAST_FAILED")
frame:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
frame:RegisterEvent("UNIT_SPELLCAST_INTERRUPTIBLE")
frame:RegisterEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE")
frame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
frame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE")
frame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
ForeverUI.CastBar = frame
ForeverUI.Layout.Register(frame, "castbar", "Barre d'incantation", "BOTTOM", "BOTTOM", 0, 190)