添加首领单位支持,更新相关状态和施法逻辑,移除不必要的 EXBoss 文件,调整配置和文档

This commit is contained in:
waynebian01
2026-08-24 16:07:54 +08:00
parent e913f6a690
commit 9d48c5adb4
12 changed files with 124 additions and 370 deletions

View File

@@ -61,4 +61,4 @@
{ "kind": "spec", "classId": 13, "specId": 2, "wowSpecId": 1468, "fileDataId": 4511812, "class": "Evoker", "name": "Preservation", "nameZhCn": "恩护", "slug": "classicon_evoker_preservation", "target": "Spec/evoker-preservation.jpg" },
{ "kind": "spec", "classId": 13, "specId": 3, "wowSpecId": 1473, "fileDataId": 5198700, "class": "Evoker", "name": "Augmentation", "nameZhCn": "增辉", "slug": "classicon_evoker_augmentation", "target": "Spec/evoker-augmentation.jpg" }
]
}
}w

View File

@@ -4,7 +4,7 @@
## Version: 1.2.1.10
## Author: Wayne Bian
## IconTexture: Interface\AddOns\Fuyutsui\core\Spell_Misc_EmotionHappy.blp
## OptionalDeps: LibRangeCheck-3.0, ExwindTools, EXBoss
## OptionalDeps: LibRangeCheck-3.0
## SavedVariables: FuyutsuiADB
## Category-enUS: Combat
@@ -31,7 +31,6 @@ core/config.lua
core/curves.lua
core/block.lua
core/stateblocks.lua
core/exboss.lua
core/commands.lua
core/quickbutton.lua
core/macro.lua

View File

