Is anyone even Cheating on AAC?
Key Required

Is anyone even Cheating on AAC?

An Average Campaign [Alpha v0.5.0]quantum_xggquantum_xgg 519 views 1K executesUpdated Aug 24, 2026
Unrated
00
Lua Script•1134 lines (48877 chars)
-- 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)

Features & Information

Its not anything crazy, its only show you information about the game and Advice about what you should do in the game. The script also can learn it self the more you play and using the data you already have when playing the game and using the script. The more you play, the accurate the data becomes. Anyway it could read parties too. Try it yourself and comments what should be a improvement for the scripts Whos even using cheat on the game 💀💀

Community Comments (0)

No comments yet for this script.

More Scripts for An Average Campaign [Alpha v0.5.0]