View Format: Multi-Page Single Page

API Reference

Complete runtime C# API for Simple Idle Forge.

Every class, method, event, and enum in the SimpleIdleForge namespace — with verified signatures, working code examples, and integration patterns.

Getting Started with Code

This page is for developers who want to write C# code to integrate Simple Idle Forge's runtime components into their game. If you are using the no-code wizard workflow exclusively, you do not need this page — the forges handle everything. But if you want custom game controllers, UI integration, save/load systems, or advanced features, this is your complete reference.

All runtime classes are pure C# — not MonoBehaviours. You create them in your own game controller and manage their lifecycle. Every class lives in the SimpleIdleForge namespace.

The Basic Game Controller Pattern

Here is the standard pattern for wiring everything together. Your MonoBehaviour holds references to the generated ScriptableObject databases (assigned in the Inspector), creates the runtime trackers in Start(), and ticks them in Update().

using SimpleIdleForge;
using UnityEngine;
using System;
using System.Collections.Generic;

public class MyIdleGame : MonoBehaviour
{
    [Header("Databases")]
    public ScriptableObject[] resourceDatabases;
    public ScriptableObject[] generatorDatabases;
    public ScriptableObject[] upgradeDatabases;
    public ScriptableObject[] prestigeDatabases;
    public ScriptableObject[] achievementDatabases;

    IdleResourcePool resourcePool;
    IdleGeneratorManager generatorManager;
    IdleUpgradeTracker upgradeTracker;
    IdlePrestigeManager prestigeManager;
    IdleMilestoneTracker milestoneTracker;
    List<IdleBonus> bonusCache = new List<IdleBonus>();

    void Start()
    {
        // Cast databases to interfaces
        var resSources = new List<IIdleResourceDataSource>();
        foreach (var db in resourceDatabases)
            if (db is IIdleResourceDataSource src) resSources.Add(src);

        var genSources = new List<IIdleGeneratorDataSource>();
        foreach (var db in generatorDatabases)
            if (db is IIdleGeneratorDataSource src) genSources.Add(src);

        var upgSources = new List<IIdleUpgradeDataSource>();
        foreach (var db in upgradeDatabases)
            if (db is IIdleUpgradeDataSource src) upgSources.Add(src);

        var presSources = new List<IIdlePrestigeDataSource>();
        foreach (var db in prestigeDatabases)
            if (db is IIdlePrestigeDataSource src) presSources.Add(src);

        var mileSources = new List<IIdleMilestoneDataSource>();
        foreach (var db in achievementDatabases)
            if (db is IIdleMilestoneDataSource src) mileSources.Add(src);

        // Initialize trackers
        resourcePool = new IdleResourcePool();
        resourcePool.Initialize(resSources);

        generatorManager = new IdleGeneratorManager();
        generatorManager.Initialize(genSources);

        upgradeTracker = new IdleUpgradeTracker();
        upgradeTracker.Initialize(upgSources);

        prestigeManager = new IdlePrestigeManager();
        prestigeManager.Initialize(presSources);

        milestoneTracker = new IdleMilestoneTracker();
        milestoneTracker.Initialize(mileSources);

        // Apply starting amounts
        foreach (var code in resourcePool.GetAllCodes())
        {
            var def = resourcePool.GetDefinition(code);
            if (def.HasValue && def.Value.startingAmount > 0)
                resourcePool.SetAmount(code, def.Value.startingAmount);
        }
    }

    void Update()
    {
        float dt = Time.deltaTime;

        // Collect global bonuses from all sources
        bonusCache.Clear();
        upgradeTracker.CollectBonusesForTarget("", bonusCache);
        prestigeManager.CollectBonusesForTarget("", bonusCache);
        milestoneTracker.CollectBonusesForTarget("", bonusCache);

        // Tick generators (produces resources)
        generatorManager.Tick(dt, resourcePool, bonusCache);

        // Auto-collect timer generators
        foreach (var code in generatorManager.GetAllCodes())
        {
            if (generatorManager.IsTimerReady(code))
                generatorManager.CollectTimer(code, resourcePool, bonusCache);
        }

        // Periodically check conditions (every 0.5s, not every frame)
        generatorManager.CheckUnlockConditions(GetValueProvider());
        milestoneTracker.CheckAll(GetValueProvider(), resourcePool);
    }

    Func<IdleConditionType, string, double> GetValueProvider()
    {
        return (type, code) =>
        {
            switch (type)
            {
                case IdleConditionType.ResourceAmount:
                    return resourcePool.GetAmount(code);
                case IdleConditionType.GeneratorLevel:
                    return generatorManager.GetLevel(code);
                case IdleConditionType.UpgradePurchased:
                    return upgradeTracker.GetPurchaseCount(code);
                case IdleConditionType.PrestigeCount:
                    return prestigeManager.GetPrestigeCount(code);
                default: return 0;
            }
        };
    }
}

The rest of this page documents every class, method, and event in detail.

IdleResourcePool

Tracks current resource amounts at runtime. Initialize with one or more databases, then add, remove, or set amounts. Respects caps and fires events on changes. This is the foundation — every other tracker interacts with it.

