Contribution clics
@@ -40,67 +42,80 @@
{clickShare.toFixed(0)}%
-
+
+ {#if (game.state.clickUpgrades ?? []).length > 0}
+
Ameliorations de ponte
+ {#each game.state.clickUpgrades ?? [] as upgrade}
+ {@const gen = game.state.generators.find((g) => g.id === upgrade.generatorId)}
+ {@const hasGen = gen && gen.owned > 0}
+ {@const cost = clickUpgradeCost(upgrade)}
+ {@const canAfford = hasGen && game.state.resources >= cost}
+
+
+
+ {upgrade.name}
+ {#if upgrade.level > 0}
+
+ nv.{upgrade.level}
+
+ {/if}
+
+
+ {#if hasGen}
+ +{upgrade.baseClickPower}/clic par niveau ({GEN_NAMES[upgrade.generatorId]})
+ {:else}
+ Necessite un {GEN_NAMES[upgrade.generatorId]}
+ {/if}
+
+
+ {#if hasGen}
+
+ {:else}
+
—
+ {/if}
+
+ {/each}
+ {/if}
+
+
+
Bonus (arbre)
+
Double Ponte
- {#if b.doubleChance > 0}
- {(b.doubleChance * 100).toFixed(0)}% chance de doubler
- {:else}
- Branche Ponte — "Double Ponte" (5 ADN)
- {/if}
+ {b.doubleChance > 0 ? `${(b.doubleChance * 100).toFixed(0)}% chance de doubler` : 'Branche Ponte — 5 ADN'}
- {#if b.doubleChance > 0}
-
{(b.doubleChance * 100).toFixed(0)}%
- {:else}
-
—
- {/if}
+
{b.doubleChance > 0 ? `${(b.doubleChance * 100).toFixed(0)}%` : '—'}
-
Ponte Critique
- {#if b.critChance > 0}
- {(b.critChance * 100).toFixed(0)}% chance de x10
- {:else}
- Branche Ponte — "Ponte Critique" (20 ADN)
- {/if}
+ {b.critChance > 0 ? `${(b.critChance * 100).toFixed(0)}% chance de x10` : 'Branche Ponte — 20 ADN'}
- {#if b.critChance > 0}
-
{(b.critChance * 100).toFixed(0)}%
- {:else}
-
—
- {/if}
+
{b.critChance > 0 ? `${(b.critChance * 100).toFixed(0)}%` : '—'}
-
Auto-Ponte
- {#if b.autoClicksPerSec > 0}
- {b.autoClicksPerSec.toFixed(1)} clics/s auto → {formatNumber(b.effectivePerSec)}/s
- {:else}
- Capstone Ponte — "Ponte Automatique" (200 ADN)
- {/if}
+ {b.autoClicksPerSec > 0 ? `${b.autoClicksPerSec.toFixed(1)} clics/s → ${formatNumber(b.effectivePerSec)}/s` : 'Capstone Ponte — 200 ADN'}
- {#if b.autoClicksPerSec > 0}
-
{formatNumber(b.effectivePerSec)}/s
- {:else}
-
—
- {/if}
+
{b.autoClicksPerSec > 0 ? `${formatNumber(b.effectivePerSec)}/s` : '—'}
-
-
- {#if b.treeMult <= 1}
-
- Depense ton ADN dans la branche Ponte pour booster tes clics
-
- {/if}
diff --git a/Frontend/src/lib/core/economy.ts b/Frontend/src/lib/core/economy.ts
index 6c378e0..ab9a7f6 100644
--- a/Frontend/src/lib/core/economy.ts
+++ b/Frontend/src/lib/core/economy.ts
@@ -86,11 +86,39 @@ export interface RunStats {
} | null;
}
+// --- Click Upgrades (achetables en têtards, liés aux générateurs) ---
+
+export interface ClickUpgrade {
+ id: string;
+ name: string;
+ generatorId: string; // lié à quel générateur
+ baseClickPower: number; // bonus clic par niveau
+ baseCost: number; // coût de base
+ level: number; // niveaux achetés
+}
+
+export const DEFAULT_CLICK_UPGRADES: ClickUpgrade[] = [
+ { id: "nid_douillet", name: "Nid Douillet", generatorId: "nid", baseClickPower: 1, baseCost: 50, level: 0 },
+ { id: "eau_fertile", name: "Eau Fertile", generatorId: "mare", baseClickPower: 3, baseCost: 500, level: 0 },
+ { id: "spores_actives", name: "Spores Actives", generatorId: "marecage", baseClickPower: 8, baseCost: 5_000, level: 0 },
+ { id: "courant_vital", name: "Courant Vital", generatorId: "etang", baseClickPower: 20, baseCost: 50_000, level: 0 },
+ { id: "source_ancestrale", name: "Source Ancestrale", generatorId: "lac", baseClickPower: 50, baseCost: 500_000, level: 0 },
+];
+
+export function clickUpgradeCost(upgrade: ClickUpgrade): number {
+ return Math.floor(upgrade.baseCost * Math.pow(1.2, upgrade.level));
+}
+
+export function totalClickUpgradePower(clickUpgrades: ClickUpgrade[]): number {
+ return clickUpgrades.reduce((sum, u) => sum + u.baseClickPower * u.level, 0);
+}
+
export interface GameState {
saveVersion: number;
resources: number;
clickMultiplier: number;
generators: Generator[];
+ clickUpgrades: ClickUpgrade[];
lastTick: number; // timestamp ms — lazy calc reference
lastOnline: number; // timestamp ms — dernière activité réelle (tick actif)
prestigeCount: number;
@@ -618,11 +646,32 @@ export function getGeneratorClickBonus(generators: Generator[]): number {
return 1 + typesOwned * 2 + totalOwned * 0.05;
}
-// Gain par clic — scaling propre : base × prestige × arbre × generateurs
+// Gain par clic — base + upgrades, le tout multiplié par prestige × arbre × infra
export function getClickGain(state: GameState): number {
const treeClickMult = getClickMultiplierFromTree(state.evolutionTree);
const genBonus = getGeneratorClickBonus(state.generators);
- return Math.floor(state.clickMultiplier * state.prestigeMultiplier * treeClickMult * genBonus);
+ const upgradePower = totalClickUpgradePower(state.clickUpgrades ?? []);
+ const base = state.clickMultiplier + upgradePower;
+ return Math.floor(base * state.prestigeMultiplier * treeClickMult * genBonus);
+}
+
+// Achat d'un click upgrade (coûte des têtards)
+export function buyClickUpgrade(state: GameState, upgradeId: string): GameState | null {
+ const idx = (state.clickUpgrades ?? []).findIndex((u) => u.id === upgradeId);
+ if (idx === -1) return null;
+
+ const upgrade = state.clickUpgrades[idx];
+ // Requires owning the linked generator
+ const gen = state.generators.find((g) => g.id === upgrade.generatorId);
+ if (!gen || gen.owned === 0) return null;
+
+ const cost = clickUpgradeCost(upgrade);
+ if (state.resources < cost) return null;
+
+ const updatedUpgrades = [...state.clickUpgrades];
+ updatedUpgrades[idx] = { ...upgrade, level: upgrade.level + 1 };
+
+ return { ...state, resources: state.resources - cost, clickUpgrades: updatedUpgrades };
}
// Breakdown complet du clic (pour affichage cockpit)
@@ -641,7 +690,8 @@ export interface ClickBreakdown {
}
export function getClickBreakdown(state: GameState): ClickBreakdown {
- const base = state.clickMultiplier;
+ const upgradePower = totalClickUpgradePower(state.clickUpgrades ?? []);
+ const base = state.clickMultiplier + upgradePower;
const prestigeMult = state.prestigeMultiplier;
const treeMult = getClickMultiplierFromTree(state.evolutionTree);
const genBonus = getGeneratorClickBonus(state.generators);
@@ -817,6 +867,8 @@ export function applyPrestige(state: GameState): GameState {
},
freeResetAvailable: true, // 1 reset gratuit offert par prestige
extraResetsUsed: 0,
+ // Click upgrades reset au prestige (comme les générateurs)
+ clickUpgrades: (state.clickUpgrades ?? DEFAULT_CLICK_UPGRADES).map((u) => ({ ...u, level: 0 })),
// evolutionTree persiste — jamais reset
};
}
@@ -835,6 +887,7 @@ export const DEFAULT_STATE: GameState = {
resources: 0,
clickMultiplier: 1,
generators: DEFAULT_GENERATORS,
+ clickUpgrades: DEFAULT_CLICK_UPGRADES,
lastTick: Date.now(),
lastOnline: Date.now(),
prestigeCount: 0,
diff --git a/Frontend/src/lib/core/migrateSave.ts b/Frontend/src/lib/core/migrateSave.ts
index 5fbfad5..ab423d4 100644
--- a/Frontend/src/lib/core/migrateSave.ts
+++ b/Frontend/src/lib/core/migrateSave.ts
@@ -3,8 +3,8 @@
// Chaque sprint ajoute un step (v2→v3, etc.)
import { CURRENT_SAVE_VERSION } from "./balance";
-import type { GameState } from "./economy";
-import { DEFAULT_EVOLUTION_TREE, DEFAULT_GENERATORS } from "./economy";
+import type { GameState, ClickUpgrade } from "./economy";
+import { DEFAULT_EVOLUTION_TREE, DEFAULT_GENERATORS, DEFAULT_CLICK_UPGRADES } from "./economy";
/**
* Détecte la version d'une save et applique les migrations nécessaires.
@@ -32,6 +32,11 @@ export function migrateSave(raw: Record
): GameState {
state.generators as Array> | undefined
);
+ // Click upgrades — merge with defaults (preserves levels, adds new upgrades)
+ state.clickUpgrades = mergeClickUpgrades(
+ state.clickUpgrades as Array> | undefined
+ );
+
return state as unknown as GameState;
}
@@ -149,3 +154,25 @@ function mergeGenerators(
return { ...defaultGen };
});
}
+
+/**
+ * Merge les click upgrades sauvegardés avec DEFAULT_CLICK_UPGRADES.
+ * Conserve le level, met à jour les stats de base.
+ */
+function mergeClickUpgrades(
+ saved: Array> | undefined
+): ClickUpgrade[] {
+ if (!saved || !Array.isArray(saved)) {
+ return DEFAULT_CLICK_UPGRADES.map((u) => ({ ...u }));
+ }
+
+ const savedById = new Map(saved.map((u) => [u.id as string, u]));
+
+ return DEFAULT_CLICK_UPGRADES.map((def) => {
+ const s = savedById.get(def.id);
+ if (s) {
+ return { ...def, level: typeof s.level === "number" ? s.level : 0 };
+ }
+ return { ...def };
+ });
+}
diff --git a/Frontend/src/lib/stores/game.svelte.ts b/Frontend/src/lib/stores/game.svelte.ts
index 6959fd3..134f50a 100644
--- a/Frontend/src/lib/stores/game.svelte.ts
+++ b/Frontend/src/lib/stores/game.svelte.ts
@@ -21,6 +21,7 @@ import {
totalProductionPerSecond,
generatorCost as genCost,
computeOfflineGains,
+ buyClickUpgrade as buyClickUpgradeFn,
} from '$lib/core/economy';
import { migrateSave } from '$lib/core/migrateSave';
import { toast } from './toast.svelte';
@@ -133,6 +134,12 @@ class Game {
if (updated) this.applyState(updated);
}
+ buyClickUpgrade(upgradeId: string) {
+ if (!this.ready) return;
+ const updated = buyClickUpgradeFn(this.state, upgradeId);
+ if (updated) this.applyState(updated);
+ }
+
buyNode(nodeId: string) {
if (!this.ready) return;
const updated = buyEvolutionNode(this.state, nodeId);