---@type Tinkr local Tinkr = ... ---@class Bastion.Globals.SpellName : { [spellId]: string } ---@class Bastion local Bastion = { Enabled = false, Globals = { ---@type Bastion.Globals.SpellName SpellName = {} }, Tick = GetGameTick() } local BastionBase = string.format("%s/%s", "scripts", "bastion") local BastionScriptsBase = string.format("%s/%s", BastionBase, "scripts") local ThirdPartyModulesBase = string.format("%s/%s", "scripts", "BastionScripts") Bastion.__index = Bastion ---@class Bastion.LoadedFiles.Table ---@field [number] { originalPath: string, loadedPath: string, reloadable: boolean, order: number, newPath: string } Bastion.LoadedFiles = {} ---@param filePath string function Bastion:CheckIfLoaded(filePath) for i, file in ipairs(Bastion.LoadedFiles) do if file.loadedPath == filePath or file.originalPath == filePath or file.newPath == filePath then return true end end return false end ---@param path string ---@param extension string ---@return string local function AppendExtension(path, extension) return string.format("%s.%s", path, extension) end ---@param path string ---@param extensions string|string[] local function CheckFileExtensions(path, extensions) local exts = {} if type(extensions) == "string" then exts = { extensions } else exts = extensions end for i, extension in ipairs(exts) do local newPath = path if newPath:sub(extension:len() * -1) ~= extension then newPath = AppendExtension(newPath, extension) end if FileExists(newPath) then return newPath:sub(1, (extension:len() + 2) * -1), true end end return path, false end --- 0 = Failed, 1 = Success, 2 = Already Loaded ---@param filePath string | { filePath: string, reloadable: boolean } ---@param ... any ---@return 0|1|2, table function Bastion:Require(filePath, ...) local loadedFile = { originalPath = type(filePath) == "table" and filePath.filePath or tostring(filePath), newPath = type(filePath) == "table" and filePath.filePath or tostring(filePath), loadedPath = type(filePath) == "table" and filePath.filePath or tostring(filePath), reloadable = type(filePath) == "table" and filePath.reloadable or false, order = #Bastion.LoadedFiles + 1, } local filePathModifier = loadedFile.originalPath:sub(1, 1) local base = filePathModifier == "@" and BastionScriptsBase or filePathModifier == "~" and BastionBase if base then loadedFile.newPath = string.format("%s%s", base, loadedFile.originalPath:sub(2)) loadedFile.loadedPath = loadedFile.newPath end local found = false -- Check if file path has a .lua or .luac extension. If not, try to add one and check if the file exists loadedFile.loadedPath, found = CheckFileExtensions(loadedFile.newPath, { "lua", "luac" }) if not found then Log(string.format("Bastion:Require - Not Found: %s (%s)", loadedFile.newPath, loadedFile.originalPath)) return 0, SafePack(nil) end if not loadedFile.reloadable then if Bastion:CheckIfLoaded(loadedFile.loadedPath) then --Log(string.format("Bastion:Require - Already loaded: %s (%s)", loadedFile.newPath, loadedFile.originalPath)) return 2, SafePack(nil) end end table.insert(Bastion.LoadedFiles, loadedFile) return 1, SafePack(require(loadedFile.loadedPath, Bastion, ...)) end local loadExamples = false local exampleNames = { "ExampleDependency.lua", "ExampleDependencyError.lua", "ExampleLibrary.lua", "ExampleModule.lua", } ---@param dir string local function Load(dir) if dir:sub(1, 1) == "@" then dir = dir:sub(2) dir = string.format("%s/%s", BastionScriptsBase, dir) end if dir:sub(1, 1) == "~" then dir = dir:sub(2) dir = string.format("%s/%s", BastionBase, dir) end local files = ListFiles(dir) for i = 1, #files do local file = files[i] local loadFile = true if not loadExamples then for j = 1, #exampleNames do if file:find(exampleNames[j]) then loadFile = false break end end end if loadFile and (file:sub(-4) == ".lua" or file:sub(-5) == ".luac") then Bastion:Require(dir .. file:sub(1, -5)) end end end local function LoadThird() local thirdPartyModulesFolders = ListFolders(ThirdPartyModulesBase) for i = 1, #thirdPartyModulesFolders do local currentFolderDir = string.format("%s/%s", ThirdPartyModulesBase, thirdPartyModulesFolders[i]) local loaderFilePath = string.format("%s/%s", currentFolderDir, "loader") if FileExists(loaderFilePath .. ".lua") or FileExists(loaderFilePath .. ".luac") then Bastion:Require(loaderFilePath, currentFolderDir) end end end local bastionFiles = { "~/src/ClassMagic/ClassMagic", "~/src/List/List", "~/src/Command/Command", "~/src/Util/Util", "~/src/Library/Library", "~/src/Notification/Notification", "~/src/NotificationList/NotificationList", "~/src/Vector3/Vector3", "~/src/Sequencer/Sequencer", "~/src/Cache/Cache", "~/src/Cacheable/Cacheable", "~/src/Refreshable/Refreshable", "~/src/Unit/Unit", "~/src/Aura/Aura", "~/src/APLTrait/APLTrait", "~/src/APLActor/APLActor", "~/src/APL/APL", "~/src/Module/Module", "~/src/UnitManager/UnitManager", "~/src/ObjectManager/ObjectManager", "~/src/EventManager/EventManager", "~/src/Spell/Spell", "~/src/SpellBook/SpellBook", "~/src/Item/Item", "~/src/ItemBook/ItemBook", "~/src/AuraTable/AuraTable", "~/src/Class/Class", "~/src/Timer/Timer", "~/src/MythicPlusUtils/MythicPlusUtils", "~/src/Config/Config", "~/src/TimeToDie/TimeToDie", } for i = 1, #bastionFiles do Bastion:Require(bastionFiles[i]) end ---@param toggle? boolean function Bastion:Toggle(toggle) Bastion.Enabled = type(toggle) ~= "nil" and toggle or not Bastion.Enabled end Bastion.Globals.Command:Register('toggle', 'Toggle bastion on/off', function() Bastion:Toggle() if Bastion.Enabled then Bastion.Util:Print("Enabled") else Bastion.Util:Print("Disabled") end end) Bastion.Globals.CombatTimer = Bastion.Timer:New("combat", function() return UnitAffectingCombat("player") end) ---@param unitTarget UnitId Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_HEALTH", function(unitTarget) --Bastion.UnitManager:Get(unitTarget):UpdateHealth() end) ---@param unit UnitToken ---@param auras UnitAuraUpdateInfo Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_AURA", function(unit, auras) ---@type Bastion.Unit | nil local u = Bastion.UnitManager:Get(unit) if u then u:GetAuras():OnUpdate(auras) end end) Bastion.Globals.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_SUCCEEDED", function(...) ---@type UnitIds, string, spellId local unit, castGUID, spellID = ... local spell = Bastion.Globals.SpellBook:GetIfRegistered(spellID) if unit == "player" and spell then spell.lastCastAt = GetTime() if spell:GetPostCastFunction() then spell:GetPostCastFunction()(spell) end end end) local playerGuid = UnitGUID("player") local missed = {} Bastion.Globals.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", function() local args = { CombatLogGetCurrentEventInfo() } ---@type string local subEvent = args[2] ---@type string local sourceGUID = args[4] ---@type string local destGUID = args[8] ---@type number local spellID = args[12] ---@type string local spellName = args[13] if subEvent == "SPELL_CAST_SUCCESS" then if (not Bastion.Globals.SpellName[spellID] or Bastion.Globals.SpellName[spellID] ~= spellName) then Bastion.Globals.SpellName[spellID] = spellName end end ---@type Bastion.Unit? local u = Bastion.UnitManager[sourceGUID] ---@type Bastion.Unit local u2 = Bastion.UnitManager[destGUID] local t = GetTime() if type(u) ~= "nil" then u:SetLastCombatTime(t) end if type(u2) ~= "nil" then u2:SetLastCombatTime(t) if subEvent == "SPELL_MISSED" and sourceGUID == playerGuid and spellID == 408 then local missType = args[15] if type(u) ~= "nil" and missType == "IMMUNE" then local castingSpell = u:GetCastingOrChannelingSpell() if castingSpell and type(castingSpell) == "table" then if not missed[castingSpell:GetID()] then missed[castingSpell:GetID()] = true end end end end end end) Bastion.Ticker = C_Timer.NewTicker(0.1, function() Bastion.Tick = GetGameTick() Bastion.Globals.CombatTimer:Check() if Bastion.Enabled then Bastion.ObjectManager:Refresh() Bastion.TimeToDie:Refresh() Bastion:TickModules() end end) Bastion.Globals.Command:Register("dumpspells", "Dump spells to a file", function() local i = 1 local rand = math.random(100000, 999999) while true do local spellName, spellSubName = GetSpellBookItemName(i, BOOKTYPE_SPELL) if not spellName then do break end end -- use spellName and spellSubName here local spellID = select(7, GetSpellInfo(spellName)) if spellID then spellName = spellName:gsub("[%W%s]", "") WriteFile("bastion-" .. UnitClass("player") .. "-" .. rand .. ".lua", "local " .. spellName .. " = Bastion.Globals.SpellBook:GetSpell(" .. spellID .. ")\n", true) end i = i + 1 end end) Bastion.Globals.Command:Register("missed", "Dump the list of immune kidney shot spells", function() for k, v in pairs(missed) do Bastion.Util:Print(k) end end) Load("@Libraries/") Load("@Modules/") Load("@") LoadThird()