@@ -165,6 +165,10 @@ Fuyutsui.state = {
Fuyutsui.blocks = {}
Fuyutsui.target = {}
Fuyutsui.focus = {}
Fuyutsui.boss = {}
for index = 1, 5 do
Fuyutsui.boss["boss" .. index] = {}
end
Fuyutsui.nameplate = {}
Fuyutsui.group = {}
Fuyutsui.groupList = {}

View File

@@ -44,4 +44,4 @@ end
Fuyutsui.powerCurves = powerCurves
Fuyutsui.curve100 = Fuyutsui:CreateColorCurveScaling(100)
Fuyutsui.curve255 = Fuyutsui:CreateColorCurve(255, 255)
Fuyutsui.castCurve = Fuyutsui:CreateColorCurve(2.55, 255)
Fuyutsui.castCurve = Fuyutsui:CreateColorCurve(25.5, 255)

View File

@@ -240,6 +240,9 @@ function Fuyutsui:UNIT_HEALTH(_, unit)
if unit == "focus" then
self:UpdateFocusHealth()
end
if unit and unit:match("^boss[1-5]$") then
self:UpdateUnitHealthBlock(unit)
end
if self.group[unit] then
self:UpdateUnitDeath(unit, "health")
end
@@ -249,6 +252,9 @@ function Fuyutsui:UNIT_MAXHEALTH(_, unit)
if unit == "player" then
self:UpdatePlayerHealth()
end
if unit and unit:match("^boss[1-5]$") then
self:UpdateUnitHealthBlock(unit)
end
if self.group[unit] then
self:UpdateUnitDeath(unit, "health")
end
@@ -440,6 +446,9 @@ function Fuyutsui:OnUpdate(elapsed)
self:UpdatePlayerCastBlocks()
self:UpdateUnitCastingOrChannelingInfo("target")
self:UpdateUnitCastingOrChannelingInfo("focus")
for index = 1, 5 do
self:UpdateUnitCastingOrChannelingInfo("boss" .. index)
end
self:UpdateGroupInRangeAndHealth()
self.timeElapsed = self.timeElapsed + elapsed
@@ -449,6 +458,11 @@ function Fuyutsui:OnUpdate(elapsed)
self:UpdateRune()
self:UpdateTargetRangeBlock()
self:UpdateFocusRangeBlock()
for index = 1, 5 do
local unit = "boss" .. index
self:UpdateUnitCanAttack(unit)
self:UpdateUnitRangeBlock(unit)
end
self:UpdateEnemyCount()
self:UpdateItemCooldown()
self.timeElapsed = 0

View File

@@ -1,342 +0,0 @@
-- Fuyutsui -> EXBoss 数据桥接。
-- EXBoss 不存在时,本文件不注册任何 EX 事件,也不创建技能像素。
local Fuyutsui = _G.Fuyutsui
if not Fuyutsui then return end
local START_EVENT = "EXBOSS_BOSS_OBSERVED_CAST_START"
local STOP_EVENT = "EXBOSS_BOSS_OBSERVED_CAST_STOP"
local OWNER = "Fuyutsui.ExBoss"
local MAX_BLOCK_INDEX = 510
-- [spellID] = 像素的逻辑名称。
-- 名称相同的 spellID 共用一个顶部像素。
local spells = {
[381516] = "打断施法",
}
local ExBoss = Fuyutsui.ExBoss or {}
Fuyutsui.ExBoss = ExBoss
ExBoss.spells = spells
ExBoss.spellPixels = ExBoss.spellPixels or {}
ExBoss.pixelGroups = ExBoss.pixelGroups or {}
ExBoss.bound = false
ExBoss.nativeBound = false
ExBoss.layoutBlocks = nil
local function IsAddonLoaded(name)
if C_AddOns and type(C_AddOns.IsAddOnLoaded) == "function" then
return C_AddOns.IsAddOnLoaded(name) == true
end
return type(IsAddOnLoaded) == "function" and IsAddOnLoaded(name) == true
end
local function NormalizeSpellID(value)
if type(issecretvalue) == "function" and issecretvalue(value) then
return nil
end
local id = tonumber(value)
return id and id > 0 and id or nil
end
local function GetMaxBlockIndex(value, currentMax)
if type(value) ~= "table" then
return currentMax
end
for key, child in pairs(value) do
if key == "index" and type(child) == "number" then
currentMax = math.max(currentMax, math.floor(child))
elseif type(child) == "table" then
currentMax = GetMaxBlockIndex(child, currentMax)
end
end
return currentMax
end
local function GetSortedSpellIDs()
local ids = {}
for spellID in pairs(spells) do
ids[#ids + 1] = spellID
end
table.sort(ids)
return ids
end
local function ClearSpellState(entry)
if type(entry) ~= "table" then return end
entry.active = false
entry.runtimeID = nil
entry.unit = nil
entry.castKind = nil
entry.castBarID = nil
entry.totalDuration = 0
entry.startedAt = 0
end
local function EnsureSpellPixels()
local blocks = Fuyutsui.blocks
if type(blocks) ~= "table" or type(blocks.state) ~= "table" or next(blocks.state) == nil then
return false
end
if ExBoss.layoutBlocks == blocks then
return true
end
local nextIndex = GetMaxBlockIndex(blocks, 0) + 1
local newPixels = {}
local newGroups = {}
for _, spellID in ipairs(GetSortedSpellIDs()) do
local name = spells[spellID]
local group = newGroups[name]
if not group then
if nextIndex > MAX_BLOCK_INDEX then
print("|cffff0000[Fuyutsui] EXBoss 技能像素空间不足,无法创建 " .. tostring(name) .. "|r")
break
end
group = {
name = name,
index = nextIndex,
spellIDs = {},
}
newGroups[name] = group
nextIndex = nextIndex + 1
end
local entry = ExBoss.spellPixels[spellID] or {}
entry.spellID = spellID
entry.name = name
entry.group = group
entry.index = group.index
group.spellIDs[#group.spellIDs + 1] = spellID
ClearSpellState(entry)
newPixels[spellID] = entry
end
ExBoss.spellPixels = newPixels
ExBoss.pixelGroups = newGroups
ExBoss.layoutBlocks = blocks
return true
end
local function GetCurrentDuration(entry)
if type(entry) ~= "table" or not entry.active or not entry.unit then
return nil
end
if entry.castKind == "channel" then
return UnitChannelDuration(entry.unit)
end
return UnitCastingDuration(entry.unit)
end
local function GetActiveGroupEntry(group)
if type(group) ~= "table" then return nil end
local activeEntry
for _, spellID in ipairs(group.spellIDs or {}) do
local entry = ExBoss.spellPixels[spellID]
if entry and entry.active then
if not activeEntry or (tonumber(entry.startedAt) or 0) > (tonumber(activeEntry.startedAt) or 0) then
activeEntry = entry
end
end
end
return activeEntry
end
local function UpdatePixelGroup(group)
if type(group) ~= "table" or not group.index then return end
local value = 0
local duration = GetCurrentDuration(GetActiveGroupEntry(group))
if duration then
local color = duration:EvaluateRemainingDuration(Fuyutsui.castCurve)
local _, _, blue = color:GetRGB()
value = blue or 0
end
Fuyutsui:CreateTexture(group.index, value)
end
local function ReadNativeSpellID(unit, castKind, eventSpellID)
local spellID = NormalizeSpellID(eventSpellID)
if spellID then return spellID end
if castKind == "channel" then
local ok, _, _, _, _, _, _, _, apiSpellID = pcall(UnitChannelInfo, unit)
return ok and NormalizeSpellID(apiSpellID) or nil
end
local ok, _, _, _, _, _, _, _, _, apiSpellID = pcall(UnitCastingInfo, unit)
return ok and NormalizeSpellID(apiSpellID) or nil
end
local function OnBossCastStart(_, payload)
if type(payload) ~= "table" then return end
local spellID = NormalizeSpellID(payload.spellID)
local name = spellID and spells[spellID]
if not name then return end
local entry = ExBoss.spellPixels[spellID]
local runtimeID = tonumber(payload.runtimeID)
local castBarID = tonumber(payload.castBarID)
local now = GetTime()
local duplicateStart = entry and entry.active
and ((runtimeID ~= nil and entry.runtimeID == runtimeID)
or (runtimeID == nil and castBarID ~= nil and entry.castBarID == castBarID)
or (entry.unit == payload.unit and math.abs(now - (tonumber(entry.startedAt) or 0)) <= 0.5))
if not duplicateStart then
print("开始施放指定技能")
end
if not EnsureSpellPixels() then return end
entry = ExBoss.spellPixels[spellID]
if not entry then return end
entry.active = true
entry.runtimeID = runtimeID
entry.unit = type(payload.unit) == "string" and payload.unit or nil
entry.castKind = tostring(payload.castKind or "cast")
entry.castBarID = castBarID
entry.totalDuration = tonumber(payload.totalDuration) or 0
entry.startedAt = tonumber(payload.startedAt) or now
UpdatePixelGroup(entry.group)
end
local function OnBossCastStop(_, payload)
if type(payload) ~= "table" then return end
local spellID = NormalizeSpellID(payload.spellID)
local runtimeID = tonumber(payload.runtimeID)
if spellID and ExBoss.spellPixels[spellID] then
local entry = ExBoss.spellPixels[spellID]
if runtimeID == nil or entry.runtimeID == nil or entry.runtimeID == runtimeID then
ClearSpellState(entry)
UpdatePixelGroup(entry.group)
end
return
end
for _, entry in pairs(ExBoss.spellPixels) do
if runtimeID ~= nil and entry.runtimeID == runtimeID then
ClearSpellState(entry)
UpdatePixelGroup(entry.group)
end
end
end
local function OnNativeBossCastStart(_, event, ...)
local unit = select(1, ...)
if type(unit) ~= "string" or not unit:match("^boss[1-5]$") then return end
local eventSpellID = select(3, ...)
local castBarID = select(select("#", ...), ...)
local castKind = event == "UNIT_SPELLCAST_CHANNEL_START" and "channel" or "cast"
local spellID = ReadNativeSpellID(unit, castKind, eventSpellID)
if not spellID or not spells[spellID] then return end
OnBossCastStart(nil, {
spellID = spellID,
unit = unit,
castKind = castKind,
castBarID = castBarID,
startedAt = GetTime(),
})
end
local function OnNativeBossCastStop(_, event, ...)
local unit = select(1, ...)
if type(unit) ~= "string" or not unit:match("^boss[1-5]$") then return end
local eventSpellID = select(3, ...)
local castBarID = select(select("#", ...), ...)
local castKind = event == "UNIT_SPELLCAST_CHANNEL_STOP" and "channel" or "cast"
local spellID = NormalizeSpellID(eventSpellID)
for _, entry in pairs(ExBoss.spellPixels) do
local sameUnit = entry.active and entry.unit == unit
local sameKind = entry.castKind == castKind
local sameCastBar = castBarID == nil or entry.castBarID == nil or entry.castBarID == castBarID
local sameSpell = spellID == nil or entry.spellID == spellID
if sameUnit and sameKind and sameCastBar and sameSpell then
local group = entry.group
ClearSpellState(entry)
UpdatePixelGroup(group)
end
end
end
local nativeFrame = CreateFrame("Frame")
local function BindNativeBossEvents()
if ExBoss.nativeBound then return end
nativeFrame:RegisterEvent("UNIT_SPELLCAST_START")
nativeFrame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
nativeFrame:RegisterEvent("UNIT_SPELLCAST_STOP")
nativeFrame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
nativeFrame:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
nativeFrame:RegisterEvent("UNIT_SPELLCAST_FAILED")
nativeFrame:RegisterEvent("UNIT_SPELLCAST_FAILED_QUIET")
nativeFrame:SetScript("OnEvent", function(frame, event, ...)
if event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START" then
OnNativeBossCastStart(frame, event, ...)
else
OnNativeBossCastStop(frame, event, ...)
end
end)
ExBoss.nativeBound = true
end
local function BindExBossEvents()
if ExBoss.bound then return true end
if not IsAddonLoaded("EXBoss") then return false end
local ExwindTools = _G.ExwindTools
if not ExwindTools or type(ExwindTools.RegisterEvent) ~= "function" then
return false
end
ExwindTools:RegisterEvent(START_EVENT, OWNER .. ".Start", OnBossCastStart)
ExwindTools:RegisterEvent(STOP_EVENT, OWNER .. ".Stop", OnBossCastStop)
BindNativeBossEvents()
ExBoss.bound = true
return true
end
local updateFrame = CreateFrame("Frame")
updateFrame:SetScript("OnUpdate", function()
if not ExBoss.bound then return end
if not EnsureSpellPixels() then return end
for _, group in pairs(ExBoss.pixelGroups) do
UpdatePixelGroup(group)
end
end)
local driver = CreateFrame("Frame")
driver:RegisterEvent("ADDON_LOADED")
driver:RegisterEvent("PLAYER_LOGIN")
driver:RegisterEvent("PLAYER_ENTERING_WORLD")
driver:RegisterEvent("PLAYER_TALENT_UPDATE")
driver:SetScript("OnEvent", function(_, event, addonName)
if event == "ADDON_LOADED" then
if addonName == "ExwindTools" or addonName == "EXBoss" then
BindExBossEvents()
end
return
end
if event == "PLAYER_LOGIN" or event == "PLAYER_ENTERING_WORLD" or event == "PLAYER_TALENT_UPDATE" then
BindExBossEvents()
EnsureSpellPixels()
end
end)
-- EXBoss 可能比 Fuyutsui 更早加载。
BindExBossEvents()

View File

@@ -43,15 +43,15 @@ Fuyutsui.DebugPrintNewSpellEntry = DebugPrintNewSpellEntry
Fuyutsui.DebugPrintSpellBlockLine = DebugPrintSpellBlockLine
local overrideSpells = {
[432459] = 1289728, -- 神圣壁垒
[432472] = 1289728, -- 圣洁武器
[444995] = 455630, -- 涌动图腾
[1242173] = 228260, -- 虚空齐射
[1241413] = 31884, -- 愤怒之锤
[24275] = 31884, -- 愤怒之锤
[1241288] = 31884, -- 愤怒之锤
[1277026] = 31884, -- 愤怒之锤
[1279408] = 31884, -- 愤怒之锤
[432459] = 1289728, -- 神圣壁垒
[432472] = 1289728, -- 圣洁武器
[444995] = 455630, -- 涌动图腾
[1242173] = 228260, -- 虚空齐射
[1241413] = 31884, -- 愤怒之锤
[24275] = 31884, -- 愤怒之锤
[1241288] = 31884, -- 愤怒之锤
[1277026] = 31884, -- 愤怒之锤
[1279408] = 31884, -- 愤怒之锤
}
function Fuyutsui:IsSpellKnown(spellID)
@@ -114,7 +114,7 @@ local dispelAbilities = {
}
local offensiveDispelAbilities = {
[1] = { 528 },
[1] = { 528, 370, 378773 },
[9] = { 2908 },
}

View File

@@ -5,6 +5,7 @@ local EvaluateColorFromBoolean = C_CurveUtil.EvaluateColorFromBoolean
local state = Fuyutsui.state
local target = Fuyutsui.target
local focus = Fuyutsui.focus
local boss = Fuyutsui.boss
local ColorValue0 = CreateColor(0, 0, 0, 1)
local ColorValue1 = CreateColor(0, 0, 1 / 255, 1)
@@ -40,7 +41,7 @@ local function GetItemCooldownPixel(self, countKey, itemID)
return 1
end
--- mode: "cast" | "channel"
--- mode: "cast" | "castElapsed" | "channel"
function Fuyutsui:GetUnitCastPixel(unit, mode)
local castCurve = self.castCurve
if mode == "channel" then
@@ -56,7 +57,12 @@ function Fuyutsui:GetUnitCastPixel(unit, mode)
local cast = UnitCastingDuration(unit)
if not cast then return 0 end
local castingDurationColor = cast:EvaluateRemainingDuration(castCurve)
local castingDurationColor
if mode == "castElapsed" then
castingDurationColor = cast:EvaluateElapsedDuration(castCurve)
else
castingDurationColor = cast:EvaluateRemainingDuration(castCurve)
end
---@diagnostic disable-next-line: param-type-mismatch
local _, _, b = castingDurationColor:GetRGB()
return b
@@ -83,7 +89,7 @@ function Fuyutsui:GetUnitInterruptiblePixel(unit, mode)
return interruptible
end
-- blocks.state 键:下列分类用名称本身;目标/焦点用 分类..名称
-- blocks.state 键:下列分类用名称本身;单位分类用 分类..名称
local bareKeyCategories = {
["状态"] = true,
["能量"] = true,
@@ -241,7 +247,8 @@ local stateBlockGetters = {
if not target.maxRange then return nil end
return target.maxRange / 255
end,
["施法"] = function(self) return self:GetUnitCastPixel("target", "cast") end,
["施法(倒计时)"] = function(self) return self:GetUnitCastPixel("target", "cast") end,
["施法(正计时)"] = function(self) return self:GetUnitCastPixel("target", "castElapsed") end,
["施法可打断"] = function(self) return self:GetUnitInterruptiblePixel("target", "cast") end,
["引导"] = function(self) return self:GetUnitCastPixel("target", "channel") end,
["引导可打断"] = function(self) return self:GetUnitInterruptiblePixel("target", "channel") end,
@@ -254,13 +261,35 @@ local stateBlockGetters = {
if not focus.maxRange then return nil end
return focus.maxRange / 255
end,
["施法"] = function(self) return self:GetUnitCastPixel("focus", "cast") end,
["施法(倒计时)"] = function(self) return self:GetUnitCastPixel("focus", "cast") end,
["施法(正计时)"] = function(self) return self:GetUnitCastPixel("focus", "castElapsed") end,
["施法可打断"] = function(self) return self:GetUnitInterruptiblePixel("focus", "cast") end,
["引导"] = function(self) return self:GetUnitCastPixel("focus", "channel") end,
["引导可打断"] = function(self) return self:GetUnitInterruptiblePixel("focus", "channel") end,
},
}
for index = 1, 5 do
local unit = "boss" .. index
local category = "首领" .. index
local cache = boss[unit]
stateBlockGetters[category] = {
["类型"] = function() return cache.type or 0 end,
["驱散类型"] = function() return 0 end,
["生命值"] = function() return cache.healthPercent or 0 end,
["距离"] = function()
if not cache.maxRange then return nil end
return cache.maxRange / 255
end,
["施法(倒计时)"] = function(self) return self:GetUnitCastPixel(unit, "cast") end,
["施法(正计时)"] = function(self) return self:GetUnitCastPixel(unit, "castElapsed") end,
["施法可打断"] = function(self) return self:GetUnitInterruptiblePixel(unit, "cast") end,
["引导"] = function(self) return self:GetUnitCastPixel(unit, "channel") end,
["引导可打断"] = function(self) return self:GetUnitInterruptiblePixel(unit, "channel") end,
}
end
for powerType, powerName in pairs(Fuyutsui.powerNameMap) do
if powerName ~= "符文" then
local pt = powerType
@@ -281,6 +310,10 @@ function Fuyutsui:UpdateStateBlock(category, name)
local cat = stateBlockGetters[category]
if not cat then return end
local getter = cat[name]
-- 兼容已有职业配置中的旧名称;新配置统一使用“施法(倒计时)”
if not getter and name == "施法" and category ~= "状态" then
getter = cat["施法(倒计时)"]
end
if not getter then return end
local key = bareKeyCategories[category] and name or (category .. name)
local b = self.blocks

View File

@@ -5,6 +5,7 @@ local rc = LibStub("LibRangeCheck-3.0")
local state = Fuyutsui.state
local target = Fuyutsui.target
local focus = Fuyutsui.focus
local boss = Fuyutsui.boss
local nameplate = Fuyutsui.nameplate
function Fuyutsui:GetUnitRange(unit)
@@ -25,6 +26,7 @@ local unitZHMap = {
local function GetUnitCache(unit)
if unit == "target" then return target end
if unit == "focus" then return focus end
if boss and boss[unit] then return boss[unit] end
end
local function isSameUnit(unit1, unit2)
@@ -110,7 +112,6 @@ function Fuyutsui:UpdateUnitRangeBlock(unit)
end
function Fuyutsui:UpdateUnitCastingOrChannelingInfo(unit)
if not UnitExists(unit) then return end
local obj = unitZHMap[unit]
if not obj then return end

View File

@@ -25,6 +25,10 @@ function Fuyutsui:UpdatePlayerBlocks()
self:UpdateFocusRangeBlock()
self:UpdateTargetHealth()
self:UpdateFocusHealth()
for index = 1, 5 do
self:UpdateUnitFullInfo("boss" .. index)
self:UpdateUnitRangeBlock("boss" .. index)
end
self:UpdateEnemyCount()
self:UpdateGroup()
self:GetItemCount()
@@ -51,10 +55,13 @@ function Fuyutsui:LoadPlayerBlocks(specIndex)
local index = 1
-- states 支持分类表:状态/能量/物品/配置开关/目标/焦点
-- blocks.state 键:除目标/焦点外用名称本身;目标/焦点用 分类..名称(如 目标生命值)
-- states 支持分类表:状态/能量/物品/配置开关/目标/焦点/首领1-5
-- blocks.state 键:基础分类用名称本身;单位分类用 分类..名称(如 目标生命值)
if type(t.states) == "table" then
local stateCategoryOrder = { "状态", "能量", "物品", "配置开关", "目标", "焦点" }
local stateCategoryOrder = {
"状态", "能量", "物品", "配置开关", "目标", "焦点",
"首领1", "首领2", "首领3", "首领4", "首领5",
}
local bareKeyCategories = {
["状态"] = true,
["能量"] = true,
@@ -63,6 +70,8 @@ function Fuyutsui:LoadPlayerBlocks(specIndex)
}
local nested = t.states["状态"] or t.states["能量"] or t.states["物品"]
or t.states["配置开关"] or t.states["目标"] or t.states["焦点"]
or t.states["首领1"] or t.states["首领2"] or t.states["首领3"]
or t.states["首领4"] or t.states["首领5"]
if nested then
for _, category in ipairs(stateCategoryOrder) do
local list = t.states[category]

View File

@@ -12,6 +12,11 @@ internal static class ClassStateCatalog
public const string CategoryResource = "能量";
public const string CategoryTarget = "目标";
public const string CategoryFocus = "焦点";
public const string CategoryBoss1 = "首领1";
public const string CategoryBoss2 = "首领2";
public const string CategoryBoss3 = "首领3";
public const string CategoryBoss4 = "首领4";
public const string CategoryBoss5 = "首领5";
public static readonly string[] TopCategories =
[
@@ -20,7 +25,12 @@ internal static class ClassStateCatalog
CategoryItem,
CategoryConfig,
CategoryTarget,
CategoryFocus
CategoryFocus,
CategoryBoss1,
CategoryBoss2,
CategoryBoss3,
CategoryBoss4,
CategoryBoss5
];
private static readonly (string Category, string[] Names)[] Categories =
@@ -50,11 +60,31 @@ internal static class ClassStateCatalog
]),
(CategoryTarget,
[
"类型", "驱散类型", "生命值", "距离", "施法", "施法可打断", "引导", "引导可打断"
"类型", "驱散类型", "生命值", "距离", "施法", "施法(正计时)", "施法可打断", "引导", "引导可打断"
]),
(CategoryFocus,
[
"类型", "驱散类型", "生命值", "距离", "施法", "施法可打断", "引导", "引导可打断"
"类型", "驱散类型", "生命值", "距离", "施法", "施法(正计时)", "施法可打断", "引导", "引导可打断"
]),
(CategoryBoss1,
[
"类型", "驱散类型", "生命值", "距离", "施法", "施法(正计时)", "施法可打断", "引导", "引导可打断"
]),
(CategoryBoss2,
[
"类型", "驱散类型", "生命值", "距离", "施法", "施法(正计时)", "施法可打断", "引导", "引导可打断"
]),
(CategoryBoss3,
[
"类型", "驱散类型", "生命值", "距离", "施法", "施法(正计时)", "施法可打断", "引导", "引导可打断"
]),
(CategoryBoss4,
[
"类型", "驱散类型", "生命值", "距离", "施法", "施法(正计时)", "施法可打断", "引导", "引导可打断"
]),
(CategoryBoss5,
[
"类型", "驱散类型", "生命值", "距离", "施法", "施法(正计时)", "施法可打断", "引导", "引导可打断"
])
];
@@ -94,6 +124,12 @@ internal static class ClassStateCatalog
return true;
}
// 兼容旧职业配置:单位分类中的“施法”已更名为“施法(倒计时)”。
if (name == "施法" && category != CategoryState && TopCategories.Contains(category))
{
return true;
}
// Lua 中已有但目录尚未收录的状态仍放在“状态”中,保证可见、可编辑。
return string.Equals(category, CategoryState, StringComparison.Ordinal)
&& FindCategory(name) is null;

View File

@@ -798,11 +798,11 @@ public sealed class StatusForm : Form
92), 1, 1);
fields.Controls.Add(CreateAboutFieldCard(
"目标",
["类型", "生命值", "距离", "施法", "施法可打断", "引导", "引导可打断"],
["类型", "生命值", "距离", "施法(倒计时)", "施法(正计时)", "施法可打断", "引导", "引导可打断"],
104), 0, 2);
fields.Controls.Add(CreateAboutFieldCard(
"焦点",
["类型", "生命值", "距离", "施法", "施法可打断", "引导", "引导可打断"],
["类型", "生命值", "距离", "施法(倒计时)", "施法(正计时)", "施法可打断", "引导", "引导可打断"],
104), 1, 2);
panel.Controls.Add(fields, 0, 2);