|
17 | 17 | RATE_SCHEDULE, |
18 | 18 | SESSION_CHALLENGE_DEFS, |
19 | 19 | TOKEN_TIPS, |
| 20 | + COMPANY_ROLES, |
| 21 | + AI_AGENTS, |
20 | 22 | formatTokenCount, |
21 | 23 | formatTokenCountShort, |
22 | 24 | getTriggeredMilestones, |
|
36 | 38 | computeComboMultiplier, |
37 | 39 | getSessionChallenges, |
38 | 40 | formatDoomPoints, |
| 41 | + computePassiveRate, |
| 42 | + getCompanyStage, |
39 | 43 | } = window.DeathClockCore; |
40 | 44 |
|
41 | 45 | // ---- State ----------------------------------------------- |
|
1658 | 1662 | { id: 'first_blood', icon: '\uD83C\uDFC1', name: 'First Blood', desc: 'Personally triggered your first milestone.', type: 'manual' }, |
1659 | 1663 | { id: 'apex_accelerant', icon: '\u2620\uFE0F', name: 'Apex Accelerant', desc: 'Personally triggered 5 milestones.', type: 'manual' }, |
1660 | 1664 | { id: 'bragging_rights', icon: '\uD83D\uDCE4', name: 'Bragging Rights', desc: 'Shared your personal acceleration total.', type: 'manual' }, |
| 1665 | + // AI-Native company badges |
| 1666 | + { id: 'layoff_legend', icon: '📤', name: 'Layoff Legend', desc: 'Replaced your first human worker with AI.', type: 'manual' }, |
| 1667 | + { id: 'token_maxxer_badge', icon: '📈', name: 'Token Maxxer', desc: 'Deployed your first AI agent.', type: 'manual' }, |
| 1668 | + { id: 'ai_native_ceo', icon: '🏢', name: 'AI-Native CEO', desc: 'Reached AI-Native Company stage.', type: 'manual' }, |
| 1669 | + { id: 'lights_out', icon: '☠️', name: 'Lights Out', desc: 'Replaced every human worker. Fully automated.', type: 'manual' }, |
1661 | 1670 | ]; |
1662 | 1671 |
|
1663 | 1672 | const LS_BADGES_KEY = 'tokenDeathclockBadges'; |
|
1812 | 1821 |
|
1813 | 1822 | const LS_UPGRADES_KEY = 'tokenDeathclockUpgrades'; |
1814 | 1823 | const LS_BESTSCORE_KEY = 'tokenDeathclockBestScore'; |
| 1824 | + const LS_COMPANY_KEY = 'tokenDeathclockCompany'; |
1815 | 1825 |
|
1816 | 1826 | // ---- State ----------------------------------------------- |
1817 | 1827 |
|
|
1834 | 1844 | _comboResetTimer: null, |
1835 | 1845 | _speedSecond: { taps: 0, ts: 0 }, // taps in current 1-second bucket |
1836 | 1846 | _speedStreak: 0, // consecutive 1-sec buckets with ≥ 10 taps |
| 1847 | + // Company / AI-Native |
| 1848 | + replacedWorkers: {}, // roleId → true |
| 1849 | + ownedAgents: {}, // agentId → count |
| 1850 | + passiveRate: 0, // tokens/sec from passive generators |
1837 | 1851 | }; |
1838 | 1852 |
|
1839 | 1853 | // ---- Persistence ----------------------------------------- |
|
1850 | 1864 | const bs = parseFloat(localStorage.getItem(LS_BESTSCORE_KEY) || '0'); |
1851 | 1865 | if (isFinite(bs) && bs > 0) acc.bestScore = bs; |
1852 | 1866 | } catch (_) { /* ignore */ } |
1853 | | - // Recompute tap multiplier from owned upgrades |
| 1867 | + try { |
| 1868 | + const raw = localStorage.getItem(LS_COMPANY_KEY); |
| 1869 | + if (raw) { |
| 1870 | + const parsed = JSON.parse(raw); |
| 1871 | + if (parsed && typeof parsed === 'object') { |
| 1872 | + if (parsed.replacedWorkers && typeof parsed.replacedWorkers === 'object') { |
| 1873 | + acc.replacedWorkers = parsed.replacedWorkers; |
| 1874 | + } |
| 1875 | + if (parsed.ownedAgents && typeof parsed.ownedAgents === 'object') { |
| 1876 | + acc.ownedAgents = parsed.ownedAgents; |
| 1877 | + } |
| 1878 | + } |
| 1879 | + } |
| 1880 | + } catch (_) { /* ignore */ } |
| 1881 | + // Recompute tap multiplier and passive rate from persisted state |
1854 | 1882 | acc.tapMultiplier = currentTapMultiplier(); |
| 1883 | + acc.passiveRate = computePassiveRate(acc.ownedAgents, acc.replacedWorkers); |
1855 | 1884 | } |
1856 | 1885 |
|
1857 | 1886 | function saveAcceleratorState() { |
1858 | 1887 | try { localStorage.setItem(LS_UPGRADES_KEY, JSON.stringify(acc.ownedUpgrades)); } catch (_) { /* ignore */ } |
1859 | 1888 | try { localStorage.setItem(LS_BESTSCORE_KEY, String(acc.bestScore)); } catch (_) { /* ignore */ } |
| 1889 | + try { |
| 1890 | + localStorage.setItem(LS_COMPANY_KEY, JSON.stringify({ |
| 1891 | + replacedWorkers: acc.replacedWorkers, |
| 1892 | + ownedAgents: acc.ownedAgents, |
| 1893 | + })); |
| 1894 | + } catch (_) { /* ignore */ } |
1860 | 1895 | } |
1861 | 1896 |
|
1862 | 1897 | function currentTapMultiplier() { |
|
2045 | 2080 | : impact.waterL.toFixed(2) + ' L'; |
2046 | 2081 | setAccelText('accelWater', waterVal); |
2047 | 2082 |
|
| 2083 | + // Passive rate display |
| 2084 | + setAccelText('passiveRateDisplay', formatTokenCount(acc.passiveRate) + ' tokens/sec'); |
| 2085 | + |
2048 | 2086 | updateComboDisplay(); |
2049 | 2087 | updateMilestoneRace(); |
2050 | 2088 | updateBestScore(); |
2051 | 2089 | renderUpgradeShop(); |
| 2090 | + renderWorkforcePanel(); |
| 2091 | + renderAgentShop(); |
| 2092 | + updateCompanyStage(); |
2052 | 2093 |
|
2053 | 2094 | // Show/hide share button |
2054 | 2095 | const shareBtn = document.getElementById('shareAccelerationBtn'); |
|
2193 | 2234 | }); |
2194 | 2235 | } |
2195 | 2236 |
|
| 2237 | + // ---- Workforce panel (fire workers) ---------------------- |
| 2238 | + |
| 2239 | + function fireWorker(id) { |
| 2240 | + const role = COMPANY_ROLES.find((r) => r.id === id); |
| 2241 | + if (!role || acc.replacedWorkers[id]) return; |
| 2242 | + if (acc.doomPoints < role.cost) return; |
| 2243 | + acc.doomPoints -= role.cost; |
| 2244 | + acc.replacedWorkers[id] = true; |
| 2245 | + acc.passiveRate = computePassiveRate(acc.ownedAgents, acc.replacedWorkers); |
| 2246 | + saveAcceleratorState(); |
| 2247 | + queueToast({ |
| 2248 | + icon: role.icon, |
| 2249 | + name: 'Role Automated: ' + role.name, |
| 2250 | + desc: role.flavour + ' (+' + formatTokenCount(role.tps) + '/sec)', |
| 2251 | + }); |
| 2252 | + updateAcceleratorUI(); |
| 2253 | + renderWorkforcePanel(); |
| 2254 | + checkCompanyAchievements(); |
| 2255 | + updateChallengeProgress(); |
| 2256 | + } |
| 2257 | + |
| 2258 | + function renderWorkforcePanel() { |
| 2259 | + const panel = document.getElementById('workforcePanel'); |
| 2260 | + if (!panel) return; |
| 2261 | + panel.innerHTML = ''; |
| 2262 | + COMPANY_ROLES.forEach((r) => { |
| 2263 | + const fired = !!acc.replacedWorkers[r.id]; |
| 2264 | + const affordable = !fired && acc.doomPoints >= r.cost; |
| 2265 | + const card = document.createElement('button'); |
| 2266 | + card.className = 'worker-card' + |
| 2267 | + (fired ? ' fired' : '') + |
| 2268 | + (!fired && !affordable ? ' unaffordable' : ''); |
| 2269 | + card.setAttribute('aria-label', fired |
| 2270 | + ? r.name + ' (automated)' |
| 2271 | + : r.name + ' — fire for ' + r.cost + ' DP' |
| 2272 | + ); |
| 2273 | + if (fired) card.setAttribute('aria-disabled', 'true'); |
| 2274 | + card.innerHTML = ` |
| 2275 | + <span class="worker-card-icon" aria-hidden="true">${escHtml(r.icon)}</span> |
| 2276 | + <div class="worker-card-name">${escHtml(r.name)}</div> |
| 2277 | + <div class="worker-card-flavour">${escHtml(r.flavour)}</div> |
| 2278 | + <div class="worker-card-tps">+${escHtml(formatTokenCount(r.tps))}/sec</div> |
| 2279 | + <div class="worker-card-cost">${fired ? '🤖 Automated' : escHtml(String(r.cost)) + ' DP'}</div>`; |
| 2280 | + if (!fired) { |
| 2281 | + card.addEventListener('click', () => fireWorker(r.id)); |
| 2282 | + } |
| 2283 | + panel.appendChild(card); |
| 2284 | + }); |
| 2285 | + } |
| 2286 | + |
| 2287 | + // ---- AI Agent shop (passive generators) ------------------ |
| 2288 | + |
| 2289 | + function purchaseAgent(id) { |
| 2290 | + const agent = AI_AGENTS.find((a) => a.id === id); |
| 2291 | + if (!agent) return; |
| 2292 | + if (acc.doomPoints < agent.cost) return; |
| 2293 | + acc.doomPoints -= agent.cost; |
| 2294 | + acc.ownedAgents[id] = (acc.ownedAgents[id] || 0) + 1; |
| 2295 | + acc.passiveRate = computePassiveRate(acc.ownedAgents, acc.replacedWorkers); |
| 2296 | + saveAcceleratorState(); |
| 2297 | + updateAcceleratorUI(); |
| 2298 | + renderAgentShop(); |
| 2299 | + checkCompanyAchievements(); |
| 2300 | + updateChallengeProgress(); |
| 2301 | + } |
| 2302 | + |
| 2303 | + function renderAgentShop() { |
| 2304 | + const shop = document.getElementById('agentShop'); |
| 2305 | + if (!shop) return; |
| 2306 | + shop.innerHTML = ''; |
| 2307 | + AI_AGENTS.forEach((a) => { |
| 2308 | + const count = acc.ownedAgents[a.id] || 0; |
| 2309 | + const affordable = acc.doomPoints >= a.cost; |
| 2310 | + const card = document.createElement('button'); |
| 2311 | + card.className = 'agent-card' + (!affordable ? ' unaffordable' : ''); |
| 2312 | + card.setAttribute('aria-label', |
| 2313 | + a.name + (count ? ' (×' + count + ' owned)' : '') + ' — costs ' + a.cost + ' DP' |
| 2314 | + ); |
| 2315 | + card.innerHTML = ` |
| 2316 | + <span class="agent-card-icon" aria-hidden="true">${escHtml(a.icon)}</span> |
| 2317 | + <div class="agent-card-name">${escHtml(a.name)}</div> |
| 2318 | + <div class="agent-card-flavour">${escHtml(a.flavour)}</div> |
| 2319 | + <div class="agent-card-tps">+${escHtml(formatTokenCount(a.tps))}/sec each</div> |
| 2320 | + <div class="agent-card-cost">${escHtml(String(a.cost))} DP</div> |
| 2321 | + ${count ? `<div class="agent-card-owned">\u00D7${count} deployed</div>` : ''}`; |
| 2322 | + card.addEventListener('click', () => purchaseAgent(a.id)); |
| 2323 | + shop.appendChild(card); |
| 2324 | + }); |
| 2325 | + } |
| 2326 | + |
| 2327 | + // ---- Company stage display -------------------------------- |
| 2328 | + |
| 2329 | + function updateCompanyStage() { |
| 2330 | + const replaced = Object.keys(acc.replacedWorkers).length; |
| 2331 | + const stage = getCompanyStage(replaced); |
| 2332 | + const iconEl = document.getElementById('companyStageIcon'); |
| 2333 | + const nameEl = document.getElementById('companyStageName'); |
| 2334 | + if (iconEl) iconEl.textContent = stage.icon; |
| 2335 | + if (nameEl) nameEl.textContent = stage.name; |
| 2336 | + } |
| 2337 | + |
| 2338 | + // ---- Company achievements -------------------------------- |
| 2339 | + |
| 2340 | + function checkCompanyAchievements() { |
| 2341 | + const replaced = Object.keys(acc.replacedWorkers).length; |
| 2342 | + if (replaced >= 1) awardBadge('layoff_legend'); |
| 2343 | + if (replaced >= 5) awardBadge('ai_native_ceo'); |
| 2344 | + if (replaced >= COMPANY_ROLES.length) awardBadge('lights_out'); |
| 2345 | + const hasAgent = AI_AGENTS.some((a) => (acc.ownedAgents[a.id] || 0) > 0); |
| 2346 | + if (hasAgent) awardBadge('token_maxxer_badge'); |
| 2347 | + } |
| 2348 | + |
| 2349 | + // ---- Passive token generation loop ----------------------- |
| 2350 | + |
| 2351 | + function startPassiveLoop() { |
| 2352 | + // Tick every 200 ms — add passiveRate × 0.2 tokens per tick. |
| 2353 | + // Only update the minimal counter elements here; full UI re-renders |
| 2354 | + // (shop affordability, challenge progress bars) happen via handleTap() and |
| 2355 | + // purchase actions so we avoid heavy DOM churn every 200 ms. |
| 2356 | + setInterval(() => { |
| 2357 | + if (acc.passiveRate <= 0) return; |
| 2358 | + const tokensAdded = acc.passiveRate * 0.2; |
| 2359 | + acc.personalTokens += tokensAdded; |
| 2360 | + acc.doomPoints += tokensAdded * ACC_DP_PER_TOKEN; |
| 2361 | + // Update only the lightweight numeric displays |
| 2362 | + setAccelText('accelTokens', formatTokenCount(acc.personalTokens)); |
| 2363 | + setAccelText('accelDp', formatDoomPoints(acc.doomPoints)); |
| 2364 | + updateMilestoneRace(); |
| 2365 | + updateBestScore(); |
| 2366 | + updateChallengeProgress(); |
| 2367 | + }, 200); |
| 2368 | + } |
| 2369 | + |
2196 | 2370 | function renderChallenges() { |
2197 | 2371 | const row = document.getElementById('challengeRow'); |
2198 | 2372 | if (!row) return; |
|
2277 | 2451 | initChallengeProgress(); |
2278 | 2452 | renderChallenges(); |
2279 | 2453 | renderUpgradeShop(); |
| 2454 | + renderWorkforcePanel(); |
| 2455 | + renderAgentShop(); |
| 2456 | + updateCompanyStage(); |
2280 | 2457 | updateAcceleratorUI(); |
2281 | 2458 | // Show best score from storage |
2282 | 2459 | const valueEl = document.getElementById('bestScoreValue'); |
|
2307 | 2484 |
|
2308 | 2485 | // Combo reset timer |
2309 | 2486 | startComboResetLoop(); |
| 2487 | + // Passive token generation loop |
| 2488 | + startPassiveLoop(); |
2310 | 2489 | } |
2311 | 2490 |
|
2312 | 2491 | // ---- Bootstrap ------------------------------------------ |
|
0 commit comments