115de7ed41
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>
200 lines
6.0 KiB
Lua
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)
|