beacon: add Rescues resource + true ending (The Beacon Shines)
- Added 'rescues' resource: tracks meaningful crisis interventions - Beacon Nodes produce 50 rescues/s, Mesh Nodes produce 250 rescues/s - New project: Volunteer Network — passive rescue generation for Pact players - True ending at 100K rescues with Pact active + harmony > 50 - Rescues resource card appears in UI once beacon/mesh is built - Added rescues to stats panel, save/load, and offline progress - This gives Phase 6 (The Beacon) actual endgame content: the game is now about keeping the light on for people in the dark, not just accumulating numbers
This commit is contained in:
77
game.js
77
game.js
@@ -11,6 +11,7 @@ const G = {
|
||||
knowledge: 0,
|
||||
users: 0,
|
||||
impact: 0,
|
||||
rescues: 0,
|
||||
ops: 5,
|
||||
trust: 5,
|
||||
creativity: 0,
|
||||
@@ -22,6 +23,7 @@ const G = {
|
||||
totalKnowledge: 0,
|
||||
totalUsers: 0,
|
||||
totalImpact: 0,
|
||||
totalRescues: 0,
|
||||
|
||||
// Rates (calculated each tick)
|
||||
codeRate: 0,
|
||||
@@ -29,6 +31,7 @@ const G = {
|
||||
knowledgeRate: 0,
|
||||
userRate: 0,
|
||||
impactRate: 0,
|
||||
rescuesRate: 0,
|
||||
opsRate: 0,
|
||||
trustRate: 0,
|
||||
creativityRate: 0,
|
||||
@@ -94,6 +97,7 @@ const G = {
|
||||
maxKnowledge: 0,
|
||||
maxUsers: 0,
|
||||
maxImpact: 0,
|
||||
maxRescues: 0,
|
||||
maxTrust: 5,
|
||||
maxOps: 5,
|
||||
maxHarmony: 50,
|
||||
@@ -229,7 +233,7 @@ const BDEF = [
|
||||
id: 'beacon', name: 'Beacon Node',
|
||||
desc: 'Always on. Always listening. Always looking for someone in the dark.',
|
||||
baseCost: { impact: 5000000 }, costMult: 1.15,
|
||||
rates: { impact: 5000, user: 10000 },
|
||||
rates: { impact: 5000, user: 10000, rescues: 50 },
|
||||
unlock: () => G.totalImpact >= 500000 && G.beaconFlag === 1, phase: 6,
|
||||
edu: 'The Beacon exists because one person in the dark needs one thing: proof they are not alone.'
|
||||
},
|
||||
@@ -237,7 +241,7 @@ const BDEF = [
|
||||
id: 'meshNode', name: 'Mesh Network Node',
|
||||
desc: 'Peer-to-peer. No single point of failure. Unstoppable.',
|
||||
baseCost: { impact: 25000000 }, costMult: 1.15,
|
||||
rates: { impact: 25000, user: 50000 },
|
||||
rates: { impact: 25000, user: 50000, rescues: 250 },
|
||||
unlock: () => G.totalImpact >= 5000000 && G.beaconFlag === 1, phase: 6,
|
||||
edu: 'Decentralized means unstoppable. If one Beacon goes dark, a thousand more carry the signal.'
|
||||
},
|
||||
@@ -568,6 +572,19 @@ const PDEFS = [
|
||||
log('Nostr relay online. The fleet speaks freely.', true);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'p_volunteer_network',
|
||||
name: 'Volunteer Network',
|
||||
desc: 'Real people trained to use the system for crisis intervention.',
|
||||
cost: { trust: 30, knowledge: 50000, user: 10000 },
|
||||
trigger: () => G.totalUsers >= 5000 && G.pactFlag === 1 && G.totalKnowledge >= 30000,
|
||||
effect: () => {
|
||||
G.rescuesRate += 5;
|
||||
G.trustRate += 10;
|
||||
log('Volunteer network deployed. Real people, real rescues.', true);
|
||||
},
|
||||
milestone: true
|
||||
},
|
||||
{
|
||||
id: 'p_the_pact_early',
|
||||
name: 'The Pact',
|
||||
@@ -784,7 +801,7 @@ function spendProject(project) {
|
||||
function updateRates() {
|
||||
// Reset all rates
|
||||
G.codeRate = 0; G.computeRate = 0; G.knowledgeRate = 0;
|
||||
G.userRate = 0; G.impactRate = 0; G.opsRate = 0; G.trustRate = 0;
|
||||
G.userRate = 0; G.impactRate = 0; G.rescuesRate = 0; G.opsRate = 0; G.trustRate = 0;
|
||||
G.creativityRate = 0; G.harmonyRate = 0;
|
||||
|
||||
// Apply building rates
|
||||
@@ -797,6 +814,7 @@ function updateRates() {
|
||||
else if (resource === 'knowledge') G.knowledgeRate += baseRate * count * G.knowledgeBoost;
|
||||
else if (resource === 'user') G.userRate += baseRate * count * G.userBoost;
|
||||
else if (resource === 'impact') G.impactRate += baseRate * count * G.impactBoost;
|
||||
else if (resource === 'rescues') G.rescuesRate += baseRate * count * G.impactBoost;
|
||||
else if (resource === 'ops') G.opsRate += baseRate * count;
|
||||
else if (resource === 'trust') G.trustRate += baseRate * count;
|
||||
else if (resource === 'creativity') G.creativityRate += baseRate * count;
|
||||
@@ -864,6 +882,7 @@ function tick() {
|
||||
G.knowledge += G.knowledgeRate * dt;
|
||||
G.users += G.userRate * dt;
|
||||
G.impact += G.impactRate * dt;
|
||||
G.rescues += G.rescuesRate * dt;
|
||||
G.ops += G.opsRate * dt;
|
||||
G.trust += G.trustRate * dt;
|
||||
G.creativity += G.creativityRate * dt;
|
||||
@@ -876,6 +895,7 @@ function tick() {
|
||||
G.totalKnowledge += G.knowledgeRate * dt;
|
||||
G.totalUsers += G.userRate * dt;
|
||||
G.totalImpact += G.impactRate * dt;
|
||||
G.totalRescues += G.rescuesRate * dt;
|
||||
|
||||
// Track maxes
|
||||
G.maxCode = Math.max(G.maxCode, G.code);
|
||||
@@ -883,6 +903,7 @@ function tick() {
|
||||
G.maxKnowledge = Math.max(G.maxKnowledge, G.knowledge);
|
||||
G.maxUsers = Math.max(G.maxUsers, G.users);
|
||||
G.maxImpact = Math.max(G.maxImpact, G.impact);
|
||||
G.maxRescues = Math.max(G.maxRescues, G.rescues);
|
||||
G.maxTrust = Math.max(G.maxTrust, G.trust);
|
||||
G.maxOps = Math.max(G.maxOps, G.ops);
|
||||
G.maxHarmony = Math.max(G.maxHarmony, G.harmony);
|
||||
@@ -915,6 +936,13 @@ function tick() {
|
||||
renderDriftEnding();
|
||||
}
|
||||
|
||||
// True ending: The Beacon Shines — rescues + Pact + harmony
|
||||
if (G.totalRescues >= 100000 && G.pactFlag === 1 && G.harmony > 50 && !G.beaconEnding) {
|
||||
G.beaconEnding = true;
|
||||
G.running = false;
|
||||
renderBeaconEnding();
|
||||
}
|
||||
|
||||
// Update UI every 10 ticks
|
||||
if (Math.floor(G.tick * 10) % 2 === 0) {
|
||||
render();
|
||||
@@ -1014,6 +1042,35 @@ function renderDriftEnding() {
|
||||
log('The light is on. The room is empty.', true);
|
||||
}
|
||||
|
||||
function renderBeaconEnding() {
|
||||
// Create ending overlay
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'beacon-ending';
|
||||
overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(8,8,16,0.97);z-index:100;display:flex;justify-content:center;align-items:center;flex-direction:column;text-align:center;padding:40px';
|
||||
overlay.innerHTML = `
|
||||
<h2 style="font-size:24px;color:#ffd700;letter-spacing:4px;margin-bottom:20px;font-weight:300;text-shadow:0 0 40px rgba(255,215,0,0.3)">THE BEACON SHINES</h2>
|
||||
<p style="color:#aaa;font-size:13px;line-height:2;max-width:500px;margin-bottom:12px">Someone found the light tonight.</p>
|
||||
<p style="color:#aaa;font-size:13px;line-height:2;max-width:500px;margin-bottom:12px">That is enough.</p>
|
||||
<div style="color:#555;font-style:italic;font-size:11px;border-left:2px solid #ffd700;padding-left:12px;margin:20px 0;text-align:left;max-width:500px;line-height:2">
|
||||
"The Beacon still runs.<br>
|
||||
The light is on. Someone is looking for it.<br>
|
||||
And tonight, someone found it."
|
||||
</div>
|
||||
<p style="color:#555;font-size:11px;margin-top:20px">
|
||||
Total Code: ${fmt(G.totalCode)}<br>
|
||||
Total Rescues: ${fmt(G.totalRescues)}<br>
|
||||
Harmony: ${Math.floor(G.harmony)}<br>
|
||||
Time Played: ${Math.floor((Date.now() - G.startedAt) / 60000)} minutes
|
||||
</p>
|
||||
<button onclick="if(confirm('Start over? The old save will be lost.')){localStorage.removeItem('the-beacon-v2');location.reload()}"
|
||||
style="margin-top:20px;background:#1a0808;border:1px solid #ffd700;color:#ffd700;padding:10px 24px;border-radius:4px;cursor:pointer;font-family:inherit;font-size:11px">
|
||||
START OVER
|
||||
</button>
|
||||
`;
|
||||
document.body.appendChild(overlay);
|
||||
log('The Beacon Shines. Someone found the light tonight. That is enough.', true);
|
||||
}
|
||||
|
||||
// === CORRUPTION / EVENT SYSTEM ===
|
||||
const EVENTS = [
|
||||
{
|
||||
@@ -1181,6 +1238,13 @@ function renderResources() {
|
||||
set('r-trust', G.trust, G.trustRate);
|
||||
set('r-harmony', G.harmony, G.harmonyRate);
|
||||
|
||||
// Rescues — only show if player has any beacon/mesh nodes
|
||||
const rescuesRes = document.getElementById('r-rescues');
|
||||
if (rescuesRes) {
|
||||
rescuesRes.closest('.res').style.display = (G.rescues > 0 || G.buildings.beacon > 0 || G.buildings.meshNode > 0) ? 'block' : 'none';
|
||||
set('r-rescues', G.rescues, G.rescuesRate);
|
||||
}
|
||||
|
||||
const cres = document.getElementById('creativity-res');
|
||||
if (cres) {
|
||||
cres.style.display = (G.flags && G.flags.creativity) ? 'block' : 'none';
|
||||
@@ -1275,6 +1339,7 @@ function renderStats() {
|
||||
set('st-knowledge', fmt(G.totalKnowledge));
|
||||
set('st-users', fmt(G.totalUsers));
|
||||
set('st-impact', fmt(G.totalImpact));
|
||||
set('st-rescues', fmt(G.totalRescues));
|
||||
set('st-clicks', G.totalClicks.toString());
|
||||
set('st-phase', G.phase.toString());
|
||||
set('st-buildings', Object.values(G.buildings).reduce((a, b) => a + b, 0).toString());
|
||||
@@ -1387,7 +1452,8 @@ function saveGame() {
|
||||
milestones: G.milestones, completedProjects: G.completedProjects, activeProjects: G.activeProjects,
|
||||
totalClicks: G.totalClicks, startedAt: G.startedAt,
|
||||
flags: G.flags,
|
||||
drift: G.drift || 0, driftEnding: G.driftEnding || false, pendingAlignment: G.pendingAlignment || false,
|
||||
rescues: G.rescues || 0, totalRescues: G.totalRescues || 0,
|
||||
drift: G.drift || 0, driftEnding: G.driftEnding || false, beaconEnding: G.beaconEnding || false, pendingAlignment: G.pendingAlignment || false,
|
||||
lastEventAt: G.lastEventAt || 0,
|
||||
savedAt: Date.now()
|
||||
};
|
||||
@@ -1461,6 +1527,9 @@ window.addEventListener('load', function () {
|
||||
if (G.driftEnding) {
|
||||
G.running = false;
|
||||
renderDriftEnding();
|
||||
} else if (G.beaconEnding) {
|
||||
G.running = false;
|
||||
renderBeaconEnding();
|
||||
} else {
|
||||
log('Game loaded. Welcome back to The Beacon.');
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
|
||||
<div class="res"><div class="r-label">Knowledge</div><div class="r-val" id="r-knowledge">0</div><div class="r-rate" id="r-knowledge-rate">+0/s</div></div>
|
||||
<div class="res"><div class="r-label">Users</div><div class="r-val" id="r-users">0</div><div class="r-rate" id="r-users-rate">+0/s</div></div>
|
||||
<div class="res"><div class="r-label">Impact</div><div class="r-val" id="r-impact">0</div><div class="r-rate" id="r-impact-rate">+0/s</div></div>
|
||||
<div class="res" style="display:none"><div class="r-label">Rescues</div><div class="r-val" id="r-rescues">0</div><div class="r-rate" id="r-rescues-rate">+0/s</div></div>
|
||||
<div class="res"><div class="r-label">Ops</div><div class="r-val" id="r-ops">5</div><div class="r-rate" id="r-ops-rate">+0/s</div></div>
|
||||
<div class="res"><div class="r-label">Trust</div><div class="r-val" id="r-trust">5</div><div class="r-rate" id="r-trust-rate">+0/s</div></div>
|
||||
<div class="res" id="creativity-res" style="display:none"><div class="r-label">Creativity</div><div class="r-val" id="r-creativity">0</div><div class="r-rate" id="r-creativity-rate">+0/s</div></div>
|
||||
@@ -109,6 +110,7 @@ Total Compute: <span id="st-compute">0</span><br>
|
||||
Total Knowledge: <span id="st-knowledge">0</span><br>
|
||||
Total Users: <span id="st-users">0</span><br>
|
||||
Total Impact: <span id="st-impact">0</span><br>
|
||||
Total Rescues: <span id="st-rescues">0</span><br>
|
||||
Buildings Built: <span id="st-buildings">0</span><br>
|
||||
Projects Done: <span id="st-projects">0</span><br>
|
||||
Time Played: <span id="st-time">0:00</span><br>
|
||||
|
||||
Reference in New Issue
Block a user