feat: More bonus (exponential) and method to list icons needed
This commit is contained in:
@@ -118,7 +118,8 @@
|
||||
'production'
|
||||
'market'
|
||||
'generators'
|
||||
'upgrades';
|
||||
'upgrades'
|
||||
'endless';
|
||||
}
|
||||
|
||||
.view {
|
||||
@@ -134,6 +135,7 @@
|
||||
.panel--market { grid-area: market; }
|
||||
.panel--generators { grid-area: generators; }
|
||||
.panel--upgrades { grid-area: upgrades; }
|
||||
.panel--endless { grid-area: endless; }
|
||||
|
||||
/* Tablet */
|
||||
@media (min-width: 760px) {
|
||||
@@ -142,7 +144,8 @@
|
||||
grid-template-areas:
|
||||
'stats stats'
|
||||
'production market'
|
||||
'generators upgrades';
|
||||
'generators upgrades'
|
||||
'endless endless';
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
@@ -159,11 +162,12 @@
|
||||
@media (min-width: 1200px) {
|
||||
.game {
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) minmax(0, 1.2fr);
|
||||
grid-template-rows: auto auto 1fr; /* spare height goes below the left column, not between its panels */
|
||||
grid-template-rows: auto auto 1fr auto; /* spare height goes below the left column, not between its panels */
|
||||
grid-template-areas:
|
||||
'stats stats stats'
|
||||
'production generators upgrades'
|
||||
'market generators upgrades';
|
||||
'market generators upgrades'
|
||||
'endless endless endless';
|
||||
}
|
||||
|
||||
.view {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
[
|
||||
{"id":"e_click","requires":"click_3","baseCost":6000000,"costGrowth":2.5,"maxLevel":10000,"effect":{"type":"click_mult","value":2}},
|
||||
{"id":"e_gen1","requires":"g1_b","baseCost":600000,"costGrowth":2.6,"maxLevel":10000,"effect":{"type":"generator_mult","target":"gen1","value":1.5}},
|
||||
{"id":"e_gen2","requires":"g2_b","baseCost":8000000,"costGrowth":2.6,"maxLevel":10000,"effect":{"type":"generator_mult","target":"gen2","value":1.5}},
|
||||
{"id":"e_gen3","requires":"g3_b","baseCost":100000000,"costGrowth":2.6,"maxLevel":10000,"effect":{"type":"generator_mult","target":"gen3","value":1.5}},
|
||||
{"id":"e_gen4","requires":"g4_b","baseCost":1200000000,"costGrowth":2.6,"maxLevel":10000,"effect":{"type":"generator_mult","target":"gen4","value":1.4}},
|
||||
{"id":"e_gen5","requires":"g5_b","baseCost":16000000000,"costGrowth":2.7,"maxLevel":10000,"effect":{"type":"generator_mult","target":"gen5","value":1.3}},
|
||||
{"id":"e_gen6","requires":"g6_b","baseCost":200000000000,"costGrowth":2.8,"maxLevel":10000,"effect":{"type":"generator_mult","target":"gen6","value":1.25}},
|
||||
{"id":"e_global","requires":"global_4","baseCost":20000000000,"costGrowth":2.4,"maxLevel":10000,"effect":{"type":"global_mult","value":1.2}},
|
||||
{"id":"e_demand","requires":"demand_4","baseCost":2000000000,"costGrowth":2.4,"maxLevel":10000,"effect":{"type":"demand_mult","value":1.2}},
|
||||
{"id":"e_value","requires":"value_3","baseCost":1200000000,"costGrowth":2.4,"maxLevel":10000,"effect":{"type":"value_mult","value":1.15}},
|
||||
{"id":"e_efficiency","requires":"mat_eff_2","baseCost":40000000,"costGrowth":2.5,"maxLevel":10000,"effect":{"type":"material_efficiency","value":1.2}},
|
||||
{"id":"e_discount","requires":"mat_disc_2","baseCost":40000000,"costGrowth":2.5,"maxLevel":10000,"effect":{"type":"batch_discount","value":0.9}},
|
||||
{"id":"e_stack","requires":"stack_5","baseCost":4000000000,"costGrowth":3.5,"maxLevel":10000,"effect":{"type":"batch_size_mult","value":2}},
|
||||
{"id":"e_cost","requires":"g3_b","baseCost":2000000000,"costGrowth":2.4,"maxLevel":10000,"effect":{"type":"generator_cost_mult","value":0.95}}
|
||||
]
|
||||
@@ -121,6 +121,9 @@
|
||||
<p class="hint" id="upgrades-empty" data-i18n="ui.noUpgrades"></p>
|
||||
<ul class="cards" id="upgrades"></ul>
|
||||
</section>
|
||||
|
||||
<!-- Endless upgrades: filled by js/ui/endlessView.js, hidden until a family of upgrades is complete -->
|
||||
<section class="panel panel--endless" id="endless" hidden></section>
|
||||
</div>
|
||||
|
||||
<!-- The other sections are built by js/ui/*View.js; they unlock with progress -->
|
||||
|
||||
@@ -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),
|
||||
|
||||
+7
-1
@@ -12,6 +12,7 @@ import {
|
||||
setPrice,
|
||||
simulateOffline,
|
||||
} from './engine/engine.js';
|
||||
import { buyEndless } from './engine/endless.js';
|
||||
import { buyChip, buyReliability, gamble } from './engine/gamble.js';
|
||||
import { buyShares, sellShares } from './engine/investments.js';
|
||||
import { buyPerk, prestige } from './engine/legacy.js';
|
||||
@@ -34,6 +35,7 @@ import { applyThemeStyles } from './services/themes.js';
|
||||
import { createAchievementsView } from './ui/achievementsView.js';
|
||||
import { createContractsView } from './ui/contractsView.js';
|
||||
import { $, refreshStaticIcons, setThemeAssets } from './ui/dom.js';
|
||||
import { createEndlessView } from './ui/endlessView.js';
|
||||
import { createGambleView } from './ui/gambleView.js';
|
||||
import { createInvestView } from './ui/investView.js';
|
||||
import { createLegacyView } from './ui/legacyView.js';
|
||||
@@ -137,6 +139,7 @@ async function refreshTexts() {
|
||||
applyTranslations();
|
||||
document.title = t('theme.title');
|
||||
view.rebuild(ctx, fmt);
|
||||
endless.rebuild(ctx, fmt);
|
||||
for (const screen of Object.values(screens)) screen.rebuild(ctx, fmt);
|
||||
tabs.rebuild();
|
||||
refreshStaticIcons();
|
||||
@@ -212,7 +215,8 @@ function render() {
|
||||
const d = derive(state, ctx);
|
||||
view.update(state, d, { income });
|
||||
tabs.update(state);
|
||||
if (tabs.active !== 'main') screens[tabs.active]?.update(state, d);
|
||||
if (tabs.active === 'main') endless.update(state);
|
||||
else screens[tabs.active]?.update(state, d);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------- actions
|
||||
@@ -232,6 +236,8 @@ const view = createView({
|
||||
nudgePrice: (delta) => act(setPrice)(state.price + delta),
|
||||
});
|
||||
|
||||
const endless = createEndlessView($('#endless'), { buy: act(buyEndless) });
|
||||
|
||||
function restart() {
|
||||
if (!confirm(t('legacy.confirm'))) return;
|
||||
const gain = prestige(state, ctx);
|
||||
|
||||
@@ -18,6 +18,7 @@ export const DATA_FILES = {
|
||||
achievements: 'achievements',
|
||||
investments: 'investments',
|
||||
tabs: 'tabs',
|
||||
endless: 'endless',
|
||||
};
|
||||
|
||||
export async function loadGameContext() {
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
// Endless upgrades: families of upgrades that continue forever, level after level.
|
||||
|
||||
import { endlessQuote, lineAvailable, lineLevel } from '../engine/endless.js';
|
||||
import { t } from '../services/i18n.js';
|
||||
import { h, icon } from './dom.js';
|
||||
import { effectText, moneyFormatter, setText } from './text.js';
|
||||
|
||||
const AMOUNTS = [1, 10, 100, 'max'];
|
||||
|
||||
export function createEndlessView(root, actions) {
|
||||
let ctx;
|
||||
let fmt;
|
||||
let money;
|
||||
let amount = 1;
|
||||
const rows = new Map();
|
||||
|
||||
const lineName = (line) =>
|
||||
line.effect.target
|
||||
? t('endless.name.generator', { target: t(`theme.gen.${line.effect.target}.name`) })
|
||||
: t(`endless.name.${line.id}`);
|
||||
|
||||
function rowFor(line) {
|
||||
const level = h('span', { class: 'badge' });
|
||||
const total = h('p', { class: 'card__meta' });
|
||||
const label = h('span', { class: 'btn__label' });
|
||||
const cost = h('small');
|
||||
const buy = h(
|
||||
'button',
|
||||
{ class: 'btn btn--buy', type: 'button', onclick: () => actions.buy(line.id, amount) },
|
||||
label,
|
||||
cost,
|
||||
);
|
||||
const li = h(
|
||||
'li',
|
||||
{ class: 'card', hidden: true },
|
||||
icon(`endless.${line.id}`),
|
||||
h(
|
||||
'div',
|
||||
{ class: 'card__body' },
|
||||
h('h3', { class: 'card__title' }, h('span', { text: lineName(line) }), level),
|
||||
h('p', { class: 'card__desc', text: t('endless.perLevel', { effect: effectText(line.effect, fmt) }) }),
|
||||
total,
|
||||
),
|
||||
buy,
|
||||
);
|
||||
return { li, level, total, label, cost, buy, line };
|
||||
}
|
||||
|
||||
function rebuild(newCtx, formatter) {
|
||||
ctx = newCtx;
|
||||
fmt = formatter;
|
||||
money = moneyFormatter(fmt);
|
||||
rows.clear();
|
||||
|
||||
const list = h('ul', { class: 'cards cards--grid' });
|
||||
for (const line of ctx.endless) {
|
||||
const row = rowFor(line);
|
||||
rows.set(line.id, row);
|
||||
list.append(row.li);
|
||||
}
|
||||
const amountGroup = h(
|
||||
'fieldset',
|
||||
{ class: 'segmented' },
|
||||
h('legend', { class: 'sr-only', text: t('endless.amount') }),
|
||||
...AMOUNTS.map((a) =>
|
||||
h(
|
||||
'label',
|
||||
{},
|
||||
h('input', {
|
||||
type: 'radio',
|
||||
name: 'endless-amount',
|
||||
value: String(a),
|
||||
checked: a === amount,
|
||||
onchange: () => {
|
||||
amount = a;
|
||||
},
|
||||
}),
|
||||
h('span', { text: a === 'max' ? t('ui.max') : `×${a}` }),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
root.replaceChildren(
|
||||
h(
|
||||
'div',
|
||||
{ class: 'panel__head' },
|
||||
h('h2', { class: 'panel__title', text: t('endless.title') }),
|
||||
amountGroup,
|
||||
),
|
||||
h('p', { class: 'hint', text: t('endless.intro') }),
|
||||
list,
|
||||
);
|
||||
}
|
||||
|
||||
function update(state) {
|
||||
let any = false;
|
||||
for (const { li, level, total, label, cost, buy, line } of rows.values()) {
|
||||
const open = lineAvailable(state, line);
|
||||
li.hidden = !open;
|
||||
if (!open) continue;
|
||||
any = true;
|
||||
|
||||
const lv = lineLevel(state, line.id);
|
||||
const maxed = lv >= line.maxLevel;
|
||||
let quote = endlessQuote(state, ctx, line.id, amount);
|
||||
if (quote.count <= 0) quote = endlessQuote(state, ctx, line.id, 1);
|
||||
|
||||
setText(level, t('endless.level', { level: fmt.number(lv) }));
|
||||
setText(
|
||||
total,
|
||||
lv > 0
|
||||
? t('endless.total', { effect: effectText({ ...line.effect, value: line.effect.value ** lv }, fmt) })
|
||||
: t('endless.none'),
|
||||
);
|
||||
setText(label, maxed ? t('endless.maxed') : t('endless.buy', { count: fmt.number(quote.count) }));
|
||||
setText(cost, maxed ? '' : money(quote.cost));
|
||||
buy.disabled = maxed || quote.count <= 0 || !(state.funds >= quote.cost);
|
||||
}
|
||||
root.hidden = !any;
|
||||
}
|
||||
|
||||
return { rebuild, update };
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Main game view. Static markup lives in index.html; this module binds the
|
||||
// controls once, builds the generator/upgrade lists, and refreshes the numbers.
|
||||
|
||||
import { generatorQuote, isUnlocked, upgradeVisible } from '../engine/engine.js';
|
||||
import { generatorQuote, isUnlocked, materialPurchase, upgradeVisible } from '../engine/engine.js';
|
||||
import { t } from '../services/i18n.js';
|
||||
import { $, h, icon, refreshStaticIcons } from './dom.js';
|
||||
|
||||
@@ -232,15 +232,16 @@ export function createView(actions) {
|
||||
|
||||
setText(el.makeYield, t('ui.makeYield', { amount: fmt.number(d.clickYield) }));
|
||||
el.make.disabled = state.material < d.materialPerUnit;
|
||||
const purchase = materialPurchase(state, d); // a partial lot when funds are short
|
||||
setText(
|
||||
el.buyMaterialInfo,
|
||||
t('ui.batchInfo', {
|
||||
amount: fmt.number(d.batchSize),
|
||||
cost: money(d.batchCost),
|
||||
amount: fmt.number(Math.floor(purchase.amount || d.batchSize)),
|
||||
cost: money(purchase.amount ? purchase.cost : d.batchCost),
|
||||
trend: TREND[state.materialTrend] ?? '',
|
||||
}),
|
||||
);
|
||||
el.buyMaterial.disabled = state.funds < d.batchCost;
|
||||
el.buyMaterial.disabled = purchase.amount <= 0;
|
||||
|
||||
updateMarket(state, d);
|
||||
updateGenerators(state, d);
|
||||
|
||||
@@ -302,5 +302,27 @@
|
||||
"a_leg_2": "Eternal Return",
|
||||
"a_evt_1": "Quick Hands"
|
||||
}
|
||||
},
|
||||
"endless": {
|
||||
"title": "Endless upgrades",
|
||||
"intro": "Finish a family of upgrades and it carries on forever: every level multiplies its effect again and costs exponentially more. There is no real end.",
|
||||
"amount": "Levels to buy",
|
||||
"level": "Level {level}",
|
||||
"perLevel": "Each level: {effect}",
|
||||
"total": "Now: {effect}",
|
||||
"none": "No level yet",
|
||||
"buy": "Buy ×{count}",
|
||||
"maxed": "Maxed",
|
||||
"name": {
|
||||
"generator": "{target} overhaul",
|
||||
"e_click": "Manual mastery",
|
||||
"e_global": "Total optimisation",
|
||||
"e_demand": "Mass appeal",
|
||||
"e_value": "Premium margin",
|
||||
"e_efficiency": "{@theme.res.material} thrift",
|
||||
"e_discount": "Hard bargaining",
|
||||
"e_stack": "{@theme.res.material} stockpiling",
|
||||
"e_cost": "Economies of scale"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,5 +302,27 @@
|
||||
"a_leg_2": "Éternel recommencement",
|
||||
"a_evt_1": "Bons réflexes"
|
||||
}
|
||||
},
|
||||
"endless": {
|
||||
"title": "Améliorations sans fin",
|
||||
"intro": "Terminez une famille d'améliorations et elle continue à l'infini : chaque niveau multiplie à nouveau son effet et coûte exponentiellement plus cher. Il n'y a pas vraiment de fin.",
|
||||
"amount": "Niveaux à acheter",
|
||||
"level": "Niveau {level}",
|
||||
"perLevel": "Chaque niveau : {effect}",
|
||||
"total": "Actuellement : {effect}",
|
||||
"none": "Aucun niveau pour l'instant",
|
||||
"buy": "Acheter ×{count}",
|
||||
"maxed": "Au maximum",
|
||||
"name": {
|
||||
"generator": "Refonte : {target}",
|
||||
"e_click": "Maîtrise manuelle",
|
||||
"e_global": "Optimisation totale",
|
||||
"e_demand": "Attrait de masse",
|
||||
"e_value": "Marge premium",
|
||||
"e_efficiency": "{@theme.res.material} : sobriété",
|
||||
"e_discount": "Négociation acharnée",
|
||||
"e_stack": "{@theme.res.material} : stockage massif",
|
||||
"e_cost": "Économies d'échelle"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user