tweaks
This commit is contained in:
parent
a322158f43
commit
b454ffbd40
21 changed files with 1349 additions and 712 deletions
317
src/game.js
317
src/game.js
|
|
@ -1,15 +1,15 @@
|
|||
import { VERSION, TURN_SECONDS, STARTING_CASH, POOP_RATE, CONVEYOR_SPEED, CONVEYOR_SPEED_GROWTH, AUTO_SCANNER_COOLDOWN, BUILD_COSTS, FACILITY_PRICES, GRID, THEME } from './core/config.js';
|
||||
import { createGame, resetLayout, newTurnStats, newTotalStats, nextSpawnDelay, getSpawnRange } from './core/state.js';
|
||||
import { createChick } from './core/entities.js';
|
||||
import { key, parseKey, clamp, randomBetween, pointToCell, cellCenter, distance, yen } from './core/utils.js';
|
||||
import { farmAt, scannerAt, scannerById, scannerBySlot, scannerCenter, scannerConnector, routeFromFarmToScanner, outputRoute, scannerOutputs, destinationLabel, destinationColor, nearestConveyorKey, buildConveyorComponents, factoryReady, facilityConnectionIssues } from './systems/routing.js';
|
||||
import { MIXER_PRICE, TRUCK_PRICE, MIXER_HALF_SECONDS, maleTruckPenalty, truckPayoutMultiplier, positivePayout, applyCashDelta, spendCash, refundCash, settleTruckRevenue, finalScore } from './systems/economy.js';
|
||||
import { snapshot, record, undo, redo } from './systems/history.js';
|
||||
import { TURN_SECONDS, CONVEYOR_SPEED, CONVEYOR_SPEED_MAX, THEME } from './core/config.js';
|
||||
import { createGame, resetLayout, newTurnStats } from './core/state.js';
|
||||
import { clamp, pointToCell, cellCenter } from './core/utils.js';
|
||||
import { facilityConnectionIssues } from './systems/routing.js';
|
||||
import { applyCashDelta, collectFairiesFee, settleTruckRevenue } from './systems/economy.js';
|
||||
import { undo, redo } from './systems/history.js';
|
||||
import { drawAll } from './render/draw.js';
|
||||
import { createBuildSystem } from './systems/buildSystem.js';
|
||||
import { createUISystem } from './systems/uiSystem.js';
|
||||
import { floating, shake, spawnPulse, scannerPulse, meatEffect, sludgeEffect, shredEffect, truckLoadEffect, shockwave, sparkBurst, smokeBurst, flyingDebris, eraseEffect, rageEffect, updateEffects } from './systems/effects.js';
|
||||
import { rollContractOffer, activateAcceptedContract, clearActiveContract, productionMultiplier, resolveContract, truckTarget, isTargetTruckCargo, shouldFineMaleTruck } from './systems/contracts.js';
|
||||
import { createChickSystem } from './systems/chickSystem.js';
|
||||
import { floating, shockwave, sparkBurst, updateEffects } from './systems/effects.js';
|
||||
import { rollContractOffer, activateAcceptedContract, clearActiveContract, resolveContract } from './systems/contracts.js';
|
||||
|
||||
const canvas = document.getElementById('gameCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
|
@ -30,18 +30,17 @@ const ui = {
|
|||
const game = createGame();
|
||||
let build;
|
||||
let uiSystem;
|
||||
let chicks;
|
||||
|
||||
function conveyorSpeedForTurn(turn) {
|
||||
return CONVEYOR_SPEED * Math.pow(CONVEYOR_SPEED_GROWTH, Math.max(0, turn - 1));
|
||||
}
|
||||
function currentConveyorSpeed() { return conveyorSpeedForTurn(game.turn); }
|
||||
function currentConveyorSpeed() { return Math.min(CONVEYOR_SPEED_MAX, CONVEYOR_SPEED); }
|
||||
|
||||
function startGame() {
|
||||
Object.assign(game, createGame());
|
||||
game.phase = 'running';
|
||||
game.view = { x: 0, y: 0 };
|
||||
resetLayout(game);
|
||||
uiSystem.hideModal();
|
||||
updateCongestion();
|
||||
chicks.updateCongestion();
|
||||
uiSystem.updatePanels();
|
||||
uiSystem.updateUI();
|
||||
}
|
||||
|
|
@ -58,17 +57,22 @@ function startNextTurn() {
|
|||
game.phase = 'running';
|
||||
game.turn += 1;
|
||||
game.timeLeft = TURN_SECONDS;
|
||||
game.shutdownTimeLeft = 0;
|
||||
game.chicks = [];
|
||||
game.effects = [];
|
||||
game.floatingTexts = [];
|
||||
game.shake = { time: 0, strength: 0 };
|
||||
game.truckCargo = [];
|
||||
game.view = { x: 0, y: 0 };
|
||||
game.stats = newTurnStats();
|
||||
for (const scanner of game.scanners) { scanner.queue = []; scanner.cooldown = 0; }
|
||||
for (const farm of game.eggFarms) { farm.nextSpawn = currentSpawnDelay(farm); farm.lastInterval = farm.nextSpawn; farm.shutterProgress = 0; farm.shutterSparked = false; }
|
||||
for (const farm of game.eggFarms) { farm.nextSpawn = chicks.currentSpawnDelay(farm); farm.lastInterval = farm.nextSpawn; farm.shutterProgress = 0; farm.shutterSparked = false; }
|
||||
game.buildTool = null;
|
||||
game.selected = null;
|
||||
updateCongestion();
|
||||
game.groupDrag = null;
|
||||
game.selectionBox = null;
|
||||
game.pan = null;
|
||||
chicks.updateCongestion();
|
||||
uiSystem.updatePanels();
|
||||
uiSystem.updateUI();
|
||||
}
|
||||
|
|
@ -77,17 +81,21 @@ function completeTurn() {
|
|||
if (game.phase !== 'running') return;
|
||||
const ship = settleTruckRevenue(game);
|
||||
const contract = resolveContract(game, applyCashDelta);
|
||||
const fairiesFee = collectFairiesFee(game);
|
||||
clearActiveContract(game);
|
||||
game.totals.turnsCompleted += 1;
|
||||
game.lastResult = { ...game.stats, ship, contract, fairiesFee, turn: game.turn, cash: game.cash };
|
||||
if (game.cash < 0) { game.phase = 'gameover'; uiSystem.showGameOver(); return; }
|
||||
game.lastResult = { ...game.stats, ship, contract, turn: game.turn, cash: game.cash };
|
||||
game.phase = 'build';
|
||||
game.truckCargo = [];
|
||||
game.chicks = [];
|
||||
game.shutdownTimeLeft = 0;
|
||||
game.buildSession += 1;
|
||||
game.undoStack = [];
|
||||
game.redoStack = [];
|
||||
game.timeLeft = 0;
|
||||
game.totals.turnsCompleted += 1;
|
||||
for (const scanner of game.scanners) scanner.queue = [];
|
||||
for (const farm of game.eggFarms) { farm.shutterProgress = 0; farm.shutterSparked = false; }
|
||||
game.contractOffer = rollContractOffer(game);
|
||||
game.buildTool = null;
|
||||
game.groupDrag = null;
|
||||
|
|
@ -98,273 +106,30 @@ function completeTurn() {
|
|||
}
|
||||
|
||||
function closeFarmShutters() {
|
||||
if (game.shutdownTimeLeft <= 0) game.shutdownTimeLeft = game.shutdownGraceSeconds || 10;
|
||||
for (const farm of game.eggFarms) {
|
||||
if (farm.shutterSparked) continue;
|
||||
farm.shutterProgress = 0.02;
|
||||
farm.shutterSparked = true;
|
||||
const c = cellCenter(farm.col, farm.row);
|
||||
shockwave(game, c.x, c.y, THEME.ink);
|
||||
sparkBurst(game, c.x, c.y + 10, 10);
|
||||
floating(game, c.x, c.y - 34, 'SHUT', THEME.ink);
|
||||
floating(game, c.x, c.y - 34, 'SHUT +10s', THEME.ink);
|
||||
}
|
||||
}
|
||||
|
||||
function spawnChick(farm) {
|
||||
const data = routeFromFarmToScanner(game, farm, true);
|
||||
if (!data) { floating(game, cellCenter(farm.col, farm.row).x, cellCenter(farm.col, farm.row).y - 18, 'NO ROUTE', THEME.danger); return false; }
|
||||
const chick = createChick(game, data);
|
||||
if (chick.sex === 'poop') game.stats.poopSpawned += 1;
|
||||
const start = data.route[0];
|
||||
if (isSpawnBlocked(start)) { explodeRoute(data.route, [{ sex: chick.sex, x: start.x, y: start.y }], 'LINE JAM!'); return false; }
|
||||
game.chicks.push(chick);
|
||||
spawnPulse(game, cellCenter(farm.col, farm.row).x, cellCenter(farm.col, farm.row).y);
|
||||
return true;
|
||||
}
|
||||
function isSpawnBlocked(start) { return game.chicks.some(ch => ch.stage !== 'flying' && Math.hypot(ch.x - start.x, ch.y - start.y) < GRID.cell * 0.68); }
|
||||
function currentSpawnDelay(farm) { return nextSpawnDelay(farm) / productionMultiplier(game); }
|
||||
|
||||
function update(timestamp) {
|
||||
if (!game.lastTimestamp) game.lastTimestamp = timestamp;
|
||||
const dt = Math.min((timestamp - game.lastTimestamp) / 1000, 0.05);
|
||||
game.lastTimestamp = timestamp;
|
||||
if (game.phase === 'running') updateRunning(dt);
|
||||
if (game.phase === 'running') chicks.updateRunning(dt, { closeFarmShutters, completeTurn });
|
||||
updateEffects(game, dt, canvas, build.equipmentHitBoxes);
|
||||
updateCongestion();
|
||||
drawAll(ctx, canvas, game, { activeQueuedChick, selectedObject: build.selectedObject, selectedTitle: build.selectedTitle });
|
||||
chicks.updateCongestion();
|
||||
drawAll(ctx, canvas, game, { activeQueuedChick: chicks.activeQueuedChick, selectedObject: build.selectedObject, selectedTitle: build.selectedTitle });
|
||||
uiSystem.updateUI();
|
||||
requestAnimationFrame(update);
|
||||
}
|
||||
|
||||
function updateRunning(dt) {
|
||||
const producing = game.timeLeft > 0;
|
||||
game.timeLeft = Math.max(0, game.timeLeft - dt);
|
||||
if (producing && game.timeLeft <= 0) closeFarmShutters();
|
||||
if (game.timeLeft <= 0) {
|
||||
for (const farm of game.eggFarms) farm.shutterProgress = Math.min(1, (farm.shutterProgress || 0) + dt * 2.8);
|
||||
}
|
||||
if (game.mixerHalfTimer > 0) game.mixerHalfTimer = Math.max(0, game.mixerHalfTimer - dt);
|
||||
if (producing && game.timeLeft > 0) {
|
||||
for (const farm of game.eggFarms) {
|
||||
farm.nextSpawn -= dt;
|
||||
if (farm.nextSpawn <= 0) { spawnChick(farm); farm.nextSpawn = currentSpawnDelay(farm); farm.lastInterval = farm.nextSpawn; }
|
||||
}
|
||||
}
|
||||
for (let i = game.chicks.length - 1; i >= 0; i -= 1) {
|
||||
const chick = game.chicks[i];
|
||||
chick.bob += dt * 7;
|
||||
if (chick.stage === 'queued') continue;
|
||||
updateRoutedChick(chick, i, dt);
|
||||
}
|
||||
updateScannerQueues(dt);
|
||||
updateCongestion();
|
||||
checkCongestionExplosions(dt);
|
||||
if (game.timeLeft <= 0 && game.chicks.length === 0) completeTurn();
|
||||
uiSystem.checkGameOver();
|
||||
}
|
||||
|
||||
function updateRoutedChick(chick, index, dt) {
|
||||
if (blockedByFrontChick(chick)) return;
|
||||
moveAlongRoute(chick, dt);
|
||||
if (chick.route && chick.targetIndex < chick.route.length) return;
|
||||
if (chick.stage === 'input' || chick.stage === 'toScanner') {
|
||||
const scanner = scannerById(game, chick.stage === 'input' ? chick.scannerId : chick.nextScannerId);
|
||||
if (!scanner) { removeChick(index, 'NO SCANNER'); return; }
|
||||
enqueueChick(chick, scanner, chick.route);
|
||||
return;
|
||||
}
|
||||
if (chick.stage === 'toMixer') resolveMixer(index);
|
||||
else if (chick.stage === 'toTruck') resolveTruck(index);
|
||||
else if (chick.stage === 'toTrash') resolveTrash(index);
|
||||
}
|
||||
function blockedByFrontChick(chick) {
|
||||
if (!chick.route || chick.targetIndex >= chick.route.length) return false;
|
||||
const next = chick.route[chick.targetIndex];
|
||||
for (const other of game.chicks) {
|
||||
if (other.id === chick.id || other.stage === 'flying') continue;
|
||||
if (Math.hypot(other.x - next.x, other.y - next.y) < GRID.cell * 0.55 && routeProgress(other) >= routeProgress(chick)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function routeProgress(chick) { return chick.targetIndex || 0; }
|
||||
function moveAlongRoute(chick, dt) {
|
||||
let remaining = currentConveyorSpeed() * dt;
|
||||
while (remaining > 0 && chick.targetIndex < chick.route.length) {
|
||||
const target = chick.route[chick.targetIndex];
|
||||
const dx = target.x - chick.x, dy = target.y - chick.y;
|
||||
const dist = Math.hypot(dx, dy);
|
||||
if (dist <= 0.001) { chick.targetIndex += 1; continue; }
|
||||
if (remaining >= dist) { chick.x = target.x; chick.y = target.y; chick.targetIndex += 1; remaining -= dist; }
|
||||
else { chick.x += dx / dist * remaining; chick.y += dy / dist * remaining; remaining = 0; }
|
||||
}
|
||||
}
|
||||
function enqueueChick(chick, scanner, routeOverride) {
|
||||
chick.stage = 'queued';
|
||||
chick.route = null;
|
||||
chick.queueRoute = routeOverride ? routeOverride.map(p => ({ x: p.x, y: p.y })) : [scannerCenter(scanner)];
|
||||
chick.scannerId = scanner.id;
|
||||
if (!scanner.queue.includes(chick.id)) scanner.queue.push(chick.id);
|
||||
positionScannerQueue(scanner);
|
||||
}
|
||||
function positionScannerQueue(scanner) {
|
||||
scanner.queue = scanner.queue.filter(id => game.chicks.some(ch => ch.id === id && ch.stage === 'queued'));
|
||||
const spacing = GRID.cell * 0.86;
|
||||
scanner.queue.forEach((id, idx) => {
|
||||
const chick = game.chicks.find(ch => ch.id === id);
|
||||
if (!chick) return;
|
||||
setPositionFromRouteEnd(chick, chick.queueRoute || [scannerCenter(scanner)], idx * spacing);
|
||||
chick.queueIndex = idx;
|
||||
});
|
||||
}
|
||||
function setPositionFromRouteEnd(chick, route, distanceBack) {
|
||||
if (!route || !route.length) return;
|
||||
if (route.length === 1 || distanceBack <= 0) { const end = route[route.length - 1]; chick.x = end.x; chick.y = end.y; return; }
|
||||
let remain = distanceBack;
|
||||
for (let i = route.length - 1; i > 0; i -= 1) {
|
||||
const a = route[i - 1], b = route[i];
|
||||
const len = Math.hypot(b.x - a.x, b.y - a.y);
|
||||
if (remain <= len) { const t = 1 - remain / len; chick.x = a.x + (b.x - a.x) * t; chick.y = a.y + (b.y - a.y) * t; return; }
|
||||
remain -= len;
|
||||
}
|
||||
const start = route[0]; chick.x = start.x; chick.y = start.y;
|
||||
}
|
||||
function updateScannerQueues(dt) {
|
||||
for (const scanner of game.scanners) {
|
||||
if (scanner.cooldown > 0) scanner.cooldown = Math.max(0, scanner.cooldown - dt);
|
||||
positionScannerQueue(scanner);
|
||||
if (scanner.kind !== 'auto' || scanner.cooldown > 0 || !scanner.queue.length) continue;
|
||||
const id = scanner.queue[0];
|
||||
const index = game.chicks.findIndex(c => c.id === id);
|
||||
if (index < 0) { scanner.queue.shift(); continue; }
|
||||
if (sortChickByIndex(index, autoSideFor(scanner, game.chicks[index]), true)) { scanner.cooldown = AUTO_SCANNER_COOLDOWN; game.stats.autoSorted += 1; game.totals.autoSorted += 1; }
|
||||
}
|
||||
}
|
||||
function autoSideFor(scanner, chick) {
|
||||
if (scanner.role === 0) return chick.sex === 'male' ? 'left' : 'right';
|
||||
return chick.sex === 'poop' ? 'left' : 'right';
|
||||
}
|
||||
function activeQueuedChick(slot) {
|
||||
const scanner = scannerBySlot(game, slot);
|
||||
if (!scanner || !scanner.queue.length) return null;
|
||||
return game.chicks.find(c => c.id === scanner.queue[0]) || null;
|
||||
}
|
||||
function sortSlot(slot, side) {
|
||||
if (game.phase !== 'running') return;
|
||||
const scanner = scannerBySlot(game, slot);
|
||||
if (!scanner || !scanner.queue.length) return;
|
||||
const index = game.chicks.findIndex(c => c.id === scanner.queue[0]);
|
||||
if (index >= 0) sortChickByIndex(index, side, false);
|
||||
}
|
||||
function sortChickByIndex(index, side, auto) {
|
||||
const chick = game.chicks[index];
|
||||
if (!chick || chick.stage !== 'queued') return false;
|
||||
const scanner = scannerById(game, chick.scannerId);
|
||||
if (!scanner) return false;
|
||||
const plan = outputRoute(game, side, { x: chick.x, y: chick.y }, scanner.id, true);
|
||||
if (!plan) { floating(game, chick.x, chick.y - 16, side === 'left' ? 'NO LEFT BELT' : 'NO RIGHT BELT', THEME.danger); return false; }
|
||||
scanner.queue = scanner.queue.filter(id => id !== chick.id);
|
||||
chick.stage = plan.destination === 'scanner' ? 'toScanner' : `to${plan.destination[0].toUpperCase()}${plan.destination.slice(1)}`;
|
||||
chick.route = plan.route; chick.targetIndex = 1; chick.nextScannerId = plan.nextScannerId || null; chick.queueRoute = null; chick.queueIndex = -1;
|
||||
scannerPulse(game, scannerCenter(scanner).x, scannerCenter(scanner).y, auto ? THEME.green : destinationColor(plan.destination));
|
||||
floating(game, chick.x, chick.y - 20, auto ? 'AUTO' : destinationLabel(plan.destination), auto ? THEME.green : destinationColor(plan.destination));
|
||||
return true;
|
||||
}
|
||||
function resolveMixer(index) {
|
||||
const chick = game.chicks[index]; if (!chick) return;
|
||||
const { x, y } = chick; game.chicks.splice(index, 1); game.stats.mixerCount += 1; game.totals.processed += 1;
|
||||
if (chick.sex === 'poop') { game.mixerHalfTimer = MIXER_HALF_SECONDS; game.stats.poopMixer += 1; game.totals.poopMixer += 1; game.stats.mistakes += 1; game.totals.mistakes += 1; sludgeEffect(game, x, y); floating(game, x, y - 18, `HALF PAY ${MIXER_HALF_SECONDS}s`, THEME.ink); return; }
|
||||
const amount = positivePayout(game, MIXER_PRICE); applyCashDelta(game, amount); if (chick.sex === 'male') { game.stats.correct += 1; game.totals.correct += 1; } else { game.stats.mistakes += 1; game.totals.mistakes += 1; } meatEffect(game, x, y); floating(game, x, y - 18, `+${yen(amount)}`, THEME.green); uiSystem.checkGameOver();
|
||||
}
|
||||
function resolveTruck(index) {
|
||||
const chick = game.chicks[index]; if (!chick) return;
|
||||
const { x, y } = chick; game.chicks.splice(index, 1); addTruckCargo(chick.sex); truckLoadEffect(game, x, y, chick.sex); game.totals.processed += 1;
|
||||
const target = truckTarget(game);
|
||||
const isTarget = isTargetTruckCargo(game, chick.sex);
|
||||
if (chick.sex === 'poop') {
|
||||
game.stats.poopTruck += 1; game.totals.poopTruck += 1;
|
||||
if (isTarget) { game.stats.correct += 1; game.totals.correct += 1; floating(game, x, y - 18, `+${yen(positivePayout(game, TRUCK_PRICE))}`, THEME.green); }
|
||||
else { game.stats.mistakes += 1; game.totals.mistakes += 1; floating(game, x, y - 18, `SOIL ${Math.round(truckPayoutMultiplier(game) * 100)}%`, THEME.ink); }
|
||||
return;
|
||||
}
|
||||
if (chick.sex === 'female') {
|
||||
game.stats.truckFemale += 1;
|
||||
if (isTarget) { const displayAmount = positivePayout(game, Math.floor(TRUCK_PRICE * truckPayoutMultiplier(game))); game.stats.correct += 1; game.totals.correct += 1; floating(game, x, y - 18, `+${yen(displayAmount)}`, THEME.green); }
|
||||
else { game.stats.mistakes += 1; game.totals.mistakes += 1; floating(game, x, y - 18, target === 'male' ? 'REJECT' : 'NO SALE', THEME.warn); }
|
||||
return;
|
||||
}
|
||||
game.stats.truckMale += 1;
|
||||
if (isTarget) { game.stats.correct += 1; game.totals.correct += 1; floating(game, x, y - 18, `+${yen(positivePayout(game, TRUCK_PRICE))}`, THEME.green); return; }
|
||||
if (shouldFineMaleTruck(game)) { const penalty = maleTruckPenalty(game); applyCashDelta(game, -penalty); game.stats.mistakes += 1; game.totals.mistakes += 1; floating(game, x, y - 18, `-${yen(penalty)}`, THEME.danger); uiSystem.checkGameOver(); }
|
||||
else { game.stats.mistakes += 1; game.totals.mistakes += 1; floating(game, x, y - 18, 'REJECT', THEME.warn); }
|
||||
}
|
||||
function resolveTrash(index) {
|
||||
const chick = game.chicks[index]; if (!chick) return;
|
||||
const { x, y } = chick; game.chicks.splice(index, 1); shredEffect(game, x, y, chick.sex); game.stats.trashCount += 1; game.totals.processed += 1;
|
||||
if (chick.sex === 'poop') { game.stats.poopTrash += 1; game.totals.poopTrash += 1; game.stats.correct += 1; game.totals.correct += 1; floating(game, x, y - 18, 'CLEAN', THEME.green); }
|
||||
else { game.stats.mistakes += 1; game.totals.mistakes += 1; floating(game, x, y - 18, 'WASTE', THEME.ink); }
|
||||
}
|
||||
function removeChick(index, label) { const chick = game.chicks[index]; if (!chick) return; game.chicks.splice(index, 1); floating(game, chick.x, chick.y - 12, label, THEME.muted); }
|
||||
function addTruckCargo(sex) { const t = game.facilities.truck; if (!t) return; game.truckCargo.push({ sex, x: randomBetween(22, t.w - 22), y: randomBetween(66, t.h - 24) }); if (game.truckCargo.length > 45) game.truckCargo.shift(); }
|
||||
|
||||
function conveyorKeyAtChick(chick) {
|
||||
const cell = pointToCell(chick.x, chick.y);
|
||||
if (cell) {
|
||||
const k = key(cell.col, cell.row);
|
||||
if (game.conveyorTiles.has(k)) return k;
|
||||
}
|
||||
// Fallback only when the chick is visually still on a conveyor center.
|
||||
const nearest = nearestConveyorKey(game, chick.x, chick.y);
|
||||
if (!nearest) return null;
|
||||
const p = parseKey(nearest);
|
||||
const c = cellCenter(p.col, p.row);
|
||||
return Math.hypot(chick.x - c.x, chick.y - c.y) <= GRID.cell * 0.55 ? nearest : null;
|
||||
}
|
||||
|
||||
function updateCongestion() {
|
||||
const { components, cellToComponent } = buildConveyorComponents(game);
|
||||
for (const chick of game.chicks) {
|
||||
if (chick.stage === 'flying') continue;
|
||||
const k = conveyorKeyAtChick(chick);
|
||||
if (!k) continue;
|
||||
const id = cellToComponent.get(k);
|
||||
const comp = components.get(id);
|
||||
if (comp) comp.count += 1;
|
||||
}
|
||||
for (const comp of components.values()) comp.ratio = comp.count / comp.capacity;
|
||||
game.congestion = components;
|
||||
game.componentLookup = cellToComponent;
|
||||
}
|
||||
function checkCongestionExplosions(dt) {
|
||||
for (const comp of game.congestion.values()) {
|
||||
if (comp.ratio > 0.8 && comp.ratio < 1) {
|
||||
for (const chick of chicksInComponent(comp.id).slice(0, 3)) if (Math.random() < 0.06) rageEffect(game, chick.x, chick.y - chick.radius - 8);
|
||||
}
|
||||
if (comp.ratio >= 1) {
|
||||
const last = game.lastExplodedComponent.get(comp.id) || 0;
|
||||
const now = performance.now();
|
||||
if (now - last > 900) { game.lastExplodedComponent.set(comp.id, now); explodeComponent(comp); }
|
||||
}
|
||||
}
|
||||
}
|
||||
function chicksInComponent(id) {
|
||||
return game.chicks.filter(ch => { const k = conveyorKeyAtChick(ch); return k && game.componentLookup?.get(k) === id; });
|
||||
}
|
||||
function explodeComponent(comp) {
|
||||
const victims = chicksInComponent(comp.id);
|
||||
if (!victims.length) return;
|
||||
for (const v of victims) { flyingDebris(game, v.sex, v.x, v.y); }
|
||||
game.chicks = game.chicks.filter(ch => !victims.some(v => v.id === ch.id));
|
||||
for (const scanner of game.scanners) scanner.queue = scanner.queue.filter(id => game.chicks.some(c => c.id === id));
|
||||
for (const k of comp.cells) { const p = parseKey(k); const c = cellCenter(p.col, p.row); shockwave(game, c.x, c.y, THEME.danger); sparkBurst(game, c.x, c.y, 6); smokeBurst(game, c.x, c.y, 4); }
|
||||
shake(game, 18, .75); const first = parseKey(comp.cells[0]); const fc = cellCenter(first.col, first.row); floating(game, fc.x, fc.y - 22, '100% JAM EXPLOSION', THEME.danger);
|
||||
}
|
||||
function explodeRoute(route, extras, label) {
|
||||
const victims = game.chicks.filter(ch => route.some(p => Math.hypot(ch.x - p.x, ch.y - p.y) < GRID.cell));
|
||||
game.chicks = game.chicks.filter(ch => !victims.some(v => v.id === ch.id));
|
||||
for (const v of victims) flyingDebris(game, v.sex, v.x, v.y);
|
||||
for (const e of extras) flyingDebris(game, e.sex, e.x, e.y);
|
||||
for (const p of route) { shockwave(game, p.x, p.y, THEME.danger); sparkBurst(game, p.x, p.y, 4); smokeBurst(game, p.x, p.y, 3); }
|
||||
shake(game, 18, .75); if (route[0]) floating(game, route[0].x, route[0].y - 18, label, THEME.danger);
|
||||
}
|
||||
|
||||
// Build/edit/selection operations live in src/systems/buildSystem.js.
|
||||
// HUD, panels, modal screens, and gameover rendering live in src/systems/uiSystem.js.
|
||||
// Hitbox generation lives in buildSystem.js.
|
||||
|
|
@ -380,7 +145,7 @@ function clickSelect(event) {
|
|||
game.selected = sel;
|
||||
game.multiSelected = [sel];
|
||||
uiSystem.updatePanels();
|
||||
if (hit.type === 'scanner' && hit.ref.kind === 'auto') build.showAutoScannerMenu(hit.ref);
|
||||
build.showSelectedMenu();
|
||||
}
|
||||
|
||||
canvas.addEventListener('contextmenu', event => event.preventDefault());
|
||||
|
|
@ -391,7 +156,12 @@ canvas.addEventListener('pointerdown', event => {
|
|||
if (event.button === 2) { startPan(event); return; }
|
||||
if (event.button !== 0) return;
|
||||
if (game.buildTool === 'erase') { build.eraseAtPoint(world); return; }
|
||||
if (game.buildTool) { build.buildAtPoint(world); return; }
|
||||
if (game.buildTool) {
|
||||
const hit = build.equipmentAtPoint(world);
|
||||
if (hit) { game.buildTool = null; clickSelect(event); return; }
|
||||
build.buildAtPoint(world);
|
||||
return;
|
||||
}
|
||||
if (build.startGroupDrag(event)) return;
|
||||
build.startSelectionBox(event);
|
||||
});
|
||||
|
|
@ -414,11 +184,12 @@ canvas.addEventListener('pointerup', event => {
|
|||
});
|
||||
canvas.addEventListener('pointercancel', () => { game.groupDrag = null; game.selectionBox = null; game.pan = null; });
|
||||
|
||||
ui.buttons.s1Left.addEventListener('click', () => sortSlot(0, 'left')); ui.buttons.s1Right.addEventListener('click', () => sortSlot(0, 'right')); ui.buttons.s2Left.addEventListener('click', () => sortSlot(1, 'left')); ui.buttons.s2Right.addEventListener('click', () => sortSlot(1, 'right'));
|
||||
ui.buttons.s1Left.addEventListener('click', () => chicks.sortSlot(0, 'left')); ui.buttons.s1Right.addEventListener('click', () => chicks.sortSlot(0, 'right')); ui.buttons.s2Left.addEventListener('click', () => chicks.sortSlot(1, 'left')); ui.buttons.s2Right.addEventListener('click', () => chicks.sortSlot(1, 'right'));
|
||||
ui.buttons.nextTurn.addEventListener('click', startNextTurn); ui.buttons.conveyor.addEventListener('click', () => build.setBuildTool('conveyor')); ui.buttons.eggFarm.addEventListener('click', () => build.setBuildTool('eggFarm')); ui.buttons.autoScanner.addEventListener('click', () => build.setBuildTool('autoScanner')); ui.buttons.manualScanner.addEventListener('click', () => build.setBuildTool('manualScanner')); ui.buttons.mixer.addEventListener('click', () => build.setBuildTool('mixer')); ui.buttons.trash.addEventListener('click', () => build.setBuildTool('trash')); ui.buttons.truck.addEventListener('click', () => build.setBuildTool('truck')); ui.buttons.erase.addEventListener('click', () => build.setBuildTool('erase'));
|
||||
ui.buttons.undo.addEventListener('click', () => { if (undo(game)) uiSystem.updatePanels(); }); ui.buttons.redo.addEventListener('click', () => { if (redo(game)) uiSystem.updatePanels(); });
|
||||
window.addEventListener('keydown', event => { if (event.repeat) return; const name = event.key.toLowerCase(); if (name === 'a') { event.preventDefault(); sortSlot(0, 'left'); } if (name === 'd') { event.preventDefault(); sortSlot(0, 'right'); } if (event.key === 'ArrowLeft') { event.preventDefault(); sortSlot(1, 'left'); } if (event.key === 'ArrowRight') { event.preventDefault(); sortSlot(1, 'right'); } if (game.phase === 'build' && (event.ctrlKey || event.metaKey) && name === 'z') { event.preventDefault(); if (undo(game)) uiSystem.updatePanels(); } if (game.phase === 'build' && (event.ctrlKey || event.metaKey) && (name === 'y' || (event.shiftKey && name === 'z'))) { event.preventDefault(); if (redo(game)) uiSystem.updatePanels(); } if (name === 'f1') { event.preventDefault(); game.debug = !game.debug; } });
|
||||
window.addEventListener('keydown', event => { if (event.repeat) return; const name = event.key.toLowerCase(); if (name === 'a') { event.preventDefault(); chicks.sortSlot(0, 'left'); } if (name === 'd') { event.preventDefault(); chicks.sortSlot(0, 'right'); } if (event.key === 'ArrowLeft') { event.preventDefault(); chicks.sortSlot(1, 'left'); } if (event.key === 'ArrowRight') { event.preventDefault(); chicks.sortSlot(1, 'right'); } if (game.phase === 'build' && (event.ctrlKey || event.metaKey) && name === 'z') { event.preventDefault(); if (undo(game)) uiSystem.updatePanels(); } if (game.phase === 'build' && (event.ctrlKey || event.metaKey) && (name === 'y' || (event.shiftKey && name === 'z'))) { event.preventDefault(); if (redo(game)) uiSystem.updatePanels(); } if (name === 'f1') { event.preventDefault(); game.debug = !game.debug; } });
|
||||
|
||||
build = createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanels: () => uiSystem?.updatePanels() });
|
||||
uiSystem = createUISystem({ game, ui, build, startGame, activeQueuedChick });
|
||||
resetLayout(game); updateCongestion(); uiSystem.updatePanels(); uiSystem.updateUI(); uiSystem.showTitle(); requestAnimationFrame(update);
|
||||
chicks = createChickSystem({ game, currentConveyorSpeed, onGameOverCheck: () => uiSystem?.checkGameOver() });
|
||||
uiSystem = createUISystem({ game, ui, build, startGame, activeQueuedChick: chicks.activeQueuedChick });
|
||||
resetLayout(game); chicks.updateCongestion(); uiSystem.updatePanels(); uiSystem.updateUI(); uiSystem.showTitle(); requestAnimationFrame(update);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue