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 CONST = require("_CatLib.const") local mod = require("mhwilds_overlay.mod") local OverlayData = require("mhwilds_overlay.data") local Config = require("mhwilds_overlay.dps_table.conf") local _M = {} local function CommonFloatText(val) if not val or val <= 0 then return "0" end return Core.FloatFixed1(val) end local function CommonIntText(val) if not val or val <= 0 then return "0" end return string.format("%d", math.floor(val)) end local function PercentageText(val, max) if not val or val <= 0 then return "0" end if max <= 0 then max = 1 end return string.format("%0.1f%%", val / max * 100) end local function StatusDamageText(hunter, key) local val = OverlayData.HunterDamageRecords[hunter][key] if not val or val <= 0 then return "0" end return CommonIntText(OverlayData.HunterDamageRecords[hunter][key]) end local function StatusPercentageText(hunter, key) local val = OverlayData.HunterDamageRecords[hunter][key] if not val or val <= 0 then return "0" end return PercentageText(val, OverlayData.QuestStats[key]) end local SupportedColumnFunc = { ["HR"] = function(hunter) local hr = OverlayData.HunterInfo[hunter].HR if not hr then return "-" end return CommonIntText(hr) end, ["Name"] = function(hunter) return OverlayData.HunterInfo[hunter].Name end, ["DPS"] = function(hunter) if not OverlayData.HunterInfo[hunter].FirstHitTime then return "0" end local time = (OverlayData.QuestStats.ElapsedTime - OverlayData.HunterInfo[hunter].FirstHitTime) if time <= 0 then return "0" end local dps = OverlayData.HunterDamageRecords[hunter].Total / time return CommonFloatText(dps) end, ["fDPS"] = function(hunter) if not OverlayData.HunterInfo[hunter].FirstHitTime then return "0" end local time = OverlayData.HunterInfo[hunter].FightingTime if time <= 0 then return "0" end local dps = OverlayData.HunterDamageRecords[hunter].Total / time return CommonFloatText(dps) end, ["qDPS"] = function(hunter) if not OverlayData.HunterInfo[hunter].FirstHitTime then return "0" end local time = OverlayData.QuestStats.ElapsedTime if time <= 0 then return "0" end local dps = OverlayData.HunterDamageRecords[hunter].Total / time return CommonFloatText(dps) end, ["Damage"] = function(hunter) return CommonFloatText(OverlayData.HunterDamageRecords[hunter].Total) end, ["DamagePercentage"] = function(hunter) return PercentageText(OverlayData.HunterDamageRecords[hunter].Total, OverlayData.QuestStats.Total) end, ["Physical"] = function(hunter) return CommonFloatText(OverlayData.HunterDamageRecords[hunter].Physical) end, ["PhysicalPercentage"] = function(hunter) return PercentageText(OverlayData.HunterDamageRecords[hunter].Physical, OverlayData.QuestStats.Physical) end, ["SelfPhysicalPercentage"] = function(hunter) return PercentageText(OverlayData.HunterDamageRecords[hunter].Physical, OverlayData.HunterDamageRecords[hunter].Total) end, ["Elemental"] = function(hunter) return CommonFloatText(OverlayData.HunterDamageRecords[hunter].Elemental) end, ["ElementalPercentage"] = function(hunter) return PercentageText(OverlayData.HunterDamageRecords[hunter].Elemental, OverlayData.QuestStats.Elemental) end, ["SelfElementalPercentage"] = function(hunter) return PercentageText(OverlayData.HunterDamageRecords[hunter].Elemental, OverlayData.HunterDamageRecords[hunter].Total) end, ["FixedDamage"] = function(hunter) return CommonFloatText(OverlayData.HunterDamageRecords[hunter].Fixed) end, ["FixedDamagePercentage"] = function(hunter) return PercentageText(OverlayData.HunterDamageRecords[hunter].Fixed, OverlayData.QuestStats.Fixed) end, ["SelfFixedDamagePercentage"] = function(hunter) return PercentageText(OverlayData.HunterDamageRecords[hunter].Fixed, OverlayData.HunterDamageRecords[hunter].Total) end, ["PoisonDamage"] = function(hunter) return CommonFloatText(OverlayData.HunterDamageRecords[hunter].PoisonDamage) end, ["PoisonDamagePercentage"] = function(hunter) return PercentageText(OverlayData.HunterDamageRecords[hunter].PoisonDamage, OverlayData.QuestStats.PoisonDamage) end, ["SelfPoisonDamagePercentage"] = function(hunter) return PercentageText(OverlayData.HunterDamageRecords[hunter].PoisonDamage, OverlayData.HunterDamageRecords[hunter].Total) end, ["BlastDamage"] = function(hunter) return CommonFloatText(OverlayData.HunterDamageRecords[hunter].BlastDamage) end, ["BlastDamagePercentage"] = function(hunter) return PercentageText(OverlayData.HunterDamageRecords[hunter].BlastDamage, OverlayData.QuestStats.BlastDamage) end, ["SelfBlastDamagePercentage"] = function(hunter) return PercentageText(OverlayData.HunterDamageRecords[hunter].BlastDamage, OverlayData.HunterDamageRecords[hunter].Total) end, ["Status"] = function(hunter) return CommonIntText(OverlayData.HunterDamageRecords[hunter].StatusTotal) end, ["StatusPercentage"] = function(hunter) return PercentageText(OverlayData.HunterDamageRecords[hunter].StatusTotal, OverlayData.QuestStats.StatusTotal) end, ["FightingTime"] = function(hunter) return CommonFloatText(OverlayData.HunterInfo[hunter].FightingTime) end, ["HitCount"] = function(hunter) return CommonIntText(OverlayData.HunterInfo[hunter].HitCount) end, ["CriticalCount"] = function(hunter) return CommonIntText(OverlayData.HunterInfo[hunter].CriticalCount) end, ["CriticalCountPercentage"] = function(hunter) local criticalableHitCount = OverlayData.HunterInfo[hunter].HitCount - OverlayData.HunterInfo[hunter].NoCriticalHitCount return PercentageText(OverlayData.HunterInfo[hunter].CriticalCount, criticalableHitCount) end, ["NegativeCriticalCount"] = function(hunter) return CommonIntText(OverlayData.HunterInfo[hunter].NegCriticalCount) end, ["NegativeCriticalCountPercentage"] = function(hunter) local criticalableHitCount = OverlayData.HunterInfo[hunter].HitCount - OverlayData.HunterInfo[hunter].NoCriticalHitCount return PercentageText(OverlayData.HunterInfo[hunter].NegCriticalCount, criticalableHitCount) end, ["SoftHitCount"] = function(hunter) return CommonIntText(OverlayData.HunterInfo[hunter].PhysicalExploitHitCount) end, ["SoftHitCountPercentage"] = function(hunter) local total = OverlayData.HunterInfo[hunter].HitCount - OverlayData.HunterInfo[hunter].NoMeatHitCount return PercentageText(OverlayData.HunterInfo[hunter].PhysicalExploitHitCount, total) end, ["ElementalSoftHitCount"] = function(hunter) return CommonIntText(OverlayData.HunterInfo[hunter].ElementalExploitHitCount) end, ["ElementalSoftHitCountPercentage"] = function(hunter) local total = OverlayData.HunterInfo[hunter].HitCount - OverlayData.HunterInfo[hunter].NoMeatHitCount return PercentageText(OverlayData.HunterInfo[hunter].ElementalExploitHitCount, total) end, ["Stun"] = function(hunter) return StatusDamageText(hunter, "Stun"); end, ["Ride"] = function(hunter) return StatusDamageText(hunter, "Ride"); end, ["Block"] = function(hunter) return StatusDamageText(hunter, "Block"); end, ["Parry"] = function(hunter) return StatusDamageText(hunter, "Parry"); end, ["Poison"] = function(hunter) return StatusDamageText(hunter, "Poison"); end, ["Paralyse"] = function(hunter) return StatusDamageText(hunter, "Paralyse"); end, ["Sleep"] = function(hunter) return StatusDamageText(hunter, "Sleep"); end, ["Blast"] = function(hunter) return StatusDamageText(hunter, "Blast"); end, ["Stamina"] = function(hunter) return StatusDamageText(hunter, "Stamina"); end, ["StunPercentage"] = function(hunter) return StatusPercentageText(hunter, "Stun"); end, ["RidePercentage"] = function(hunter) return StatusPercentageText(hunter, "Ride"); end, ["BlockPercentage"] = function(hunter) return StatusPercentageText(hunter, "Block"); end, ["ParryPercentage"] = function(hunter) return StatusPercentageText(hunter, "Parry"); end, ["PoisonPercentage"] = function(hunter) return StatusPercentageText(hunter, "Poison"); end, ["ParalysePercentage"] = function(hunter) return StatusPercentageText(hunter, "Paralyse"); end, ["SleepPercentage"] = function(hunter) return StatusPercentageText(hunter, "Sleep"); end, ["BlastPercentage"] = function(hunter) return StatusPercentageText(hunter, "Blast"); end, ["StaminaPercentage"] = function(hunter) return StatusPercentageText(hunter, "Stamina"); end, } local ColumnCanvasRect = { Enable = true, OffsetX = 0, OffsetY = 0, Width = 1000, Height = 1000, Absolute = true, -- to hide relative position menu options UseBackground = false, } local ColumnWidthCache = {} local rootCanvas = Draw.NewDivCanvas({ Enable = true, OffsetX = 0, OffsetY = 0, Width = 1000, Height = 1000, Absolute = true, UseBackground = false, }) local columnCanvas = Draw.NewDivCanvas({ Enable = true, OffsetX = 0, OffsetY = 0, Width = 1000, Height = 1000, Absolute = true, UseBackground = false, }) local lineCanvas = Draw.NewDivCanvas({ Enable = true, OffsetX = 0, OffsetY = 0, Width = 1000, Height = 1000, Absolute = true, -- to hide relative position menu options UseBackground = false, }) local bgCanvas = Draw.NewDivCanvas({ Enable = true, OffsetX = 0, OffsetY = 0, Width = 1000, Height = 1000, UseBackground = false, }) local DPSLineRect = { } local DPSLineRowRect = { } local function DrawStatTable() if OverlayData.QuestStats.ElapsedTime <= 0 then return end local cfg = Config if not cfg.Enable then return end local x = cfg.PosX local y = cfg.PosY local data = OverlayData.HunterInfo local rows = 0 if data then rows = Core.GetTableSize(data) end local QuestHeight = 0 if cfg.QuestInfo.Enable then QuestHeight = cfg.QuestInfo.Height if cfg.QuestInfo.AutoHeight then local _, h = Draw.LoadD2dFontWithConfig(cfg.QuestInfo.FontConfig):measure("高度") QuestHeight = h end end local TableHeaderHeight = 0 local TableRowsHeight = 0 if cfg.Header.Enable then TableHeaderHeight = cfg.Header.Height end if cfg.Row.Enable then TableRowsHeight = rows * cfg.Row.Height end local TableHeight = TableHeaderHeight + TableRowsHeight local HEIGHT = QuestHeight + TableHeight local WIDTH = 0 local columns = mod.Runtime.StatTableColumns --------------------------------- -- Width and Height calculation --------------------------------- for i, columnCfg in pairs(columns) do if not columnCfg.Enable then goto continue end local width = columnCfg.Width if width <= 0 then width = cfg.Column.Width end if cfg.Column.AutoColumnWidth or columnCfg.AutoColumnWidth or cfg.Column.AutoHideZeroStatusColumns or columnCfg.AutoHideZeroStatusColumns then local longestMsg = "" local shortestMsg = "" local maxLen = 0 local minLen = 999 for hunter, data in pairs(OverlayData.HunterInfo) do local msg = "UNDEF" if SupportedColumnFunc[columnCfg.ColumnKey] then msg = SupportedColumnFunc[columnCfg.ColumnKey](hunter) end local len = string.len(msg) if len > maxLen then maxLen = len longestMsg = msg end if len < minLen then minLen = len shortestMsg = msg end end if maxLen <= 0 then width = 0 ColumnWidthCache[columnCfg.ColumnKey] = width goto continue end -- 有数据,或者不自动隐藏时,与header长度进行对比 if (cfg.Column.AutoHideZeroStatusColumns or columnCfg.AutoHideZeroStatusColumns) and (longestMsg == "" or (not (longestMsg ~= "0" and shortestMsg ~= "100.0%" and maxLen > 0))) then width = 0 else if cfg.Column.AutoColumnWidth or columnCfg.AutoColumnWidth then local font = Draw.LoadD2dFontWithConfig(columnCfg.FontConfig) width = font:measure(". " .. longestMsg) local headerWidth = font:measure(". " .. columnCfg.Name) if headerWidth > width then width = headerWidth end else width = columnCfg.Width if width <= 0 then width = cfg.Column.Width end end end end WIDTH = WIDTH + width ColumnWidthCache[columnCfg.ColumnKey] = width ::continue:: end bgCanvas.RePos(x, y) bgCanvas.ReSize(WIDTH, HEIGHT) bgCanvas.Debug(mod.Config.Debug) bgCanvas.Init() --------------------------------- ---- Quest Info --------------------------------- if cfg.QuestInfo.Enable then local posX = x local posY = y local width = WIDTH local height = QuestHeight local msg = "" local limit = math.floor(OverlayData.QuestStats.LimitTime) if cfg.QuestInfo.QuestTime and limit >= 0 then local elapsed = OverlayData.QuestStats.ElapsedTime local elapsedMin = math.floor(elapsed / 60.0) local elapsedSecs = elapsed % 60.0 local timeMsg = string.format("%02d:%02.0f/%d:00", elapsedMin, elapsedSecs, limit) msg = msg .. timeMsg end if cfg.QuestInfo.QuestTargets then if msg ~= "" then msg = msg .. " - " end for i, ctx in pairs(OverlayData.QuestStats.EnemyContexts) do if i > 1 then msg = msg .. ", " end msg = msg .. Core.GetEnemyName(ctx:get_EmID()) end end if cfg.QuestInfo.Background.Enable and cfg.QuestInfo.Background.UseBackground then if WIDTH <= 0 then local width = Draw.LoadD2dFontWithConfig(cfg.QuestInfo.FontConfig):measure(msg) bgCanvas.ReSize(width) end cfg.QuestInfo.Background.Height = QuestHeight bgCanvas.Rect(cfg.QuestInfo.Background) else local nx, ny = bgCanvas.NextPos() bgCanvas.RePos(nx, ny+QuestHeight) end if msg ~= "" then Draw.SmartText(posX, posY, width, height, cfg.QuestInfo.FontConfig, msg) end end --------------------------------- -- DPS Line Pre-Calculation -- calculation max data of dps line --------------------------------- local allHunterTotal = 0 local allHunterHighest = 0 local hunterSort = {} if cfg.Row.Enable or cfg.DPSLine.Enable then -- 计算总和 for hunter, data in pairs(OverlayData.HunterInfo) do local total = 0 for k, conf in pairs(cfg.DPSLine.FillKeys) do local val = OverlayData.HunterDamageRecords[hunter][conf.Key] if not val then goto continue end allHunterTotal = allHunterTotal + val total = total + val ::continue:: end if total > allHunterHighest then allHunterHighest = total end table.insert(hunterSort, { Hunter = hunter, Value = total, }) end if cfg.Row.Sort then table.sort(hunterSort, function(l, r) return l.Value > r.Value end) end end --------------------------------- -- Column Header -- Draw columns first to calculate auto width --------------------------------- if cfg.Column.Enable then rootCanvas.RePos(x, y + QuestHeight) rootCanvas.Debug(mod.Config.Debug) rootCanvas.Init() local lastOffsetX = ColumnCanvasRect.OffsetX local headerH = 0 if cfg.Header.Enable then headerH = cfg.Header.Height if cfg.Header.AutoHeight then _, headerH = Draw.LoadD2dFontWithConfig(cfg.Header.FontConfig):measure("H高") end if cfg.Header.Background.Enable and cfg.Header.Background.UseBackground then cfg.Header.Background.Height = headerH bgCanvas.Rect(cfg.Header.Background) else local nx, ny = bgCanvas.NextPos() bgCanvas.RePos(nx, ny+headerH) end end local rowH = cfg.Row.Height if cfg.Row.AutoHeight then _, rowH = Draw.LoadD2dFontWithConfig(cfg.Row.FontConfig):measure("H") end if cfg.Row.Enable and #hunterSort >0 then if cfg.Row.Background.Enable and cfg.Row.Background.UseBackground then cfg.Row.Background.Height = rowH* #hunterSort bgCanvas.Rect(cfg.Row.Background) else local nx, ny = bgCanvas.NextPos() bgCanvas.RePos(nx, ny+ rowH* #hunterSort) end end -- 渲染 DPS Line if cfg.DPSLine.Enable and allHunterTotal>0 then if cfg.DPSLine.AutoWidth then DPSLineRect.Width = WIDTH else DPSLineRect.Width = cfg.DPSLine.Width end if cfg.DPSLine.AutoHeight then DPSLineRect.Height = rowH else DPSLineRect.Height = cfg.DPSLine.Height end lineCanvas.ReSize(DPSLineRect.Width) DPSLineRowRect.Height = DPSLineRect.Height local dpsLineIndex = 0 local posX = x+cfg.DPSLine.OffsetX local posY = y + QuestHeight + headerH for i = 1, #hunterSort, 1 do local hunter = hunterSort[i].Hunter lineCanvas.RePos(posX, posY + dpsLineIndex * (rowH+cfg.DPSLine.OffsetY)) lineCanvas.Debug(mod.Config.Debug) lineCanvas.Init() local hitIndex = OverlayData.HunterInfo[hunter].HitIndex local hitIndexStr = tostring(hitIndex) local hasVal = false for k, conf in pairs(cfg.DPSLine.FillKeys) do local val = OverlayData.HunterDamageRecords[hunter][conf.Key] if not val or val <= 0 then goto continue end hasVal = true local ratio = 1 if cfg.DPSLine.WidthAffectedByRatio then if cfg.DPSLine.DPSHighestFull then ratio = val / allHunterHighest else ratio = val / allHunterTotal end else ratio = val / hunterSort[i].Value end DPSLineRowRect.Width = DPSLineRect.Width*ratio if hitIndex then DPSLineRowRect.BackgroundColor = conf.Colors[hitIndexStr] if not DPSLineRowRect.BackgroundColor then if not mod.Runtime.Colors[hitIndex] then mod.Runtime.Colors[hitIndex] = Draw.RandomColor(0x44) end DPSLineRowRect.BackgroundColor = mod.Runtime.Colors[hitIndex] -- Draw.SetAlpha(0x44, DPSLineRowRect.BackgroundColor) end else DPSLineRowRect.BackgroundColor = conf.Colors.Others end if mod.Config.Debug then lineCanvas.Text({ BlockReserveX = true, }, string.format("%0.1f/%0.0f", ratio, WIDTH)) end lineCanvas.Rect(DPSLineRowRect) ::continue:: end lineCanvas.End() if hasVal then dpsLineIndex = dpsLineIndex + 1 end end end columnCanvas.RePos(x, y + QuestHeight) columnCanvas.Debug(mod.Config.Debug) for i, columnCfg in pairs(columns) do if not columnCfg.Enable then goto continue end local width = ColumnWidthCache[columnCfg.ColumnKey] if not width or width <= 0 then goto continue end columnCanvas.RePos(x+lastOffsetX, y+QuestHeight+ColumnCanvasRect.OffsetY) columnCanvas.Init() -- 渲染表头 Header if cfg.Header.Enable then columnCfg.Header.Width = width columnCfg.Header.Height = headerH columnCanvas.Rect(columnCfg.Header) cfg.Header.FontConfig.Width = width cfg.Header.FontConfig.Height = headerH columnCanvas.Text(cfg.Header.FontConfig, columnCfg.Name) end -- 渲染具体数据 for i = 1, #hunterSort, 1 do local hunter = hunterSort[i].Hunter columnCfg.Row.Width = width columnCfg.Row.Height = rowH columnCanvas.Rect(columnCfg.Row) local msg = "-" if SupportedColumnFunc[columnCfg.ColumnKey] then msg = SupportedColumnFunc[columnCfg.ColumnKey](hunter) else msg = "UNDEF" end cfg.Row.FontConfig.Width = width cfg.Row.FontConfig.Height = rowH columnCanvas.Text(cfg.Row.FontConfig, msg) end lastOffsetX = lastOffsetX + width ::continue:: end columnCanvas.End() end bgCanvas.End() end _M.DebugRun = function () local cfg = Config for hunter, data in pairs(OverlayData.HunterInfo) do for k, column in pairs(cfg.Column.Columns) do if not column or not column.Enable then goto continue end local msg = "-" if SupportedColumnFunc[k] then msg = SupportedColumnFunc[k](hunter) else msg = "NO " .. k .. " handler" end imgui.text(msg) ::continue:: end end end _M.D2dRender = function () DrawStatTable() end -- mod.OnDebugFrame(function () -- local cfg = Config -- for hunter, data in pairs(OverlayData.HunterInfo) do -- for k, column in pairs(cfg.Column.Columns) do -- if not column or not column.Enable then -- goto continue -- end -- local msg = "-" -- if SupportedColumnFunc[k] then -- msg = SupportedColumnFunc[k](hunter) -- else -- msg = "NO " .. k .. " handler" -- end -- imgui.text(msg) -- ::continue:: -- end -- end -- end) function _M.ClearData() end mod.D2dRegister(function () DrawStatTable() end, "StatTable") return _M