From 1e5289abafb2a393e294218491e2d2fcc814f3c1 Mon Sep 17 00:00:00 2001 From: vert Date: Fri, 7 Feb 2025 16:32:07 +1100 Subject: [PATCH] UI (NOT WORKING) - Abunai Interface added - Jeffs Config added Simple shaman script added for testing purpose --- scripts/.gitkeep | 0 scripts/ExampleModule.lua | 24 -- scripts/Libraries/ExampleDependency.lua | 21 -- scripts/Libraries/ExampleDependencyError.lua | 15 - scripts/Libraries/ExampleLibrary.lua | 25 -- scripts/Shaman.lua | 337 +++++++++++++++++++ scripts/ShamanSettings.lua | 124 +++++++ src/Config/Config.lua | 85 +++++ src/Interface/Interface.lua | 268 +++++++++++++++ src/_bastion.lua | 4 + 10 files changed, 818 insertions(+), 85 deletions(-) delete mode 100644 scripts/.gitkeep delete mode 100644 scripts/ExampleModule.lua delete mode 100644 scripts/Libraries/ExampleDependency.lua delete mode 100644 scripts/Libraries/ExampleDependencyError.lua delete mode 100644 scripts/Libraries/ExampleLibrary.lua create mode 100644 scripts/Shaman.lua create mode 100644 scripts/ShamanSettings.lua create mode 100644 src/Config/Config.lua create mode 100644 src/Interface/Interface.lua diff --git a/scripts/.gitkeep b/scripts/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/ExampleModule.lua b/scripts/ExampleModule.lua deleted file mode 100644 index 4289a24..0000000 --- a/scripts/ExampleModule.lua +++ /dev/null @@ -1,24 +0,0 @@ -local Tinkr, Bastion = ... -local ExampleModule = Bastion.Module:New('ExampleModule') -local Player = Bastion.UnitManager:Get('player') - --- Create a local spellbook -local SpellBook = Bastion.SpellBook:New() - -local FlashHeal = SpellBook:GetSpell(2061) - --- Get a global spell (this can collide with other modules, so be careful) --- This is useful for caching common spells that you might not actually cast, and to avoid needless spell creation inline -local FlashHeal = Bastion.Globals.SpellBook:GetSpell(2061) - -local AdvancedMath = Bastion:Import('AdvancedMath') - -print(AdvancedMath:Add(1, 2)) - -ExampleModule:Sync(function() - if Player:GetHP() <= 50 then - FlashHeal:Cast(Player) - end -end) - -Bastion:Register(ExampleModule) diff --git a/scripts/Libraries/ExampleDependency.lua b/scripts/Libraries/ExampleDependency.lua deleted file mode 100644 index 87c1449..0000000 --- a/scripts/Libraries/ExampleDependency.lua +++ /dev/null @@ -1,21 +0,0 @@ -local Tinkr, Bastion = ... - -local Player = Bastion.UnitManager:Get('player') - -Bastion:RegisterLibrary(Bastion.Library:New({ - name = 'Dependable', - exports = { - default = function() - local Dependable = {} - - Dependable.__index = Dependable - - function Dependable:Test(a) - print(a) - end - - return Dependable - end, - Test = 5 - } -})) diff --git a/scripts/Libraries/ExampleDependencyError.lua b/scripts/Libraries/ExampleDependencyError.lua deleted file mode 100644 index 5fc4485..0000000 --- a/scripts/Libraries/ExampleDependencyError.lua +++ /dev/null @@ -1,15 +0,0 @@ -local Tinkr, Bastion = ... - -Bastion:RegisterLibrary(Bastion.Library:New({ - name = 'Circular', - exports = { - default = function(self) - -- Return default first, and then the remaining exports - local Math, OtherExports = self:Import('AdvancedMath') - - print(Math:Add(1, 2)) - - return 'Circular' - end - } -})) diff --git a/scripts/Libraries/ExampleLibrary.lua b/scripts/Libraries/ExampleLibrary.lua deleted file mode 100644 index ddb7afa..0000000 --- a/scripts/Libraries/ExampleLibrary.lua +++ /dev/null @@ -1,25 +0,0 @@ -local Tinkr, Bastion = ... - -Bastion:RegisterLibrary(Bastion.Library:New({ - name = 'AdvancedMath', - exports = { - default = function(self) -- Function exports are called when the library is loaded - -- Return default first, and then the remaining exports - local Dependable, OtherExports = self:Import('Dependable') - - local CircularDependency = self:Import('Circular') -- Causes a circular dependency error - - Dependable:Test(OtherExports.Test) - - local AdvancedMath = {} - - AdvancedMath.__index = AdvancedMath - - function AdvancedMath:Add(a, b) - return a + b - end - - return AdvancedMath - end - } -})) diff --git a/scripts/Shaman.lua b/scripts/Shaman.lua new file mode 100644 index 0000000..163549c --- /dev/null +++ b/scripts/Shaman.lua @@ -0,0 +1,337 @@ +local Tinkr, Bastion = ... + +local Util = Tinkr.Util +if UnitClass('player') ~= 'Shaman' then return end + +Bastion.Rotation = {} +local BR = Bastion.Rotation +BR.Units = { + Player = Bastion.UnitManager:Get('player'), + Target = Bastion.UnitManager:Get('target'), + None = Bastion.UnitManager:Get('none') + } + +local ShamanModule = Bastion.Module:New('Shaman') +local SpellBook = Bastion.Globals.SpellBook + +local Player = BR.Units.Player +local None = BR.Units.None +local Target = BR.Units.Target + +Tinkr:require("scripts/bastion/scripts/ShamanSettings", Bastion) +BR.myconf = Tinkr.Util.Config:New('shaman') -- for saving variables + +local function _highestrank(spell) + local name = GetSpellInfo(spell) + local highrankid = (select(7, GetSpellInfo(name))) + if not highrankid then + highrankid = 1 + end + return highrankid +end + +local s = { + -- Basic Attack + AutoAttack = SpellBook:GetSpell(6603), + + -- Shocks + EarthShock = SpellBook:GetSpell(_highestrank(8042)), + FrostShock = SpellBook:GetSpell(_highestrank(8056)), + FlameShock = SpellBook:GetSpell(_highestrank(8050)), + + -- Specific Ranks for Utility + EarthShockRank1 = SpellBook:GetSpell(8042), -- Rank 1 (Interrupt) + FrostShockRank1 = SpellBook:GetSpell(8056), -- Rank 1 (Slow), + + -- Healing Spells + HealingWave = SpellBook:GetSpell(_highestrank(331)), + LesserHealingWave = SpellBook:GetSpell(_highestrank(8004)), + ChainHeal = SpellBook:GetSpell(_highestrank(1064)), + + -- Healing Sub Ranks (Healing Wave) + HealingWaveRank1 = SpellBook:GetSpell(331), -- Rank 1 + HealingWaveRank2 = SpellBook:GetSpell(332), -- Rank 2 + HealingWaveRank3 = SpellBook:GetSpell(547), -- Rank 3 + HealingWaveRank4 = SpellBook:GetSpell(913), -- Rank 4 + HealingWaveRank5 = SpellBook:GetSpell(939), -- Rank 5 + HealingWaveRank6 = SpellBook:GetSpell(959), -- Rank 6 + HealingWaveRank7 = SpellBook:GetSpell(8005), -- Rank 7 + HealingWaveRank8 = SpellBook:GetSpell(10395), -- Rank 8 + HealingWaveRank9 = SpellBook:GetSpell(10396), -- Rank 9 (Max Rank) + + -- Healing Sub Ranks (Lesser Healing Wave) + LesserHealingWaveRank1 = SpellBook:GetSpell(8004), -- Rank 1 + LesserHealingWaveRank2 = SpellBook:GetSpell(8008), -- Rank 2 + LesserHealingWaveRank3 = SpellBook:GetSpell(8010), -- Rank 3 + LesserHealingWaveRank4 = SpellBook:GetSpell(10466), -- Rank 4 + LesserHealingWaveRank5 = SpellBook:GetSpell(10467), -- Rank 5 + LesserHealingWaveRank6 = SpellBook:GetSpell(10468), -- Rank 6 (Max Rank) + + -- Healing Sub Ranks (Chain Heal) + ChainHealRank1 = SpellBook:GetSpell(1064), -- Rank 1 + ChainHealRank2 = SpellBook:GetSpell(10622), -- Rank 2 + ChainHealRank3 = SpellBook:GetSpell(10623), -- Rank 3 + ChainHealRank4 = SpellBook:GetSpell(25422), -- Rank 4 (Max Rank) + + -- Totems + EarthbindTotem = SpellBook:GetSpell(2484), + StoneclawTotem = SpellBook:GetSpell(_highestrank(5730)), + StrengthOfEarthTotem = SpellBook:GetSpell(_highestrank(8075)), + TremorTotem = SpellBook:GetSpell(_highestrank(8143)), + SearingTotem = SpellBook:GetSpell(_highestrank(3599)), + FireNovaTotem = SpellBook:GetSpell(_highestrank(1535)), + HealingStreamTotem = SpellBook:GetSpell(_highestrank(5394)), + ManaSpringTotem = SpellBook:GetSpell(_highestrank(5675)), + WindfuryTotem = SpellBook:GetSpell(_highestrank(8512)), + GraceOfAirTotem = SpellBook:GetSpell(_highestrank(8835)), + + -- Buffs + LightningShield = SpellBook:GetSpell(_highestrank(324)), + + -- Offensive Spells + LightningBolt = SpellBook:GetSpell(_highestrank(403)), + ChainLightning = SpellBook:GetSpell(_highestrank(421)), + + -- Utility + Purge = SpellBook:GetSpell(_highestrank(370)), + CurePoison = SpellBook:GetSpell(526), + CureDisease = SpellBook:GetSpell(2870), + GhostWolf = SpellBook:GetSpell(2645), + WaterWalking = SpellBook:GetSpell(_highestrank(546)), + WaterBreathing = SpellBook:GetSpell(_highestrank(131)), + + -- Weapon Buffs + FlametongueWeapon = SpellBook:GetSpell(_highestrank(8024)), + FrostbrandWeapon = SpellBook:GetSpell(_highestrank(8033)), + WindfuryWeapon = SpellBook:GetSpell(_highestrank(8232)), + RockbiterWeapon = SpellBook:GetSpell(_highestrank(8017)), + + -- Talents + ElementalMastery = SpellBook:GetSpell(16166), + NaturesSwiftness = SpellBook:GetSpell(16188), + ManaTideTotem = SpellBook:GetSpell(_highestrank(16190)), + + --Buffs + refreshment = SpellBook:GetList(430, 431, 432, 1133, 1135, 1137, 10250, 22734, 27089, 433, 434, 435, 1127, 1129, 1131, 5004, 11199, 11200, 22731, 25696) +} + + +-- Create APLs +local DefaultAPL = Bastion.APL:New('default') +local HealingAPL = Bastion.APL:New('healing') +local DamageAPL = Bastion.APL:New('damage') +local CooldownAPL = Bastion.APL:New('cooldown') +local DefensiveAPL = Bastion.APL:New('defensive') + + -- Default APL +--DefaultAPL:AddAPL(DefensiveAPL, function() return true end) +--DefaultAPL:AddAPL(CooldownAPL, function() return true end) +DefaultAPL:AddAPL(HealingAPL, function() return true end) +--DefaultAPL:AddAPL(ExecuteAPL, function() return true end) +--DefaultAPL:AddAPL(DamagePriorityAPL, function() return not HealingNeeded() and AggressiveDamagePhase() end) +--DefaultAPL:AddAPL(DamageAPL, function() return not HealingNeeded() end) + +local DispelTarget = Bastion.UnitManager:CreateCustomUnit('dispel', function(unit) + local lowest = nil + local lowestHP = math.huge + Bastion.UnitManager:EnumFriends(function(unit) + if unit:IsDead() then return false end + if not Player:CanSee(unit) then return false end + if Player:GetDistance(unit) > 40 then return false end + + if not unit:IsDead() and Player:CanSee(unit) and (unit:GetAuras():HasAnyDispelableAura(s.CurePoison) or unit:GetAuras():HasAnyDispelableAura(s.CureDisease)) then + local hp = unit:GetHP() + if hp < lowestHP then + lowest = unit + lowestHP = hp + end + end + end) + if lowest == nil then + lowest = None + end + return lowest +end) + +local Lowest = Bastion.UnitManager:CreateCustomUnit('lowest', function(unit) + local lowest = nil + local lowestHP = math.huge + Bastion.UnitManager:EnumFriends(function(unit) + if unit:IsDead() then return false end + if Player:GetDistance(unit) > 40 then return false end + if not Player:CanSee(unit) then return false end + local hp = unit:GetHP() + if hp < lowestHP then + lowest = unit + lowestHP = hp + end + if not lowest then + lowest = Player + end + end) + if lowest == nil then + lowest = None + end + return lowest +end) + +local InterruptTarget = Bastion.UnitManager:CreateCustomUnit('interrupttarget', function(unit) + local interrupt = nil + Bastion.UnitManager:EnumEnemies(function(unit) + if unit:IsDead() then return false end + if not Player:CanSee(unit) then return false end + if Player:GetDistance(unit) > 20 then return false end + if unit:IsInterruptibleAt(40) and Player:IsFacing(unit) then + interrupt = unit + --print(unit) + return true + end + end) + if interrupt == nil then + interrupt = None + end + return interrupt +end) + +local PurgeTarget = Bastion.UnitManager:CreateCustomUnit('purgetarget', function(unit) + local purgeTarget = nil + local highestPriority = 0 + + -- List of high-priority buffs to purge + local highPriorityBuffs = { + 642, -- Divine Shield + 1022, -- Blessing of Protection + 19752, -- Divine Intervention + 1044, -- Blessing of Freedom + 6940, -- Blessing of Sacrifice + 1461, -- Arcane Intellect + 23028, -- Arcane Brilliance + 10157, -- Arcane Intellect (Rank 6) + 27126, -- Arcane Brilliance (Rank 2) + } + + Bastion.UnitManager:EnumEnemies(function(unit) + -- Basic checks + if unit:IsDead() then return false end + if not Player:CanSee(unit) then return false end + if Player:GetDistance(unit) > 30 then return false end + if not Player:IsFacing(unit) then return false end + if not unit:IsAffectingCombat() then return false end + + -- Check if unit has purgeable buffs + if unit:GetAuras():HasPurgeableBuff() then + local currentPriority = 0 + + -- Check if unit has any high priority buffs + for _, buffId in ipairs(highPriorityBuffs) do + if unit:GetAuras():Find(buffId):IsUp() then + currentPriority = 2 + break + end + end + + -- If no high priority buffs found, but unit has other purgeable buffs + if currentPriority == 0 then + currentPriority = 1 + end + + -- Update target if this unit has higher priority + if currentPriority > highestPriority then + purgeTarget = unit + highestPriority = currentPriority + end + end + end) + + if purgeTarget == nil then + purgeTarget = None + end + + return purgeTarget +end) + +-- Utility function to check if healing is needed + local function HealingNeeded() + return Lowest:Exists() and Lowest:GetHP() < 90 + end + + -- Utility function to check if emergency healing is needed + local function EmergencyHealingNeeded() + return Lowest:Exists() and Lowest:GetHP() < 50 + end + + + + +local function out_of_combat_function() + if Player:GetAuras():FindAnyOf(s.refreshment):IsUp() then return end + +end + +HealingAPL:AddSpell( + s.LesserHealingWave:CastableIf(function(self) + if not Lowest:Exists() then return false end + if Lowest:GetHP() > 20 then return false end + return self:IsKnownAndUsable() and HealingNeeded() + end):SetTarget(Lowest) +) + +HealingAPL:AddSpell( + s.HealingWave:CastableIf(function(self) + if not Lowest:Exists() then return false end + if Lowest:GetHP() > 50 or Lowest:GetHP() < 20 then return false end + return self:IsKnownAndUsable() and HealingNeeded() + end):SetTarget(Lowest) +) + +HealingAPL:AddSpell( + s.CurePoison:CastableIf(function(self) + if not DispelTarget:Exists() then return false end + return (DispelTarget:GetAuras():HasAnyDispelableAura(s.CurePoison)) and self:IsKnownAndUsable() + end):SetTarget(DispelTarget) +) + +HealingAPL:AddSpell( + s.CureDisease:CastableIf(function(self) + if not DispelTarget:Exists() then return false end + return (DispelTarget:GetAuras():HasAnyDispelableAura(s.CureDisease)) and self:IsKnownAndUsable() + end):SetTarget(DispelTarget) +) + +DamageAPL:AddSpell( + s.Purge:CastableIf(function(self) + if not PurgeTarget:Exists() then return false end + return self:IsKnownAndUsable() and PurgeTarget:GetAuras():HasPurgeableBuff() + end):SetTarget(PurgeTarget) +) + + + + + + + + +ShamanModule:Sync(function() + if Player:GetAuras():FindAnyOf(s.refreshment):IsUp() then return end + if Player:IsDead() then return end + if Player:IsCastingOrChanneling() then return end + if IsMounted() then return end + if InterruptTarget:Exists() and s.EarthShockRank1:IsKnownAndUsable() then + s.EarthShockRank1:Cast(InterruptTarget) + end + + if Player:IsAffectingCombat() then + + DefaultAPL:Execute() + else + if Player:GetPP() > 5 then + HealingAPL:Execute() + end + out_of_combat_function() + end +end) + + +Bastion:Register(ShamanModule) +ShamanModule:Toggle() diff --git a/scripts/ShamanSettings.lua b/scripts/ShamanSettings.lua new file mode 100644 index 0000000..76e7ca7 --- /dev/null +++ b/scripts/ShamanSettings.lua @@ -0,0 +1,124 @@ +local Unlocker, Bastion = ... +--! Creating the initial GUI !-- + +Bastion.Settings = Bastion.Interface.Category:New("Shaman") + + +Bastion.Settings:AddSubsection("General") + +Bastion.Settings:Slider({ + category = "bastion", + var = "tickrate", + name = "Engine Tickrate (milliseconds)", + tooltip = "How often the Bastion Engine runs. The lower this number, the more resources Bastion will use.\n\nYou must reload for changes to take effect.", + default = 100, + min = 50, + max = 500, + step = 50, +}) + + +Bastion.Settings:Dropdown({ + category = "bastion", + var = "pause_key", + name = "Hold-to-Pause", + tooltip = "Bastion will not run while this button is held down.", + default = false, + options = { + { "None", false }, + { "Shift", "SHIFT" }, + { "Control", "CTRL" }, + { "Alt", "ALT" }, + } +}) + +Bastion.Settings:AddSubsection("Engine Options") + +Bastion.Settings:Checkbox({ + category = "bastion", + var = "debug", + name = "Debug Mode", + tooltip = "Toggles Debug Mode for the Bastion Engine.", + default = false, + onClick = function() + Bastion.DebugMode = not Bastion.DebugMode + if Bastion.DebugMode then + Bastion:Debug("Debug Enabled") + else + Bastion:Print("Debug Disabled") + end + end, +}) + +Bastion.MainBar = Bastion.Interface.Hotbar:New({ + buttonCount = 3, + name = "Bastion", + options = Bastion.Settings +}) + +if Bastion:IsClassic() then + Bastion.MainBar:AddButton({ + name = "Toggle", + texture = "Interface\\ICONS\\Inv_drink_15", + tooltip = "This button toggle's Bastion on and off.", + toggle = true, + onClick = function() + Bastion.Enabled = not Bastion.Enabled + if Bastion.Enabled then + Bastion:Print("Enabled") + else + Bastion:Print("Disabled") + end + end, + }) + Bastion.MainBar:AddButton({ + name = "Options", + tooltip = "This button opens the Bastion Options menu.", + texture = "Interface\\ICONS\\Inv_misc_gear_01", + toggle = false, + onClick = function() + if SettingsPanel and SettingsPanel:IsVisible() then + HideUIPanel(SettingsPanel) + HideUIPanel(GameMenuFrame) + else + Settings.OpenToCategory(Bastion.MainBar.options.settings:GetID()) + end + end, + }) + +else + Bastion.MainBar:AddButton({ + name = "Toggle", + texture = "Interface\\ICONS\\ACHIEVEMENT_GUILDPERK_MRPOPULARITY", + tooltip = "This button toggle's Bastion on and off.", + toggle = true, + onClick = function() + -- if Bastion.Settings.config:Read("bastion_stimulant", true) then + -- Bastion.Stimulant:Toggle() + -- end + Bastion.Enabled = not Bastion.Enabled + if Bastion.Enabled then + Bastion:Print("Enabled") + else + Bastion:Print("Disabled") + end + end, + }) + Bastion.MainBar:AddButton({ + name = "Options", + tooltip = "This button opens the Bastion Options menu.", + texture = "Interface\\ICONS\\INV_Engineering_90_Gizmo", + toggle = false, + onClick = function() + if SettingsPanel and SettingsPanel:IsVisible() then + HideUIPanel(SettingsPanel) + HideUIPanel(GameMenuFrame) + else + Settings.OpenToCategory(Bastion.MainBar.options.settings:GetID()) + end + end, + }) +end + + +Bastion.Settings:Register() \ No newline at end of file diff --git a/src/Config/Config.lua b/src/Config/Config.lua new file mode 100644 index 0000000..2769078 --- /dev/null +++ b/src/Config/Config.lua @@ -0,0 +1,85 @@ +---@type Tinkr +local Tinkr, +---@class Bastion +Bastion = ... + +---@class Bastion.Config +---@field instantiated boolean +---@field config Tinkr.Util.Config.Instance +---@field defaults nil | table +local Config = { + instantiated = false, +} + +function Config:__index(k) + local response = Config[k] + + if response == nil then + response = rawget(self, k) + end + + if response == nil and self.instantiated then + response = self:Read(k) + end + + return response +end + +function Config:__newindex(key, value) + if self.instantiated then + self:Write(key, value) + else + rawset(self, key, value) + end +end + +---@generic D +---@param name string +---@param defaults? D +function Config:New(name, defaults) + ---@type Bastion.Config + local self = setmetatable({}, Config) + self.config = Tinkr.Util.Config:New(name) + self.defaults = type(defaults) == "table" and defaults or {} + self.instantiated = true + return self +end + +---@generic D +---@param key string +---@param default? D +---@return D +function Config:Read(key, default) + if type(default) == "nil" then + default = self.defaults[key] + end + return self.config:Read(key, default) +end + +function Config:Reset() + if type(self.defaults) == "table" then + -- Clear all values currently in the config. + for key, _ in pairs(self.config.data) do + self:Write(key, nil) + end + -- Use default table to write new defaults. + for key, value in pairs(self.defaults) do + self:Write(key, value) + end + return true + end + return false +end + +---@param key string +---@param value any +function Config:Write(key, value) + self.config:Write(key, value) +end + +---@param key string +function Config:Sync(key) + self.config:Sync(key) +end + +Bastion.Config = Config diff --git a/src/Interface/Interface.lua b/src/Interface/Interface.lua new file mode 100644 index 0000000..b4eb085 --- /dev/null +++ b/src/Interface/Interface.lua @@ -0,0 +1,268 @@ +local Tinkr, Bastion = ... +local Interface = {} + +--[[ + The current settings is named "Vertical" as it uses the Dragonflight Vertical + layout, rather than the Canvas layout. Vertical looks better, is easier to + format and use. Canvas layout allows you to add multi-select dropdowns, text + inputs, and more. I prefer vertical. Canvas support will be added in the future. +]]-- + +local Hotbar = {} +Hotbar.__index = Hotbar + +Bastion.Hotbars = {} + +-- Constructor +---@return Hotbar +function Hotbar:New(details) + local self = setmetatable({}, Hotbar) + self.button_size = 45 + self.button_spacing = 5 + self.button_count = details.buttonCount + self.name = details.name + self.options = details.options + + local position = self.options.config:Read("bar_location", { "CENTER", 0, -50 }) + + + self.frame = CreateFrame("Frame", nil, UIParent, "SecureHandlerStateTemplate") + self.frame:SetWidth(self.button_count * (32 + self.button_spacing) - self.button_spacing) -- width = total width of all buttons + total width of all spacings + self.frame:SetHeight(32 + 20) + + + self.frame:SetPoint(position[1], position[2], position[3]) + + self.title = self.frame:CreateFontString(nil, "OVERLAY", "Game15Font") + + self.title:SetPoint("BOTTOMLEFT", self.frame, "LEFT", 0, 10) -- adjust these values as necessary + + self.title:SetText(self.name) -- replace this with your desired title + + self.frame:EnableMouse(true) + self.frame:SetMovable(true) + self.frame:SetClampedToScreen(true) + self.frame:RegisterForDrag("LeftButton") + self.frame:SetScript("OnDragStart", self.frame.StartMoving) + self.frame:SetScript("OnDragStop", function() + self.frame:StopMovingOrSizing() + local point, _, _, x, y = self.frame:GetPoint() + self.options.config:Write("bar_location", { point, x, y }) + end) + + if not self.options.config:Read("display_hotbar_" .. self.options.name, true) then + self.frame:Hide() + end + + self.options:Checkbox({ + category = "display_hotbar", + var = self.name, + name = self.name .. " Hotbar", + tooltip = "Display the hotbar for " .. self.name, + default = true + }) + + self.buttons = {} + + table.insert(Bastion.Hotbars, self) + return self +end + +-- Needed to ensure hotbars are positioned properly and hidden when a user hides them in settings +function Hotbar:Refresh() + if not self.options.config:Read("display_hotbar_" .. self.options.name, true) then + if self.frame:IsVisible() then + self.frame:Hide() + end + else + if not self.frame:IsVisible() then + self.frame:Show() + end + end + + return self +end + +-- Adds a button to the hotbar. +-- TODO: Make this not a hot mess for classic/retail +function Hotbar:AddButton(details) + local button = CreateFrame("CheckButton", nil, self.frame, "UIPanelButtonTemplate") + button:SetNormalTexture(details.texture) + button:SetPoint("CENTER") + button:SetSize(32, 32) + table.insert(self.buttons, button) + if details.onClick then + if details.toggle then + local toggleAnim = false + button:SetScript("OnClick", function() + -- Toggle animation + if toggleAnim then + ActionButton_HideOverlayGlow(button) + toggleAnim = false + else + ActionButton_ShowOverlayGlow(button) + toggleAnim = true + end + details.onClick() + end) + else + button:SetScript("OnClick", details.onClick) + end + end + button:SetScript("OnEnter", function(self) + GameTooltip:SetOwner(self, "ANCHOR_RIGHT") + GameTooltip:AddLine(details.name, 1, 1, 1) -- sets the tooltip as the button's name + GameTooltip:AddLine(details.tooltip) + GameTooltip:Show() + end) + button:SetScript("OnLeave", function(self) + GameTooltip:Hide() + end) + for i, btn in ipairs(self.buttons) do + button:SetPoint("BOTTOMLEFT", (i - 1) * (32 + self.button_spacing), 0) -- Position each button + end + + return self +end + +local Category = {} +Category.__index = Category + +-- Constructor +---@param name String +---@param config Config +---@return Category +function Category:New(name, parent) + local self = setmetatable({}, Category) + self.name = name + self.parent = parent or nil + + if self.parent then + self.settings, self.layout = Settings.RegisterVerticalLayoutSubcategory(parent.settings, self.name) + self.config = self.parent.config + else + self.settings, self.layout = Settings.RegisterVerticalLayoutCategory(self.name) + self.config = Bastion.Config:New(name) + end + + return self +end + +function Category:AddSubsection(section_title) + local section = CreateSettingsListSectionHeaderInitializer(section_title) + self.layout:AddInitializer(section) + return self +end + +-- Register the Category object with the Blizzard UI +---@return nil +function Category:Register() + Settings.RegisterAddOnCategory(self.settings) +end + +-- Adds a checkbox to the Category +---@param details Table +function Category:Checkbox(details) + local default_value = details.default or false + local variable_name = details.category .. "_" .. details.var + + local setting = Settings.RegisterAddOnSetting(self.settings, details.name, variable_name, type(default_value), self.config:Read(variable_name, default_value)) + + Settings.SetOnValueChangedCallback(variable_name, function(event) + self.config:Write(variable_name, setting:GetValue()) + end) + + local initializer = Settings.CreateCheckBox(self.settings, setting, details.tooltip and ("\n" .. details.tooltip) or "") + + if details.disabled then + initializer:AddModifyPredicate(function() return false end) + self.config:Write(variable_name, false) + end + + return self +end + +-- Adds a checkbox to the Category +---@param details Table +function Category:SubCheck(details) + local default_value = details.default or false + local variable_name = details.category .. "_" .. details.var + + local setting = Settings.RegisterAddOnSetting(self.settings, details.name, variable_name, type(default_value), self.config:Read(variable_name, default_value)) + + Settings.SetOnValueChangedCallback(variable_name, function(event) + print(variable_name, setting:GetValue()) + self.config:Write(variable_name, setting:GetValue()) + details.sub:Toggle() + end) + + local initializer = Settings.CreateCheckBox(self.settings, setting, details.tooltip and ("\n" .. details.tooltip) or "") + + return self +end + +-- Adds a slider to the Category +---@param details Table +function Category:Slider(details) + details = details or {} + local default_value = details.default or 0 + local min_value = details.min or 0 + local max_value = details.max or 100 + + local step = details.step or 1 + local variable_name = details.category .. "_" .. details.var + + local setting = Settings.RegisterAddOnSetting(self.settings, details.name, variable_name, type(default_value), tonumber(self.config:Read(variable_name, default_value))) + + Settings.SetOnValueChangedCallback(variable_name, function(event) + self.config:Write(variable_name, setting:GetValue()) + end) + + local options = Settings.CreateSliderOptions(min_value, max_value, step) + + options:SetLabelFormatter(MinimalSliderWithSteppersMixin.Label.Right) + + local initializer = Settings.CreateSlider(self.settings, setting, options, details.tooltip and ("\n" .. details.tooltip) or "") + + -- if details.disabled then + -- initializer:AddModifyPredicate(function() return false end) + -- self.config:Write(variable_name, false) + -- end + + return self +end + +-- Adds a dropdown to the category +---@param details Table +function Category:Dropdown(details) + local default_value = details.default + local variable_name = details.category .. "_" .. details.var + + local setting = Settings.RegisterAddOnSetting(self.settings, details.name, variable_name, type(default_value), self.config:Read(variable_name, default_value)) + + local function GetOptions() + local container = Settings.CreateControlTextContainer() + for k, v in ipairs(details.options) do + container:Add(v[2], v[1]) + end + return container:GetData() + end + + Settings.SetOnValueChangedCallback(variable_name, function(event) + self.config:Write(variable_name, setting:GetValue()) + end) + + local initializer = Settings.CreateDropDown(self.settings, setting, GetOptions, details.tooltip or "") + + if details.disabled then + initializer:AddModifyPredicate(function() return false end) + self.config:Write(variable_name, false) + end + + return self +end + +Interface.Hotbar = Hotbar +Interface.Category = Category +print("Interface Loaded") +Bastion.Interface = Interface \ No newline at end of file diff --git a/src/_bastion.lua b/src/_bastion.lua index 6d92449..c4b9282 100644 --- a/src/_bastion.lua +++ b/src/_bastion.lua @@ -74,6 +74,8 @@ function Bastion.Bootstrap() Bastion.Cache = Bastion.require("Cache") ---@type Cacheable Bastion.Cacheable = Bastion.require("Cacheable") + ---@type Config + Bastion.Configiration = Bastion.require("Config") ---@type Refreshable Bastion.Refreshable = Bastion.require("Refreshable") ---@type Unit @@ -113,6 +115,8 @@ function Bastion.Bootstrap() Bastion.MythicPlusUtils = Bastion.require("MythicPlusUtils"):New() ---@type NotificationsList Bastion.Notifications = Bastion.NotificationsList:New() + ---@type Interface + Bastion.UI = Bastion.require("Interface") local LIBRARIES = {} local MODULES = {}