Files
deepgrain-foundry/src/ui/claims.js
T

116 lines
8.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Shared player state; the world save remains a complete standalone fallback.
let career = null, claimBusy = false;
function careerState() {
const carriedBag = Array.from(bag);
for (const f of flyers) if (f.bag) carriedBag[f.m]++;
return { money, earned, bag: carriedBag, player: { ...player }, seen: Array.from(seen), ledger: { ...ledger }, stats: { ...stats } };
}
function validCareer(c, id, worldId) {
const obj = v => v && typeof v === 'object' && !Array.isArray(v);
const count = v => Number.isFinite(v) && v >= 0;
const counts = v => obj(v) && Object.values(v).every(count);
return obj(c) && c.id === id && Array.isArray(c.claims) && c.claims.length > 0 &&
c.claims.every(v => typeof v === 'string' && v.length > 0) && new Set(c.claims).size === c.claims.length && c.claims.includes(worldId) &&
Number.isSafeInteger(c.claimNo) && c.claimNo >= c.claims.length && count(c.money) && count(c.earned) &&
Array.isArray(c.bag) && c.bag.length > 0 && c.bag.every(v => Number.isInteger(v) && v >= 0 && v <= 2147483647) &&
Array.isArray(c.seen) && c.seen.length > 0 && c.seen.every(v => v === 0 || v === 1) &&
obj(c.player) && Number.isInteger(c.player.bitTier) && !!BIT_TIERS[c.player.bitTier] &&
Number.isInteger(c.player.tankTier) && !!TANK_TIERS[c.player.tankTier] &&
typeof c.player.lens === 'boolean' && typeof c.player.flask === 'boolean' &&
counts(c.ledger) && counts(c.stats) && ['dug', 'moved', 'placed', 'ignitions', 'cast', 'bars'].every(k => count(c.stats[k]));
}
function applyCareerState(c) {
money = c.money; earned = c.earned;
bag = new Int32Array(NMAT); bag.set(c.bag.slice(0, NMAT));
bagTotal = bag.reduce((n, v) => n + v, 0);
// These grains are already included in the shared bag, even on an older claim.
flyers = flyers.filter(f => !f.bag);
for (const k of Object.keys(player)) player[k] = c.player[k];
seen = new Uint8Array(NMAT); seen.set(c.seen.slice(0, NMAT));
ledger = { ...c.ledger };
for (const k of Object.keys(stats)) delete stats[k];
Object.assign(stats, c.stats);
}
async function loadCareer() {
career = null; GAME.careerId = ''; GAME.claimNo = 1;
try {
if (!GAME.worldId) GAME.worldId = newWorldId();
const entry = (await listWorlds()).find(w => w.id === GAME.worldId);
const c = entry && entry.career ? await DB.get('career:' + entry.career) : null;
if (entry && validCareer(c, entry.career, GAME.worldId)) {
career = c; applyCareerState(c); GAME.claimNo = Number.isSafeInteger(entry.claimNo) && entry.claimNo > 0 ? entry.claimNo : 1;
} else {
// Preserve surviving claim membership when repairing a missing or damaged record.
const claims = entry && entry.career ? (await listWorlds()).filter(w => w.career === entry.career) : [];
career = { id: entry && entry.career || newWorldId(), claims: claims.length ? claims.map(w => w.id) : [GAME.worldId],
claimNo: Math.max(1, ...claims.map(w => Number.isSafeInteger(w.claimNo) && w.claimNo > 0 ? w.claimNo : 1)), ...careerState() };
GAME.claimNo = entry && Number.isSafeInteger(entry.claimNo) && entry.claimNo > 0 ? entry.claimNo : 1;
await worldWrite(async () => {
const list = await listWorlds();
let w = list.find(w => w.id === GAME.worldId);
if (!w) { w = { id: GAME.worldId, name: GAME.worldName || 'World', seed: worldSeed, w: W, h: H, savedAt: 0 }; list.push(w); }
Object.assign(w, { career: career.id, claimNo: GAME.claimNo, debt });
await DB.op('readwrite', st => { st.put(career, 'career:' + career.id); st.put(list, 'worlds'); });
});
}
GAME.careerId = career.id;
} catch (e) { career = null; toast('Career could not be loaded. Using this world’s own money and bag.'); }
}
async function openNewClaim() {
if (claimBusy || !career || debt > 0 || money < 5000) return false;
claimBusy = true; const paused = GAME.paused; GAME.paused = true;
let previous;
try {
if (!await saveWorld('main', true)) throw new Error('Save failed');
previous = { save: serializeWorld(), id: GAME.worldId, name: GAME.worldName, time: GAME.playTime, career, claimNo: GAME.claimNo };
const carried = careerState(), n = career.claimNo + 1, w = W, h = H;
carried.money -= 5000;
let seed; do { seed = Math.floor(Math.random() * 900000) + 100000; } while (seed === worldSeed);
await setupNewWorld(seed, w, h, `Claim ${n}`, carried);
debt = CLAIM_DEBT * 2 ** (n - 1);
career = { ...career, claims: [...career.claims, GAME.worldId], claimNo: n };
GAME.claimNo = n;
if (!await saveWorld('auto', true)) throw new Error('Save failed');
showLoading(false); closeAllModals(); enterGame();
showCard({ kind: 'New claim', title: `Claim ${n}`, body: [`You owe ${fmtMoney(debt)} on this claim. Your money, bag, upgrades and what you’ve learned came along. Buildings stayed on the old claim.`], hold: 1.8 });
return true;
} catch (e) {
if (previous) {
deserializeWorld(previous.save.meta, previous.save.buffers);
GAME.worldId = previous.id; GAME.worldName = previous.name; GAME.playTime = previous.time;
career = previous.career; GAME.claimNo = previous.claimNo;
}
showLoading(false); GAME.paused = paused; toast('Could not open a claim. Your old claim has been kept.'); return false;
} finally { claimBusy = false; }
}
async function travel(worldId) {
if (claimBusy || !career || worldId === GAME.worldId || !career.claims.includes(worldId)) return false;
claimBusy = true; const paused = GAME.paused; let arrived = false; GAME.paused = true;
try {
const entry = (await listWorlds()).find(w => w.id === worldId);
if (!entry || entry.career !== GAME.careerId) return false;
if (!await saveWorld('main', true)) throw new Error('Save failed');
const sv = await worldNewest(worldId);
if (!sv || !await startFromSave(sv)) return false;
closeAllModals(); arrived = true; return true;
} catch (e) { toast('Could not travel. Check browser storage and try again.'); return false; }
finally { claimBusy = false; if (!arrived) GAME.paused = paused; }
}
async function renderClaimsTab(body) {
body.innerHTML = '<p class=note>Loading claims…</p>';
try {
const list = (await listWorlds()).filter(w => career && w.career === GAME.careerId && career.claims.includes(w.id));
if (nbTab !== 'Claims') return;
body.innerHTML = list.map(w => `<div class="world-row"><div class="world-info"><strong>${esc(w.name)}${w.id === GAME.worldId ? ' · here' : ''}</strong><div class="note">${esc(w.w)}×${esc(w.h)} grains · ${(w.id === GAME.worldId ? debt : w.debt) <= 0 ? 'paid off ✓' : fmtMoney(w.id === GAME.worldId ? debt : w.debt || 0) + ' owed'} · Last played: ${w.savedAt ? esc(new Date(w.savedAt).toLocaleString()) : 'Just opened'}</div></div>${w.id === GAME.worldId ? '' : `<button class="btn" data-travel="${esc(w.id)}">Travel</button>`}</div>`).join('') +
(debt <= 0 ? `<button class="btn primary" id="claim-open" ${money < 5000 || !career ? 'disabled' : ''}>Open a new claim: $5,000 down, then ${fmtMoney(CLAIM_DEBT * 2 ** (career ? career.claimNo : 1))} owed</button>${money < 5000 ? '<p class=note>You need $5,000 for the down payment.</p>' : ''}` : '<p class=note>A new claim opens once this one is paid off.</p>') +
'<p class=note>Machines on other claims wait until you come back.</p>';
const button = $('claim-open');
if (button) button.onclick = () => dialog('Open a new claim?', `<p class="note">Pay $5,000 down and owe ${fmtMoney(CLAIM_DEBT * 2 ** career.claimNo)} on fresh land. Your buildings stay here.</p>`, [
{ label: 'Cancel', cls: 'ghost' }, { label: 'Open claim', cls: 'primary', fn: () => { closeModal('m-dialog'); openNewClaim(); } },
]);
body.querySelectorAll('[data-travel]').forEach(b => b.onclick = () => dialog('Travel to this claim?', '<p class=note>Your current claim will be saved. Money, bag, upgrades and knowledge come with you.</p>', [
{ label: 'Cancel', cls: 'ghost' }, { label: 'Travel', cls: 'primary', fn: () => { closeModal('m-dialog'); travel(b.dataset.travel); } },
]));
} catch (e) { if (nbTab === 'Claims') body.innerHTML = '<p class=note>Could not load claims from browser storage.</p>'; }
}