
Key Required

-- 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)No comments yet for this script.



