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/src/Bastion/Bastion.lua b/src/Bastion/Bastion.lua new file mode 100644 index 0000000..34f48c0 --- /dev/null +++ b/src/Bastion/Bastion.lua @@ -0,0 +1,333 @@ +---@type Tinkr +local Tinkr = ... + +---@class Bastion.Globals.SpellName : { [spellId]: string } + +---@class Bastion +local Bastion = { + Enabled = false, + Globals = { + ---@type Bastion.Globals.SpellName + SpellName = {} + }, + ---@class Bastion.LoadedFiles.Table + ---@field [number] { originalPath: string, loadedPath: string, reloadable: boolean, order: number, newPath: string } + LoadedFiles = {}, + LoadThird = true, + Paths = { + Scripts = "scripts", + BastionBase = "scripts/bastion", + BastionScripts = "scripts/bastion/scripts", + ThirdParty = "scripts/BastionScripts", + }, + Tick = 0, +} + +function Bastion:__index(key) + if Bastion[key] then + return Bastion[key] + end + return rawget(self, key) +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/EventManager/EventManager", + "~/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/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", +} + +---@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 = filePath.filePath or filePath, + newPath = filePath.filePath or filePath, + loadedPath = filePath.filePath or filePath, + reloadable = not filePath.reloadable and false or false, + order = #Bastion.LoadedFiles + 1, + } + + local pathResolutionShortcut = loadedFile.originalPath:sub(1, 1) == "@" and Bastion.Paths.BastionScripts or + loadedFile.originalPath:sub(1, 1) == "~" and Bastion.Paths.BastionBase or + loadedFile.originalPath:sub(1, 1) == "!" and Bastion.Paths.Scripts + + if pathResolutionShortcut then + loadedFile.newPath = string.format("%s%s", pathResolutionShortcut, loadedFile.originalPath:sub(2)) + loadedFile.loadedPath = loadedFile.newPath + end + + if DirectoryExists(loadedFile.newPath) then + local fileList = ListFiles(loadedFile.newPath) + if #fileList > 0 then + local loadResults = {} + for i = 1, #fileList do + local file = loadedFile.newPath .. fileList[i] + local status, returns = Bastion:Require(file, ...) + table.insert(loadResults, { file = file, status = status, returns = returns }) + end + return 1, loadResults + end + Log(string.format("Bastion:Require - No files found in directory: %s", loadedFile.newPath)) + return 0, SafePack(nil) + 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 + +---@param load? boolean +---@param folder? string +local function LoadThird(load, folder) + load = type(load) == "nil" and Bastion.LoadThird or load + folder = folder or Bastion.Paths.ThirdParty + if not load then + return + end + local thirdPartyModulesFolders = ListFolders(folder) + for i = 1, #thirdPartyModulesFolders do + local moduleFolder = thirdPartyModulesFolders[i] + local currentFolderDir = string.format("%s/%s", folder, moduleFolder) + local loaderFilePath = string.format("%s/%s", currentFolderDir, "loader") + if FileExists(loaderFilePath .. ".lua") or FileExists(loaderFilePath .. ".luac") then + Bastion:Require(loaderFilePath, currentFolderDir) + end + end +end + +---@param toggle? boolean +function Bastion:Toggle(toggle) + Bastion.Enabled = type(toggle) ~= "nil" and toggle or not Bastion.Enabled +end + +local loaded = false +function Bastion:Load() + if loaded then + return self + end + + for i = 1, #bastionFiles do + self:Require(bastionFiles[i]) + end + + self.Globals.Command:Register('toggle', 'Toggle bastion on/off', function() + self:Toggle() + if self.Enabled then + self.Util:Print("Enabled") + else + self.Util:Print("Disabled") + end + end) + + self.Globals.CombatTimer = self.Timer:New("combat", function() + return UnitAffectingCombat("player") + end) + + ---@param unitTarget UnitId + self.Globals.EventManager:RegisterWoWEvent("UNIT_HEALTH", function(unitTarget) + --Bastion.UnitManager:Get(unitTarget):UpdateHealth() + end) + + ---@param unit UnitToken + ---@param auras UnitAuraUpdateInfo + self.Globals.EventManager:RegisterWoWEvent("UNIT_AURA", function(unit, auras) + ---@type Bastion.Unit | nil + local u = self.UnitManager:Get(unit) + + if u then + u:GetAuras():OnUpdate(auras) + end + end) + + self.Globals.EventManager:RegisterWoWEvent("UNIT_SPELLCAST_SUCCEEDED", function(...) + ---@type UnitIds, string, spellId + local unit, castGUID, spellID = ... + local spell = self.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 = {} + + self.Globals.EventManager:RegisterWoWEvent("COMBAT_LOG_EVENT_UNFILTERED", function() + local args = { CombatLogGetCurrentEventInfo() } + + local subEvent = args[2] + local sourceGUID = args[4] + local destGUID = args[8] + local spellID = args[12] + local spellName = args[13] + + if subEvent == "SPELL_CAST_SUCCESS" then + if (not self.Globals.SpellName[spellID] or self.Globals.SpellName[spellID] ~= spellName) then + self.Globals.SpellName[spellID] = spellName + end + end + + local u = self.UnitManager[sourceGUID] + local u2 = self.UnitManager[destGUID] + local t = GetTime() + + if u then + u:SetLastCombatTime(t) + end + + if u2 then + u2:SetLastCombatTime(t) + if subEvent == "SPELL_MISSED" and sourceGUID == playerGuid and spellID == 408 then + local missType = args[15] + + if 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) + + self.Ticker = C_Timer.NewTicker(0.1, function() + Bastion.Tick = GetGameTick() + self.Globals.CombatTimer:Check() + + if Bastion.Enabled then + self.ObjectManager:Refresh() + self.TimeToDie:Refresh() + self:TickModules() + end + end) + + self.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) + + self.Globals.Command:Register("missed", "Dump the list of immune kidney shot spells", function() + for k, v in pairs(missed) do + self.Util:Print(k) + end + end) + + self:Require("@/Libraries/") + self:Require("@/Modules/") + self:Require("@/") + LoadThird() + loaded = true + + return self +end + +return Bastion diff --git a/src/_bastion.lua b/src/_bastion.lua index 9001101..df01db3 100644 --- a/src/_bastion.lua +++ b/src/_bastion.lua @@ -1,327 +1,4 @@ ---@type Tinkr local Tinkr = ... ----@class Bastion.Globals.SpellName : { [spellId]: string } - ----@class Bastion -local Bastion = { - Enabled = false, - Globals = { - ---@type Bastion.Globals.SpellName - SpellName = {} - }, - Tick = GetGameTick() -} - -Bastion.__index = Bastion - -local BastionBase = string.format("%s/%s", "scripts", "bastion") -local BastionScriptsBase = string.format("%s/%s", BastionBase, "scripts") -local ThirdPartyModulesBase = string.format("%s/%s", "scripts", "BastionScripts") - ----@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/EventManager/EventManager", - "~/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/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() } - - local subEvent = args[2] - local sourceGUID = args[4] - local destGUID = args[8] - local spellID = args[12] - 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 - - local u = Bastion.UnitManager[sourceGUID] - local u2 = Bastion.UnitManager[destGUID] - local t = GetTime() - - if u then - u:SetLastCombatTime(t) - end - - if u2 then - u2:SetLastCombatTime(t) - if subEvent == "SPELL_MISSED" and sourceGUID == playerGuid and spellID == 408 then - local missType = args[15] - - if 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() +require("scripts/bastion/src/Bastion/Bastion"):Load()