feat: New features (research, invest, gambling, new game+, trophies)

This commit is contained in:
gauvainboiche
2026-10-08 14:03:00 +02:00
parent 1caa4d92b9
commit fc0d5e3ba4
56 changed files with 4332 additions and 249 deletions
+114
View File
@@ -0,0 +1,114 @@
// Research tab: labs produce points over time; points buy nodes of the tree.
import { labCost, researchStatus } from '../engine/research.js';
import { t } from '../services/i18n.js';
import { h, icon } from './dom.js';
import { effectText, moneyFormatter, setText } from './text.js';
const BRANCHES = ['prod', 'mkt', 'log', 'fin', 'ins'];
export function createResearchView(root, actions) {
let ctx;
let fmt;
let money;
let els;
const nodeRows = new Map();
function nodeRow(node) {
const status = h('p', { class: 'card__meta' });
const buy = h('button', {
class: 'btn btn--buy',
type: 'button',
onclick: () => actions.buyNode(node.id),
});
const li = h(
'li',
{ class: 'card' },
icon(`research.${node.id}`),
h(
'div',
{ class: 'card__body' },
h('h3', { class: 'card__title', text: t(`research.node.${node.id}`) }),
h('p', { class: 'card__desc', text: effectText(node.effect, fmt) }),
status,
),
buy,
);
return { li, status, buy, node };
}
function rebuild(newCtx, formatter) {
ctx = newCtx;
fmt = formatter;
money = moneyFormatter(fmt);
nodeRows.clear();
els = {
rp: h('strong'),
rate: h('span', { class: 'hint' }),
labs: h('span', { class: 'hint' }),
buyLab: h('button', { class: 'btn btn--wide', type: 'button', onclick: () => actions.buyLab() }),
};
const branchPanels = BRANCHES.map((branch) => {
const list = h('ul', { class: 'cards' });
for (const node of ctx.research.filter((n) => n.branch === branch)) {
const row = nodeRow(node);
nodeRows.set(node.id, row);
list.append(row.li);
}
return h(
'section',
{ class: 'panel' },
h('h2', { class: 'panel__title', text: t(`research.branch.${branch}`) }),
list,
);
});
root.replaceChildren(
h(
'section',
{ class: 'panel panel--wide' },
h('h2', { class: 'panel__title', text: t('tab.research') }),
h('p', { class: 'hint', text: t('research.intro') }),
h(
'div',
{ class: 'tile' },
icon('res.research'),
h('div', {}, h('span', { class: 'tile__label', text: t('theme.res.research') }), els.rp, els.rate),
),
els.labs,
els.buyLab,
),
...branchPanels,
);
}
function update(state, d) {
const r = ctx.rules.research;
setText(els.rp, fmt.number(Math.floor(state.rp)));
setText(els.rate, t('research.rate', { rate: fmt.number(Math.round(d.rpRate * 1000) / 1000) }));
setText(els.labs, t('research.labs', { count: state.labs, perLab: fmt.number(r.rpPerLabPerSecond * d.mods.rp) }));
const maxed = state.labs >= r.labMax;
const cost = labCost(state, ctx);
setText(els.buyLab, maxed ? t('research.labMax') : t('research.buyLab', { cost: money(cost) }));
els.buyLab.disabled = maxed || state.funds < cost;
for (const { li, status, buy, node } of nodeRows.values()) {
const s = researchStatus(state, node);
li.classList.toggle('card--done', s === 'owned');
li.classList.toggle('card--locked', s === 'locked');
buy.hidden = s !== 'available';
buy.disabled = state.rp < node.cost;
setText(buy, t('research.cost', { cost: fmt.number(node.cost) }));
if (s === 'owned') setText(status, t('research.owned'));
else if (s === 'locked') {
const names = node.requires.map((id) => t(`research.node.${id}`)).join(', ');
setText(status, t('research.requires', { names }));
} else setText(status, '');
}
}
return { rebuild, update };
}