feat: More bonus (exponential) and method to list icons needed
This commit is contained in:
@@ -29,6 +29,7 @@ export function buildContext({
|
||||
achievements,
|
||||
investments,
|
||||
tabs,
|
||||
endless,
|
||||
}) {
|
||||
const generatorsById = byId(generators, 'generator');
|
||||
const upgradesById = byId(upgrades, 'upgrade');
|
||||
@@ -36,6 +37,7 @@ export function buildContext({
|
||||
const perksById = byId(legacy, 'legacy perk');
|
||||
const achievementsById = byId(achievements, 'achievement');
|
||||
const societiesById = byId(investments.societies, 'society');
|
||||
const endlessById = byId(endless, 'endless line');
|
||||
byId(tabs, 'tab');
|
||||
|
||||
for (const u of upgrades) {
|
||||
@@ -50,6 +52,12 @@ export function buildContext({
|
||||
if (!researchById.has(req)) throw new Error(`Research "${node.id}": unknown requirement "${req}"`);
|
||||
}
|
||||
}
|
||||
for (const line of endless) {
|
||||
checkEffect(`Endless "${line.id}"`, line.effect, generatorsById);
|
||||
if (line.requires && !upgradesById.has(line.requires)) {
|
||||
throw new Error(`Endless "${line.id}": unknown requirement "${line.requires}"`);
|
||||
}
|
||||
}
|
||||
for (const perk of legacy) checkEffect(`Perk "${perk.id}"`, perk.effect, generatorsById);
|
||||
for (const a of achievements) checkEffect(`Achievement "${a.id}"`, a.effect, generatorsById);
|
||||
for (const s of investments.societies) {
|
||||
@@ -65,11 +73,13 @@ export function buildContext({
|
||||
achievements,
|
||||
investments,
|
||||
tabs,
|
||||
endless,
|
||||
generatorsById,
|
||||
upgradesById,
|
||||
researchById,
|
||||
perksById,
|
||||
achievementsById,
|
||||
societiesById,
|
||||
endlessById,
|
||||
};
|
||||
}
|
||||
|
||||
Vendored
+13
-1
@@ -72,11 +72,23 @@ const APPLY = {
|
||||
|
||||
export const EFFECT_TYPES = Object.keys(APPLY);
|
||||
|
||||
// Endless upgrades stack without limit, so keep every result finite and sensible.
|
||||
const CAP = 1e100; // low enough that products of several multipliers stay finite; real play never gets close
|
||||
const FLOORS = { generatorCost: 0.05, batchDiscount: 0.001 };
|
||||
|
||||
function finalize(m) {
|
||||
const keep = (value) => (Number.isFinite(value) ? Math.min(CAP, value) : CAP);
|
||||
for (const key of Object.keys(m)) if (typeof m[key] === 'number') m[key] = keep(m[key]);
|
||||
for (const id of Object.keys(m.generators)) m.generators[id] = keep(m.generators[id]);
|
||||
for (const [key, floor] of Object.entries(FLOORS)) m[key] = Math.max(floor, m[key]);
|
||||
return m;
|
||||
}
|
||||
|
||||
/** Fold a list of `{ effect, times }` entries into one set of multipliers. */
|
||||
export function foldEffects(entries) {
|
||||
const m = newModifiers();
|
||||
for (const { effect, times = 1 } of entries) {
|
||||
if (effect && times > 0) APPLY[effect.type]?.(m, effect, times);
|
||||
}
|
||||
return m;
|
||||
return finalize(m);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Endless upgrades: once a family of upgrades is complete (its `requires`
|
||||
// milestone is owned), it continues forever as a repeatable line. Every level
|
||||
// multiplies the effect once more and costs exponentially more, up to
|
||||
// `maxLevel` levels, which nobody will ever reach.
|
||||
|
||||
import { bulkCost, maxAffordable } from './formulas.js';
|
||||
|
||||
export const lineLevel = (state, id) => state.endless[id] ?? 0;
|
||||
|
||||
export function lineAvailable(state, line) {
|
||||
return !line.requires || state.upgrades.includes(line.requires);
|
||||
}
|
||||
|
||||
/** How many levels `amount` ("max" or a number) buys right now, and what they cost. */
|
||||
export function endlessQuote(state, ctx, id, amount) {
|
||||
const line = ctx.endlessById.get(id);
|
||||
const level = lineLevel(state, id);
|
||||
const wanted =
|
||||
amount === 'max' ? maxAffordable(line.baseCost, line.costGrowth, level, state.funds) : amount;
|
||||
const count = Math.max(0, Math.min(wanted, line.maxLevel - level));
|
||||
return { count, cost: bulkCost(line.baseCost, line.costGrowth, level, count) };
|
||||
}
|
||||
|
||||
export function buyEndless(state, ctx, id, amount = 1) {
|
||||
const line = ctx.endlessById.get(id);
|
||||
if (!line || !lineAvailable(state, line)) return false;
|
||||
const { count, cost } = endlessQuote(state, ctx, id, amount);
|
||||
if (count <= 0 || !(state.funds >= cost)) return false; // an infinite cost is never affordable
|
||||
state.funds -= cost;
|
||||
state.endless[id] = lineLevel(state, id) + count;
|
||||
return true;
|
||||
}
|
||||
@@ -99,11 +99,22 @@ export function click(state, ctx) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* What a material purchase gives right now: the whole lot when it is affordable,
|
||||
* otherwise as much of it as the funds allow (same unit price). Partial lots
|
||||
* make sure a player can never be locked out of buying material.
|
||||
*/
|
||||
export function materialPurchase(state, d) {
|
||||
if (state.funds >= d.batchCost) return { amount: d.batchSize, cost: d.batchCost };
|
||||
if (!(state.funds >= 0.01)) return { amount: 0, cost: 0 };
|
||||
return { amount: (d.batchSize * state.funds) / d.batchCost, cost: state.funds };
|
||||
}
|
||||
|
||||
export function buyMaterial(state, ctx) {
|
||||
const d = derive(state, ctx);
|
||||
if (state.funds < d.batchCost) return false;
|
||||
state.funds -= d.batchCost;
|
||||
state.material += d.batchSize;
|
||||
const purchase = materialPurchase(state, derive(state, ctx));
|
||||
if (purchase.amount <= 0) return false;
|
||||
state.funds -= purchase.cost;
|
||||
state.material += purchase.amount;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -175,11 +186,13 @@ function step(state, ctx, dt, rng, totals, offline) {
|
||||
state.totalProduced += made;
|
||||
state.lifetimeProduced += made;
|
||||
|
||||
// Automatic restock: patient while the price is high, urgent when nearly out.
|
||||
if (d.autoBuyMaterial && state.funds >= d.batchCost) {
|
||||
// Automatic restock: patient (full lots, at a fair price) while stock is fine,
|
||||
// urgent (whatever the funds allow) when nearly out.
|
||||
if (d.autoBuyMaterial) {
|
||||
const { autoBuyBelowFactor } = ctx.rules.materialMarket;
|
||||
const low = state.material < d.batchSize / 2 && state.materialFactor <= autoBuyBelowFactor;
|
||||
if (low || state.material < d.batchSize / 10) buyMaterial(state, ctx);
|
||||
const patient =
|
||||
state.material < d.batchSize / 2 && state.materialFactor <= autoBuyBelowFactor && state.funds >= d.batchCost;
|
||||
if (patient || state.material < d.batchSize / 10) buyMaterial(state, ctx);
|
||||
}
|
||||
|
||||
// Customers buy what they want, up to what is in stock.
|
||||
|
||||
@@ -8,6 +8,9 @@ export function collectEffects(state, ctx) {
|
||||
const entries = [];
|
||||
|
||||
for (const id of state.upgrades) entries.push({ effect: ctx.upgradesById.get(id)?.effect });
|
||||
for (const [id, level] of Object.entries(state.endless)) {
|
||||
entries.push({ effect: ctx.endlessById.get(id)?.effect, times: level });
|
||||
}
|
||||
for (const id of state.research) entries.push({ effect: ctx.researchById.get(id)?.effect });
|
||||
for (const id of state.achievements) entries.push({ effect: ctx.achievementsById.get(id)?.effect });
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ export function createState(ctx, now = Date.now()) {
|
||||
materialFactor: 1,
|
||||
generators: Object.fromEntries(ctx.generators.map((g) => [g.id, 0])),
|
||||
upgrades: [],
|
||||
endless: {},
|
||||
invest: newInvest(ctx),
|
||||
chips: 0,
|
||||
reliabilityLevel: 0,
|
||||
@@ -107,6 +108,15 @@ function sanitizeInvest(raw, ctx, base) {
|
||||
return out;
|
||||
}
|
||||
|
||||
function sanitizeEndless(raw, ctx) {
|
||||
const levels = {};
|
||||
for (const line of ctx.endless) {
|
||||
const level = int(raw?.[line.id], line.maxLevel);
|
||||
if (level > 0) levels[line.id] = level;
|
||||
}
|
||||
return levels;
|
||||
}
|
||||
|
||||
function sanitizeContracts(raw, ctx) {
|
||||
const templates = new Set(ctx.rules.contracts.templates.map((t) => t.id));
|
||||
const rewardTypes = new Set(ctx.rules.contracts.rewards);
|
||||
@@ -186,6 +196,7 @@ export function sanitizeState(raw, ctx, now = Date.now()) {
|
||||
materialFactor: num(raw.materialFactor, 1, materialMarket.minFactor, materialMarket.maxFactor),
|
||||
generators,
|
||||
upgrades: knownIds(raw.upgrades, ctx.upgradesById),
|
||||
endless: sanitizeEndless(raw.endless, ctx),
|
||||
invest: sanitizeInvest(raw.invest, ctx, base.invest),
|
||||
chips: int(raw.chips, gamble.maxChips),
|
||||
reliabilityLevel: int(raw.reliabilityLevel, gamble.reliability.maxLevel),
|
||||
|
||||
Reference in New Issue
Block a user