local re = re local sdk = sdk local d2d = d2d local imgui = imgui local log = log local json = json local draw = draw local require = require local tostring = tostring local pairs = pairs local ipairs = ipairs local math = math local string = string local table = table local type = type local _ = _ local Core = require("_CatLib") local Debug = require("_CatLib.debug") local Draw = require("_CatLib.draw") local Div = require("_CatLib.div") local CONST = require("_CatLib.const") local mod = require("mhwilds_overlay.mod") local OverlayDrawHp = require("mhwilds_overlay.draw.hp") local OverlayData = require("mhwilds_overlay.data") local Config = require("mhwilds_overlay.boss.conf") local Data = require("mhwilds_overlay.boss.data") local _M = {} local EnemyIdNameMap = Core.GetEnumMap("app.EnemyDef.ID") local ElementImages = {} local CrownImages = {} local EnemyImages = {} local EnemyImageUnknown local PlateImage local BreakableIcon local SeverableIcon ---@param ctx EnemyContext local function TryLoadEnemyImage(ctx) local emId = ctx:get_EmID() local name = EnemyIdNameMap[emId] if not name then return end if EnemyImages[name] == false then return EnemyImageUnknown end if EnemyImages[name] ~= nil then return EnemyImages[name] end local iconPath = string.format("enemy/tex_EmIcon_%s_IMLM4.tex.241106027.png", string.upper(name)) local image = mod.LoadImage(iconPath) if image then EnemyImages[name] = image return image end EnemyImages[name] = false -- log.error(string.format("Enemy Icon: %s missing", iconPath)) return EnemyImageUnknown end local MeatSlotNames = Core.GetEnumMap("app.user_data.EmParamParts.MEAT_SLOT") local StateNames = Core.GetEnumMap("app.cEmModuleScar.cScarParts.STATE") local condRowTextHeight = nil local partRowTextHeight = nil local scarRowTextHeight = nil local LevelTextWidth, LevelTextHeight = nil, nil local QuestElapsedTime = -1 local QuestTimeLimit = 1 local IsInBattle = false ---@class TimestampCache ---@field LastVal number ---@field LastChangedTimestamp number ---@type table> _M.ConditionUpdateTimestampCache = {} ---@type table> _M.PartUpdateTimestampCache = {} ---@type table> _M.ScarUpdateTimestampCache = {} local EmPartIndexCache = {} function _M.ClearData() _M.ConditionUpdateTimestampCache = {} _M.PartUpdateTimestampCache = {} _M.ScarUpdateTimestampCache = {} QuestElapsedTime = -1 QuestTimeLimit = 1 IsInBattle = false EmPartIndexCache = {} end local CondCanvas = Draw.NewDivCanvas({ Enable = true, OffsetX = 0, OffsetY = 0, Width = 1000, Height = 200, Absolute = true, UseBackground = false, }) -- 怒 cEnemyAngryCondition 打满之后,打眠再起来清空了,但是仍然是怒态 -- cond: app.cEnemyActivateValueBase, app.cEnemyBadConditionStun ---@param cond app.cEnemyBadCondition ---@param condData CondData function _M.DrawEnemyCondition(ctx, cond, x, y, width, condData, config, condConfig) if not cond then return end local condType = condData.condType local isActivate = condData.isActivate local current = condData.current local max = condData.max local ratio = condData.ratio -- check display or not local alwaysShow = config.AlwaysShow or condConfig.AlwaysShow if not alwaysShow then if current <= 0 and not isActivate then return false end end -- check auto hide if _M.ConditionUpdateTimestampCache[ctx] == nil then _M.ConditionUpdateTimestampCache[ctx] = {} end local currentTime = QuestElapsedTime local interval = config.AutoHideSeconds if _M.ConditionUpdateTimestampCache[ctx][condType] == nil then _M.ConditionUpdateTimestampCache[ctx][condType] = { LastChangedTimestamp = -1, LastVal = -1, } end local hideTimer = nil if config.AutoHide and not alwaysShow then if _M.ConditionUpdateTimestampCache[ctx][condType].LastVal ~= current then _M.ConditionUpdateTimestampCache[ctx][condType].LastVal = current _M.ConditionUpdateTimestampCache[ctx][condType].LastChangedTimestamp = currentTime else hideTimer = currentTime - _M.ConditionUpdateTimestampCache[ctx][condType].LastChangedTimestamp if hideTimer > interval then -- skip unchanged return false end end end -- display local ms = {} local s = {} local count = condData.count if config.ShowCondLevel and count then s[#s+1] = string.format("[%d]", count) end if config.ShowCondName then if mod.Config.Debug then s[#s+1] = tostring(condType) .. "|" .. condConfig.DisplayName else s[#s+1] = condConfig.DisplayName end end if #s > 0 then ms[#ms+1] = table.concat(s, " ") s = {} end if config.ShowCondValue then s[#s+1] = string.format("%0.1f", current) end if config.ShowCondMaxValue then if config.ShowCondValue then s[#s+1] = "/" end s[#s+1] = string.format("%d", max) end if mod.Config.Debug then s[#s+1] = " - " .. Core.FloatFixed1(hideTimer) s[#s+1] = " - " .. Core.FloatFixed1(ratio * 100) .. "%" end if #s > 0 then ms[#ms+1] = table.concat(s, "") end CondCanvas.RePos(x, y) CondCanvas.ReSize(width, 1000) CondCanvas.Debug(mod.Config.Debug) CondCanvas.Init() local rectCfg = config.CondRect if not condConfig.UseDefaultRectStyle then rectCfg = condConfig.Rect end CondCanvas.Rect(rectCfg, ratio) if #ms > 0 then local fontCfg = config.FontStyle if not condConfig.UseDefaultFontStyle then fontCfg = condConfig.FontStyle end local msg = table.concat(ms, ": ") if isActivate and condType == CONST.EnemyConditionType.Poison then local current = cond._StockValueActive if current > 0 then s = {} if config.ShowCondValue then s[#s+1] = string.format("%0.1f", current) end if config.ShowCondMaxValue then if config.ShowCondValue then s[#s+1] = "/" end local max = cond._StockValueActiveLimit s[#s+1] = string.format("%d", math.ceil(max)) end msg = msg .. " " .. table.concat(s, "") end end CondCanvas.Text(fontCfg, msg) end CondCanvas.End() return true end local Ctx_ConditionModule = Core.TypeField("app.cEnemyContext", "Conditions") local ConditionModule_Conditions = Core.TypeField("app.cEmModuleConditions", "_Conditions") ---@param ctx EnemyContext function _M.DrawEnemyConditions(ctx, x, y) -- cEnemyContext local theme = mod.Runtime.Themes[Config.ThemeIndex] local config = theme.Condition if not config.Enable then return x, y end if not config.AlwaysShow then if config.OnlyInBattle and not IsInBattle then return x, y end end local condModule = Ctx_ConditionModule:get_data(ctx) if not condModule then return x, y end -- app.cEnemyActivateValueBase[], app.cEnemyBadConditionStun etc local conds = ConditionModule_Conditions:get_data(condModule) if not conds then return x, y end x = x + config.OffsetX y = y + config.OffsetY local displayIndex = 0 local col = config.Columns local barHeight = config.Height local rowHeight = barHeight + config.RowMargin if config.ShowCondName or config.ShowCondLevel or config.ShowCondValue or config.ShowCondMaxValue then rowHeight = rowHeight + condRowTextHeight end local margin = config.ColumnMargin local width = math.floor((config.Width - (col - 1) * margin) / col) local theme = mod.Runtime.Themes[Config.ThemeIndex] local themeConfig = theme.Condition -- mod.RecordCost("EnemyCond.ForEach", function () Data.UpdateConditions(ctx, conds) for _, cond in pairs(Data.Enemy_Conds[ctx]) do if not cond then goto continue end local data = Data.Cond_CondData[cond] if not data or not data.condType then goto continue end local condName = CONST.EnemyConditionTypeNames[data.condType] local condConfig = themeConfig.Conditions[condName] if not condConfig or not condConfig.Enable then goto continue end local colIndex, yCount, condX, condY colIndex = (displayIndex % col) yCount = math.floor(displayIndex / col) condX = x + colIndex * (width + margin) -- - 120 condY = y + yCount * rowHeight local shown = _M.DrawEnemyCondition(ctx, cond, condX, condY, width, data, themeConfig, condConfig) if shown then displayIndex = displayIndex + 1 end ::continue:: end -- end, true) if displayIndex > 0 then y = y + (math.ceil(displayIndex / col)) * rowHeight end return x, y end local function GetEnemyPartType(id, partIndex) if Data.Em_PartsIndexMap[id] then return Data.Em_PartsIndexMap[id][partIndex] else return nil end end local PartCanvas = Draw.NewDivCanvas({ Enable = true, OffsetX = 0, OffsetY = 0, Width = 1000, Height = 200, Absolute = true, UseBackground = false, }) local ScarTextCanvas = Draw.NewDivCanvas({ Enable = true, OffsetX = 0, OffsetY = 0, Width = 1000, Height = 200, Absolute = true, UseBackground = false, }) function _M.DrawEnemyPart(ctx, partIndex, x, y, width, height, isSeverable, isBreakable) local data = Data.EnemyPartsData[ctx][partIndex] if not data then return end local theme = mod.Runtime.Themes[Config.ThemeIndex] local config = theme.Part -- local partType = data.PartType local partType = GetEnemyPartType(ctx:get_EmID(), partIndex) if not partType then return end local partName = CONST.EnemyPartsTypeNames[partType] local partConfig = config.Parts[partName] if not partConfig or not partConfig.Enable then return end local current = math.ceil(data.HP) local max = math.ceil(data.MaxHP) if max <= 0 then max = 999999 if current <= 0 then return false end end if current >= 9998 and max >= 9998 and (max - current) < 2 then return end if config.HideHigherThanMaxHpPart then local monsterMaxHP = OverlayData.EnemyInfo[ctx].MaxHP if max > monsterMaxHP then return end end local alwaysShow = config.AlwaysShow or partConfig.AlwaysShow local isAlwaysShowBreakblePart = false if isSeverable and config.SeverableAlwaysShow then isAlwaysShowBreakblePart = true elseif isBreakable and config.BreakableAlwaysShow then isAlwaysShowBreakblePart = true end if isAlwaysShowBreakblePart then if config.BrokenNoAlwaysShow and Data.EM_BreakablePartsCache[ctx] and Data.EM_BreakablePartsCache[ctx].Broken[tostring(partIndex)] == true then -- broken, don't always show else alwaysShow = true end end -- Check should display if not alwaysShow then if current == max or current <= 0 then return false end end if _M.PartUpdateTimestampCache[ctx] == nil then _M.PartUpdateTimestampCache[ctx] = {} end local interval = config.AutoHideSeconds local currentTime = QuestElapsedTime if _M.PartUpdateTimestampCache[ctx][partType] == nil then _M.PartUpdateTimestampCache[ctx][partType] = { LastChangedTimestamp = -1, LastVal = -1, } end -- Check auto hide local hideTimer = nil if config.AutoHide and not alwaysShow then if _M.PartUpdateTimestampCache[ctx][partType].LastVal ~= current then _M.PartUpdateTimestampCache[ctx][partType].LastVal = current _M.PartUpdateTimestampCache[ctx][partType].LastChangedTimestamp = currentTime else hideTimer = currentTime - _M.PartUpdateTimestampCache[ctx][partType].LastChangedTimestamp if hideTimer > interval then -- skip unchanged return false end end end -- Display local ratio = current / max local s = {} if config.ShowPartName then s[#s+1] = partConfig.DisplayName end if config.ShowPartLevel then s[#s+1] = string.format("[%d]", data.Count) end if config.ShowPartName or config.ShowPartLevel then if config.ShowPartHP or config.ShowPartMaxHP then s[#s+1] = ": " end end if config.ShowPartHP then s[#s+1] = string.format("%0.1f", current) end if config.ShowPartMaxHP then if config.ShowPartHP then s[#s+1] = "/" end s[#s+1] = string.format("%d", max) end -- if isSeverable then -- s[#s+1] = " SEVERABLE" -- elseif isBreakable then -- s[#s+1] = " BREAKABLE" -- end -- if Data.EM_BreakablePartsCache[ctx].Broken[tostring(partIndex)] then -- s[#s+1] = " BROKEN" -- end if mod.Config.Debug then s[#s+1] = " - Hide: " .. Core.FloatFixed1(hideTimer) s[#s+1] = " - " .. Core.FloatFixed1(ratio * 100) .. "%" end PartCanvas.RePos(x, y) PartCanvas.ReSize(width, 1000) PartCanvas.Debug(mod.Config.Debug) PartCanvas.Init() -- local PartDiv = Div.new() -- PartDiv:RePos(x, y) -- PartDiv:ReSize(width, height) -- PartDiv.display = "flex" -- PartDiv.flexDirection = "column" -- PartDiv.justifyContent = "space-between" -- PartDiv.alignItems = "flex-start" -- local PartHeaderDiv = Div.new() -- PartHeaderDiv.display = "flex" -- PartHeaderDiv.flexDirection = "row" -- PartHeaderDiv.justifyContent = "space-between" -- PartHeaderDiv.alignItems = "center" -- PartHeaderDiv.width = PartDiv.width -- local PartBarDiv = Div.new() -- local PartScarsDiv = Div.new() -- PartScarsDiv.display = "flex" -- PartScarsDiv.flexDirection = "row" -- PartScarsDiv.justifyContent = "space-between" -- PartScarsDiv.display = "flex-start" -- PartDiv:add(PartHeaderDiv) -- PartDiv:add(PartBarDiv) -- PartDiv:add(PartScarsDiv) -- -- PartDiv.verticalDirection = "bottom-to-top" -- PartDiv.autoBreak = false local fontCfg = config.FontStyle if not partConfig.UseDefaultFontStyle then fontCfg = partConfig.FontStyle end local rectCfg = config.PartRect if not partConfig.UseDefaultPartStyle then rectCfg = partConfig.Rect end PartCanvas.Rect(rectCfg, ratio) -- local rectDiv = Div.new() -- rectCfg.Width = width -- rectDiv.renderer = Draw.RectRenderer(rectCfg, ratio) -- PartBarDiv:add(rectDiv) local partMsg = table.concat(s,"") if partMsg ~= "" then PartCanvas.Text(fontCfg, partMsg) -- local nameDiv = Div.new() -- nameDiv.width = "auto" -- nameDiv.renderer = Draw.TextRenderer(fontCfg, partMsg) -- PartHeaderDiv:add(nameDiv) end if isSeverable then if config.ShowSeverableIcon then PartCanvas.Image({ BlockRenderX = false, BlockRenderY = false, OffsetY = fontCfg.OffsetY, RightAlign = true, Width = partRowTextHeight, Height = partRowTextHeight, MarginX = 0, }, SeverableIcon) -- local imageDiv = Div.new() -- imageDiv.renderer = Draw.ImageRenderer({ -- BlockRenderX = false, -- BlockRenderY = false, -- OffsetY = fontCfg.OffsetY, -- RightAlign = true, -- Width = partRowTextHeight, -- Height = partRowTextHeight, -- MarginX = 0, -- }, SeverableIcon) -- PartHeaderDiv:add(imageDiv) end elseif isBreakable then if config.ShowBreakableIcon then PartCanvas.Image({ BlockRenderX = false, BlockRenderY = false, OffsetY = fontCfg.OffsetY, RightAlign = true, Width = partRowTextHeight, Height = partRowTextHeight, MarginX = 0, }, BreakableIcon) -- local imageDiv = Div.new() -- imageDiv.renderer = Draw.ImageRenderer({ -- BlockRenderX = false, -- BlockRenderY = false, -- OffsetY = fontCfg.OffsetY, -- RightAlign = true, -- Width = partRowTextHeight, -- Height = partRowTextHeight, -- MarginX = 0, -- }, BreakableIcon) -- PartHeaderDiv:add(imageDiv) end end local scarConfig = config.Scar if not partConfig.UseDefaultScarStyle then scarConfig = partConfig.Scar end if scarConfig.Enable then local ringCfg = config.ScarRing if not partConfig.UseDefaultScarStyle then ringCfg = partConfig.ScarRing end local lvTextCfg = config.ScarLevelText if not partConfig.UseDefaultScarLevelFontStyle then lvTextCfg = partConfig.ScarLevelText end for _, scarData in pairs(data.Scars) do local state = scarData.State -- 0 Normal 1 Tear 2 Raw if state < 0 or state > 2 then goto continue end local color = scarConfig.NormalColor local current = 0 local max = 1 local count = 0 if state == 0 then current, max = scarData.Normal.HP, scarData.Normal.MaxHP count = scarData.Normal.Count elseif state == 1 then current, max = scarData.Tear.HP, scarData.Tear.MaxHP count = scarData.Tear.Count color = scarConfig.TearColor elseif state == 2 then current, max = scarData.Raw.HP, scarData.Raw.MaxHP count = scarData.Raw.Count color = scarConfig.RawColor end if scarData.IsRide then if not scarConfig.ShowRideScar then goto continue; end if scarConfig.UseSpecialRideScarColor then color = scarConfig.RideColor end end if scarData.IsLegendary then if not scarConfig.ShowLegendaryScar then goto continue; end if scarConfig.UseSpecialLegendaryScarColor then color = scarConfig.LegendaryColor end end if max <= 0 then max = 999999 end local scarRatio = current/max ringCfg.Color = color local x, y = PartCanvas.NextPos() local _, _, _, _, _, _, w, h = PartCanvas.Circle(ringCfg, scarRatio) -- local ringDiv = Div.new() -- ringDiv.renderer = Draw.CircleRenderer(ringCfg, scarRatio) -- PartScarsDiv:add(ringDiv) if ringCfg.Enable and lvTextCfg.Enable then ScarTextCanvas.RePos(x, y) ScarTextCanvas.ReSize(w, h) ScarTextCanvas.Init() ScarTextCanvas.Text(lvTextCfg, tostring(count)) ScarTextCanvas.End() end ::continue:: end end PartCanvas.End() return true -- return PartDiv end function _M.DrawEnemyParts(ctx, x, y) -- cEnemyContext if not ctx.Parts or not Data.EnemyPartsData[ctx] then return x, y end local theme = mod.Runtime.Themes[Config.ThemeIndex] local config = theme.Part if not config.Enable then return x, y end if not config.AlwaysShow then if config.OnlyInBattle and not IsInBattle then return x, y end end local breakParts = ctx.Parts._BreakPartsByPartsIdx if not breakParts then return x, y end x = x + config.OffsetX y = y + config.OffsetY -- local PartsDiv = Div.new() -- PartsDiv:RePos(x, y) -- PartsDiv.display = "inline" local len = breakParts:get_Count() local displayIndex = 0 local col = config.Columns local barHeight = config.PartRect.Height local rowHeight = barHeight + config.RowMargin if config.ShowPartName or config.ShowPartLevel or config.ShowPartHP or config.ShowPartMaxHP or config.ShowBreakableIcon or config.ShowSeverableIcon then rowHeight = rowHeight + partRowTextHeight end if config.Scar.Enable and config.ScarRing.Enable then rowHeight = rowHeight + (config.ScarRing.Radius)*2 end local breakableConf = Data.EM_BreakablePartsCache[ctx] local hasBreakable = breakableConf and breakableConf.Count > 0 local margin = config.ColumnMargin local width = math.floor((config.Width - (col - 1) * margin) / col) for i = 0, len - 1, 1 do local colIndex = (displayIndex % col) local yCount = math.floor(displayIndex / col) local partX = x + colIndex * (width + margin) local partY = y + yCount * rowHeight local severable = hasBreakable and breakableConf.Severable[tostring(i)] if config.OnlySeverable and not severable then goto continue end local breakble = hasBreakable and breakableConf.Breakable[tostring(i)] if config.OnlyBreakable and not breakble then goto continue end local shown = _M.DrawEnemyPart(ctx, i, partX, partY, width, barHeight, severable, breakble) if shown then displayIndex = displayIndex + 1 end -- local div = _M.DrawEnemyPart(ctx, i, partX, partY, width, 3*barHeight, severable, breakble) -- if div then -- displayIndex = displayIndex + 1 -- div.margin.right = config.ColumnMargin -- div.margin.bottom = config.RowMargin -- if i == 0 then -- div:RePos(partX, partY) -- div.display = "flex" -- end -- PartsDiv:add(div) -- end ::continue:: end if displayIndex > 0 then y = y + (math.ceil(displayIndex / col)) * rowHeight end -- PartsDiv:ReSize(config.Width, 3*barHeight * len / col) -- PartsDiv:render(mod.Config.Debug) return x, y end local ScarCanvas = Draw.NewDivCanvas({ Enable = true, OffsetX = 0, OffsetY = 0, Width = 1000, Height = 200, Absolute = true, UseBackground = false, }) function _M.DrawEnemyScar(ctx, scar, scarIndex, x, y, width, height) local data = Data.EnemyScarData[ctx][scarIndex] if not data then return end local state = scar._State -- 0 Normal 1 Tear 2 Raw if state < 0 or state > 2 then return end local theme = mod.Runtime.Themes[Config.ThemeIndex] local partIndex = scar._PartsIndex_1 local partType = GetEnemyPartType(ctx:get_EmID(), partIndex) local theme = mod.Runtime.Themes[Config.ThemeIndex] local config = theme.Scar local color = config.ScarColors.NormalColor local current = 0 local max = 1 local count = 0 if state == 0 and data.Normal then current, max = data.Normal.HP, data.Normal.MaxHP count = data.Normal.Count elseif state == 1 and data.Tear then current, max = data.Tear.HP, data.Tear.MaxHP count = data.Tear.Count color = config.ScarColors.TearColor elseif state == 2 and data.Raw then current, max = data.Raw.HP, data.Raw.MaxHP count = data.Raw.Count color = config.ScarColors.RawColor else return end if data.IsRide then if not config.ScarColors.ShowRideScar then return end if config.ScarColors.UseSpecialRideScarColor then color = config.ScarColors.RideColor end end if data.IsLegendary then if not config.ScarColors.ShowLegendaryScar then return end if config.ScarColors.UseSpecialLegendaryScarColor then color = config.ScarColors.LegendaryColor end end -- getActiveParts() -- resetCurrentStateVital() -- setDispName(app.cEmModuleScar.cScarParts, app.user_data.EmParamParts.cDataBase) -- _ParamParts if max <= 0 then max = 999999 end max = math.ceil(max) local alwaysShow = config.AlwaysShow -- Check should display if not alwaysShow then if current == max or current <= 0 then return false end end if _M.ScarUpdateTimestampCache[ctx] == nil then _M.ScarUpdateTimestampCache[ctx] = {} end local interval = config.AutoHideSeconds local currentTime = QuestElapsedTime if _M.ScarUpdateTimestampCache[ctx][scarIndex] == nil then _M.ScarUpdateTimestampCache[ctx][scarIndex] = { LastChangedTimestamp = -1, LastVal = -1, } end -- Check auto hide local hideTimer = nil if config.AutoHide and not alwaysShow then if _M.ScarUpdateTimestampCache[ctx][scarIndex].LastVal ~= current then _M.ScarUpdateTimestampCache[ctx][scarIndex].LastVal = current _M.ScarUpdateTimestampCache[ctx][scarIndex].LastChangedTimestamp = currentTime else hideTimer = currentTime - _M.ScarUpdateTimestampCache[ctx][scarIndex].LastChangedTimestamp if hideTimer > interval then -- skip unchanged return false end end end -- Display local ms = {} local s = {} if config.ShowScarLevel then s[#s+1] = string.format("[%d]", tostring(count)) end if config.ShowPartName and partType then local partName = CONST.EnemyPartsTypeNames[partType] local partConfig = theme.Part.Parts[partName] if partConfig then s[#s+1] = partConfig.DisplayName end end if config.ShowScarIndex then s[#s+1] = string.format("[%d]", scarIndex) end if #s > 0 then ms[#ms+1] = table.concat(s, "") s = {} end if config.ShowScarHP then s[#s+1] = string.format("%0.1f", current) end if config.ShowScarMaxHP then if config.ShowScarHP then s[#s+1] = "/" end s[#s+1] = string.format("%d", max) end local ratio = current / max if mod.Config.Debug then s[#s+1] = " - Hide: " .. Core.FloatFixed1(hideTimer) s[#s+1] = " - " .. Core.FloatFixed1(ratio * 100) .. "%" end if #s > 0 then ms[#ms+1] = table.concat(s, "") end ScarCanvas.RePos(x, y) ScarCanvas.ReSize(width, 1000) ScarCanvas.Debug(mod.Config.Debug) ScarCanvas.Init() config.ScarRect.Color = color ScarCanvas.Rect(config.ScarRect, ratio) if #ms > 0 then ScarCanvas.Text(config.FontStyle, table.concat(ms, ": ")) end ScarCanvas.End() return true -- local ScarDiv = Div.new() -- -- ScarDiv:RePos(x, y) -- ScarDiv:ReSize(width, height) -- ScarDiv.verticalDirection = "bottom-to-top" -- ScarDiv.autoBreak = false -- config.ScarRect.Color = color -- local rectDiv = Div.new() -- config.ScarRect.Width = width -- rectDiv.renderer = Draw.RectRenderer(config.ScarRect, ratio) -- ScarDiv:add(rectDiv) -- if #ms > 0 then -- local nameDiv = Div.new() -- nameDiv.renderer = Draw.TextRenderer(config.FontStyle, table.concat(ms, ": ")) -- ScarDiv:add(nameDiv) -- end -- return ScarDiv end function _M.DrawEnemyScars(ctx, x, y) -- cEnemyContext if not ctx.Scar or not Data.EnemyScarData[ctx] then return x, y end local theme = mod.Runtime.Themes[Config.ThemeIndex] local config = theme.Scar if not config.Enable then return x, y end if not config.AlwaysShow then if config.OnlyInBattle and not IsInBattle then return x, y end end local scarParts = ctx.Scar._ScarParts if not scarParts then return x, y end local len = scarParts:get_Count() if len <= 0 then return x, y end x = x + config.OffsetX y = y + config.OffsetY -- local ScarsDiv = Div.new() -- ScarsDiv:RePos(x, y) -- ScarsDiv.display = "inline" local displayIndex = 0 local col = config.Columns local barHeight = config.Height local rowHeight = barHeight + config.RowMargin if config.ShowPartName or config.ShowScarLevel or config.ShowScarIndex or config.ShowScarHP or config.ShowScarMaxHP then rowHeight = rowHeight + partRowTextHeight end for i = 0, len - 1, 1 do local margin = config.ColumnMargin local colIndex = (displayIndex % col) local yCount = math.floor(displayIndex / col) local width = math.floor((config.Width - (col - 1) * margin) / col) local partX = x + colIndex * (width + margin) local partY = y + yCount * rowHeight local shown = _M.DrawEnemyScar(ctx, scarParts:get_Item(i), i, partX, partY, width, barHeight) if shown then displayIndex = displayIndex + 1 end -- local div = _M.DrawEnemyScar(ctx, scarParts:get_Item(i), i, partX, partY, width, barHeight) -- if div then -- displayIndex = displayIndex + 1 -- div.margin.right = config.ColumnMargin -- div.margin.bottom = config.RowMargin -- ScarsDiv:add(div) -- end end if displayIndex > 0 then y = y + (math.ceil(displayIndex / col)) * rowHeight end -- ScarsDiv:ReSize(config.Width, config.Height * len / col) -- ScarsDiv:render(mod.Config.Debug) return x, y end ---@param ctx EnemyContext function _M.DrawHpBar(ctx, x, y, width, height, i) local theme = mod.Runtime.Themes[Config.ThemeIndex] local data = OverlayData.EnemyInfo[ctx] local plateX, plateY = x, y local plateSize = 0 if theme.EnemyIcon.Enable then plateSize = theme.EnemyIcon.BackgroundSize end local barStartX = plateX + plateSize local _, nextY = OverlayDrawHp.DrawHpBar(theme, ctx, barStartX, y, width, height, i == 0) if theme.EnemyIcon.Enable then local enemySize = theme.EnemyIcon.EnemySize local enemyOffset = (plateSize - enemySize)/2 local enemyX, enemyY = plateX + enemyOffset, plateY + enemyOffset Draw.Image(PlateImage, plateX, plateY, plateSize, plateSize) Draw.Image(TryLoadEnemyImage(ctx), enemyX, enemyY, enemySize, enemySize) end return x + plateSize, math.max(y + plateSize, nextY) end function _M.DrawEnemy(ctx, i, x, y, width, height) -- cEnemyContext -- mod.RecordCost("HpBar", function () x, y = _M.DrawHpBar(ctx, x, y, width, height, i) -- end, true) local data = OverlayData.EnemyInfo[ctx] if OverlayData.IsQuestHost and data.HP > 0 then if Config.DrawPartsFirst then -- mod.RecordCost("Parts", function () x, y = _M.DrawEnemyParts(ctx, x, y) -- end, true) -- mod.RecordCost("Conditions", function () x, y = _M.DrawEnemyConditions(ctx, x, y) -- end, true) else -- mod.RecordCost("Conditions", function () x, y = _M.DrawEnemyConditions(ctx, x, y) -- end, true) -- mod.RecordCost("Parts", function () x, y = _M.DrawEnemyParts(ctx, x, y) -- end, true) end -- mod.RecordCost("Scars", function () x, y = _M.DrawEnemyScars(ctx, x, y) -- end, true) end return x, y end function _M.DrawEnemyContext(ctx, i, total) -- cEnemyContext if not ctx then return false end local data = OverlayData.EnemyInfo[ctx] if not data or not data.HP then return false end if Config.OnlyDamaged and data.HP == data.MaxHP then return false end if total > 1 and data.HP <= 0 then -- hide dead bar when multi targets return false end local x = Config.PosX + i * (Config.ColumnMargin) local y = Config.PosY local width = 1000 local height = 1000 mod.RecordCost("Enemy", function () _M.DrawEnemy(ctx, i, x, y, width, height) end, true) return true end function _M.DrawAll() if not Config.Enable then return end if Data.EnemyContexts == nil or #Data.EnemyContexts <= 0 then return end IsInBattle = Core.IsInBattle() if Config.OnlyInBattle and not IsInBattle then return end -- 下面两行换成 OverlayData 里的数据就会不显示,可能是执行顺序的问题 QuestTimeLimit = Core.GetQuestTimeLimit() QuestElapsedTime = Core.GetQuestElapsedTime() if QuestTimeLimit <= 0 then -- 不知道为什么,但偶尔仅本组件会没清除掉? mod.verbose("Clear due to QuestTimeLimit < 0") _M.ClearData() Data.ClearData() return end OverlayDrawHp.QuestTimeLimit = QuestTimeLimit OverlayDrawHp.QuestElapsedTime = QuestElapsedTime local total = #Data.EnemyContexts mod.InitCost("Enemy") mod.InitCost("HpBar") mod.InitCost("Parts") mod.InitCost("Conditions") mod.InitCost("EnemyCond.ForLoop") mod.InitCost("EnemyCond.ForEach") mod.InitCost("EnemyCond.ForEach.Inside") mod.InitCost("EnemyCond.Calc") mod.InitCost("EnemyCond.PrepareData") mod.InitCost("EnemyCond.Draw") mod.InitCost("Scars") local displayIndex = 0 for i = 1, total, 1 do local drawn = _M.DrawEnemyContext(Data.EnemyContexts[i], displayIndex, total) if drawn then displayIndex = displayIndex + 1 end end -- if mod.Config.Debug then -- local ctx = Data.EnemyContexts[1] -- _M.DrawEnemyContext(ctx, displayIndex, total + 2) -- _M.DrawEnemyContext(ctx, displayIndex + 1, total + 2) -- end end mod.D2dRegister(function () local theme = mod.Runtime.Themes[Config.ThemeIndex] Core.LoadD2dFont(theme.Condition.FontStyle.FontSize) Core.LoadD2dFont(theme.Part.FontStyle.FontSize) Core.LoadD2dFont(theme.Part.ScarLevelText.FontSize) Core.LoadD2dFont(theme.Scar.FontStyle.FontSize) ElementImages["Fire"] = mod.LoadImage("icons/fire.png") ElementImages["Water"] = mod.LoadImage("icons/water.png") ElementImages["Ice"] = mod.LoadImage("icons/ice.png") ElementImages["Thunder"] = mod.LoadImage("icons/thunder.png") ElementImages["Dragon"] = mod.LoadImage("icons/dragon.png") BreakableIcon = mod.LoadImage("icons/breakable.png") SeverableIcon = mod.LoadImage("icons/severable.png") CrownImages[CONST.CrownType.Small] = mod.LoadImage("icons/crown_small.png") CrownImages[CONST.CrownType.Big] = mod.LoadImage("icons/crown_silver.png") CrownImages[CONST.CrownType.King] = mod.LoadImage("icons/crown_king.png") PlateImage = mod.LoadImage("enemy/plate.png") EnemyImageUnknown = mod.LoadImage("enemy/tex_EmIcon_EM0000_00_0_IMLM4.tex.241106027.png") if condRowTextHeight == nil then _, condRowTextHeight = Core.LoadD2dFont(theme.Condition.FontStyle.FontSize):measure("名字TextHeight") _, partRowTextHeight = Core.LoadD2dFont(theme.Part.FontStyle.FontSize):measure("名字TextHeight") _, scarRowTextHeight = Core.LoadD2dFont(theme.Scar.FontStyle.FontSize):measure("名字TextHeight") LevelTextWidth, LevelTextHeight = Core.LoadD2dFont(theme.Part.ScarLevelText.FontSize):measure("0") end end, function () _M.DrawAll() end, "Boss") return _M