You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
482 lines
19 KiB
482 lines
19 KiB
--- ============================ HEADER ============================
|
|
--- ======= LOCALIZE =======
|
|
-- Addon
|
|
local addonName, HR = ...;
|
|
-- HeroLib
|
|
local HL = HeroLib;
|
|
local Cache = HeroCache;
|
|
local Unit = HL.Unit;
|
|
local Player = Unit.Player;
|
|
local Target = Unit.Target;
|
|
local Spell = HL.Spell;
|
|
local Item = HL.Item;
|
|
local GUI = HL.GUI;
|
|
local CreatePanelOption = GUI.CreatePanelOption;
|
|
|
|
-- Lua
|
|
local mathmax = math.max;
|
|
local mathmin = math.min;
|
|
local pairs = pairs;
|
|
local select = select;
|
|
-- File Locals
|
|
local Masque; -- , MasqueGroupLeft;
|
|
local MasqueGroups = {};
|
|
local UIFrames;
|
|
local MasqueFrameList;
|
|
local PrevResult, CurrResult;
|
|
|
|
local function ModMasque(Frame, Disabled)
|
|
if Disabled then
|
|
Frame.__MSQ_Normal:Hide();
|
|
Frame.Texture:SetAllPoints( Frame );
|
|
if Frame.CooldownFrame then
|
|
Frame.CooldownFrame:SetAllPoints( Frame );
|
|
end
|
|
if Frame.Backdrop then
|
|
Frame.Backdrop:Show();
|
|
Frame.Backdrop:SetFrameLevel(mathmin(Frame.Backdrop:GetFrameLevel(), 7));
|
|
end
|
|
else
|
|
if Frame.Backdrop then
|
|
Frame.Backdrop:Hide();
|
|
end
|
|
end
|
|
end
|
|
local function MasqueUpdate( Addon, Group, SkinID, Backdrop, Shadow, Gloss, Colors, Disabled )
|
|
if Addon==HR and MasqueGroups and MasqueFrameList then
|
|
local k = MasqueFrameList[Group];
|
|
if k then
|
|
if type(k.Icon) == "table" then
|
|
for _, tblIcon in pairs(k.Icon) do
|
|
ModMasque(tblIcon, Disabled)
|
|
end
|
|
else
|
|
ModMasque(k, Disabled)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
--- ============================ CONTENT ============================
|
|
--- ======= BINDINGS =======
|
|
BINDING_HEADER_HEROROTATION = "HeroRotation";
|
|
BINDING_NAME_HEROROTATION_CDS = "Toggle CDs";
|
|
BINDING_NAME_HEROROTATION_AOE = "Toggle AoE";
|
|
BINDING_NAME_HEROROTATION_TOGGLE = "Toggle On/Off";
|
|
BINDING_NAME_HEROROTATION_UNLOCK = "Unlock the addon to move icons";
|
|
BINDING_NAME_HEROROTATION_LOCK = "Lock the addon in place";
|
|
|
|
--- ======= MAIN FRAME =======
|
|
HR.MainFrame = CreateFrame("Frame", "HeroRotation_MainFrame", UIParent);
|
|
HR.MainFrame:SetFrameStrata(HR.GUISettings.General.MainFrameStrata);
|
|
HR.MainFrame:SetFrameLevel(10);
|
|
HR.MainFrame:SetWidth(112);
|
|
HR.MainFrame:SetHeight(96);
|
|
|
|
HR.MainFrame:SetClampedToScreen(true);
|
|
-- Resize
|
|
function HR.MainFrame:ResizeUI (Multiplier)
|
|
local FramesToResize = {
|
|
-- TODO: Put the Size in one Array in UI.lua and pull it out here
|
|
{HR.MainFrame, 112, 96},
|
|
{HR.MainIconFrame, 64, 64},
|
|
{HR.SmallIconFrame, 64, 32},
|
|
{HR.SmallIconFrame.Icon[1], HR.GUISettings.General.BlackBorderIcon and 30 or 32, HR.GUISettings.General.BlackBorderIcon and 30 or 32},
|
|
{HR.SmallIconFrame.Icon[2], HR.GUISettings.General.BlackBorderIcon and 30 or 32, HR.GUISettings.General.BlackBorderIcon and 30 or 32},
|
|
{HR.LeftIconFrame, 48, 48},
|
|
{HR.SuggestedIconFrame, 32, 32},
|
|
{HR.RightSuggestedIconFrame, 32, 32},
|
|
{HR.MainIconPartOverlayFrame, 64, 64},
|
|
};
|
|
for _, Value in pairs(FramesToResize) do
|
|
Value[1]:SetWidth(Value[2]*Multiplier);
|
|
Value[1]:SetHeight(Value[3]*Multiplier);
|
|
end
|
|
for i = 1, HR.MaxQueuedCasts do
|
|
HR.MainIconFrame.Part[i]:SetWidth(64*Multiplier);
|
|
HR.MainIconFrame.Part[i]:SetHeight(64*Multiplier);
|
|
end
|
|
HR.SuggestedIconFrame:SetPoint("BOTTOM", HR.MainIconFrame, "LEFT", -HR.LeftIconFrame:GetWidth()/2, HR.LeftIconFrame:GetHeight()/2+(HR.GUISettings.General.BlackBorderIcon and 3*Multiplier or 4*Multiplier));
|
|
HR.RightSuggestedIconFrame:SetPoint("BOTTOM", HR.MainIconFrame, "RIGHT", HR.LeftIconFrame:GetWidth()/2, HR.LeftIconFrame:GetHeight()/2 + (HR.GUISettings.General.BlackBorderIcon and 3*Multiplier or 4*Multiplier)); -- todo matt fix this location
|
|
HeroRotationDB.GUISettings["Scaling.ScaleUI"] = Multiplier;
|
|
end
|
|
function HR.MainFrame:ResizeButtons (Multiplier)
|
|
local FramesToResize = {
|
|
-- TODO: Put the Size in one Array in UI.lua and pull it out here
|
|
{HR.ToggleIconFrame, 64, 20},
|
|
{HR.ToggleIconFrame.Button[1], 20, 20},
|
|
{HR.ToggleIconFrame.Button[2], 20, 20},
|
|
{HR.ToggleIconFrame.Button[3], 20, 20}
|
|
};
|
|
for Key, Value in pairs(FramesToResize) do
|
|
Value[1]:SetWidth(Value[2]*Multiplier);
|
|
Value[1]:SetHeight(Value[3]*Multiplier);
|
|
end
|
|
for i = 1, 3 do
|
|
HR.ToggleIconFrame.Button[i]:SetPoint("LEFT", HR.ToggleIconFrame, "LEFT", HR.ToggleIconFrame.Button[i]:GetWidth()*(i-1)+i, 0);
|
|
end
|
|
HeroRotationDB.GUISettings["Scaling.ScaleButtons"] = Multiplier;
|
|
end
|
|
-- Lock/Unlock
|
|
local LockSpell = Spell(18282);
|
|
function HR.MainFrame:Unlock ()
|
|
-- Grey Texture
|
|
HR.ResetIcons();
|
|
HR.Cast(LockSpell); -- Main Icon
|
|
HR.Cast(LockSpell, {true}); -- Small Icon 1
|
|
HR.Cast(LockSpell, {true}); -- Small Icon 2
|
|
HR.CastLeft(LockSpell); -- Left Icon
|
|
HR.CastSuggested(LockSpell); -- Suggested Icon
|
|
HR.CastRightSuggested(LockSpell); -- Right Suggested Icon
|
|
-- Unlock the UI
|
|
for Key, Value in pairs(UIFrames) do
|
|
Value:EnableMouse(true);
|
|
end
|
|
HR.MainFrame:SetMovable(true);
|
|
HR.ToggleIconFrame:SetMovable(true);
|
|
HeroRotationDB.Locked = false;
|
|
end
|
|
function HR.MainFrame:Lock ()
|
|
for Key, Value in pairs(UIFrames) do
|
|
Value:EnableMouse(false);
|
|
end
|
|
HR.MainFrame:SetMovable(false);
|
|
HR.ToggleIconFrame:SetMovable(false);
|
|
HeroRotationDB.Locked = true;
|
|
end
|
|
function HR.MainFrame:ToggleLock ()
|
|
if HeroRotationDB.Locked then
|
|
HR.MainFrame:Unlock ();
|
|
HR.Print("HeroRotation UI is now |cff00ff00unlocked|r.");
|
|
else
|
|
HR.MainFrame:Lock ();
|
|
HR.Print("HeroRotation UI is now |cffff0000locked|r.");
|
|
end
|
|
end
|
|
-- Start Move
|
|
local function StartMove (self)
|
|
self:StartMoving();
|
|
end
|
|
HR.MainFrame:SetScript("OnMouseDown", StartMove);
|
|
-- Stop Move
|
|
local function StopMove (self)
|
|
self:StopMovingOrSizing();
|
|
if not HeroRotationDB then HeroRotationDB = {}; end
|
|
local point, relativeTo, relativePoint, xOffset, yOffset, relativeToName;
|
|
point, relativeTo, relativePoint, xOffset, yOffset = self:GetPoint();
|
|
if not relativeTo then
|
|
relativeToName = "UIParent";
|
|
else
|
|
relativeToName = relativeTo:GetName();
|
|
end
|
|
HeroRotationDB.IconFramePos = {
|
|
point,
|
|
relativeToName,
|
|
relativePoint,
|
|
xOffset,
|
|
yOffset
|
|
};
|
|
end
|
|
HR.MainFrame:SetScript("OnMouseUp", StopMove);
|
|
HR.MainFrame:SetScript("OnHide", StopMove);
|
|
-- AddonLoaded
|
|
HR.MainFrame:RegisterEvent("ADDON_LOADED");
|
|
HR.MainFrame:SetScript("OnEvent", function (self, Event, Arg1)
|
|
if Event == "ADDON_LOADED" then
|
|
if Arg1 == "HeroRotation" then
|
|
MasqueFrameList = {
|
|
["Main Icon"] = HR.MainIconFrame,
|
|
["Top Icons"] = HR.SmallIconFrame,
|
|
["Left Icon"] = HR.LeftIconFrame,
|
|
["Suggested Icon"] = HR.SuggestedIconFrame,
|
|
["Right Suggested Icon"] = HR.RightSuggestedIconFrame,
|
|
["Part Overlay"] = HR.MainIconPartOverlayFrame,
|
|
};
|
|
if not Masque then
|
|
Masque = LibStub( "Masque", true )
|
|
if Masque then
|
|
Masque:Register( "HeroRotation", MasqueUpdate, HR )
|
|
for FrameName, Frame in pairs(MasqueFrameList) do
|
|
MasqueGroups[Frame] = Masque:Group( addonName, FrameName)
|
|
end
|
|
end
|
|
end
|
|
-- Panels
|
|
if type(HeroRotationDB) ~= "table" then
|
|
HeroRotationDB = {};
|
|
end
|
|
if type(HeroRotationCharDB) ~= "table" then
|
|
HeroRotationCharDB = {};
|
|
end
|
|
if type(HeroRotationDB.GUISettings) ~= "table" then
|
|
HeroRotationDB.GUISettings = {};
|
|
end
|
|
if type(HeroRotationCharDB.GUISettings) ~= "table" then
|
|
HeroRotationCharDB.GUISettings = {};
|
|
end
|
|
HR.GUI.LoadSettingsRecursively(HR.GUISettings);
|
|
HR.GUI.CorePanelSettingsInit();
|
|
-- UI
|
|
if HeroRotationDB and type(HeroRotationDB.IconFramePos) == "table" and #HeroRotationDB.IconFramePos == 5 then
|
|
HR.MainFrame:SetPoint(HeroRotationDB.IconFramePos[1], _G[HeroRotationDB.IconFramePos[2]], HeroRotationDB.IconFramePos[3], HeroRotationDB.IconFramePos[4], HeroRotationDB.IconFramePos[5]);
|
|
else
|
|
HR.MainFrame:SetPoint("CENTER", UIParent, "CENTER", -200, 0);
|
|
end
|
|
HR.MainFrame:SetFrameStrata(HR.GUISettings.General.MainFrameStrata);
|
|
HR.MainFrame:Show();
|
|
HR.MainIconFrame:Init();
|
|
HR.SmallIconFrame:Init();
|
|
HR.LeftIconFrame:Init();
|
|
HR.SuggestedIconFrame:Init();
|
|
HR.RightSuggestedIconFrame:Init();
|
|
HR.ToggleIconFrame:Init();
|
|
if HeroRotationDB.GUISettings["Scaling.ScaleUI"] then
|
|
HR.MainFrame:ResizeUI(HeroRotationDB.GUISettings["Scaling.ScaleUI"]);
|
|
end
|
|
if HeroRotationDB.GUISettings["Scaling.ScaleButtons"] then
|
|
HR.MainFrame:ResizeButtons(HeroRotationDB.GUISettings["Scaling.ScaleButtons"]);
|
|
end
|
|
for k, v in pairs(MasqueFrameList) do
|
|
if type(v.Icon) == "table" then
|
|
for _, tblIcon in pairs(v.Icon) do
|
|
tblIcon.GetNormalTexture = function(self) return nil end;
|
|
tblIcon.SetNormalTexture = function(self, Texture) self.Texture = Texture end;
|
|
end
|
|
else
|
|
v.GetNormalTexture = function(self) return nil end;
|
|
v.SetNormalTexture = function(self, Texture) self.Texture = Texture end;
|
|
end
|
|
end
|
|
if MasqueGroups then
|
|
for k, v in pairs(MasqueGroups) do
|
|
if type(k.Icon) == "table" then
|
|
for _, tblIcon in pairs(k.Icon) do
|
|
if v then v:AddButton( tblIcon, { Icon = tblIcon.Texture, Cooldown = (tblIcon.CooldownFrame or nil) } ) end;
|
|
end
|
|
else
|
|
if v then v:AddButton( k, { Icon = k.Texture, Cooldown = k.CooldownFrame } ) end;
|
|
end
|
|
end
|
|
for k, v in pairs(MasqueGroups) do
|
|
if v then v:ReSkin() end
|
|
end
|
|
end
|
|
UIFrames = {
|
|
HR.MainFrame,
|
|
HR.MainIconFrame,
|
|
HR.MainIconPartOverlayFrame,
|
|
HR.MainIconFrame.Part[1],
|
|
HR.MainIconFrame.Part[2],
|
|
HR.MainIconFrame.Part[3],
|
|
HR.SmallIconFrame,
|
|
HR.SmallIconFrame.Icon[1],
|
|
HR.SmallIconFrame.Icon[2],
|
|
HR.LeftIconFrame,
|
|
HR.SuggestedIconFrame,
|
|
HR.RightSuggestedIconFrame,
|
|
HR.ToggleIconFrame
|
|
};
|
|
|
|
-- Load additionnal settings
|
|
local CP_General = GUI.GetPanelByName("General")
|
|
if CP_General then
|
|
CreatePanelOption("Slider", CP_General, "General.SetAlpha", {0, 1, 0.05}, "Addon Alpha", "Change the addon's alpha setting.");
|
|
CreatePanelOption("Button", CP_General, "ButtonMove", "Lock/Unlock", "Enable the moving of the frames.", function() HR.MainFrame:ToggleLock(); end);
|
|
CreatePanelOption("Button", CP_General, "ButtonReset", "Reset Buttons", "Resets the anchor of buttons.", function() HR.ToggleIconFrame:ResetAnchor(); end);
|
|
end
|
|
local CP_Scaling = GUI.GetPanelByName("Scaling")
|
|
if CP_Scaling then
|
|
CreatePanelOption("Slider", CP_Scaling, "Scaling.ScaleUI", {0.5, 5, 0.1}, "UI Scale", "Scale of the Icons.", function(value) HR.MainFrame:ResizeUI(value); end);
|
|
CreatePanelOption("Slider", CP_Scaling, "Scaling.ScaleButtons", {0.5, 5, 0.1}, "Buttons Scale", "Scale of the Buttons.", function(value) HR.MainFrame:ResizeButtons(value); end);
|
|
CreatePanelOption("Slider", CP_Scaling, "Scaling.ScaleHotkey", {0.5, 5, 0.1}, "Hotkey Scale", "Scale of the Hotkeys.");
|
|
end
|
|
|
|
-- Modules
|
|
C_Timer.After(2, function ()
|
|
HR.MainFrame:UnregisterEvent("ADDON_LOADED");
|
|
HR.PulsePreInit();
|
|
HR.PulseInit();
|
|
end
|
|
);
|
|
end
|
|
end
|
|
end
|
|
);
|
|
|
|
--- ======= MAIN =======
|
|
function HR.PulsePreInit ()
|
|
HR.MainFrame:Lock();
|
|
end
|
|
local EnabledRotation = {
|
|
-- Death Knight
|
|
[250] = "HeroRotation_DeathKnight", -- Blood
|
|
[251] = "HeroRotation_DeathKnight", -- Frost
|
|
[252] = "HeroRotation_DeathKnight", -- Unholy
|
|
-- Demon Hunter
|
|
[577] = "HeroRotation_DemonHunter", -- Havoc
|
|
[581] = "HeroRotation_DemonHunter", -- Vengeance
|
|
-- Druid
|
|
[102] = "HeroRotation_Druid", -- Balance
|
|
[103] = "HeroRotation_Druid", -- Feral
|
|
[104] = "HeroRotation_Druid", -- Guardian
|
|
--[105] = "HeroRotation_Druid", -- Restoration
|
|
-- Evoker
|
|
[1467] = "HeroRotation_Evoker", -- Devastation
|
|
--[1468] = "HeroRotation_Evoker", -- Preservation
|
|
-- Hunter
|
|
[253] = "HeroRotation_Hunter", -- Beast Mastery
|
|
[254] = "HeroRotation_Hunter", -- Marksmanship
|
|
[255] = "HeroRotation_Hunter", -- Survival
|
|
-- Mage
|
|
[62] = "HeroRotation_Mage", -- Arcane
|
|
--[63] = "HeroRotation_Mage", -- Fire
|
|
[64] = "HeroRotation_Mage", -- Frost
|
|
-- Monk
|
|
--[268] = "HeroRotation_Monk", -- Brewmaster
|
|
[269] = "HeroRotation_Monk", -- Windwalker
|
|
--[270] = "HeroRotation_Monk", -- Mistweaver
|
|
-- Paladin
|
|
--[65] = "HeroRotation_Paladin", -- Holy
|
|
[66] = "HeroRotation_Paladin", -- Protection
|
|
[70] = "HeroRotation_Paladin", -- Retribution
|
|
-- Priest
|
|
--[256] = "HeroRotation_Priest", -- Discipline
|
|
--[257] = "HeroRotation_Priest", -- Holy
|
|
[258] = "HeroRotation_Priest", -- Shadow
|
|
-- Rogue
|
|
[259] = "HeroRotation_Rogue", -- Assassination
|
|
[260] = "HeroRotation_Rogue", -- Outlaw
|
|
[261] = "HeroRotation_Rogue", -- Subtlety
|
|
-- Shaman
|
|
[262] = "HeroRotation_Shaman", -- Elemental
|
|
[263] = "HeroRotation_Shaman", -- Enhancement
|
|
--[264] = "HeroRotation_Shaman", -- Restoration
|
|
-- Warlock
|
|
[265] = "HeroRotation_Warlock", -- Affliction
|
|
[266] = "HeroRotation_Warlock", -- Demonology
|
|
[267] = "HeroRotation_Warlock", -- Destruction
|
|
-- Warrior
|
|
[71] = "HeroRotation_Warrior", -- Arms
|
|
[72] = "HeroRotation_Warrior", -- Fury
|
|
[73] = "HeroRotation_Warrior" -- Protection
|
|
};
|
|
local LatestSpecIDChecked = 0;
|
|
function HR.PulseInit ()
|
|
local Spec = GetSpecialization();
|
|
-- Delay by 1 second until the WoW API returns a valid value.
|
|
if Spec == nil then
|
|
HL.PulseInitialized = false;
|
|
C_Timer.After(1, function ()
|
|
HR.PulseInit();
|
|
end
|
|
);
|
|
else
|
|
-- Force a refresh from the Core
|
|
-- TODO: Make it a function instead of copy/paste from Core Events.lua
|
|
Cache.Persistent.Player.Spec = {GetSpecializationInfo(Spec)};
|
|
local SpecID = Cache.Persistent.Player.Spec[1];
|
|
|
|
-- Delay by 1 second until the WoW API returns a valid value.
|
|
if SpecID == nil then
|
|
HL.PulseInitialized = false;
|
|
C_Timer.After(1, function ()
|
|
HR.PulseInit();
|
|
end
|
|
);
|
|
else
|
|
-- Load the Class Module if it's possible and not already loaded
|
|
if EnabledRotation[SpecID] and not IsAddOnLoaded(EnabledRotation[SpecID]) then
|
|
LoadAddOn(EnabledRotation[SpecID]);
|
|
HL.LoadOverrides(SpecID)
|
|
end
|
|
|
|
-- Check if there is a Rotation for this Spec
|
|
if LatestSpecIDChecked ~= SpecID then
|
|
if EnabledRotation[SpecID] and HR.APLs[SpecID] then
|
|
for Key, Value in pairs(UIFrames) do
|
|
Value:Show();
|
|
end
|
|
HR.MainFrame:SetScript("OnUpdate", HR.Pulse);
|
|
-- Spec Registers
|
|
-- Spells
|
|
Player:RegisterListenedSpells(SpecID);
|
|
HL.UnregisterAuraTracking();
|
|
-- Enums Filters
|
|
Player:FilterTriggerGCD(SpecID);
|
|
Spell:FilterProjectileSpeed(SpecID);
|
|
-- Module Init Function
|
|
if HR.APLInits[SpecID] then
|
|
HR.APLInits[SpecID]();
|
|
end
|
|
-- Special Checks
|
|
if GetCVar("nameplateShowEnemies") ~= "1" then
|
|
HR.Print("It looks like enemy nameplates are disabled. This may cause the addon to operate incorrectly, leading to incorrect or missing spell suggestions.");
|
|
end
|
|
else
|
|
HR.Print("No Rotation found for this class/spec (SpecID: ".. SpecID .. "), addon disabled. This is likely due to the rotation being unsupported at this time. Please check supported rotations here: https://github.com/herotc/hero-rotation#supported-rotations");
|
|
for Key, Value in pairs(UIFrames) do
|
|
Value:Hide();
|
|
end
|
|
HR.MainFrame:SetScript("OnUpdate", nil);
|
|
end
|
|
LatestSpecIDChecked = SpecID;
|
|
end
|
|
if not HL.PulseInitialized then HL.PulseInitialized = true; end
|
|
end
|
|
end
|
|
end
|
|
|
|
HR.Timer = {
|
|
Pulse = 0
|
|
};
|
|
function HR.Pulse ()
|
|
if GetTime() > HR.Timer.Pulse and HR.Locked() then
|
|
HR.Timer.Pulse = GetTime() + HL.Timer.PulseOffset;
|
|
|
|
HR.ResetIcons();
|
|
|
|
-- Check if the current spec is available (might not always be the case)
|
|
-- Especially when switching from area (open world -> instance)
|
|
local SpecID = Cache.Persistent.Player.Spec[1];
|
|
if SpecID then
|
|
-- Check if we are ready to cast something to save FPS.
|
|
if HR.ON() and HR.Ready() then
|
|
HL.CacheHasBeenReset = false;
|
|
Cache.Reset();
|
|
-- Rotational Debug Output
|
|
if HR.GUISettings.General.RotationDebugOutput then
|
|
CurrResult = HR.APLs[SpecID]();
|
|
if CurrResult and CurrResult ~= PrevResult then
|
|
HR.Print(CurrResult);
|
|
PrevResult = CurrResult;
|
|
end
|
|
else
|
|
HR.APLs[SpecID]();
|
|
end
|
|
end
|
|
if MasqueGroups then
|
|
for k, v in pairs(MasqueGroups) do
|
|
if v then v:ReSkin() end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Is the player ready ?
|
|
function HR.Ready ()
|
|
local AreWeReady
|
|
if HR.GUISettings.General.ShowWhileMounted then
|
|
AreWeReady = not Player:IsDeadOrGhost() and not Player:IsInVehicle() and not C_PetBattles.IsInBattle();
|
|
else
|
|
AreWeReady = not Player:IsDeadOrGhost() and not Player:IsMounted() and not Player:IsInVehicle() and not C_PetBattles.IsInBattle();
|
|
end
|
|
return AreWeReady
|
|
end
|
|
|
|
-- Used to force a short/long pulse wait, it also resets the icons.
|
|
function HR.ChangePulseTimer (Offset)
|
|
HR.ResetIcons();
|
|
HR.Timer.Pulse = GetTime() + Offset;
|
|
end
|
|
|