Game/Marks.cs
namespace Monolith;
public sealed class MarkDef
{
public string Id;
public string Name;
public string Hint;
/// <summary>Has this been earned, given the player's current state?</summary>
public Func<PlayerProgress, bool> Earned;
/// <summary>Progress toward it, 0 to 1, for the UI. Optional.</summary>
public Func<PlayerProgress, float> Progress;
}
/// <summary>
/// Achievements, and the quiet global multiplier they carry.
///
/// The multiplier is the smaller half of what these are for. Their real job is to be a **quest
/// log wearing a different hat**: a player who has run out of ideas reads the list and finds
/// six things they had not thought to try. That is what Cookie Clicker's achievements actually
/// do, and why several of these deliberately ask for something awkward rather than for a bigger
/// number you were going to reach anyway.
/// </summary>
public static class Marks
{
public static readonly MarkDef[] All =
{
new()
{
Id = "first-cube",
Name = "First Cut",
Hint = "Remove a cube.",
Earned = p => p.Data.LifetimeCubes >= 1,
Progress = p => (float)Math.Clamp( p.Data.LifetimeCubes, 0, 1 ),
},
new()
{
Id = "excavator",
Name = "Excavator",
Hint = "Remove 100,000 cubes.",
Earned = p => p.Data.LifetimeCubes >= 100_000,
Progress = p => (float)(p.Data.LifetimeCubes / 100_000.0),
},
new()
{
Id = "strip-miner",
Name = "Strip Miner",
Hint = "Remove 10 million cubes.",
Earned = p => p.Data.LifetimeCubes >= 10_000_000,
Progress = p => (float)(p.Data.LifetimeCubes / 10_000_000.0),
},
new()
{
Id = "half-way",
Name = "Halfway Down",
Hint = "Reach stage 50.",
Earned = p => p.Data.BestStageEver >= 50,
Progress = p => p.Data.BestStageEver / 50f,
},
new()
{
Id = "centurion",
Name = "Centurion",
Hint = "Reach stage 100.",
Earned = p => p.Data.BestStageEver >= 100,
Progress = p => p.Data.BestStageEver / 100f,
},
new()
{
Id = "first-collapse",
Name = "Collapse",
Hint = "Prestige once.",
Earned = p => p.Data.Collapses >= 1,
Progress = p => p.Data.Collapses,
},
new()
{
Id = "ascendant",
Name = "Ascendant",
Hint = "Prestige five times.",
Earned = p => p.Data.Collapses >= 5,
Progress = p => p.Data.Collapses / 5f,
},
new()
{
Id = "buried",
Name = "Buried Deep",
Hint = "Detonate 25 charges at full depth.",
Earned = p => p.Data.DeepDetonations >= 25,
Progress = p => p.Data.DeepDetonations / 25f,
},
new()
{
Id = "perfect-hand",
Name = "Perfect Hand",
Hint = "Catch 20 active reloads.",
Earned = p => p.Data.PerfectReloads >= 20,
Progress = p => p.Data.PerfectReloads / 20f,
},
new()
{
Id = "unstable",
Name = "Unstable",
Hint = "Set off 100 volatile cubes.",
Earned = p => p.Data.VolatileDetonations >= 100,
Progress = p => p.Data.VolatileDetonations / 100f,
},
new()
{
Id = "air-superiority",
Name = "Air Superiority",
Hint = "Destroy 50 interceptors.",
Earned = p => p.Data.InterceptorKills >= 50,
Progress = p => p.Data.InterceptorKills / 50f,
},
new()
{
Id = "unseen",
Name = "Unseen",
Hint = "Shoot down 10 Spotters.",
Earned = p => p.Data.SpottersDowned >= 10,
Progress = p => p.Data.SpottersDowned / 10f,
},
new()
{
Id = "resonant",
Name = "Resonant",
Hint = "Reach 10 Resonance stacks at once.",
Earned = p => p.Data.BestResonance >= 10,
Progress = p => p.Data.BestResonance / 10f,
},
new()
{
Id = "swift",
Name = "Swift",
Hint = "Finish the ladder in under 20 minutes.",
Earned = p => p.Data.BestLadderSeconds > 0f && p.Data.BestLadderSeconds < 1200f,
Progress = p => p.Data.BestLadderSeconds <= 0f ? 0f : 1200f / p.Data.BestLadderSeconds,
},
new()
{
Id = "contributor",
Name = "Contributor",
Hint = "Earn prestige credit from felling the Monolith.",
Earned = p => p.Data.MonolithCredits >= 1,
Progress = p => p.Data.MonolithCredits,
},
};
public static MarkDef Get( string id ) => All.FirstOrDefault( m => m.Id == id );
}