local re = re local sdk = sdk local d2d = d2d local imgui = imgui local log = log local json = json local draw = draw local Vector3f = Vector3f 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 OverlayData = require("mhwilds_overlay.data") local Data = require("mhwilds_overlay.status.data") local Config = require("mhwilds_overlay.status.conf") local _M = {} local CanvasRect = { Enable = true, OffsetX = 0, OffsetY = 100, Width = 1000, Height = 200, Absolute = true, UseBackground = false, } local MergedBuffCanvas = Draw.NewDivCanvas(CanvasRect) local SingleBuffCanvas = Draw.NewDivCanvas({ Enable = true, OffsetX = 0, OffsetY = 100, Width = 100, Height = 200, Absolute = true, UseBackground = false, }) local SkillBuffCanvas = Draw.NewDivCanvas(CanvasRect) local MusicSkillBuffCanvas = Draw.NewDivCanvas(CanvasRect) local OtomoSkillBuffCanvas = Draw.NewDivCanvas(CanvasRect) local ItemBuffCanvas = Draw.NewDivCanvas(CanvasRect) local ASkillBuffCanvas = Draw.NewDivCanvas(CanvasRect) local WeaponBuffCanvas = Draw.NewDivCanvas(CanvasRect) local PlayerStaminaCanvas = Draw.NewDivCanvas(CanvasRect) ---@param canvas DivCanvas ---@param text FontConfig ---@param ring CircleConfig ---@param data BuffStatus ---@param conf OverlayStatusSkillStatusConfig ---@param groupConf OverlayStatusGroupConfig function _M.DrawBuff(canvas, text, ring, data, conf, groupConf) if data.Timer <= 0 then return nil end -- if data.Activated ~= true then -- return nil -- end local root = Div.new() root.height = "auto" root.width = 140 if groupConf.ShowName then local nameDiv = Div.new() nameDiv.renderer = Draw.TextRenderer(text, tostring(data.Name)) root:add(nameDiv) end local ratio = 0 if data.Timer > 0 then ratio = data.Timer/data.MaxTimer end local ringDiv = Div.new() ringDiv.renderer = Draw.CircleRenderer(ring, ratio) root:add(ringDiv) if groupConf.ShowTime then local timerDiv = Div.new() timerDiv.renderer = Draw.TextRenderer(text, Core.GetTimeString(data.Timer)) root:add(timerDiv) end -- local activated = Div.new() -- activated.renderer = Draw.TextRenderer(text, tostring(data.Activated)) -- root:add(activated) return root end function _M.DrawBuffs() local WidgetConf = Config.BuffWidgetConfig if not WidgetConf.Enable then return end local skillCanvas, musicCanvas, otomoCanvas, itemCanvas, mantleCanvas, wpCanvas local skillDiv, musicDiv, otomoDiv, itemDiv, mantleDiv, wpDiv if WidgetConf.MergeAllBuffs then MergedBuffCanvas.Debug(mod.Config.Debug) MergedBuffCanvas.Init() MergedBuffCanvas.RePos(WidgetConf.OffsetX, WidgetConf.OffsetY) local mergedDiv = Div.new() mergedDiv.position.x = MergedBuffCanvas.PosX mergedDiv.position.y = MergedBuffCanvas.PosY mergedDiv.width = 1000 mergedDiv.height = 200 mergedDiv.display = "inline" skillCanvas, musicCanvas, otomoCanvas, itemCanvas, mantleCanvas, wpCanvas = MergedBuffCanvas, MergedBuffCanvas, MergedBuffCanvas, MergedBuffCanvas, MergedBuffCanvas, MergedBuffCanvas skillDiv, musicDiv, otomoDiv, itemDiv, mantleDiv, wpDiv = mergedDiv, mergedDiv, mergedDiv, mergedDiv, mergedDiv, mergedDiv else SkillBuffCanvas.Debug(mod.Config.Debug) SkillBuffCanvas.Init() skillCanvas = SkillBuffCanvas skillDiv = Div.new() skillDiv.position.x = skillCanvas.PosX skillDiv.position.y = skillCanvas.PosY skillDiv.width = 1000 skillDiv.height = 200 skillDiv.display = "inline" MusicSkillBuffCanvas.Debug(mod.Config.Debug) MusicSkillBuffCanvas.Init() musicCanvas = MusicSkillBuffCanvas musicDiv = Div.new() musicDiv.position.x = musicCanvas.PosX musicDiv.position.y = musicCanvas.PosY musicDiv.width = 1000 musicDiv.height = 200 musicDiv.display = "inline" OtomoSkillBuffCanvas.Debug(mod.Config.Debug) OtomoSkillBuffCanvas.Init() otomoCanvas = OtomoSkillBuffCanvas otomoDiv = Div.new() otomoDiv.position.x = otomoCanvas.PosX otomoDiv.position.y = otomoCanvas.PosY otomoDiv.width = 1000 otomoDiv.height = 200 otomoDiv.display = "inline" ItemBuffCanvas.Debug(mod.Config.Debug) ItemBuffCanvas.Init() itemCanvas = ItemBuffCanvas itemDiv = Div.new() itemDiv.position.x = itemCanvas.PosX itemDiv.position.y = itemCanvas.PosY itemDiv.width = 1000 itemDiv.height = 200 itemDiv.display = "inline" ASkillBuffCanvas.Debug(mod.Config.Debug) ASkillBuffCanvas.Init() mantleCanvas = ASkillBuffCanvas mantleDiv = Div.new() mantleDiv.position.x = mantleCanvas.PosX mantleDiv.position.y = mantleCanvas.PosY mantleDiv.width = 1000 mantleDiv.height = 200 mantleDiv.display = "inline" WeaponBuffCanvas.Debug(mod.Config.Debug) WeaponBuffCanvas.Init() wpCanvas = WeaponBuffCanvas wpDiv = Div.new() wpDiv.position.x = wpCanvas.PosX wpDiv.position.y = wpCanvas.PosY wpDiv.width = 1000 wpDiv.height = 200 wpDiv.display = "inline" end for key, data in pairs(Data.SkillData) do -- if not data.Name then -- Data.SkillData[key].Name = Core.GetSkillName(key) -- end local conf = Config.GetOrInitSkillConfig(key, data.Name) if conf.Enable then local div = _M.DrawBuff(skillCanvas, conf.Text, conf.Circle, data, conf, Config.BuffWidgetConfig.SkillConfig) if div then skillDiv:add(div) end end end for key, data in pairs(Data.MuiscSkillData) do -- if not data.Name then -- Data.MuiscSkillData[key].Name = Core.GetMusicSkillName(key) -- end local conf = Config.GetOrInitMusicSkillConfig(key, data.Name) if conf.Enable then local div = _M.DrawBuff(musicCanvas, conf.Text, conf.Circle, data, conf, Config.BuffWidgetConfig.MusicSkillConfig) if div then musicDiv:add(div) end end end for key, data in pairs(Data.OtomoSkillData) do local conf = Config.GetOrInitOtomoSkillConfig(key, data.Name) if conf.Enable then local div = _M.DrawBuff(otomoCanvas, conf.Text, conf.Circle, data, conf, Config.BuffWidgetConfig.OtomoSkillConfig) if div then otomoDiv:add(div) end end end for key, data in pairs(Data.ItemBuffData) do -- if not data.Name then -- Data.ItemBuffData[key].Name = key -- end local conf = Config.GetOrInitItemConfig(key, data.Name) if conf.Enable then local div = _M.DrawBuff(itemCanvas, conf.Text, conf.Circle, data, conf, Config.BuffWidgetConfig.ItemBuffConfig) if div then itemDiv:add(div) end end end for key, data in pairs(Data.ASkillData) do -- if not data.Name then -- Data.ASkillData[key].Name = Core.GetASkillName(key) -- end local conf = Config.GetOrInitASkillConfig(key, data.Name) if conf.Enable then local div = _M.DrawBuff(mantleCanvas, conf.Text, conf.Circle, data, conf, Config.BuffWidgetConfig.ASkillConfig) if div then mantleDiv:add(div) end end end for key, data in pairs(Data.WeaponBuffData) do local conf = Config.GetOrInitWeaponConfig(key, data.Name) if conf.Enable then local div = _M.DrawBuff(wpCanvas, conf.Text, conf.Circle, data, conf, Config.BuffWidgetConfig.WeaponConfig) if div then wpDiv:add(div) end end end local debug = mod.Config.Debug if WidgetConf.MergeAllBuffs then MergedBuffCanvas.End() itemDiv:render(itemCanvas.PosX, itemCanvas.PosY, itemCanvas.Width, itemCanvas.Height, debug) else SkillBuffCanvas.End() MusicSkillBuffCanvas.End() OtomoSkillBuffCanvas.End() ItemBuffCanvas.End() ASkillBuffCanvas.End() skillDiv:render(skillCanvas.PosX, skillCanvas.PosY, skillCanvas.Width, skillCanvas.Height, debug) musicDiv:render(musicCanvas.PosX, musicCanvas.PosY, musicCanvas.Width, musicCanvas.Height, debug) otomoDiv:render(otomoCanvas.PosX, otomoCanvas.PosY, otomoCanvas.Width, otomoCanvas.Height, debug) itemDiv:render(itemCanvas.PosX, itemCanvas.PosY, itemCanvas.Width, itemCanvas.Height, debug) mantleDiv:render(mantleCanvas.PosX, mantleCanvas.PosY, mantleCanvas.Width, mantleCanvas.Height, debug) wpDiv:render(wpCanvas.PosX, wpCanvas.PosY, wpCanvas.Width, wpCanvas.Height, debug) end end local function NewStaminaCache() return { LastNonFillTime = 0, peakRatio = 0, lastRatio = 1, lastHitTimestamp = -1, redRatio = 0, initialRedRatio = 0, -- 消逝过程中,初始的 red ratio fadeStartTimestampe = nil, -- 消逝起始时间 currentSegIndex = nil, } end local StaminaCache = NewStaminaCache() function _M.ClearCache() StaminaCache = NewStaminaCache() end ---@param conf OverlayStatusHunterStaminaConfig local function UpdateUsedStamina(current, max, segIndex, conf) if not conf.ShowUsedStamina then return end local ratio = current / max -- 百分比变多了 if ratio > StaminaCache.lastRatio and StaminaCache.redRatio > 0 then -- StaminaCache.redRatio = 1 - ratio -- StaminaCache.initialRedRatio = StaminaCache.redRatio local delta = (ratio-StaminaCache.lastRatio) StaminaCache.initialRedRatio = StaminaCache.initialRedRatio - delta StaminaCache.redRatio = StaminaCache.redRatio - delta end -- if ratio > StaminaCache.peakRatio then -- StaminaCache.peakRatio = ratio -- end local simTime = OverlayData.SimulatedTime if conf.Segmented then if StaminaCache.currentSegIndex == nil then StaminaCache.currentSegIndex = segIndex StaminaCache.lastRatio = ratio end if segIndex ~= StaminaCache.currentSegIndex then if segIndex < StaminaCache.currentSegIndex then -- 消耗 StaminaCache.redRatio = 1 - ratio StaminaCache.initialRedRatio = StaminaCache.redRatio StaminaCache.lastHitTimestamp = simTime else -- 恢复 StaminaCache.redRatio = 0 StaminaCache.initialRedRatio = 0 StaminaCache.lastRatio = ratio end StaminaCache.currentSegIndex = segIndex end end StaminaCache.lastTime = simTime if ratio < StaminaCache.lastRatio then -- if StaminaCache.fadeStartTimestampe == nil then StaminaCache.redRatio = StaminaCache.lastRatio - ratio + StaminaCache.redRatio -- else -- -- 衰减中,则直接清空 -- StaminaCache.redRatio = StaminaCache.lastRatio - ratio -- end StaminaCache.initialRedRatio = StaminaCache.redRatio StaminaCache.lastHitTimestamp = simTime StaminaCache.fadeStartTimestampe = nil StaminaCache.fadeStartStamina = nil -- if StaminaCache.fadeStartTimestampe == nil then -- StaminaCache.fadeStartStamina = nil -- end -- if StaminaCache.lastRatio > StaminaCache.peakRatio then -- StaminaCache.peakRatio = StaminaCache.lastRatio -- end elseif StaminaCache.initialRedRatio > 0 and (simTime - StaminaCache.lastHitTimestamp > conf.UsedStaminaDelay or (current <= 0 and simTime - StaminaCache.lastHitTimestamp > conf.UsedStaminaDelay)) then if StaminaCache.fadeStartTimestampe == nil then StaminaCache.fadeStartTimestampe = simTime StaminaCache.fadeStartStamina = ratio StaminaCache.initialRedRatio = StaminaCache.redRatio else local fadeTime = conf.UsedStaminaFadeTime local elapsed = math.min(simTime - StaminaCache.fadeStartTimestampe, fadeTime) local progress = math.min(elapsed / fadeTime, 1) -- Ease out StaminaCache.redRatio = StaminaCache.initialRedRatio * (1 - progress)^2 -- StaminaCache.redRatio = math.max(StaminaCache.redRatio+StaminaCache.fadeStartStamina-ratio, 0) local theoryRed = StaminaCache.redRatio + StaminaCache.fadeStartStamina local effectiveRed = theoryRed - ratio if effectiveRed < 0 then effectiveRed = 0 end StaminaCache.redRatio = math.max(math.min(StaminaCache.redRatio, effectiveRed), 0) if StaminaCache.redRatio <= 0 then StaminaCache.redRatio = 0 StaminaCache.initialRedRatio = 0 StaminaCache.fadeStartTimestampe = nil StaminaCache.fadeStartStamina = nil end end end -- peakRatio 模式如果不增加 lastRatio check,会导致跑步没有白条,但是可以让每次翻滚都生成新的白条 -- if ratio + StaminaCache.redRatio > StaminaCache.peakRatio then -- StaminaCache.redRatio = StaminaCache.peakRatio - ratio -- end StaminaCache.lastRatio = ratio end mod.OnDebugFrame(function () imgui.text("Hunter Stats") if Data.HunterData.HealthData then local data = Data.HunterData.HealthData imgui.text(string.format("HP: %s/%s, Red: %s, Heal: %s", tostring(data.Health), tostring(data.MaxHealth), tostring(data.RedHealth), tostring(data.Heal))) end if Data.HunterData.StaminaData then local data = Data.HunterData.StaminaData imgui.text(string.format("SP: %s/%s, Limit: %s", tostring(data.Stamina), tostring(data.MaxStamina), tostring(data.StaminaLimit))) imgui.text(string.format("SP: %s%% (Used)", tostring(StaminaCache.redRatio*100))) imgui.text(string.format("SP: %s/%s (Tough)", tostring(data.StaminaTough), tostring(data.MaxStaminaTough))) end end) ---@return CircleConfig local OuterStaminaRing = { Enable = true, Absolute = true, IsFill = true, IsRing = true, Radius = 44, RingWidth = 24, Color = Draw.ReverseRGB(0xFF65D84D), -- Draw.ReverseRGB(0xFF76B295), -- Draw.ReverseRGB(0xFF8EB116), BackgroundRatio = 1, UseBackground = true, BackgroundColor = Draw.ReverseRGB(0x90000000), RingUseCircleBackground = false, RingAutoCircleBackgroundRadius = true, IsFillOutline = true, OutlineThickness = 0, OutlineColor = Draw.ReverseRGB(0xFF76B295), Clockwise = false, } ---@param conf OverlayStatusHunterStaminaConfig function _M.DrawStamina(x, y, data, ratio, conf) if not conf.StaminaCircle.Enable then return end local perSegValue = conf.SegmentValue local maxSegIndex = math.ceil(data.MaxStamina/conf.SegmentValue) if not conf.Segmented then perSegValue = data.MaxStamina maxSegIndex = 1 end local currentSegIndex = math.ceil(data.Stamina / perSegValue) if currentSegIndex == 0 then currentSegIndex = 1 end local currentSegMax = perSegValue if currentSegIndex == maxSegIndex then currentSegMax = data.MaxStamina - (maxSegIndex - 1) * perSegValue end local currentSegCurrent = data.Stamina - (currentSegIndex-1)*perSegValue if conf.ShowUsedStamina then local ExpectedMax = currentSegMax if conf.Segmented then ExpectedMax = conf.SegmentValue end UpdateUsedStamina(currentSegCurrent, ExpectedMax, currentSegIndex, conf) end local wasColor = conf.StaminaCircle.Color local color = wasColor if ratio <= conf.ThresholdRatio or data.Stamina <= conf.ThresholdValue then color = conf.ThresholdColor elseif conf.EnableColorTransition then local gradientRatio = 1-(ratio-conf.ThresholdRatio)/(1-conf.ThresholdRatio) color = Draw.LinearGradientColor(wasColor, conf.TransitionToColor, gradientRatio) end PlayerStaminaCanvas.Debug(mod.Config.Debug) PlayerStaminaCanvas.Init() local CenterR = conf.StaminaCircle.Radius for i = 1, maxSegIndex, 1 do local current, max local ratio if i == currentSegIndex then current = currentSegCurrent max = currentSegMax ratio = current/max elseif i > currentSegIndex then current = 0 ratio = 0 if currentSegIndex == 1 then max = data.MaxStamina - (maxSegIndex - 1) * perSegValue else max = perSegValue end else current = perSegValue max = perSegValue ratio = 1 end local MaxRatio = 1 if i == maxSegIndex or i == 1 then local ExpectedMax = 100 if conf.Segmented then ExpectedMax = conf.SegmentValue end -- i == maxSegIndex: 体力小于100,太饿了的时候 -- i == 1: 体力不足以填满新的槽 MaxRatio = math.min(1, max / ExpectedMax) if MaxRatio < 1 then ratio = current / ExpectedMax end end if i == 1 then -- 绘制最内圈 conf.StaminaCircle.BackgroundRatio = MaxRatio conf.StaminaCircle.Color = color PlayerStaminaCanvas.RePos(x, y) if not conf.ShowUsedStamina or ratio == 0 or ratio == 1 then PlayerStaminaCanvas.Circle(conf.StaminaCircle, ratio) else PlayerStaminaCanvas.Circle(conf.StaminaCircle, ratio, StaminaCache.redRatio, conf.UsedStaminaColor) end conf.StaminaCircle.Color = wasColor conf.StaminaCircle.BackgroundRatio = 1 else -- 绘制任何外圈 OuterStaminaRing.Enable = conf.StaminaCircle.Enable OuterStaminaRing.BackgroundRatio = MaxRatio OuterStaminaRing.Color = color OuterStaminaRing.RingWidth = conf.SegmentRingWidth OuterStaminaRing.Radius = conf.StaminaCircle.Radius + (conf.SegmentRingWidth + conf.SegmentRingMargin)*(i-1) local dr = OuterStaminaRing.Radius - CenterR PlayerStaminaCanvas.RePos(x-dr, y-dr) if not conf.ShowUsedStamina or ratio == 0 or ratio == 1 then PlayerStaminaCanvas.Circle(OuterStaminaRing, ratio) else PlayerStaminaCanvas.Circle(OuterStaminaRing, ratio, StaminaCache.redRatio, conf.UsedStaminaColor) end OuterStaminaRing.BackgroundRatio = 1 end end PlayerStaminaCanvas.End() end ---@param conf OverlayStatusHunterStaminaConfig function _M.DrawInsectStamina(x, y, data, ratio, conf) local wasColor = conf.InsectStaminaCircle.Color local color = wasColor if ratio <= conf.InsectThresholdRatio or data.Stamina <= conf.InsectThresholdValue then color = conf.InsectThresholdColor elseif conf.InsectEnableColorTransition then local gradientRatio = 1-(ratio-conf.InsectThresholdRatio)/(1-conf.InsectThresholdRatio) color = Draw.LinearGradientColor(wasColor, conf.InsectTransitionToColor, gradientRatio) end PlayerStaminaCanvas.Debug(mod.Config.Debug) PlayerStaminaCanvas.Init() local CenterR = conf.InsectStaminaCircle.Radius -- 绘制最内圈 conf.InsectStaminaCircle.BackgroundRatio = ratio conf.InsectStaminaCircle.Color = color PlayerStaminaCanvas.RePos(x, y) PlayerStaminaCanvas.Circle(conf.InsectStaminaCircle, ratio) conf.InsectStaminaCircle.Color = wasColor conf.InsectStaminaCircle.BackgroundRatio = 1 PlayerStaminaCanvas.End() end -- mod.OnFrame(function () -- if Data.HunterData.WeaponType == CONST.WeaponType.InsectGlaive then -- local data = Data.HunterData.InsectStaminaData -- imgui.text(string.format("%s/%s - %s", tostring(data.Stamina), tostring(data.MaxStamina), tostring(data.Ratio))) -- end -- end) local Smoother = require("_CatLib.smoother").new(0.1) function _M.DrawPlayer() local pos = Data.HunterData.Pos local data = Data.HunterData.StaminaData if pos and data and data.Stamina and Config.HunterConfig.Stamina.Enable then local conf = Config.HunterConfig.Stamina local ratio = 0 ratio = data.Stamina/data.MaxStamina -- 200 local autoHideTimeout = conf.AutoHide and OverlayData.SimulatedTime - StaminaCache.LastNonFillTime > conf.DisappearDelay local isIG = Data.HunterData.WeaponType == CONST.WeaponType.InsectGlaive if autoHideTimeout and ratio >= 1 and not isIG then -- disappear else if ratio < 1 then StaminaCache.LastNonFillTime = OverlayData.SimulatedTime end local x, y if conf.RelativePosition then local uix = pos.x + conf.WorldOffsetX local uiy = pos.y + conf.WorldOffsetY local uiz = pos.z + conf.WorldOffsetZ local uiPos = Vector3f.new(uix, uiy, uiz) local screenPos = draw.world_to_screen(uiPos) if screenPos then x = screenPos.x + conf.RelativeOffsetX y = screenPos.y + conf.RelativeOffsetY x, y = Smoother:update(x, y) end else x, y = conf.FixedOffsetX, conf.FixedOffsetY end if x and y then if autoHideTimeout and ratio >= 1 then -- disappear else _M.DrawStamina(x, y, data, ratio, conf) end if isIG then local data = Data.HunterData.InsectStaminaData local ratio = data.Ratio local offset = 0 if conf.InsectStaminaCircleCenterPlayer then offset = conf.StaminaCircle.Radius - conf.InsectStaminaCircle.Radius end if autoHideTimeout and ratio >= 1 then -- disappear else _M.DrawInsectStamina(x+offset, y+offset, data, ratio, conf) end end end end end end function _M.DrawAll() if Core.IsLoading() then return end _M.DrawPlayer() _M.DrawBuffs() end local SkillToGroupSkillFunc = Core.TypeMethod("app.HunterSkillDef", "convertSkillToGroupSkill(app.HunterDef.Skill)") local function DebugShowBuff(key,data) if data.Level then imgui.text(string.format("%s Lv%d(%s): %0.1f/%0.1f", tostring(data.Name), data.Level, tostring(key), data.Timer, data.MaxTimer)) else imgui.text(string.format("%s(%s): %0.1f/%0.1f", tostring(data.Name), tostring(key), data.Timer, data.MaxTimer)) end end mod.OnDebugFrame(function () if true then return end imgui.push_font(Core.LoadImguiCJKFont()) -- for i = 1, 209, 1 do -- local groupSkill = SkillToGroupSkillFunc:call(nil, i) -- local name = Core.GetSkillName(i) -- imgui.text(string.format("%d: %s -> %d: %s", i, name, groupSkill, Core.GetSkillName(groupSkill))) -- imgui.text(string.format("%d: %s, %s, %s", i, Core.GetSkillName(i, 1), Core.GetSkillName(i, 2), Core.GetSkillName(i, 3))) -- end for key, data in pairs(Data.SkillData) do if not data.Name then Data.SkillData[key].Name = Core.GetSkillName(key, data.Level) end DebugShowBuff(key,data) end for key, data in pairs(Data.MuiscSkillData) do if not data.Name then Data.MuiscSkillData[key].Name = Core.GetMusicSkillName(key) end DebugShowBuff(key,data) end for key, data in pairs(Data.OtomoSkillData) do DebugShowBuff(key,data) end for key, data in pairs(Data.ItemBuffData) do if not data.Name then Data.ItemBuffData[key].Name = key end DebugShowBuff(key,data) end for key, data in pairs(Data.ASkillData) do if not data.Name then Data.ASkillData[key].Name = Core.GetASkillName(key) end DebugShowBuff(key,data) end for key, data in pairs(Data.WeaponBuffData) do DebugShowBuff(key,data) end end) mod.D2dRegister(function () end, function () if not Config.Enable then return end mod.RecordCost("PlayerStauts", function () _M.DrawAll() end) end, "PlayerStatus") return _M