Added Next Achievement section to Statistics panel with progress bar. Tracks all 30 achievements across click, stardust, building, production, prestige, click power, and time categories. Shows closest unachieved milestone with percentage progress updating every tick.
289 lines
13 KiB
JavaScript
289 lines
13 KiB
JavaScript
/**
|
|
* Cosmic Garden - Statistics Panel
|
|
* Tracks and displays game statistics
|
|
*/
|
|
|
|
const Stats = {
|
|
/**
|
|
* Initialize the stats panel
|
|
*/
|
|
init() {
|
|
this.render();
|
|
},
|
|
|
|
/**
|
|
* Render the stats panel
|
|
*/
|
|
render() {
|
|
const container = document.getElementById('stats-content');
|
|
if (!container) return;
|
|
|
|
container.innerHTML = `
|
|
<div class="stat-item">
|
|
<div class="stat-label">Total Stardust Earned</div>
|
|
<div class="stat-value" id="stat-total-stardust">0 ✨</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-label">Current Production</div>
|
|
<div class="stat-value" id="stat-production">0 ✨/sec</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-label">Total Clicks</div>
|
|
<div class="stat-value" id="stat-clicks">0</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-label">Click Power</div>
|
|
<div class="stat-value" id="stat-click-power">1 ✨</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-label">Time Played</div>
|
|
<div class="stat-value" id="stat-time-played">0:00:00</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-label">Buildings Owned</div>
|
|
<div class="stat-value" id="stat-buildings">0</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-label">Upgrades Purchased</div>
|
|
<div class="stat-value" id="stat-upgrades">0/${Object.keys(UPGRADES).length}</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-label">Achievements Unlocked</div>
|
|
<div class="stat-value" id="stat-achievements">0/${Object.keys(ACHIEVEMENTS).length}</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-label">Next Achievement</div>
|
|
<div id="stat-next-achievement" style="font-size:0.85rem; color:var(--text-secondary); margin-top:4px">Loading...</div>
|
|
<div class="stat-bar">
|
|
<div class="stat-bar-fill" id="stat-achievement-progress" style="width: 0%"></div>
|
|
</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-label">Game Progress</div>
|
|
<div class="stat-bar">
|
|
<div class="stat-bar-fill" id="stat-progress-bar" style="width: 0%"></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
},
|
|
|
|
/**
|
|
* Update stats display
|
|
*/
|
|
update() {
|
|
// Total stardust
|
|
const totalStardustEl = document.getElementById('stat-total-stardust');
|
|
if (totalStardustEl) {
|
|
totalStardustEl.textContent = `${Game.formatNumber(Game.state.totalStardust)} ✨`;
|
|
}
|
|
|
|
// Current production
|
|
const productionEl = document.getElementById('stat-production');
|
|
if (productionEl) {
|
|
productionEl.textContent = `${Game.formatNumber(Game.getProductionPerSecond())} ✨/sec`;
|
|
}
|
|
|
|
// Total clicks
|
|
const clicksEl = document.getElementById('stat-clicks');
|
|
if (clicksEl) {
|
|
clicksEl.textContent = Game.formatNumber(Game.state.totalClicks);
|
|
}
|
|
|
|
// Click power
|
|
const clickPowerEl = document.getElementById('stat-click-power');
|
|
if (clickPowerEl) {
|
|
clickPowerEl.textContent = `${Game.formatNumber(Game.getClickPower())} ✨`;
|
|
}
|
|
|
|
// Time played
|
|
const timePlayedEl = document.getElementById('stat-time-played');
|
|
if (timePlayedEl) {
|
|
const elapsed = Math.floor((Date.now() - Game.state.startTime) / 1000);
|
|
timePlayedEl.textContent = this.formatTime(elapsed);
|
|
}
|
|
|
|
// Buildings owned
|
|
const buildingsEl = document.getElementById('stat-buildings');
|
|
if (buildingsEl) {
|
|
const total = Object.values(Game.state.buildings).reduce((a, b) => a + b, 0);
|
|
buildingsEl.textContent = total;
|
|
}
|
|
|
|
// Upgrades purchased
|
|
const upgradesEl = document.getElementById('stat-upgrades');
|
|
if (upgradesEl) {
|
|
upgradesEl.textContent = `${Game.state.upgrades.length}/${Object.keys(UPGRADES).length}`;
|
|
}
|
|
|
|
// Achievements unlocked
|
|
const achievementsEl = document.getElementById('stat-achievements');
|
|
if (achievementsEl) {
|
|
achievementsEl.textContent = `${Achievements.getCount()}/${Achievements.getTotal()}`;
|
|
}
|
|
|
|
// Game progress (based on total stardust milestones)
|
|
const progressBarEl = document.getElementById('stat-progress-bar');
|
|
if (progressBarEl) {
|
|
const progress = this.calculateProgress();
|
|
progressBarEl.style.width = `${progress}%`;
|
|
}
|
|
|
|
// Next achievement tracking
|
|
this.updateNextAchievement();
|
|
},
|
|
|
|
/**
|
|
* Update the next achievement display with progress bar
|
|
*/
|
|
updateNextAchievement() {
|
|
const nextEl = document.getElementById('stat-next-achievement');
|
|
const progressEl = document.getElementById('stat-achievement-progress');
|
|
if (!nextEl) return;
|
|
|
|
// Find the closest unachieved milestone
|
|
let closest = null;
|
|
let closestProgress = -1;
|
|
|
|
for (const [id, achievement] of Object.entries(ACHIEVEMENTS)) {
|
|
if (Game.state.achievements.includes(id)) continue;
|
|
|
|
const result = this.calculateAchievementProgress(achievement);
|
|
if (result && result.progress > closestProgress) {
|
|
closestProgress = result.progress;
|
|
closest = { ...result, name: achievement.name, icon: achievement.icon };
|
|
}
|
|
}
|
|
|
|
if (closest) {
|
|
nextEl.textContent = `${closest.icon} ${closest.name}`;
|
|
if (progressEl) {
|
|
progressEl.style.width = `${Math.min(100, Math.max(0, closest.progress))}%`;
|
|
}
|
|
} else {
|
|
nextEl.textContent = 'All achievements unlocked! 🎉';
|
|
if (progressEl) {
|
|
progressEl.style.width = '100%';
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Calculate progress toward a specific achievement
|
|
*/
|
|
calculateAchievementProgress(achievement) {
|
|
const state = Game.state;
|
|
|
|
// Click achievements
|
|
if (achievement.id === 'firstClick') return { current: state.totalClicks, target: 1, progress: Math.min(100, (state.totalClicks / 1) * 100) };
|
|
if (achievement.id === 'clicker') return { current: state.totalClicks, target: 100, progress: Math.min(100, (state.totalClicks / 100) * 100) };
|
|
if (achievement.id === 'spammer') return { current: state.totalClicks, target: 1000, progress: Math.min(100, (state.totalClicks / 1000) * 100) };
|
|
if (achievement.id === 'clickMaster') return { current: state.totalClicks, target: 10000, progress: Math.min(100, (state.totalClicks / 10000) * 100) };
|
|
|
|
// Stardust achievements
|
|
if (achievement.id === 'dustCollector') return { current: state.totalStardust, target: 1000, progress: Math.min(100, (state.totalStardust / 1000) * 100) };
|
|
if (achievement.id === 'stardustMagnate') return { current: state.totalStardust, target: 100000, progress: Math.min(100, (state.totalStardust / 100000) * 100) };
|
|
if (achievement.id === 'cosmicTycoon') return { current: state.totalStardust, target: 10000000, progress: Math.min(100, (state.totalStardust / 10000000) * 100) };
|
|
if (achievement.id === 'galaxyBaron') return { current: state.totalStardust, target: 1000000000, progress: Math.min(100, (state.totalStardust / 1000000000) * 100) };
|
|
|
|
// Building achievements
|
|
if (achievement.id === 'firstPlant') {
|
|
const totalBuildings = Object.values(state.buildings).reduce((a, b) => a + b, 0);
|
|
return { current: totalBuildings, target: 1, progress: Math.min(100, (totalBuildings / 1) * 100) };
|
|
}
|
|
if (achievement.id === 'gardenStart') {
|
|
const totalBuildings = Object.values(state.buildings).reduce((a, b) => a + b, 0);
|
|
return { current: totalBuildings, target: 10, progress: Math.min(100, (totalBuildings / 10) * 100) };
|
|
}
|
|
if (achievement.id === 'gardenCenter') {
|
|
const totalBuildings = Object.values(state.buildings).reduce((a, b) => a + b, 0);
|
|
return { current: totalBuildings, target: 50, progress: Math.min(100, (totalBuildings / 50) * 100) };
|
|
}
|
|
if (achievement.id === 'cosmicGarden') {
|
|
const totalBuildings = Object.values(state.buildings).reduce((a, b) => a + b, 0);
|
|
return { current: totalBuildings, target: 100, progress: Math.min(100, (totalBuildings / 100) * 100) };
|
|
}
|
|
|
|
// Specific building achievements
|
|
if (achievement.id === 'sproutFarmer') return { current: state.buildings.starSprout || 0, target: 25, progress: Math.min(100, ((state.buildings.starSprout || 0) / 25) * 100) };
|
|
if (achievement.id === 'bloomGarden') return { current: state.buildings.moonBloom || 0, target: 25, progress: Math.min(100, ((state.buildings.moonBloom || 0) / 25) * 100) };
|
|
if (achievement.id === 'solarPowered') return { current: state.buildings.solarLeaf || 0, target: 25, progress: Math.min(100, ((state.buildings.solarLeaf || 0) / 25) * 100) };
|
|
if (achievement.id === 'voidWeaver') return { current: state.buildings.voidFern || 0, target: 25, progress: Math.min(100, ((state.buildings.voidFern || 0) / 25) * 100) };
|
|
if (achievement.id === 'supernovaGrowth') return { current: state.buildings.supernovaSeed || 0, target: 25, progress: Math.min(100, ((state.buildings.supernovaSeed || 0) / 25) * 100) };
|
|
|
|
// Production achievements
|
|
if (achievement.id === 'steadyStream') {
|
|
const prod = Game.getProductionPerSecond();
|
|
return { current: prod, target: 100, progress: Math.min(100, (prod / 100) * 100) };
|
|
}
|
|
if (achievement.id === 'rushingRiver') {
|
|
const prod = Game.getProductionPerSecond();
|
|
return { current: prod, target: 1000, progress: Math.min(100, (prod / 1000) * 100) };
|
|
}
|
|
if (achievement.id === 'cosmicFlood') {
|
|
const prod = Game.getProductionPerSecond();
|
|
return { current: prod, target: 100000, progress: Math.min(100, (prod / 100000) * 100) };
|
|
}
|
|
|
|
// Prestige achievements
|
|
if (achievement.id === 'firstPrestige') return { current: state.prestigeCount || 0, target: 1, progress: Math.min(100, ((state.prestigeCount || 0) / 1) * 100) };
|
|
if (achievement.id === 'stellarAdaptation') return { current: state.prestigeCount || 0, target: 5, progress: Math.min(100, ((state.prestigeCount || 0) / 5) * 100) };
|
|
if (achievement.id === 'cosmicAscension') return { current: state.prestigeCount || 0, target: 10, progress: Math.min(100, ((state.prestigeCount || 0) / 10) * 100) };
|
|
if (achievement.id === 'eternalGardener') return { current: state.totalPrestigeEssence || 0, target: 100, progress: Math.min(100, ((state.totalPrestigeEssence || 0) / 100) * 100) };
|
|
if (achievement.id === 'quantumGrowth') return { current: state.totalPrestigeEssence || 0, target: 1000, progress: Math.min(100, ((state.totalPrestigeEssence || 0) / 1000) * 100) };
|
|
|
|
// Click power achievements
|
|
if (achievement.id === 'powerfulHarvester') {
|
|
const cp = Game.getClickPower();
|
|
return { current: cp, target: 1000, progress: Math.min(100, (cp / 1000) * 100) };
|
|
}
|
|
if (achievement.id === 'divineTouch') {
|
|
const cp = Game.getClickPower();
|
|
return { current: cp, target: 10000, progress: Math.min(100, (cp / 10000) * 100) };
|
|
}
|
|
|
|
// Time-based achievement
|
|
if (achievement.id === 'dedicatedGardener') {
|
|
const elapsed = Date.now() - state.startTime;
|
|
return { current: elapsed, target: 3600000, progress: Math.min(100, (elapsed / 3600000) * 100) };
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Format elapsed time as HH:MM:SS
|
|
*/
|
|
formatTime(seconds) {
|
|
const hours = Math.floor(seconds / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
const secs = seconds % 60;
|
|
|
|
if (hours > 0) {
|
|
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
|
}
|
|
return `${minutes}:${secs.toString().padStart(2, '0')}`;
|
|
},
|
|
|
|
/**
|
|
* Calculate game progress percentage
|
|
* Based on total stardust earned vs all building costs combined
|
|
*/
|
|
calculateProgress() {
|
|
let totalCost = 0;
|
|
for (const building of Object.values(BUILDINGS)) {
|
|
// Approximate cost to buy one of each building
|
|
totalCost += building.baseCost;
|
|
}
|
|
|
|
// Add upgrade costs
|
|
for (const upgrade of Object.values(UPGRADES)) {
|
|
totalCost += upgrade.cost;
|
|
}
|
|
|
|
const progress = Math.min(100, (Game.state.totalStardust / totalCost) * 100);
|
|
return Math.round(progress);
|
|
}
|
|
};
|
|
|
|
window.Stats = Stats;
|