Namespace: SimpleIdleForge
Type: class (pure C#, not a MonoBehaviour)

Events

Event Signature Description
OnResourceChanged Action<string, double, double> Fired whenever a resource amount changes. Args: resourceCode, oldAmount, newAmount.
OnCapReached Action<string, double> Fired when a resource reaches its cap. Args: resourceCode, capAmount.
OnResourceDepleted Action<string> Fired when a resource reaches zero. Args: resourceCode.

Methods

Method Returns Description
Initialize(IIdleResourceDataSource dataSource) void Initialize from a single database. All resources are added with their startingAmount.
Initialize(IEnumerable<IIdleResourceDataSource> dataSources) void Initialize from multiple databases (merged, deduped by code).
GetAmount(string resourceCode) double Current amount. Returns 0 if the resource code is unknown.
HasResource(string resourceCode) bool Whether a resource exists in the pool.
GetDefinition(string resourceCode) IdleResourceDefinition? The full definition struct, or null if not found.
GetAllCodes() IEnumerable<string> All resource codes in the pool.
GetCap(string resourceCode) double Cap value for a resource. Returns 0 if unlimited (no cap).
GetFillRatio(string resourceCode) float Fill ratio 0.0–1.0. Returns 0 if no cap is set.
CanAfford(string resourceCode, double cost) bool Single-resource affordability check.
CanAffordAll(IEnumerable<KeyValuePair<string, double>> costs) bool Multi-resource affordability check. All costs must be affordable.
GetCapEfficiency(string resourceCode) float Production efficiency at current fill level (from IdleCapBehavior). Returns 1.0 if no cap behavior.
Add(string resourceCode, double amount) double Add to a resource. Returns actual amount added (may be less due to cap).
Remove(string resourceCode, double amount) double Remove from a resource. Cannot go below 0. Returns actual amount removed.
SetAmount(string resourceCode, double amount) void Set an exact amount. Respects cap and 0 floor.
TrySpend(string resourceCode, double cost) bool Spend if affordable. Returns false if not enough.
TrySpendAll(IEnumerable<KeyValuePair<string, double>> costs) bool Atomic multi-resource spend. Checks all first, then deducts all. Returns false if any cost cannot be met.
ResetToStarting(string resourceCode) void Reset a single resource to its definition's startingAmount.
ResetAll() void Reset all resources to their starting amounts.
CreateSnapshot() IdleResourcePoolSnapshot Create a serializable snapshot of all resource amounts for save/load.
RestoreFromSnapshot(IdleResourcePoolSnapshot snapshot) void Restore resource amounts from a snapshot. Unknown codes are ignored.

Usage Example

// Check if the player can afford a custom purchase
if (resourcePool.CanAfford("GOLD", 5000) && resourcePool.CanAfford("GEMS", 10))
{
    resourcePool.Remove("GOLD", 5000);
    resourcePool.Remove("GEMS", 10);
    // Grant the item...
}

// Or use the atomic multi-resource spend
var costs = new Dictionary<string, double> { { "GOLD", 5000 }, { "GEMS", 10 } };
if (resourcePool.TrySpendAll(costs))
{
    // Both resources deducted atomically
}

// Listen for events
resourcePool.OnResourceChanged += (code, oldVal, newVal) =>
{
    Debug.Log($"{code}: {oldVal} -> {newVal}");
    UpdateResourceUI(code, newVal);
};

resourcePool.OnCapReached += (code, cap) =>
{
    ShowNotification($"{code} is full! ({cap})");
};

// Cap efficiency query for UI
float efficiency = resourcePool.GetCapEfficiency("ENERGY");
// 1.0 = full production, 0.5 = half production, 0.0 = stopped

IdleGeneratorManager

Tracks generator levels, handles purchases, evaluates prerequisites, and ticks production. Supports both Continuous (per-frame) and Timer (batch) production modes. The workhorse of any idle game.

Namespace: SimpleIdleForge
Type: class (pure C#, not a MonoBehaviour)

Events

Event Signature Description
OnGeneratorLevelChanged Action<string, int, int> Fired when a generator is purchased or leveled. Args: generatorCode, oldLevel, newLevel.
OnGeneratorUnlocked Action<string> Fired when a generator is unlocked. Args: generatorCode.
OnMilestoneReached Action<string, IdleGeneratorMilestone> Fired when a generator milestone threshold is crossed. Args: generatorCode, milestone.
OnGeneratorAutomated Action<string> Fired when a generator becomes automated. Args: generatorCode.
OnTimerCompleted Action<string> Fired when a Timer-mode generator completes a cycle. Args: generatorCode.

Methods

Method Returns Description
Initialize(IIdleGeneratorDataSource dataSource) void Initialize from a single generator database.
Initialize(IEnumerable<IIdleGeneratorDataSource> dataSources) void Initialize from multiple databases (merged, deduped by code).
GetLevel(string generatorCode) int Current level. Returns 0 if unknown.
IsUnlocked(string generatorCode) bool Whether the generator is unlocked (prerequisites met).
IsAutomated(string generatorCode) bool Whether the generator is automated.
GetDefinition(string generatorCode) IdleGeneratorDefinition? The full definition struct, or null if not found.
GetAllCodes() IReadOnlyList<string> All generator codes.
HasGenerator(string generatorCode) bool Whether a generator exists in the manager.
GetBaseProduction(string generatorCode, string resourceCode) double Base production per second for a generator at its current level for a specific resource. Does NOT include bonuses.
GetNextLevelCost(string generatorCode, string costResourceCode) double Cost to buy the next level for a specific resource cost entry.
CanAfford(string generatorCode, Func<string, double> getResourceAmount) bool Check if a single level purchase is affordable across all cost entries.
GetMaxAffordableLevels(string generatorCode, Func<string, double> getResourceAmount) int Maximum levels the player can buy right now.
GetNextMilestoneLevel(string generatorCode) int Next milestone level threshold, or -1 if no more milestones.
Buy(string generatorCode, IdleResourcePool resourcePool) bool Buy one level. Deducts costs from resource pool. Returns true if successful.
BuyLevels(string generatorCode, int count, IdleResourcePool resourcePool) bool Buy multiple levels. Deducts total costs atomically. Returns true if successful.
BuyMax(string generatorCode, IdleResourcePool resourcePool) int Buy the maximum affordable levels. Returns number of levels purchased.
SetLevel(string generatorCode, int level) void Set a generator's level directly (for save/load or cheats).
Unlock(string generatorCode) void Unlock a generator manually (bypassing prerequisites).
SetAutomated(string generatorCode, bool value = true) void Set a generator as automated (or remove automation).
CheckUnlockConditions(Func<IdleConditionType, string, double> valueProvider) void Evaluate prerequisites against current game state and unlock qualifying generators.
Tick(float deltaSeconds, IdleResourcePool resourcePool, List<IdleBonus> bonusCache = null) void Tick all generators: add production to the resource pool for a time delta. Handles both Continuous and Timer modes.
GetTimerProgress(string code) float Timer progress 0.0–1.0 for UI progress bars. Returns 0 for Continuous generators.
IsTimerReady(string code) bool Whether a Timer-mode generator has completed its cycle and is ready for manual collection.
CollectTimer(string code, IdleResourcePool resourcePool, List<IdleBonus> bonusCache = null) bool Manually collect a completed timer cycle. Returns true if collection happened.
GetEffectiveInterval(string code) double Effective interval in seconds for a Timer-mode generator, accounting for Speed bonuses.
ResetGenerator(string generatorCode) void Reset a single generator to level 0 and re-evaluate its locked state.
ResetAll() void Reset all generators to level 0 and locked state.
CreateSnapshot() IdleGeneratorManagerSnapshot Create a snapshot for save/load.
RestoreFromSnapshot(IdleGeneratorManagerSnapshot snapshot) void Restore from a snapshot.

Important: CanAfford Signature

CanAfford takes a Func<string, double> delegate, not an IdleResourcePool directly. This keeps the generator manager decoupled from any specific resource source. The standard usage pattern is:

// Standard usage with IdleResourcePool
bool canBuy = generatorManager.CanAfford("MINE", code => resourcePool.GetAmount(code));

// Or with a shorthand lambda
bool canBuy = generatorManager.CanAfford("MINE", resourcePool.GetAmount);

Timer Production Example

Timer-mode generators (Adventure Capitalist style) produce in batches instead of continuously. Automated timers collect automatically. Non-automated timers wait for manual collection.

// Update timer progress bar UI
float progress = generatorManager.GetTimerProgress("STEEL_FOUNDRY");
mySlider.value = progress; // 0.0 to 1.0

// Check if timer batch is ready
if (generatorManager.IsTimerReady("STEEL_FOUNDRY"))
    generatorManager.CollectTimer("STEEL_FOUNDRY", resourcePool, bonusCache);

// Get effective interval (accounts for Speed bonuses)
double interval = generatorManager.GetEffectiveInterval("STEEL_FOUNDRY");
// Display: "12.0s per cycle" or faster with speed upgrades

Buy Max Example

// Buy as many levels as the player can afford
int levelsBought = generatorManager.BuyMax("MINE", resourcePool);
if (levelsBought > 0)
    Debug.Log($"Bought {levelsBought} levels of MINE");

// Or buy a specific number of levels
if (generatorManager.BuyLevels("MINE", 10, resourcePool))
    Debug.Log("Bought 10 levels of MINE");

IdleUpgradeTracker

Tracks upgrade purchase counts, evaluates prerequisites, handles cost deduction, and fires special events for Automate, Unlock, and ResourceGrant effect types. Supports both one-time and repeatable upgrades.

Namespace: SimpleIdleForge
Type: class (pure C#, not a MonoBehaviour)

Events

Event Signature Description
OnUpgradePurchased Action<string, int> Fired when an upgrade is purchased. Args: upgradeCode, newPurchaseCount.
OnAutomatorPurchased Action<string, string> Fired when an Automate-type upgrade is purchased. Args: upgradeCode, targetCode.
OnUnlockPurchased Action<string, string> Fired when an Unlock-type upgrade is purchased. Args: upgradeCode, targetCode.

Methods

Method Returns Description
Initialize(IIdleUpgradeDataSource dataSource) void Initialize from a single upgrade database.
Initialize(IEnumerable<IIdleUpgradeDataSource> dataSources) void Initialize from multiple databases (merged, deduped by code).
GetPurchaseCount(string upgradeCode) int Number of times this upgrade has been purchased. 0 if unknown.
IsPurchased(string upgradeCode) bool Whether this upgrade has been purchased at least once.
IsMaxed(string upgradeCode) bool Whether this upgrade has reached its maxPurchases limit. False if unlimited (maxPurchases <= 0).
CanPurchase(string upgradeCode) bool Whether this upgrade can be purchased (not maxed out).
GetDefinition(string upgradeCode) IdleUpgradeDefinition? The full definition struct, or null if not found.
GetAllCodes() IReadOnlyList<string> All upgrade codes.
HasUpgrade(string upgradeCode) bool Whether an upgrade exists in the tracker.
ArePrerequisitesMet(string upgradeCode, Func<IdleConditionType, string, double> valueProvider) bool Check if all prerequisites for an upgrade are met.
CanAfford(string upgradeCode, Func<string, double> getResourceAmount) bool Check if an upgrade is affordable (all cost entries).
Purchase(string upgradeCode, IdleResourcePool resourcePool) bool Purchase an upgrade. Deducts costs, increments count, fires events, handles special effects (Automate/Unlock/ResourceGrant). Returns true if successful.
SetPurchaseCount(string upgradeCode, int count) void Set purchase count directly (for save/load).
ResetAll() void Reset all purchase counts to 0.
ResetWhere(Func<IdleUpgradeDefinition, bool> shouldReset) void Reset only upgrades matching a predicate. Useful for selective prestige resets.
CollectBonusesForTarget(string targetCode, List<IdleBonus> output) void Collect all active bonuses from purchased upgrades for a target. Feed into IdleBonusResolver.
CreateSnapshot() IdleUpgradeTrackerSnapshot Create a snapshot for save/load.
RestoreFromSnapshot(IdleUpgradeTrackerSnapshot snapshot) void Restore from a snapshot.

Purchase Example

string code = "MINE_BOOST";
if (!upgradeTracker.IsMaxed(code)
    && upgradeTracker.CanAfford(code, c => resourcePool.GetAmount(c))
    && upgradeTracker.ArePrerequisitesMet(code, GetValueProvider()))
{
    upgradeTracker.Purchase(code, resourcePool);
    // Costs deducted, effect applied, events fired
}

Selective Prestige Reset

// Reset all upgrades EXCEPT those targeting "PRESTIGE_POINTS"
upgradeTracker.ResetWhere(def => def.targetCode != "PRESTIGE_POINTS");

// Reset all upgrades except those with the Automate effect type
upgradeTracker.ResetWhere(def => def.effectType != IdleBonusType.Automate);

IdlePrestigeManager

Manages prestige layers: counts, total currency earned, reward calculations, reset rule execution, and permanent bonus collection. The prestige loop — reset progress in exchange for permanent bonuses — is the core progression mechanic of most idle games.

Namespace: SimpleIdleForge
Type: class (pure C#, not a MonoBehaviour)

Events

Event Signature Description
OnPrestigeExecuted Action<string, double> Fired when a prestige is executed. Args: prestigeCode, pointsEarned.
OnPrestigeAvailable Action<string> Fired when a prestige becomes available (minimum met). Args: prestigeCode.

Methods

Method Returns Description
Initialize(IIdlePrestigeDataSource dataSource) void Initialize from a single prestige database.
Initialize(IEnumerable<IIdlePrestigeDataSource> dataSources) void Initialize from multiple databases (merged, deduped by code).
GetPrestigeCount(string prestigeCode) int Number of times this prestige has been executed.
GetTotalCurrencyEarned(string prestigeCode) double Total prestige currency earned across all resets for this layer.
HasPrestiged(string prestigeCode) bool Whether the player has prestiged at least once.
GetDefinition(string prestigeCode) IdlePrestigeDefinition? The full definition struct, or null if not found.
GetAllCodes() IReadOnlyList<string> All prestige layer codes.
HasPrestige(string prestigeCode) bool Whether a prestige layer exists in the manager.
CanPrestige(string prestigeCode, double currentSourceAmount) bool Check if prestige is available (source amount meets minimum and would earn points).
GetPotentialReward(string prestigeCode, double currentSourceAmount) double Get the potential reward without executing prestige.
GetNetGain(string prestigeCode, double currentSourceAmount) double Get the net gain (potential minus already earned total).
CheckAvailability(Func<string, double> getResourceAmount) void Check all prestige layers and fire OnPrestigeAvailable events for those that qualify.
ExecutePrestige(string prestigeCode, IdleResourcePool resources, IdleGeneratorManager generators, IdleUpgradeTracker upgrades) double Execute prestige: calculate reward, apply reset rules, grant prestige currency. Returns points earned, or 0 if prestige failed.
CollectBonusesForTarget(string targetCode, List<IdleBonus> output) void Collect all permanent bonuses from all prestige layers for a target. Bonuses scale with total currency earned.
GetPermanentBonus(string targetCode, IdleBonusType type) double Convenience: get the total permanent bonus value for a target and bonus type, summed across all prestige layers.
SetPrestigeCount(string prestigeCode, int count) void Set prestige count directly (for save/load).
SetTotalCurrencyEarned(string prestigeCode, double amount) void Set total currency earned directly (for save/load).
ResetAll() void Reset all prestige state to 0.
CreateSnapshot() IdlePrestigeManagerSnapshot Create a snapshot for save/load.
RestoreFromSnapshot(IdlePrestigeManagerSnapshot snapshot) void Restore from a snapshot.

Prestige Example

double sourceAmount = resourcePool.GetAmount("GOLD");
if (prestigeManager.CanPrestige("REBIRTH", sourceAmount))
{
    double preview = prestigeManager.GetPotentialReward("REBIRTH", sourceAmount);
    // Show confirmation dialog: "Prestige for {preview} points?"

    double earned = prestigeManager.ExecutePrestige(
        "REBIRTH", resourcePool, generatorManager, upgradeTracker);
    // Resets applied, currency granted, permanent bonuses active

    Debug.Log($"Prestiged for {earned} points!");
}

// Check permanent bonus values
double prodBonus = prestigeManager.GetPermanentBonus("MINE", IdleBonusType.AddPercent);
Debug.Log($"MINE has +{prodBonus * 100}% permanent production bonus");

IdleMilestoneTracker

Tracks achievement and milestone completion. Evaluates conditions each tick, fires completion events, auto-grants ResourceGrant rewards, and collects bonus rewards for the bonus resolution pipeline. Supports both one-time and repeatable milestones, plus hidden (secret) achievements.

Namespace: SimpleIdleForge
Type: class (pure C#, not a MonoBehaviour)

Events

Event Signature Description
OnMilestoneCompleted Action<string, IdleMilestoneDefinition> Fired when a milestone is completed for the first time. Args: milestoneCode, definition.
OnMilestoneRepeated Action<string, int> Fired when a repeatable milestone is completed again. Args: milestoneCode, completionCount.
OnMilestoneProgress Action<string, double, double> Fired to report progress toward an incomplete milestone. Args: milestoneCode, currentValue, targetValue.

Methods

Method Returns Description
Initialize(IIdleMilestoneDataSource dataSource) void Initialize from a single milestone database.
Initialize(IEnumerable<IIdleMilestoneDataSource> dataSources) void Initialize from multiple databases (merged, deduped by code).
IsCompleted(string milestoneCode) bool Whether this milestone has been completed at least once.
GetCompletionCount(string milestoneCode) int Number of times this milestone has been completed (for repeatables).
GetCompletedCount() int Total number of completed milestones.
GetTotalCount() int Total number of milestones in the tracker.
GetDefinition(string milestoneCode) IdleMilestoneDefinition? The full definition struct, or null if not found.
GetAllCodes() IReadOnlyList<string> All milestone codes.
HasMilestone(string milestoneCode) bool Whether a milestone exists in the tracker.
GetCompletedMilestones() List<IdleMilestoneDefinition> All completed milestone definitions.
GetInProgressMilestones() List<IdleMilestoneDefinition> All incomplete milestone definitions.
GetCurrentProgress(string milestoneCode, Func<IdleConditionType, string, double> valueProvider) double Get the current progress value for a milestone's condition.
CheckAll(Func<IdleConditionType, string, double> valueProvider, IdleResourcePool resourcePool = null) void Check all milestones against current game state. The optional resourcePool is used for auto-granting ResourceGrant rewards.
CollectBonusesForTarget(string targetCode, List<IdleBonus> output) void Collect all non-ResourceGrant bonuses from completed milestones for a target. Feed into IdleBonusResolver.
SetCompleted(string milestoneCode, int count = 1) void Mark a milestone as completed directly (for save/load).
ResetAll() void Reset all milestones to incomplete.
ResetWhere(Func<IdleMilestoneDefinition, bool> shouldReset) void Reset milestones matching a predicate.
CreateSnapshot() IdleMilestoneTrackerSnapshot Create a snapshot for save/load.
RestoreFromSnapshot(IdleMilestoneTrackerSnapshot snapshot) void Restore from a snapshot.

Achievement UI Example

// Display achievement progress in a UI panel
foreach (var code in milestoneTracker.GetAllCodes())
{
    var def = milestoneTracker.GetDefinition(code);
    if (!def.HasValue) continue;

    bool completed = milestoneTracker.IsCompleted(code);
    bool hidden = def.Value.isHidden && !completed;

    if (hidden)
    {
        // Show as "???" in the UI
        ShowHiddenAchievement();
    }
    else
    {
        double current = milestoneTracker.GetCurrentProgress(code, GetValueProvider());
        double target = def.Value.conditionValue;
        float ratio = target > 0 ? (float)(current / target) : 0f;
        ShowAchievement(def.Value.displayName, def.Value.description, ratio, completed);
    }
}

// Display completion stats
int done = milestoneTracker.GetCompletedCount();
int total = milestoneTracker.GetTotalCount();
achievementCountLabel.text = $"{done} / {total}";

IdleBuffManager

Manages temporary boosts with durations — ad rewards, consumable potions, event bonuses, and similar timed multipliers. Call Tick() each frame to expire buffs. Use CollectBonusesForTarget() to feed active buffs into the bonus resolution pipeline.

Namespace: SimpleIdleForge
Type: class (pure C#, not a MonoBehaviour)

Events

Event Signature Description
OnBuffApplied Action<string, IdleActiveBuff> Fired when a buff is applied. Args: buffId, buff.
OnBuffExpired Action<string> Fired when a buff expires naturally (timer ran out). Args: buffId.
OnBuffRemoved Action<string> Fired when a buff is manually removed. Args: buffId.

Methods

Method Returns Description
ApplyBuff(string buffId, IdleTargetType targetType, string targetCode, IdleBonusType bonusType, double value, float duration) void Apply a temporary buff. Duration 0 = permanent (must be removed manually). If a buff with the same ID exists, it is replaced.
ApplyGlobalBuff(string buffId, IdleBonusType bonusType, double value, float duration) void Convenience: apply a global buff (affects all production).
RemoveBuff(string buffId) void Remove a buff manually (before it expires). Fires OnBuffRemoved.
RemoveAll() void Remove all active buffs. Fires OnBuffRemoved for each.
RefreshBuff(string buffId, float newDuration) void Restart a buff's timer with a new duration.
Tick(float deltaTime) void Update all buff timers. Call each frame with Time.deltaTime. Expired buffs are automatically removed.
IsActive(string buffId) bool Check if a buff is currently active.
GetBuff(string buffId) IdleActiveBuff Get an active buff by ID, or null if not active.
GetAllActiveBuffs() IReadOnlyCollection<IdleActiveBuff> Get all currently active buffs.
ActiveCount int Property: number of active buffs.
GetBuffsForTarget(string targetCode) List<IdleActiveBuff> Get all active buffs affecting a specific target (includes global buffs).
CollectBonusesForTarget(string targetCode, List<IdleBonus> output) void Collect all active buff bonuses for a target. Feed into IdleBonusResolver.
CreateSnapshot() IdleBuffManagerSnapshot Create a snapshot for save/load.
RestoreFromSnapshot(IdleBuffManagerSnapshot snapshot) void Restore from a snapshot. Expired buffs in the snapshot are skipped.

Buff Examples

// Apply 2x global production for 120 seconds (ad reward)
buffManager.ApplyBuff("ad_reward_2x", IdleTargetType.Global, "",
    IdleBonusType.Multiply, 2.0, 120f);

// Apply permanent +10% to a specific generator
buffManager.ApplyBuff("passive_mine_boost", IdleTargetType.Generator, "MINE",
    IdleBonusType.AddPercent, 0.1, 0f); // duration 0 = permanent

// Apply a Speed buff (makes Timer generators faster)
buffManager.ApplyBuff("speed_potion", IdleTargetType.Global, "",
    IdleBonusType.Speed, 0.5, 60f); // +50% speed for 60 seconds

// In Update loop
buffManager.Tick(Time.deltaTime);

// Include buffs in bonus collection
bonusCache.Clear();
upgradeTracker.CollectBonusesForTarget("MINE", bonusCache);
prestigeManager.CollectBonusesForTarget("MINE", bonusCache);
milestoneTracker.CollectBonusesForTarget("MINE", bonusCache);
buffManager.CollectBonusesForTarget("MINE", bonusCache);

// Display active buff timers in UI
foreach (var buff in buffManager.GetAllActiveBuffs())
{
    if (!buff.IsPermanent)
        ShowBuffTimer(buff.buffId, buff.remainingDuration, buff.Progress);
}

IdleProgressCalculator

This is THE killer feature. No other Asset Store package provides reusable offline progress calculation as a static utility.

Calculates offline earnings, current production rates, and time-to-afford estimates. All methods are static — no state, no initialization needed. Call on demand when the game loads, when the player returns from the background, or when the UI needs production rate data.

Namespace: SimpleIdleForge
Type: static class

Methods

Method Returns Description
GetProductionSnapshot(IIdleGeneratorDataSource, IdleGeneratorManager, params Action<string, List<IdleBonus>>[]) IdleProductionSnapshot Calculate current production rates across all generators from a single database. Pass CollectBonusesForTarget methods as bonus collectors.
GetProductionSnapshot(IEnumerable<IIdleGeneratorDataSource>, IdleGeneratorManager, params Action<string, List<IdleBonus>>[]) IdleProductionSnapshot Multi-database overload. Merges production rates across all databases.
CalculateOfflineProgress(IdleProductionSnapshot, float offlineSeconds, IdleOfflineEfficiency, float maxOfflineSeconds = 0) IdleProgressReport Calculate offline earnings from a production snapshot. Applies efficiency config and optional max-time cap.
CalculateOfflineProgress(IIdleGeneratorDataSource, IdleGeneratorManager, float offlineSeconds, IdleOfflineEfficiency, float maxOfflineSeconds = 0, params Action<string, List<IdleBonus>>[]) IdleProgressReport Convenience overload: builds production snapshot internally.
ApplyOfflineProgress(IdleProgressReport, IdleResourcePool) void Grant earned resources from a progress report to the resource pool.
GetTimeToAfford(string resourceCode, double amount, IdleResourcePool, IdleProductionSnapshot) float Seconds until the player can afford an amount. Returns 0 if affordable now, float.PositiveInfinity if no production.
GetTimeToAffordFormatted(string resourceCode, double amount, IdleResourcePool, IdleProductionSnapshot) string Formatted time-to-afford: "Now", "Never", or formatted time string.
GetTimeToAffordCosts(IdleCostEntry[], int currentLevel, IdleResourcePool, IdleProductionSnapshot) float Worst-case time across all cost entries for a multi-resource purchase.

Complete Offline Progress Example

// On game load — calculate what happened while away
float offlineSeconds = 3600f; // 1 hour away

// Get current production rates with all bonuses
var snapshot = IdleProgressCalculator.GetProductionSnapshot(
    generatorDatabase, generatorManager,
    upgradeTracker.CollectBonusesForTarget,
    prestigeManager.CollectBonusesForTarget,
    milestoneTracker.CollectBonusesForTarget);

// Configure offline efficiency (step-based diminishing returns)
var offlineConfig = new IdleOfflineEfficiency
{
    mode = IdleOfflineMode.Step,
    steps = new IdleOfflineStep[]
    {
        new IdleOfflineStep { hoursThreshold = 0, efficiency = 0.8f },   // First hour: 80%
        new IdleOfflineStep { hoursThreshold = 1, efficiency = 0.5f },   // 1-4 hours: 50%
        new IdleOfflineStep { hoursThreshold = 4, efficiency = 0.25f },  // 4-12 hours: 25%
        new IdleOfflineStep { hoursThreshold = 12, efficiency = 0.1f }   // 12+ hours: 10%
    }
};

// Calculate and apply
var report = IdleProgressCalculator.CalculateOfflineProgress(
    snapshot, offlineSeconds, offlineConfig, maxOfflineSeconds: 86400f);

if (report.HasProgress)
{
    IdleProgressCalculator.ApplyOfflineProgress(report, resourcePool);
    Debug.Log(report.formattedSummary);
    // "You were away for 1h 0m:\n+5.4K Gold\n+230 Wood"
}

// Time-to-afford for UI countdowns
string timeText = IdleProgressCalculator.GetTimeToAffordFormatted(
    "GOLD", 10000, resourcePool, snapshot);
// "2m 34s" or "Now" or "Never"

IdleProductionSnapshot

The snapshot returned by GetProductionSnapshot() contains production rates you can use directly in your UI:

// Display production rates
foreach (var kvp in snapshot.productionPerSecond)
{
    string rate = IdleNumberFormatter.FormatRate(kvp.Value);
    Debug.Log($"{kvp.Key}: {rate}"); // "GOLD: 1.5M/s"
}

// Get rate for a specific resource
double goldRate = snapshot.GetTotalRate("GOLD");

IdleProgressReport

The report returned by CalculateOfflineProgress() contains everything needed for the "welcome back" screen:

Property Type Description
totalSecondsOffline float Total seconds the player was offline.
efficiencyApplied float Efficiency multiplier that was applied (0.0–1.0).
earnedResources Dictionary<string, double> Resources earned during offline time, keyed by resource code.
formattedSummary string Pre-formatted summary for UI ("You were away for 4h 23m:...").
formattedTime string Formatted offline time ("4h 23m").
HasProgress bool Whether any meaningful progress was made.
GetEarned(string resourceCode) double Get earned amount for a specific resource, or 0.

IdleAutoPurchaser

Handles automatic purchasing of generators and upgrades. Tracks which generators/upgrades are automated (from Automate-type upgrades), and supports a configurable global auto-buy strategy for generators. Processes purchases on a configurable tick interval, not every frame.

Namespace: SimpleIdleForge
Type: class (pure C#, not a MonoBehaviour)

Events

Event Signature Description
OnAutoPurchased Action<string, string> Fired when an auto-purchase occurs. Args: type ("generator" or "upgrade"), code.

Methods

Method Returns Description
SetGeneratorStrategy(IdleAutoBuyStrategy strategy) void Set the global auto-buy strategy for generators (Cheapest, MostExpensive, RoundRobin, or Disabled).
SetTickInterval(float interval) void Set how often auto-buy checks run, in seconds. Default 1s. Minimum 0.1s.
AutomateGenerator(string generatorCode) void Enable auto-buy for a specific generator.
RemoveGeneratorAutomation(string generatorCode) void Disable auto-buy for a specific generator.
AutomateUpgrade(string upgradeCode) void Enable auto-buy for a specific upgrade.
RemoveUpgradeAutomation(string upgradeCode) void Disable auto-buy for a specific upgrade.
IsGeneratorAutomated(string generatorCode) bool Check if a generator is automated.
IsUpgradeAutomated(string upgradeCode) bool Check if an upgrade is automated.
GetAutomatedGenerators() IReadOnlyCollection<string> Get all automated generator codes.
GetAutomatedUpgrades() IReadOnlyCollection<string> Get all automated upgrade codes.
WireToUpgradeTracker(IdleUpgradeTracker upgrades) void Subscribe to upgrade tracker events to auto-enable generator automation when Automate-type upgrades are purchased.
Tick(float deltaTime, IdleGeneratorManager generators, IdleUpgradeTracker upgrades, IdleResourcePool resources) void Process auto-purchases. Call each frame with deltaTime. Purchases happen at the configured tick interval.
ClearAll() void Clear all automation settings. Does not change the strategy.
ResetForPrestige() void Reset for prestige — clears automation but keeps the strategy setting.

Auto-Purchaser Example

// Setup
var autoPurchaser = new IdleAutoPurchaser();
autoPurchaser.SetGeneratorStrategy(IdleAutoBuyStrategy.Cheapest);
autoPurchaser.SetTickInterval(0.5f); // check every 0.5 seconds

// Wire to upgrade tracker so Automate upgrades take effect automatically
autoPurchaser.WireToUpgradeTracker(upgradeTracker);

// In Update loop
autoPurchaser.Tick(Time.deltaTime, generatorManager, upgradeTracker, resourcePool);

// Listen for auto-purchases (for UI notifications)
autoPurchaser.OnAutoPurchased += (type, code) =>
{
    Debug.Log($"Auto-purchased {type}: {code}");
};

// On prestige: reset automation but keep strategy
autoPurchaser.ResetForPrestige();

IdleStatisticsTracker

Tracks cumulative lifetime statistics: total earned, total spent, peak amounts, purchase counts, prestige count, milestones completed, play time, and more. Wire it to all other trackers for automatic recording, or call Record methods manually. Useful for achievement conditions, stats UI, and analytics.

Namespace: SimpleIdleForge
Type: class (pure C#, not a MonoBehaviour)

Events

Event Signature Description
OnStatChanged Action<string, double> Fired when any stat changes. Args: statName (e.g., "earned:GOLD", "prestige"), newValue.

Methods

Method Returns Description
WireToTrackers(IdleResourcePool, IdleGeneratorManager, IdleUpgradeTracker, IdlePrestigeManager, IdleMilestoneTracker, IdleBuffManager) void Subscribe to all tracker events for automatic stat recording. All parameters are optional (pass null to skip). Call once after initializing all trackers.
Tick(float deltaTime) void Call each frame to track total play time.
RecordEarned(string resourceCode, double amount) void Record resources earned (production, offline, rewards).
RecordSpent(string resourceCode, double amount) void Record resources spent (purchases, costs).
RecordPeak(string resourceCode, double currentAmount) void Record current amount to track peak (highest ever) values.
RecordGeneratorPurchase(string generatorCode) void Record a generator level-up.
RecordUpgradePurchase(string upgradeCode) void Record an upgrade purchase.
RecordPrestige() void Record a prestige execution.
RecordMilestoneCompleted() void Record a milestone/achievement completion.
RecordBuffUsed() void Record a buff being applied.
RecordOfflineTime(float seconds) void Record offline time (from IdleProgressCalculator).
GetTotalEarned(string resourceCode) double Lifetime total earned for a resource.
GetTotalSpent(string resourceCode) double Lifetime total spent for a resource.
GetPeakAmount(string resourceCode) double Highest amount ever held for a resource.
GetGeneratorPurchases(string generatorCode) int Total level-ups for a generator.
GetUpgradePurchases(string upgradeCode) int Total purchases for an upgrade.
GetTrackedResourceCodes() IEnumerable<string> All resource codes that have earned stats.
GetTrackedGeneratorCodes() IEnumerable<string> All generator codes that have purchase stats.

Properties

Property Type Description
TotalPrestigeCount int Lifetime prestige count.
TotalMilestonesCompleted int Lifetime milestones completed.
TotalBuffsUsed int Lifetime buffs applied.
TotalPlayTimeSeconds double Total play time in seconds.
TotalOfflineSeconds double Total offline time in seconds.
FormattedPlayTime string Total play time as formatted string ("4d 12h 35m").

Additional Query Methods

Method Returns Description
GetTotalGeneratorsPurchased() int Total generators purchased across all types.
GetTotalUpgradesPurchased() int Total upgrades purchased across all types.
ResetAll() void Reset all statistics to zero. Use with caution.
CreateSnapshot() IdleStatisticsSnapshot Create a snapshot for save/load.
RestoreFromSnapshot(IdleStatisticsSnapshot snapshot) void Restore from a snapshot.

Statistics Tracker Example

// Setup — wire to all trackers for automatic recording
var stats = new IdleStatisticsTracker();
stats.WireToTrackers(resourcePool, generatorManager, upgradeTracker,
    prestigeManager, milestoneTracker, buffManager);

// In Update loop
stats.Tick(Time.deltaTime);

// After offline progress is applied
stats.RecordOfflineTime(offlineSeconds);

// Display in Stats UI
statsText.text = $@"Play Time: {stats.FormattedPlayTime}
Total Gold Earned: {IdleNumberFormatter.Format(stats.GetTotalEarned("GOLD"))}
Peak Gold: {IdleNumberFormatter.Format(stats.GetPeakAmount("GOLD"))}
Generators Purchased: {stats.GetTotalGeneratorsPurchased()}
Upgrades Purchased: {stats.GetTotalUpgradesPurchased()}
Prestiges: {stats.TotalPrestigeCount}
Achievements: {stats.TotalMilestonesCompleted}";

Static Utilities

These static classes provide core calculations used throughout the system. You can also call them directly in your own code for UI formatting, cost previews, bonus resolution, and condition evaluation.

IdleNumberFormatter

Formats large numbers for idle game UI. Supports suffix notation (K/M/B/T/Qa/Qi/...), scientific notation, engineering notation, and full numbers with commas. Also formats time durations and per-second rates.

Method Returns Description
Format(double value, IdleNumberFormat format = Short, int precision = 2) string Format a number. Examples: "1.50K", "1.50e3", "1.50x10^3", "1,500".
FormatTime(float seconds) string Format seconds as readable time. "45s", "2m 34s", "1h 23m", "4d 12h".
FormatRate(double perSecond, IdleNumberFormat format = Short, int precision = 2) string Format a per-second rate. "1.50M/s".
// Number formatting
IdleNumberFormatter.Format(1500)                              // "1.50K"
IdleNumberFormatter.Format(1500000)                           // "1.50M"
IdleNumberFormatter.Format(1.5e12)                            // "1.50T"
IdleNumberFormatter.Format(1.5e15)                            // "1.50Qa"
IdleNumberFormatter.Format(1500, IdleNumberFormat.Scientific) // "1.50e3"
IdleNumberFormatter.Format(1500, IdleNumberFormat.Full)       // "1,500"

// Time formatting
IdleNumberFormatter.FormatTime(45)    // "45s"
IdleNumberFormatter.FormatTime(154)   // "2m 34s"
IdleNumberFormatter.FormatTime(5000)  // "1h 23m"
IdleNumberFormatter.FormatTime(90000) // "1d 1h"

// Rate formatting
IdleNumberFormatter.FormatRate(1500000) // "1.50M/s"

IdleCostCalculator

Computes purchase costs with scaling formulas. Handles single-level costs, bulk costs (geometric series for Exponential), and max-affordable calculations (O(1) for Exponential via logarithm).

Method Returns Description
GetCostAtLevel(double baseCost, int level, IdleCostScaling scaling, double factor) double Cost to purchase at a given level (0-based).
GetTotalCost(double baseCost, int fromLevel, int toLevel, IdleCostScaling scaling, double factor) double Total cost to purchase from fromLevel to toLevel (exclusive). Uses closed-form for Exponential/Linear.
GetMaxAffordableLevels(double baseCost, int currentLevel, double budget, IdleCostScaling scaling, double factor) int Maximum levels affordable with a budget. O(1) for Exponential via logarithm.
CanAffordGenerator(IdleGeneratorDefinition gen, int currentLevel, Func<string, double> getResourceAmount) bool Check if a generator purchase is affordable across all cost entries.
CanAffordUpgrade(IdleUpgradeDefinition upgrade, int purchaseCount, Func<string, double> getResourceAmount) bool Check if an upgrade purchase is affordable across all cost entries.
// Cost at level 50 with exponential scaling (Cookie Clicker uses 1.15)
double cost = IdleCostCalculator.GetCostAtLevel(10, 50, IdleCostScaling.Exponential, 1.15);
// 10 * 1.15^50 = ~10,836

// Total cost to buy levels 0 through 49 (50 levels total)
double totalCost = IdleCostCalculator.GetTotalCost(10, 0, 50, IdleCostScaling.Exponential, 1.15);

// How many levels can I afford with 100,000 gold?
int maxLevels = IdleCostCalculator.GetMaxAffordableLevels(10, 0, 100000,
    IdleCostScaling.Exponential, 1.15);

IdleBonusResolver

Resolves stacked bonuses in a strict order. This is the single source of truth for how bonuses combine. The pipeline is:

finalValue = (base + flatSum) * (1 + percentSum) * multiply1 * multiply2 * ...

Method Returns Description
Resolve(double baseValue, List<IdleBonus> bonuses) IdleBonusResult Resolve a base value with a list of bonuses. Returns a result struct with baseValue, flatTotal, percentTotal, multiplyTotal, finalValue, and bonusCount.
// Example: base production of 100/s with upgrades
var bonuses = new List<IdleBonus>
{
    new IdleBonus(IdleBonusType.AddFlat, 50, "upgrade:EXTRA_MINERS"),     // +50 flat
    new IdleBonus(IdleBonusType.AddPercent, 0.5, "upgrade:BETTER_PICKS"), // +50%
    new IdleBonus(IdleBonusType.Multiply, 2.0, "prestige:REBIRTH"),       // x2
};

var result = IdleBonusResolver.Resolve(100, bonuses);
// (100 + 50) * (1 + 0.5) * 2.0 = 450
Debug.Log($"Base: {result.baseValue}, Final: {result.finalValue}"); // "Base: 100, Final: 450"

IdleConditionEvaluator

Shared utility for evaluating comparison conditions. Used internally by IdleMilestoneTracker, IdleUpgradeTracker, and IdleGeneratorManager, but you can call it directly too.

Method Returns Description
Evaluate(double currentValue, IdleComparison comparison, double targetValue) bool Evaluate a comparison between two values.
EvaluateAllPrerequisites(IdlePrerequisite[] prerequisites, Func<IdleConditionType, string, double> valueProvider) bool Evaluate all prerequisites in an array. Returns true if ALL are met (empty = true).

IdlePrestigeCalculator

Computes prestige rewards and inverse lookups. Supports four formula types: Linear, Sqrt (most common), Log, and Polynomial.

Method Returns Description
CalculatePrestigeReward(IdlePrestigeDefinition prestige, double sourceAmount) double Calculate how many prestige points would be earned from a source amount.
GetSourceAmountForReward(IdlePrestigeDefinition prestige, double desiredReward) double Inverse lookup: how much source amount is needed to earn N points.
CanPrestige(IdlePrestigeDefinition prestige, double currentSourceAmount) bool Check if prestige is available (meets minimum and would earn points).

Snapshot Serialization

Every runtime tracker has CreateSnapshot() and RestoreFromSnapshot() methods. The IdleGameSnapshot class bundles all tracker snapshots into a single serializable object. All snapshot classes are [Serializable] and compatible with JsonUtility.ToJson().

Complete Save/Load Pattern

// ── SAVE ──
var gameSnapshot = new IdleGameSnapshot
{
    timestampTicks = DateTime.UtcNow.Ticks,
    resources = resourcePool.CreateSnapshot(),
    generators = generatorManager.CreateSnapshot(),
    upgrades = upgradeTracker.CreateSnapshot(),
    prestige = prestigeManager.CreateSnapshot(),
    milestones = milestoneTracker.CreateSnapshot(),
    buffs = buffManager.CreateSnapshot()
};
string json = JsonUtility.ToJson(gameSnapshot);
PlayerPrefs.SetString("idle_save", json);

// ── LOAD ──
string savedJson = PlayerPrefs.GetString("idle_save", "");
if (!string.IsNullOrEmpty(savedJson))
{
    var snapshot = JsonUtility.FromJson<IdleGameSnapshot>(savedJson);
    resourcePool.RestoreFromSnapshot(snapshot.resources);
    generatorManager.RestoreFromSnapshot(snapshot.generators);
    upgradeTracker.RestoreFromSnapshot(snapshot.upgrades);
    prestigeManager.RestoreFromSnapshot(snapshot.prestige);
    milestoneTracker.RestoreFromSnapshot(snapshot.milestones);
    buffManager.RestoreFromSnapshot(snapshot.buffs);

    // Calculate offline progress
    long savedTicks = snapshot.timestampTicks;
    float offlineSeconds = (float)(DateTime.UtcNow - new DateTime(savedTicks)).TotalSeconds;
    // ... use IdleProgressCalculator to calculate and apply offline earnings ...
}

Snapshot Types

Type Created By Contents
IdleGameSnapshot You (manual construction) timestampTicks + all per-component snapshots
IdleResourcePoolSnapshot IdleResourcePool.CreateSnapshot() List of ResourceAmountEntry (resourceCode + amount)
IdleGeneratorManagerSnapshot IdleGeneratorManager.CreateSnapshot() List of GeneratorLevelEntry (generatorCode + level + timerElapsed)
IdleUpgradeTrackerSnapshot IdleUpgradeTracker.CreateSnapshot() List of UpgradePurchaseEntry (upgradeCode + purchaseCount)
IdlePrestigeManagerSnapshot IdlePrestigeManager.CreateSnapshot() List of PrestigeStateEntry (prestigeCode + prestigeCount + totalCurrencyEarned)
IdleMilestoneTrackerSnapshot IdleMilestoneTracker.CreateSnapshot() List of completed codes + MilestoneRepeatEntry for repeatables
IdleBuffManagerSnapshot IdleBuffManager.CreateSnapshot() List of IdleBuffSnapshot (full buff state). Expired buffs skipped on restore.
IdleStatisticsSnapshot IdleStatisticsTracker.CreateSnapshot() Earned/spent/peaks per resource, purchases per generator/upgrade, lifetime counters

API Hook Interfaces

Simple Idle Forge provides three interfaces that you implement in your own code. We never touch ads, IAP, analytics, or platform-specific systems. Your implementation decides where rewards come from, how data is stored, and how notifications are displayed.

IIdleSaveHandler

Implement this to handle persistence. Your implementation decides the storage backend (PlayerPrefs, JSON file, cloud save, etc.).

Method Returns Description
SaveState(IdleGameSnapshot snapshot) void Save the complete game state.
LoadState() IdleGameSnapshot Load the complete game state. Returns null if no save exists.
GetOfflineSeconds() float Get the number of seconds since the last save (for offline progress).
HasSave() bool Check if a save exists.
DeleteSave() void Delete the saved state.
public class MyPlayerPrefsSaveHandler : IIdleSaveHandler
{
    private const string SaveKey = "idle_game_save";
    private const string TimestampKey = "idle_game_timestamp";

    public void SaveState(IdleGameSnapshot snapshot)
    {
        string json = JsonUtility.ToJson(snapshot);
        PlayerPrefs.SetString(SaveKey, json);
        PlayerPrefs.SetString(TimestampKey, DateTime.UtcNow.Ticks.ToString());
        PlayerPrefs.Save();
    }

    public IdleGameSnapshot LoadState()
    {
        string json = PlayerPrefs.GetString(SaveKey, "");
        if (string.IsNullOrEmpty(json)) return null;
        return JsonUtility.FromJson<IdleGameSnapshot>(json);
    }

    public float GetOfflineSeconds()
    {
        string ticksStr = PlayerPrefs.GetString(TimestampKey, "");
        if (string.IsNullOrEmpty(ticksStr)) return 0;
        long ticks = long.Parse(ticksStr);
        return (float)(DateTime.UtcNow - new DateTime(ticks)).TotalSeconds;
    }

    public bool HasSave() => PlayerPrefs.HasKey(SaveKey);
    public void DeleteSave() { PlayerPrefs.DeleteKey(SaveKey); PlayerPrefs.DeleteKey(TimestampKey); }
}

IIdleRewardHandler

Implement this to handle ad rewards, IAP boosts, daily logins, or any external reward source. The idle system calls your implementation when rewards are processed.

Method Returns Description
OnRewardClaimed(string rewardType, string resourceCode, double amount) void Called when a reward is claimed and applied to the game state.
IsRewardAvailable(string rewardType) bool Check if a reward type is currently available (e.g., ad loaded, IAP owned).
GetRewardMultiplier(string rewardType) double Get the multiplier for a reward type (e.g., 2.0 for "double production" ad).
GetRewardDuration(string rewardType) float Get the duration in seconds for a timed reward (e.g., 120 for "2 minute boost").

IIdleNotificationHandler

Implement this to handle game notifications and UI updates. Your implementation decides how notifications are displayed (toast, popup, log, HUD, etc.).

Method Returns Description
OnMilestoneReached(string milestoneCode, string displayName, string description) void Called when a milestone is completed.
OnPrestigeAvailable(string prestigeCode, double potentialReward) void Called when prestige becomes available for the first time.
OnOfflineProgressReady(IdleProgressReport report) void Called when offline progress has been calculated and is ready to display.
OnGeneratorMilestoneReached(string generatorCode, int level, string milestoneDescription) void Called when a generator milestone threshold is reached.
OnBuffExpired(string buffId) void Called when a temporary buff expires.

Events Reference

Complete table of every event across all runtime trackers. Subscribe to these in your game controller for UI updates, sound effects, analytics, and state synchronization.

Class Event Signature When Fired
IdleResourcePool OnResourceChanged Action<string, double, double> Any resource amount changes (resourceCode, oldAmount, newAmount)
IdleResourcePool OnCapReached Action<string, double> A resource hits its cap (resourceCode, capAmount)
IdleResourcePool OnResourceDepleted Action<string> A resource reaches zero (resourceCode)
IdleGeneratorManager OnGeneratorLevelChanged Action<string, int, int> A generator is purchased or leveled (code, oldLevel, newLevel)
IdleGeneratorManager OnGeneratorUnlocked Action<string> A generator is unlocked (code)
IdleGeneratorManager OnMilestoneReached Action<string, IdleGeneratorMilestone> A generator milestone threshold is crossed (code, milestone)
IdleGeneratorManager OnGeneratorAutomated Action<string> A generator becomes automated (code)
IdleGeneratorManager OnTimerCompleted Action<string> A Timer-mode generator completes a cycle (code)
IdleUpgradeTracker OnUpgradePurchased Action<string, int> An upgrade is purchased (code, newPurchaseCount)
IdleUpgradeTracker OnAutomatorPurchased Action<string, string> An Automate-type upgrade is purchased (upgradeCode, targetCode)
IdleUpgradeTracker OnUnlockPurchased Action<string, string> An Unlock-type upgrade is purchased (upgradeCode, targetCode)
IdlePrestigeManager OnPrestigeExecuted Action<string, double> A prestige is executed (prestigeCode, pointsEarned)
IdlePrestigeManager OnPrestigeAvailable Action<string> A prestige becomes available (prestigeCode)
IdleMilestoneTracker OnMilestoneCompleted Action<string, IdleMilestoneDefinition> A milestone is completed for the first time (code, definition)
IdleMilestoneTracker OnMilestoneRepeated Action<string, int> A repeatable milestone is completed again (code, completionCount)
IdleMilestoneTracker OnMilestoneProgress Action<string, double, double> Progress reported for an incomplete milestone (code, currentValue, targetValue)
IdleBuffManager OnBuffApplied Action<string, IdleActiveBuff> A buff is applied (buffId, buff)
IdleBuffManager OnBuffExpired Action<string> A buff expires naturally (buffId)
IdleBuffManager OnBuffRemoved Action<string> A buff is manually removed (buffId)
IdleAutoPurchaser OnAutoPurchased Action<string, string> An auto-purchase occurs (type "generator"/"upgrade", code)
IdleStatisticsTracker OnStatChanged Action<string, double> Any stat changes (statName, newValue)

Enums Reference

All enums in the SimpleIdleForge namespace with every value and its meaning.

IdleCostScaling

Cost scaling formula type for generators and repeatable upgrades.

Value Formula Description
None cost(n) = baseCost No scaling — cost stays constant.
Linear cost(n) = baseCost + (n * factor) Linear increase per level.
Exponential cost(n) = baseCost * factor^n The most common idle game formula. Cookie Clicker uses factor = 1.15.
Polynomial cost(n) = baseCost * n^factor Power curve. Factor 2 = quadratic, factor 0.5 = sub-quadratic.
Logarithmic cost(n) = baseCost * (1 + factor * ln(n+1)) Gentle late-game curve. Costs grow very slowly at high levels.

IdleBonusType

Bonus and effect type for upgrades, milestones, prestige bonuses, and buffs.

Value Description
Multiply Multiply the target value. Applied last in the bonus pipeline. Each multiplier stacks multiplicatively.
AddFlat Add a flat amount to the base value. Applied first in the bonus pipeline.
AddPercent Add a percentage of the base value. All percents are summed before multiplying. 0.5 = +50%.
Unlock Unlock a previously locked feature, generator, or upgrade. Not a numeric bonus.
Automate Automate purchasing of a target generator. Not a numeric bonus.
ResourceGrant Grant a one-time resource amount (from milestones or prestige rewards). Not a production bonus.
Speed Modify timer interval for Timer-mode generators. Formula: interval / (1 + totalSpeed). Positive = faster.

IdleProductionMode

Production mode for generators.

Value Description
Continuous Produces resources every frame at a continuous rate (X per second). Classic Cookie Clicker style.
Timer Produces resources in batches every N seconds. Adventure Capitalist style. Requires manual collection unless automated.

IdleTargetType

Target type for upgrades, bonuses, and buffs.

Value Description
Generator Affects a specific generator (identified by targetCode).
Resource Affects a specific resource (identified by targetCode).
Global Affects everything globally. targetCode is ignored or empty.

IdleResetTarget

Target type for prestige reset rules.

Value Description
Resource Reset a resource amount. targetCode = specific code or "ALL".
Generator Reset generator levels. targetCode = specific code or "ALL".
Upgrade Reset upgrade purchase counts. targetCode = specific code or "ALL".

IdleConditionType

Condition type for prerequisites and milestone conditions.

Value Description
ResourceAmount Check a resource's current amount.
GeneratorLevel Check a generator's current level.
TotalGenerators Check total generators owned across all types.
UpgradePurchased Check if an upgrade has been purchased (count > 0).
PrestigeCount Check prestige count for a layer.
TotalProduction Check total production rate for a resource.
MilestoneCompleted Check if a milestone/achievement has been completed.

IdleComparison

Comparison operator for prerequisites and milestone conditions.

Value Operator Description
Equals == Current value equals target (with epsilon tolerance).
NotEquals != Current value does not equal target.
GreaterThan > Current value is strictly greater than target.
LessThan < Current value is strictly less than target.
GreaterOrEqual >= Current value is greater than or equal to target. The most common operator for milestones.
LessOrEqual <= Current value is less than or equal to target.

IdlePrestigeFormula

Prestige reward formula type. Controls how prestige points scale with source amount.

Value Formula Description
Linear floor(scale * (source / base)) Points grow linearly. Simple but rarely used — too generous at high amounts.
Sqrt floor(scale * sqrt(source / base)) The most common formula. Diminishing returns encourage repeated prestiges.
Log floor(scale * log10(source / base + 1)) Very aggressive diminishing returns. Points grow extremely slowly.
Polynomial floor(scale * (source / base)^exponent) Configurable curve. exponent < 1 for diminishing, > 1 for accelerating.

IdleNumberFormat

Number display format for idle game UI.

Value Example Output Description
Short 1.50K, 1.50M, 1.50B, 1.50T, 1.50Qa Suffix notation. The most common format for idle games.
Scientific 1.50e3, 1.50e6, 1.50e9 Scientific notation. Compact for extremely large numbers.
Engineering 1.50x10^3, 1.50x10^6 Engineering notation. Exponent always a multiple of 3.
Full 1,500 / 1,500,000 Full number with comma separators. Falls back to scientific for very large values.

IdleAutoBuyStrategy

Strategy for the IdleAutoPurchaser's global generator auto-buy behavior.

Value Description
Cheapest Buy the cheapest affordable generator first. Efficient for steady growth.
MostExpensive Buy the most expensive affordable generator first. Best value per purchase.
RoundRobin Buy generators in round-robin order. Keeps levels balanced.
Disabled No global auto-buying. Individual generators can still be automated via Automate upgrades.

IdleOfflineMode

How offline efficiency scales with time away. Used by IdleOfflineEfficiency.

Value Description
Flat Constant efficiency regardless of time (e.g., always 50%). Simplest option.
Curve Efficiency follows a Unity AnimationCurve over hours offline. Full designer control.
Step Efficiency changes at defined hour breakpoints. Easy to configure with clear tiers.