-- Quantum X | An Average Campaign -- Autonomous Real-Time Self-Learning Tactical AI & Dynamic Combat Engine -- Clean up any existing instances if _G.QuantumX_UI and typeof(_G.QuantumX_UI.Destroy) == "function" then pcall(function() _G.QuantumX_UI:Destroy() end) end if _G.QuantumX_HUD and typeof(_G.QuantumX_HUD.Destroy) == "function" then pcall(function() _G.QuantumX_HUD:Destroy() end) end if _G.AACTacticalUI and typeof(_G.AACTacticalUI.Destroy) == "function" then pcall(function() _G.AACTacticalUI:Destroy() end) end if _G.AACTacticalHUD and typeof(_G.AACTacticalHUD.Destroy) == "function" then pcall(function() _G.AACTacticalHUD:Destroy() end) end -- Core Services local Players = game:GetService("Players") local ReplicatedStorage = game:GetService("ReplicatedStorage") local TweenService = game:GetService("TweenService") local UserInputService = game:GetService("UserInputService") local RunService = game:GetService("RunService") local CoreGui = game:GetService("CoreGui") local HttpService = game:GetService("HttpService") local LocalPlayer = Players.LocalPlayer local PlayerGui = LocalPlayer:WaitForChild("PlayerGui", 10) -- Clean number formatting helper local function formatNum(val) if val == nil then return "0" end local n = tonumber(val) if not n then return tostring(val) end if math.abs(n - math.round(n)) < 0.05 then return tostring(math.round(n)) else return string.format("%.1f", n) end end -- Clean percent formatting helper local function formatPercent(val) if val == nil then return "0%" end local n = tonumber(val) or 0 local p = n * 100 if math.abs(p - math.round(p)) < 0.05 then return string.format("%d%%", math.round(p)) else return string.format("%.1f%%", p) end end -- Load game dictionaries with safe fallbacks local Dictionaries = ReplicatedStorage:FindFirstChild("Dictionaries") local AbilitiesDict = {} local EffectsDict = {} local ItemsDict = {} local ClassesDict = {} local RacesDict = {} local BoonsDict = {} local UpgradesDict = {} local DamageTypesDict = {} if Dictionaries then pcall(function() if Dictionaries:FindFirstChild("Abilities") then AbilitiesDict = require(Dictionaries.Abilities) end if Dictionaries:FindFirstChild("Effects") then EffectsDict = require(Dictionaries.Effects) end if Dictionaries:FindFirstChild("Items") then ItemsDict = require(Dictionaries.Items) end if Dictionaries:FindFirstChild("Classes") then ClassesDict = require(Dictionaries.Classes) end if Dictionaries:FindFirstChild("Races") then RacesDict = require(Dictionaries.Races) end if Dictionaries:FindFirstChild("Boons") then BoonsDict = require(Dictionaries.Boons) end if Dictionaries:FindFirstChild("Upgrades") then UpgradesDict = require(Dictionaries.Upgrades) end if Dictionaries:FindFirstChild("DamageTypes") then DamageTypesDict = require(Dictionaries.DamageTypes) end end) end if not next(DamageTypesDict) then DamageTypesDict = { Physical = Color3.fromRGB(232, 88, 91), Fire = Color3.fromRGB(255, 111, 28), Ice = Color3.fromRGB(71, 210, 217), Cold = Color3.fromRGB(71, 210, 217), Thunder = Color3.fromRGB(16, 200, 217), Lightning = Color3.fromRGB(67, 77, 217), Holy = Color3.fromRGB(255, 235, 87), Dark = Color3.fromRGB(120, 40, 140), Void = Color3.fromRGB(60, 20, 70), Force = Color3.fromRGB(129, 85, 167), Poison = Color3.fromRGB(86, 184, 79), Acid = Color3.fromRGB(113, 184, 38), Bleed = Color3.fromRGB(156, 43, 45), Necrotic = Color3.fromRGB(39, 74, 41), Psychic = Color3.fromRGB(193, 70, 181), True = Color3.fromRGB(255, 255, 255) } end local BLACKLISTED_ABILITIES = { ["True Strike"] = true, ["Coup De Grace"] = true, ["Test Strike"] = true, ["Spell Scroll: Sealing Curse"] = true, ["Admin Strike"] = true, ["Debug Kill"] = true } local KNOWN_STATUS_EFFECTS = { Shield = true, Guard = true, Bravery = true, Vulnerable = true, Weak = true, Frail = true, Poison = true, Bleed = true, Voidblaze = true, Burn = true, Scorched = true, Entangled = true, Stun = true, Empowered = true, Strengthened = true, Regen = true, Barkskin = true, Thorns = true, Hemorrhage = true, Ruptured = true, Moonkin_Toxin = true, Weeping_Wound = true, Seared_Soul = true, ["Grovetender's Blessing"] = true, ["A_Mote_of_Hope"] = true, ["Abyssal_Mark"] = true, ["Adrenaline_Surge"] = true, ["Alert"] = true, ["Applied_Toxins"] = true, ["Arcane_Ascension"] = true, ["Arcane_Surge"] = true, ["Atrophy"] = true, ["Backalley_Toxin"] = true, ["Banefire"] = true, ["Banelord"] = true, ["Battle_Prowess"] = true, ["Blessing"] = true, ["Divine_Protection"] = true } local CLASS_SKILLS = { Warrior = { "Strike", "Heavy Strike", "Guard", "Cleave", "Taunt", "Shield Bash", "Whirlwind", "Quick Strike" }, Brawler = { "Strike", "Pummel", "Flurry of Blows", "Uppercut", "Haymaker", "Ground Slam" }, Rogue = { "Knife Slash", "Poison Dagger", "Shadowstep", "Backstab", "Swift Strikes", "Bleedout", "Gouge" }, Mage = { "Fireball", "Arcane Blast", "Frost Nova", "Lightning Strike", "Mystic Shield", "Ignite", "Cold Snap" }, Priest = { "Holy Strike", "Smite", "Healing Word", "Cure Light Wounds", "Cure Wounds", "Bless", "Radiance", "Holy Flame", "Divine Blessing", "Prayer of Healing", "Holy Smite" }, Ranger = { "Shoot", "Rain of Arrows", "Hunter's Mark", "Piercing Shot", "Faerie Arrow", "Quick Shot" }, Bard = { "Inspire", "Discord", "Lullaby", "Harmonic Blast", "Song of Courage" }, Artificer = { "Pistol Shot", "Deploy Turret", "Overcharge", "Static Discharge", "Repair" } } -- ======================================================================== -- STATE & DATA STORE -- ======================================================================== local AACState = { PlaceType = "Unknown", AreaName = "Exploring", CurrentStage = 1, MaxStage = 6, GameStatus = "Lobby", CurrentTurnNumber = 1, ActiveTurnActor = "None", ActiveTurnIsPlayer = false, ActiveTurnEntity = nil, Cooldowns = {}, PartyCooldowns = {}, SummonCooldowns = {}, TurnHistory = {}, LastStats = nil, PartyData = {}, PartyStats = {}, CombatEntities = {}, SelectedPartyTeammate = nil, PlayerRace = "Human", PlayerBoons = {}, LastStatsRequestTick = 0, LastPlayerActionMove = nil, LastPlayerActionTarget = nil, LastPlayerActionTick = 0, -- HUD Settings HUDConfig = { MainHUD_Enabled = true, PartyHUD_Enabled = true, EnemyHUD_Enabled = true, ShowVitals = true, ShowBestMove = true, ShowSkillsList = true, ShowNextTurn = true, ShowGuardMath = true, ShowDoTMath = true, MobileButton_Enabled = true } } local PLACE_IDS = { LOBBY = 80734098185936, NORMAL = 111943251737481 } -- ======================================================================== -- REAL-TIME ONLINE MACHINE LEARNING AI ENGINE (SGD & Q-LEARNING) -- ======================================================================== local AACOnlineLearningEngine = { StorageFile = "QuantumX_AI/learned_knowledge.json", StorageEnabled = (typeof(writefile) == "function" and typeof(readfile) == "function"), -- Model Metrics & Online SGD Parameters LearningRate = 0.08, TotalOnlineGradientSteps = 0, TotalObservations = 0, TotalBattles = 0, MeanAbsoluteError = 0.0, LossHistory = {}, -- Learned Feature Parameters LearnedResistances = {}, LearnedProfiles = {}, SkillCalibrations = {}, -- [skill_targetKey] = multiplier, [skill] = generalMultiplier -- Live Combat Trackers (Reset per battle) ActiveCooldowns = {}, -- [instKey] = { [moveName] = remainingTurns } ActiveLastMove = {}, -- [instKey] = lastMoveName PendingAttacker = nil, PendingTarget = nil, PendingMove = nil, PendingTimestamp = 0, LastKnownHPs = {}, -- [instKey] = hp LastKnownShields = {} -- [instKey] = shield } function AACOnlineLearningEngine.GetCleanEnemyKey(name) if not name or type(name) ~= "string" then return "Unknown Enemy" end local cleaned = name:gsub("%s*%d+$", ""):gsub("%d+$", ""):gsub("^%s*(.-)%s*$", "%1") return (cleaned ~= "") and cleaned or name end function AACOnlineLearningEngine.GetOrCreateProfile(enemyName) local key = AACOnlineLearningEngine.GetCleanEnemyKey(enemyName) if not AACOnlineLearningEngine.LearnedProfiles[key] then AACOnlineLearningEngine.LearnedProfiles[key] = { name = key, totalObservations = 0, moveUsageCounts = {}, moveTransitions = {}, turnPreferences = {}, hpTriggers = {}, learnedCooldowns = {}, learnedEnergyCosts = {}, learnedDamageStats = {}, learnedEffects = {}, lastSeenTurn = 0 } end return AACOnlineLearningEngine.LearnedProfiles[key] end function AACOnlineLearningEngine.GetLearnedResistance(enemyName, damageType) local key = AACOnlineLearningEngine.GetCleanEnemyKey(enemyName) damageType = damageType or "Physical" if AACOnlineLearningEngine.LearnedResistances[key] and AACOnlineLearningEngine.LearnedResistances[key][damageType] then return AACOnlineLearningEngine.LearnedResistances[key][damageType] end return 1.0 end function AACOnlineLearningEngine.GetSkillCalibration(skillName, targetName) if not skillName then return 1.0 end local targKey = targetName and AACOnlineLearningEngine.GetCleanEnemyKey(targetName) or "General" local specificKey = skillName .. "_" .. targKey if AACOnlineLearningEngine.SkillCalibrations[specificKey] then return AACOnlineLearningEngine.SkillCalibrations[specificKey] end if AACOnlineLearningEngine.SkillCalibrations[skillName] then return AACOnlineLearningEngine.SkillCalibrations[skillName] end return 1.0 end -- Persistent Storage Save & Load function AACOnlineLearningEngine.SaveKnowledge() if not AACOnlineLearningEngine.StorageEnabled then return end pcall(function() if typeof(makefolder) == "function" and typeof(isfolder) == "function" then if not isfolder("QuantumX_AI") then makefolder("QuantumX_AI") end end local data = { version = "3.0", savedAt = os.date("%Y-%m-%d %H:%M:%S"), totalBattles = AACOnlineLearningEngine.TotalBattles, totalSteps = AACOnlineLearningEngine.TotalOnlineGradientSteps, totalObservations = AACOnlineLearningEngine.TotalObservations, mae = AACOnlineLearningEngine.MeanAbsoluteError, resistances = AACOnlineLearningEngine.LearnedResistances, profiles = AACOnlineLearningEngine.LearnedProfiles, calibrations = AACOnlineLearningEngine.SkillCalibrations } local json = HttpService:JSONEncode(data) writefile(AACOnlineLearningEngine.StorageFile, json) end) end function AACOnlineLearningEngine.LoadKnowledge() if not AACOnlineLearningEngine.StorageEnabled then return end pcall(function() if typeof(isfile) == "function" and isfile(AACOnlineLearningEngine.StorageFile) then local json = readfile(AACOnlineLearningEngine.StorageFile) local data = HttpService:JSONDecode(json) if data then if data.profiles then AACOnlineLearningEngine.LearnedProfiles = data.profiles end if data.resistances then AACOnlineLearningEngine.LearnedResistances = data.resistances end if data.calibrations then AACOnlineLearningEngine.SkillCalibrations = data.calibrations end AACOnlineLearningEngine.TotalBattles = data.totalBattles or 0 AACOnlineLearningEngine.TotalOnlineGradientSteps = data.totalSteps or 0 AACOnlineLearningEngine.TotalObservations = data.totalObservations or 0 AACOnlineLearningEngine.MeanAbsoluteError = data.mae or 0.0 end end end) end -- Initialize persistent knowledge on load AACOnlineLearningEngine.LoadKnowledge() -- Real-Time Online Gradient Descent (SGD) & Calibration Update on Any Hit function AACOnlineLearningEngine.TrainOnlineHit(attacker, target, moveName, actualDamage, predictedDamage, damageType) if not target or not actualDamage or actualDamage <= 0 then return end local targName = (typeof(target) == "Instance" and target.Name) or tostring(target) local targKey = AACOnlineLearningEngine.GetCleanEnemyKey(targName) damageType = damageType or "Physical" predictedDamage = math.max(1.0, predictedDamage or 1.0) local error = actualDamage - predictedDamage local absError = math.abs(error) local loss = error * error AACOnlineLearningEngine.TotalOnlineGradientSteps = AACOnlineLearningEngine.TotalOnlineGradientSteps + 1 table.insert(AACOnlineLearningEngine.LossHistory, 1, loss) if #AACOnlineLearningEngine.LossHistory > 50 then table.remove(AACOnlineLearningEngine.LossHistory) end -- Update Mean Absolute Error (MAE) if AACOnlineLearningEngine.MeanAbsoluteError == 0.0 then AACOnlineLearningEngine.MeanAbsoluteError = absError else AACOnlineLearningEngine.MeanAbsoluteError = (AACOnlineLearningEngine.MeanAbsoluteError * 0.85) + (absError * 0.15) end -- Real-Time Direct Calibration Ratio (Instant 100% Convergence) if moveName then local ratio = actualDamage / predictedDamage if ratio > 0.05 and ratio < 5.0 then local specKey = moveName .. "_" .. targKey AACOnlineLearningEngine.SkillCalibrations[specKey] = ratio AACOnlineLearningEngine.SkillCalibrations[moveName] = ratio end end -- Gradient update for Enemy Damage Resistance AACOnlineLearningEngine.LearnedResistances[targKey] = AACOnlineLearningEngine.LearnedResistances[targKey] or {} local curRes = AACOnlineLearningEngine.LearnedResistances[targKey][damageType] or 1.0 local gradStep = (error / predictedDamage) * AACOnlineLearningEngine.LearningRate local newRes = math.clamp(curRes + gradStep, 0.10, 3.5) AACOnlineLearningEngine.LearnedResistances[targKey][damageType] = newRes task.spawn(AACOnlineLearningEngine.SaveKnowledge) end function AACOnlineLearningEngine.RecordMoveUsage(actorName, enemyInstance, moveName) if not actorName or not moveName or BLACKLISTED_ABILITIES[moveName] then return end local prof = AACOnlineLearningEngine.GetOrCreateProfile(actorName) prof.totalObservations = prof.totalObservations + 1 AACOnlineLearningEngine.TotalObservations = AACOnlineLearningEngine.TotalObservations + 1 prof.moveUsageCounts[moveName] = (prof.moveUsageCounts[moveName] or 0) + 1 prof.lastSeenTurn = AACState.CurrentTurnNumber local curTurn = AACState.CurrentTurnNumber or 1 prof.turnPreferences[curTurn] = prof.turnPreferences[curTurn] or {} prof.turnPreferences[curTurn][moveName] = (prof.turnPreferences[curTurn][moveName] or 0) + 1 if enemyInstance and enemyInstance:IsA("Model") then local curHp = enemyInstance:GetAttribute("HP") or 100 local maxHp = enemyInstance:GetAttribute("MaxHP") or curHp or 100 if (curHp / maxHp) <= 0.40 then prof.hpTriggers[moveName] = (prof.hpTriggers[moveName] or 0) + 1 end end local instKey = enemyInstance or actorName local prevMove = AACOnlineLearningEngine.ActiveLastMove[instKey] if prevMove and prevMove ~= moveName then prof.moveTransitions[prevMove] = prof.moveTransitions[prevMove] or {} prof.moveTransitions[prevMove][moveName] = (prof.moveTransitions[prevMove][moveName] or 0) + 1 end AACOnlineLearningEngine.ActiveLastMove[instKey] = moveName local abData = AbilitiesDict[moveName] local knownCd = (abData and tonumber(abData.Cooldown)) or (prof.learnedCooldowns[moveName]) or 0 local knownCost = (abData and tonumber(abData.Cost)) or (prof.learnedEnergyCosts[moveName]) or 0 if knownCd > 0 then prof.learnedCooldowns[moveName] = knownCd AACOnlineLearningEngine.ActiveCooldowns[instKey] = AACOnlineLearningEngine.ActiveCooldowns[instKey] or {} AACOnlineLearningEngine.ActiveCooldowns[instKey][moveName] = knownCd end if knownCost > 0 then prof.learnedEnergyCosts[moveName] = knownCost end AACOnlineLearningEngine.PendingAttacker = instKey AACOnlineLearningEngine.PendingMove = moveName AACOnlineLearningEngine.PendingTimestamp = tick() task.spawn(AACOnlineLearningEngine.SaveKnowledge) end function AACOnlineLearningEngine.RecordObservedDamage(actorName, moveName, damageDealt, dmgType, multihit) if not actorName or not moveName or not damageDealt or damageDealt <= 0 then return end local prof = AACOnlineLearningEngine.GetOrCreateProfile(actorName) local stat = prof.learnedDamageStats[moveName] if not stat then prof.learnedDamageStats[moveName] = { min = damageDealt, max = damageDealt, sum = damageDealt, hits = 1, avg = damageDealt, dmgType = dmgType or "Physical", multihit = multihit or 1 } else stat.min = math.min(stat.min, damageDealt) stat.max = math.max(stat.max, damageDealt) stat.sum = stat.sum + damageDealt stat.hits = stat.hits + 1 stat.avg = math.round(stat.sum / stat.hits) stat.dmgType = dmgType or stat.dmgType stat.multihit = multihit or stat.multihit end end function AACOnlineLearningEngine.TickTurn() for instKey, cdTable in pairs(AACOnlineLearningEngine.ActiveCooldowns) do for mName, rem in pairs(cdTable) do if rem > 0 then cdTable[mName] = rem - 1 end end end end function AACOnlineLearningEngine.ResetActiveCombat() AACOnlineLearningEngine.ActiveCooldowns = {} AACOnlineLearningEngine.ActiveLastMove = {} AACOnlineLearningEngine.PendingAttacker = nil AACOnlineLearningEngine.PendingTarget = nil AACOnlineLearningEngine.PendingMove = nil AACOnlineLearningEngine.LastKnownHPs = {} AACOnlineLearningEngine.LastKnownShields = {} AACOnlineLearningEngine.TotalBattles = AACOnlineLearningEngine.TotalBattles + 1 task.spawn(AACOnlineLearningEngine.SaveKnowledge) end -- Skill Probabilities Calculator (This Turn vs Next Turn) function AACOnlineLearningEngine.CalculateSkillProbabilities(enemy) if not enemy then return {}, {}, {} end local eName = enemy.Name local prof = AACOnlineLearningEngine.GetOrCreateProfile(eName) local instKey = enemy or eName local eHp = enemy:GetAttribute("HP") or enemy:GetAttribute("MaxHP") or 100 local eMaxHp = enemy:GetAttribute("MaxHP") or eHp or 100 local eEnergy = enemy:GetAttribute("Energy") or 0 local eLevel = enemy:GetAttribute("Level") or 1 local isStunned = (enemy:GetAttribute("Stun") and enemy:GetAttribute("Stun") > 0) or false local curTurn = AACState.CurrentTurnNumber or 1 local candidateMoves = {} local moveSet = {} local function addMove(mName) if mName and not moveSet[mName] and not BLACKLISTED_ABILITIES[mName] then moveSet[mName] = true table.insert(candidateMoves, mName) end end for mName, _ in pairs(prof.moveUsageCounts) do addMove(mName) end local cleanName = AACOnlineLearningEngine.GetCleanEnemyKey(eName):lower() for abName, abData in pairs(AbilitiesDict) do local abLower = abName:lower() if abData and not BLACKLISTED_ABILITIES[abName] then if (cleanName:find("shaman") and (abLower:find("bolt") or abLower:find("curse") or abLower:find("hex") or abLower:find("flame"))) or (cleanName:find("dragon") and (abLower:find("dragon") or abLower:find("breath") or abLower:find("claw") or abLower:find("roar"))) or (cleanName:find("spider") and (abLower:find("spider") or abLower:find("web") or abLower:find("venom") or abLower:find("bite"))) or (cleanName:find("wolf") and (abLower:find("howl") or abLower:find("bite") or abLower:find("slash"))) or (cleanName:find("bandit") and (abLower:find("slash") or abLower:find("stab") or abLower:find("shot"))) or (cleanName:find("cultist") and (abLower:find("dark") or abLower:find("ritual") or abLower:find("void"))) then if (abData.Damage or 0) > 0 and (abData.Damage or 0) <= 60 then addMove(abName) end end end end if #candidateMoves == 0 then addMove("Attack") end local instCooldowns = AACOnlineLearningEngine.ActiveCooldowns[instKey] or {} local prevMove = AACOnlineLearningEngine.ActiveLastMove[instKey] local transitions = (prevMove and prof.moveTransitions[prevMove]) or {} local turnPref = prof.turnPreferences[curTurn] or {} -- 1. THIS TURN PROBABILITIES local rawScoresThis = {} local totalScoreThis = 0.0 for _, mName in ipairs(candidateMoves) do local remCd = instCooldowns[mName] or 0 local cost = prof.learnedEnergyCosts[mName] or (AbilitiesDict[mName] and AbilitiesDict[mName].Cost) or 0 if cost == "X" then cost = eEnergy end if isStunned or remCd > 0 or (cost > eEnergy) then rawScoresThis[mName] = 0.0 else local usage = prof.moveUsageCounts[mName] or 1 local transBonus = (transitions[mName] or 0) * 3.0 local turnBonus = (turnPref[mName] or 0) * 2.0 local hpBonus = ((eHp / eMaxHp <= 0.40) and (prof.hpTriggers[mName] or 0) * 3.0) or 0 local score = usage + transBonus + turnBonus + hpBonus rawScoresThis[mName] = score totalScoreThis = totalScoreThis + score end end local probsThis = {} for _, mName in ipairs(candidateMoves) do if totalScoreThis > 0 then probsThis[mName] = rawScoresThis[mName] / totalScoreThis else probsThis[mName] = (mName == "Attack" or #candidateMoves == 1) and 1.0 or 0.0 end end -- 2. NEXT TURN PROBABILITIES local simEnergy = eEnergy + 1 local rawScoresNext = {} local totalScoreNext = 0.0 for _, mName in ipairs(candidateMoves) do local remCd = instCooldowns[mName] or 0 local simCd = math.max(0, remCd - 1) local cost = prof.learnedEnergyCosts[mName] or (AbilitiesDict[mName] and AbilitiesDict[mName].Cost) or 0 if cost == "X" then cost = simEnergy end local knownCd = prof.learnedCooldowns[mName] or (AbilitiesDict[mName] and AbilitiesDict[mName].Cooldown) or 0 local likelyUsedThis = probsThis[mName] or 0.0 if simCd > 0 or (cost > simEnergy) then rawScoresNext[mName] = 0.0 else local usage = prof.moveUsageCounts[mName] or 1 local score = usage if knownCd > 0 and likelyUsedThis >= 0.60 then score = score * (1.0 - likelyUsedThis) end rawScoresNext[mName] = score totalScoreNext = totalScoreNext + score end end local probsNext = {} for _, mName in ipairs(candidateMoves) do if totalScoreNext > 0 then probsNext[mName] = rawScoresNext[mName] / totalScoreNext else probsNext[mName] = (mName == "Attack" or #candidateMoves == 1) and 1.0 or 0.0 end end -- 3. COMPILE SKILLS TABLE local detailedSkills = {} for _, mName in ipairs(candidateMoves) do local abData = AbilitiesDict[mName] or {} local stat = prof.learnedDamageStats[mName] local baseDmg = stat and stat.avg or abData.Damage or 0 if baseDmg == 0 then if eMaxHp <= 15 then baseDmg = math.max(2, 2 + eLevel) elseif eMaxHp <= 35 then baseDmg = math.max(4, math.round(4 + eLevel * 1.5)) elseif eMaxHp <= 70 then baseDmg = math.max(8, math.round(8 + eLevel * 2)) else baseDmg = math.max(14, math.round(14 + eLevel * 2.5)) end end local multihit = (stat and stat.multihit) or (abData.Multihit) or 1 local totalDmg = math.round(baseDmg * multihit) local dmgType = (stat and stat.dmgType) or (abData.DamageType) or "Physical" local cost = prof.learnedEnergyCosts[mName] or (abData.Cost) or 0 local cd = prof.learnedCooldowns[mName] or (abData.Cooldown) or 0 local remCd = instCooldowns[mName] or 0 local effectsList = {} if prof.learnedEffects[mName] then for ef, _ in pairs(prof.learnedEffects[mName]) do table.insert(effectsList, ef) end end if abData.Effects and type(abData.Effects) == "table" then for _, ef in ipairs(abData.Effects) do if type(ef) == "string" and not table.find(effectsList, ef) then table.insert(effectsList, ef) end end end table.insert(detailedSkills, { name = mName, damage = totalDmg, baseDamage = baseDmg, multihit = multihit, damageType = dmgType, cost = cost, cooldown = cd, remainingCooldown = remCd, probThisTurn = probsThis[mName] or 0.0, probNextTurn = probsNext[mName] or 0.0, effects = effectsList, targetType = abData.TargetType or "SingleEnemy", isAoe = (abData.TargetType == "AllEnemy" or abData.TargetType == "All") }) end table.sort(detailedSkills, function(a, b) if math.abs(a.probThisTurn - b.probThisTurn) > 0.05 then return a.probThisTurn > b.probThisTurn end return a.damage > b.damage end) return probsThis, probsNext, detailedSkills end -- ======================================================================== -- LIVE PLAYER DATA READER -- ======================================================================== local function getLivePlayerStats() local pClass = LocalPlayer:GetAttribute("Class") or "Adventurer" local pSubclass = LocalPlayer:GetAttribute("Subclass") or "None" local pLevel = LocalPlayer:GetAttribute("Level") or 1 local pExp = LocalPlayer:GetAttribute("EXP") or 0 local pHp = LocalPlayer:GetAttribute("HP") or 100 local pMaxHp = LocalPlayer:GetAttribute("MaxHP") or 100 local pEnergy = LocalPlayer:GetAttribute("Energy") or 0 local pMaxEnergy = LocalPlayer:GetAttribute("MaxEnergy") or 6 local pGold = LocalPlayer:GetAttribute("Gold") or 0 local isMyTurn = LocalPlayer:GetAttribute("Turn") == true if PlayerGui then local playerGui = PlayerGui:FindFirstChild("PlayerGUI") if playerGui and playerGui:FindFirstChild("PlayerInfo") then local pInfo = playerGui.PlayerInfo if pInfo:FindFirstChild("HealthFrame") and pInfo.HealthFrame:FindFirstChild("HealthText") then local rawH = pInfo.HealthFrame.HealthText.Text local curH, maxH = rawH:match("(%d+)%s*/%s*(%d+)") if curH and maxH then pHp = tonumber(curH) or pHp pMaxHp = tonumber(maxH) or pMaxHp end end if pInfo:FindFirstChild("EnergyFrame") and pInfo.EnergyFrame:FindFirstChild("EnergyText") then local rawE = pInfo.EnergyFrame.EnergyText.Text local curE, maxE = rawE:match("(%d+)%s*/%s*(%d+)") if curE and maxE then pEnergy = tonumber(curE) or pEnergy pMaxEnergy = tonumber(maxE) or pMaxEnergy end end if pInfo:FindFirstChild("Info") then local rawInfo = pInfo.Info.Text local cls, lvl = rawInfo:match("^(.-)%s+Lvl%s+(%d+)") if cls and lvl then pClass = cls:gsub("^%s*(.-)%s*$", "%1") pLevel = tonumber(lvl) or pLevel end end if pInfo:FindFirstChild("TurnTimer") and pInfo.TurnTimer.Visible then isMyTurn = true end end end local cStats = AACState.LastStats or {} local critChance = tonumber(cStats.CritChance) or 0.05 local critDamage = tonumber(cStats.CritDamage) or 1.50 local lifesteal = tonumber(cStats.Lifesteal) or 0.00 local blockChance = tonumber(cStats.BlockChance) or 0.10 local blockDR = tonumber(cStats.BlockDR) or 0.50 local dodgeChance = tonumber(cStats.DodgeChance) or 0.05 local energyGain = tonumber(cStats.EnergyGain) or 0.00 local incomingHealing = tonumber(cStats.IncomingHealing) or 1.00 local outgoingHealing = tonumber(cStats.OutgoingHealing) or 1.00 return { class = pClass, subclass = pSubclass, level = pLevel, exp = pExp, hp = pHp, maxHp = pMaxHp, energy = pEnergy, maxEnergy = pMaxEnergy, gold = pGold, isMyTurn = isMyTurn, critChance = critChance, critDamage = critDamage, lifesteal = lifesteal, blockChance = blockChance, blockDR = blockDR, dodgeChance = dodgeChance, energyGain = energyGain, incomingHealing = incomingHealing, outgoingHealing = outgoingHealing } end local function getPlayerOnlyRaceAndBoons(actorPlayer) if not actorPlayer or not actorPlayer:IsA("Player") then return "Human", {} end local isLocal = (actorPlayer == LocalPlayer) local race = actorPlayer:GetAttribute("Race") local boons = {} local pData = AACState.PartyData[actorPlayer.Name] if pData then if not race and pData.Race then race = pData.Race end if pData.BoonsEquipped and type(pData.BoonsEquipped) == "table" then for _, b in ipairs(pData.BoonsEquipped) do if not table.find(boons, b) then table.insert(boons, b) end end end end if isLocal and AACState.LastStats and type(AACState.LastStats) == "table" then if not race and AACState.LastStats.Race then race = AACState.LastStats.Race end if AACState.LastStats.BoonsEquipped and type(AACState.LastStats.BoonsEquipped) == "table" then for _, b in ipairs(AACState.LastStats.BoonsEquipped) do if not table.find(boons, b) then table.insert(boons, b) end end end end race = race or "Human" return race, boons end local function getLivePlayerSkills() local skillsList = {} local cooldowns = {} if PlayerGui then local playerGui = PlayerGui:FindFirstChild("PlayerGUI") if playerGui and playerGui:FindFirstChild("PlayerInfo") and playerGui.PlayerInfo:FindFirstChild("AbilitiesFrame") then local scroll = playerGui.PlayerInfo.AbilitiesFrame:FindFirstChild("AbilitiesScrollingFrame") if scroll then for _, btn in ipairs(scroll:GetChildren()) do if btn:IsA("ImageButton") and AbilitiesDict[btn.Name] and not BLACKLISTED_ABILITIES[btn.Name] then table.insert(skillsList, btn.Name) local cd = 0 if btn:FindFirstChild("Cooldown") and btn.Cooldown.Visible and btn.Cooldown:FindFirstChild("CooldownText") then local cdNum = btn.Cooldown.CooldownText.Text:match("(%d+)") cd = tonumber(cdNum) or 1 end cooldowns[btn.Name] = cd end end end end end if #skillsList == 0 then local pData = AACState.PartyData[LocalPlayer.Name] if pData and pData.Abilities and #pData.Abilities > 0 then for _, ab in ipairs(pData.Abilities) do if AbilitiesDict[ab] and not BLACKLISTED_ABILITIES[ab] then table.insert(skillsList, ab) cooldowns[ab] = AACState.Cooldowns[ab] or 0 end end end end if #skillsList == 0 then local pClass = LocalPlayer:GetAttribute("Class") or "Priest" local classList = CLASS_SKILLS[pClass] or CLASS_SKILLS.Priest for _, sk in ipairs(classList) do if AbilitiesDict[sk] and not BLACKLISTED_ABILITIES[sk] then table.insert(skillsList, sk) cooldowns[sk] = AACState.Cooldowns[sk] or 0 end end end return skillsList, cooldowns end local function detectPlaceAndArea() local placeId = game.PlaceId if placeId == PLACE_IDS.LOBBY or workspace:FindFirstChild("Lost Tavern") or workspace:FindFirstChild("Tavern") then return "Main Lobby", "Lost Tavern", 0, 0 end local rawAreaText = "" if PlayerGui then local areaGui = PlayerGui:FindFirstChild("AreaGUI") if areaGui and areaGui:FindFirstChild("Status") then rawAreaText = areaGui.Status.Text end end local areaName = AACState.AreaName or "Campaign Area" local stage = AACState.CurrentStage or 1 local maxStage = AACState.MaxStage or 6 if rawAreaText ~= "" then local aName, curS, maxS = rawAreaText:match("^(.-)%s*%((%d+)%s*/%s*(%d+)%)") if aName and curS and maxS then areaName = aName:gsub("^%s*(.-)%s*$", "%1") stage = tonumber(curS) or stage maxStage = tonumber(maxS) or maxStage else areaName = rawAreaText end end local isEvent = false local lowerName = areaName:lower() if lowerName:find("event") or lowerName:find("festival") or lowerName:find("halloween") or lowerName:find("christmas") or lowerName:find("special") then isEvent = true end if PlayerGui and PlayerGui:FindFirstChild("EventParty") then isEvent = true end if workspace:FindFirstChild("EventParts") or ReplicatedStorage:FindFirstChild("EventRemotes") then isEvent = true end local placeType = isEvent and "Event Campaign" or "Normal Campaign" return placeType, areaName, stage, maxStage end local function detectGameStatus() local placeType, _, _, _ = detectPlaceAndArea() if placeType == "Main Lobby" then return "In Lobby" end if PlayerGui then local restGui = PlayerGui:FindFirstChild("RestGUI") if restGui and restGui:FindFirstChild("RestFrame") and restGui.RestFrame.Visible then local frame1 = restGui.RestFrame:FindFirstChild("Frame1") if frame1 and frame1:FindFirstChild("Stash") and frame1.Stash.Visible then return "Camp (Stash)" end return "Camp (Rest)" end local encGui = PlayerGui:FindFirstChild("EncounterGUI") if encGui and encGui:FindFirstChild("EncounterFrame") and encGui.EncounterFrame.Visible then return "Event Decision" end local gameOver = PlayerGui:FindFirstChild("GameOver") if gameOver and gameOver:FindFirstChild("GameOverFrame") and gameOver.GameOverFrame.Visible then return "Game Over" end local combatUi = PlayerGui:FindFirstChild("PlayerGUI") if combatUi and combatUi:FindFirstChild("PlayerInfo") and combatUi.PlayerInfo.Position.Y.Scale < 1.05 then return "In Battle" end if combatUi and combatUi:FindFirstChild("TurnFrame") and combatUi.TurnFrame.Position.X.Scale > -0.01 then return "In Battle" end end local enemiesFolder = workspace:FindFirstChild("Enemies") if enemiesFolder and #enemiesFolder:GetChildren() > 0 then for _, enemy in ipairs(enemiesFolder:GetChildren()) do if enemy:IsA("Model") and enemy:GetAttribute("isAlive") ~= false then return "In Battle" end end end local summonsFolder = workspace:FindFirstChild("Summons") if summonsFolder and #summonsFolder:GetChildren() > 0 then return "In Battle" end if workspace:FindFirstChild("Restmap") and #workspace.Restmap:GetChildren() > 0 then return "Camp (Rest)" end return "Exploring" end -- Active Enemies Radar local function getActiveEnemies() local enemies = {} local seen = {} local function addCandidate(cand) if not cand or not cand.Parent then return end if seen[cand] then return end if cand == LocalPlayer.Character then return end for _, p in ipairs(Players:GetPlayers()) do if p.Character == cand or p.Name == cand.Name then return end end if cand:GetAttribute("isPlayerSummon") == true then return end local isAlive = cand:GetAttribute("isAlive") local hp = cand:GetAttribute("HP") if isAlive == false then return end if hp and hp <= 0 then return end seen[cand] = true table.insert(enemies, cand) end local enemiesFolder = workspace:FindFirstChild("Enemies") or game.Workspace:FindFirstChild("Enemies") if enemiesFolder then for _, child in ipairs(enemiesFolder:GetChildren()) do addCandidate(child) end end local summonsFolder = workspace:FindFirstChild("Summons") or game.Workspace:FindFirstChild("Summons") if summonsFolder then for _, child in ipairs(summonsFolder:GetChildren()) do addCandidate(child) end end local battlemap = workspace:FindFirstChild("Battlemap") or game.Workspace:FindFirstChild("Battlemap") if battlemap then for _, child in ipairs(battlemap:GetChildren()) do if child:IsA("Model") and child:GetAttribute("HP") then addCandidate(child) end end end for _, entityData in pairs(AACState.CombatEntities) do local ref = entityData.Reference if ref and ref.Parent and not ref:IsA("Player") then addCandidate(ref) end end if PlayerGui then local playerGui = PlayerGui:FindFirstChild("PlayerGUI") if playerGui and playerGui:FindFirstChild("TurnFrame") and playerGui.TurnFrame:FindFirstChild("TurnScrollingFrame") then for _, btn in ipairs(playerGui.TurnFrame.TurnScrollingFrame:GetChildren()) do if btn:IsA("ImageButton") and btn:FindFirstChild("TurnText") then local rawText = btn.TurnText.Text local eName = rawText:match("^(.-)%s*%(") or rawText eName = eName:gsub("^%s*(.-)%s*$", "%1") if eName ~= "" and eName ~= LocalPlayer.Name and not Players:FindFirstChild(eName) then local foundModel = (enemiesFolder and enemiesFolder:FindFirstChild(eName)) or (summonsFolder and summonsFolder:FindFirstChild(eName)) or workspace:FindFirstChild(eName) if foundModel then addCandidate(foundModel) end end end end end end for _, obj in ipairs(workspace:GetChildren()) do if obj:IsA("Model") and obj:GetAttribute("HP") and not Players:GetPlayerFromCharacter(obj) then addCandidate(obj) end end return enemies end -- Status Effects Reader local function getEntityEffects(entity) local effects = {} if not entity then return effects end local attrs = entity:GetAttributes() for name, val in pairs(attrs) do local isValidEffect = (EffectsDict and EffectsDict[name] ~= nil) or KNOWN_STATUS_EFFECTS[name] == true if isValidEffect and val ~= 0 and val ~= false and val ~= "" then local stacks = tonumber(val) or 1 effects[name] = { stacks = stacks, name = name } end end if entity == LocalPlayer and PlayerGui then local playerGui = PlayerGui:FindFirstChild("PlayerGUI") if playerGui and playerGui:FindFirstChild("EffectsFrame") then for _, efObj in ipairs(playerGui.EffectsFrame:GetChildren()) do if efObj:IsA("GuiObject") and efObj.Name ~= "EffectsTemplate" then local efName = efObj.Name local isValidEffect = (EffectsDict and EffectsDict[efName] ~= nil) or KNOWN_STATUS_EFFECTS[efName] == true if isValidEffect then local dur = 1 if efObj:FindFirstChild("EffectLabel") and efObj.EffectLabel:FindFirstChild("Duration") then dur = tonumber(efObj.EffectLabel.Duration.Text) or 1 end if not effects[efName] then effects[efName] = { stacks = dur, name = efName } end end end end end end return effects end local function getEffectCleanSummary(entity, effectName, stacks) local hp = entity:GetAttribute("HP") or 100 local maxHp = entity:GetAttribute("MaxHP") or 100 stacks = tonumber(stacks) or 1 if effectName == "Shield" then return "Blocks " .. formatNum(stacks) .. " damage" elseif effectName == "Bravery" then return "+Bonus attack power" elseif effectName == "Vulnerable" then return "+25% extra damage taken" elseif effectName == "Weak" then return "-20% reduced attack damage" elseif effectName == "Frail" then return "-30% reduced shield and block power" elseif effectName == "Guard" then return "-50% damage taken (Guarding)" elseif effectName == "Burn" or effectName == "Scorched" then return "+25% extra Fire damage taken" elseif effectName == "Seared_Soul" then return "+50% extra Dark/Void damage taken" elseif effectName == "Empowered" or effectName == "Strengthened" then return "+25% bonus attack damage" elseif effectName == "Poison" then local dot = 1 + (hp * 0.025) + (stacks * 0.5) return "Takes ~" .. formatNum(dot) .. " Poison damage each turn" elseif effectName == "Voidblaze" then local dot = (maxHp * 0.025) + 0.5 + (stacks * 1.5) if entity:GetAttribute("Scorched") then dot = dot * 1.3 end if entity:GetAttribute("Seared_Soul") then dot = dot * 1.5 end return "Takes ~" .. formatNum(dot) .. " Void damage each turn" elseif effectName == "Bleed" then local dot = 1.8 + (0.7 * stacks) if entity:GetAttribute("Weeping_Wound") then dot = dot + (2 + 0.01 * maxHp) end if entity:GetAttribute("Moonkin_Toxin") then dot = (dot + 2) * 1.2 end if entity:GetAttribute("Hemorrhage") then dot = dot * 1.3 end if entity:GetAttribute("Ruptured") then dot = dot * 1.2 end return "Takes ~" .. formatNum(dot) .. " Bleed damage each turn" elseif effectName == "Entangled" then return "Takes 2 damage each turn" elseif effectName == "Stun" then return "Stunned: Cannot move or act" elseif effectName:find("Blessing") or effectName:find("Regen") then return "Healing and regeneration active" elseif effectName:find("Barkskin") or effectName:find("Thorns") then return "+Defense and reflects damage" end if EffectsDict[effectName] and EffectsDict[effectName].Description then return EffectsDict[effectName].Description end return "Status active (" .. formatNum(stacks) .. " stacks)" end -- DoT Fatality Predictor local function simulateEntityDoTFatality(entity, incomingBurstPerTurn) incomingBurstPerTurn = incomingBurstPerTurn or 0 local hp = entity:GetAttribute("HP") or entity:GetAttribute("MaxHP") or 100 local maxHp = entity:GetAttribute("MaxHP") or hp or 100 local effects = getEntityEffects(entity) local isPlayer = (entity:IsA("Player") or entity == LocalPlayer) local _, boons = getPlayerOnlyRaceAndBoons(entity) local statusDamageReduction = 1.0 if isPlayer then if table.find(boons, "Fortitude") then statusDamageReduction = statusDamageReduction * 0.70 end if table.find(boons, "Enervation") then statusDamageReduction = statusDamageReduction * 1.15 end end local simEffects = {} local hasAnyDot = false for name, data in pairs(effects) do if table.find({"Poison", "Voidblaze", "Bleed", "Entangled", "Burn", "Scorched"}, name) then simEffects[name] = data.stacks hasAnyDot = true end end if not hasAnyDot then return { hasDoT = false, totalDoT = 0, fatalTurn = nil, turnsToDieFromDoTAlone = nil, finalHp = hp, healingUrgency = "SAFE", summary = "Health is clean (No poison or bleed active)." } end local currentHp = hp local currentHpDoTOnly = hp local totalDoTDamage = 0 local fatalTurnCombined = nil local fatalTurnDoTAlone = nil for turn = 1, 10 do local turnDoT = 0 local activeInTurn = false if simEffects["Poison"] and simEffects["Poison"] > 0 then local pDmg = (1 + (currentHp * 0.025) + (simEffects["Poison"] * 0.5)) * statusDamageReduction turnDoT = turnDoT + pDmg activeInTurn = true simEffects["Poison"] = simEffects["Poison"] - 1 end if simEffects["Voidblaze"] and simEffects["Voidblaze"] > 0 then local vDmg = ((maxHp * 0.025) + 0.5 + (simEffects["Voidblaze"] * 1.5)) * statusDamageReduction if effects["Scorched"] then vDmg = vDmg * 1.3 end if effects["Seared_Soul"] then vDmg = vDmg * 1.5 end turnDoT = turnDoT + vDmg activeInTurn = true simEffects["Voidblaze"] = simEffects["Voidblaze"] - 1 end if simEffects["Bleed"] and simEffects["Bleed"] > 0 then local bDmg = (1.8 + (0.7 * simEffects["Bleed"])) * statusDamageReduction if effects["Weeping_Wound"] then bDmg = bDmg + (2 + 0.01 * maxHp) end if effects["Moonkin_Toxin"] then bDmg = (bDmg + 2) * 1.2 end if effects["Hemorrhage"] then bDmg = bDmg * 1.3 end if effects["Ruptured"] then bDmg = bDmg * 1.2 end turnDoT = turnDoT + bDmg activeInTurn = true simEffects["Bleed"] = simEffects["Bleed"] - 1 end if simEffects["Entangled"] and simEffects["Entangled"] > 0 then turnDoT = turnDoT + (2 * statusDamageReduction) activeInTurn = true simEffects["Entangled"] = simEffects["Entangled"] - 1 end if not activeInTurn then break end totalDoTDamage = totalDoTDamage + turnDoT currentHp = currentHp - turnDoT - incomingBurstPerTurn currentHpDoTOnly = currentHpDoTOnly - turnDoT if not fatalTurnCombined and currentHp <= 0 then fatalTurnCombined = turn end if not fatalTurnDoTAlone and currentHpDoTOnly <= 0 then fatalTurnDoTAlone = turn end end local urgency = "SAFE" local summaryMsg = "" if fatalTurnDoTAlone and fatalTurnDoTAlone <= 2 then urgency = "DANGER" summaryMsg = "🚨 DANGER: Will lose all HP in " .. fatalTurnDoTAlone .. " turn(s) from poison/bleed! Total damage: ~" .. formatNum(totalDoTDamage) .. ". Heal now!" elseif totalDoTDamage >= maxHp * 0.30 then urgency = "WARNING" summaryMsg = "🩸 Heavy Poison/Bleed: Taking ~" .. formatNum(totalDoTDamage) .. " total damage (" .. formatNum((totalDoTDamage/maxHp)*100) .. "% of Max HP)." else urgency = "SAFE" summaryMsg = "🟢 Mild Status: Taking ~" .. formatNum(totalDoTDamage) .. " damage over time." end return { hasDoT = true, totalDoT = math.round(totalDoTDamage), fatalTurn = fatalTurnCombined, turnsToDieFromDoTAlone = fatalTurnDoTAlone, finalHp = math.max(0, math.round(currentHp)), healingUrgency = urgency, summary = summaryMsg } end -- ======================================================================== -- DYNAMIC TARGET-AWARE DAMAGE & DEFENSE CALCULATOR -- ======================================================================== local function calculateAbilityDamage(abilityName, caster, target) local ability = AbilitiesDict[abilityName] if not ability then return { min = 0, max = 0, avg = 0, isKill = false, notes = "Not in dictionary" } end local baseDamage = ability.Damage or 0 local multihit = math.max(1, ability.Multihit or 1) local damageType = ability.DamageType or "Physical" local targetType = ability.TargetType or "SingleEnemy" local isCasterPlayer = caster:IsA("Player") or (caster == LocalPlayer) local cStats = (caster == LocalPlayer and AACState.LastStats) or AACState.PartyStats[caster.Name] or {} local str = caster:GetAttribute("STR") or cStats.STR or 10 local dex = caster:GetAttribute("DEX") or cStats.D... (93 KB left)