tweak
This commit is contained in:
parent
368de90c6c
commit
b13e07a074
29 changed files with 166 additions and 4276 deletions
|
|
@ -1,114 +0,0 @@
|
|||
# Balance Patch Notes v27.14
|
||||
|
||||
## Event system
|
||||
|
||||
The old shipment-goal terminology has been replaced by one-day Events in player-facing UI and balance notes. Internal names are mostly event-based; a few generic helper concepts are preserved only where they are not player-facing.
|
||||
|
||||
Events start appearing from Day 8 with a 10% chance. An offered event is forced and applies only to the next running day.
|
||||
|
||||
Implemented events:
|
||||
|
||||
1. Premium Female Shipment
|
||||
- Female -> Truck event.
|
||||
- Target = estimated total production x 0.3.
|
||||
- Success pays an event bonus.
|
||||
|
||||
2. Contaminant Sample Export
|
||||
- Poop is valid Truck cargo.
|
||||
- All poop income is doubled.
|
||||
- Target = estimated total production x 0.3.
|
||||
- Success pays an event bonus.
|
||||
|
||||
3. Health Inspection
|
||||
- Poop mistake fines are doubled.
|
||||
- Shredding enough poop pays an event bonus.
|
||||
|
||||
4. Conveyor Jam Warning
|
||||
- Jam explosion threshold becomes 85% congestion.
|
||||
- Finishing with zero explosions pays an event bonus.
|
||||
|
||||
5. Power Saving Order
|
||||
- Conveyor speed x0.8.
|
||||
- Auto Scanner cooldown x1.2.
|
||||
- Settlement pays a flat subsidy.
|
||||
|
||||
6. Overtime Day
|
||||
- Day length becomes 80 seconds.
|
||||
- No extra effect.
|
||||
|
||||
7. Short Shift
|
||||
- Day length becomes 40 seconds.
|
||||
- Next Fairies tribute is reduced by 10%.
|
||||
|
||||
8. Chick Boom
|
||||
- Egg interval x0.65.
|
||||
- Poop rate +5 percentage points.
|
||||
|
||||
9. Rotten Feed
|
||||
- Poop rate +20 percentage points.
|
||||
- Shredder poop payout x1.5.
|
||||
|
||||
10. Maintenance Audit
|
||||
- No penalty.
|
||||
- If no maintained machine is below 50% remaining durability, settlement pays a bonus.
|
||||
|
||||
## Related balance behavior
|
||||
|
||||
- Shredder Upgrade payout is affected by Event poop/shredder multipliers.
|
||||
- COMPOSTER payout is also affected by Event shredder payout multipliers.
|
||||
- Poop target Truck events no longer fine poop when poop is the valid cargo.
|
||||
- Chemical Weapons Subsidy remains 70 poop / 1200 yen.
|
||||
- Fairies tribute is paid on Next Day press.
|
||||
- Normal Loan card and repayment state are removed. Emergency Loan remains.
|
||||
- The old tax system is removed. Fairies tribute is the only recurring day-start levy.
|
||||
|
||||
## v27.14 consistency/debug changes
|
||||
|
||||
- Debug UI restored intentionally for balancing builds. HTML/CSS/JS include comments stating it should not be removed during cleanup unless explicitly requested.
|
||||
- Debug card selector is grouped by gameplay genre.
|
||||
- Debug single-card grant supports selecting the target for target-consuming build-phase cards.
|
||||
- Debug All Cards still uses automatic valid targets.
|
||||
- Removed unused special weight logic left over from old SPARE PARTS BIN behavior.
|
||||
- Removed unused Egg Farm cash upgrade cost data; Egg upgrades are card-based.
|
||||
- Shredder Upgrade now sums upgrades across all Shredder facilities, not only the original primary Shredder.
|
||||
- Maintenance and repair target collection now includes additional Mixer/Shredder facilities.
|
||||
- Maintenance Audit low-durability checks now include additional Mixer/Shredder facilities.
|
||||
- Fixed duplicate Emergency Loan button in the bankruptcy decision modal.
|
||||
- Fixed stale debug-removal CSS fragments left from the previous audit pass.
|
||||
- Fixed corrupted score-breakdown formula text.
|
||||
|
||||
## v27.15 dark factory visual patch
|
||||
|
||||
- Wired `assets/dark_factory/facilities/` sprites into canvas rendering.
|
||||
- Added pixelated rendering for facility sprites and dark factory grid/floor colors.
|
||||
- Changed CSS theme from green/white panels to dark industrial panels with amber/red/blue accents.
|
||||
- Moved UI emphasis: status HUD across the top, event/status strip down the left, build/equipment panel on the right, sorting controls at the bottom.
|
||||
- Kept user-supplied chick/poop slots unmodified. Chick art is not auto-requested; existing poop compatibility path remains.
|
||||
- Debug UI remains intentionally present for balancing builds.
|
||||
|
||||
|
||||
## v27.17 verified visual patch
|
||||
|
||||
- Versioned CSS and JavaScript launch filenames prevent the old visual bundle from being reused from cache.
|
||||
- Removed all player-facing remnants of wage-based Repairman hiring.
|
||||
- Maintenance Room remains a 2×2 buildable object and dispatches one Repairman per room.
|
||||
- Conveyor slats now animate perpendicular to travel, with blue-green rubber on Boost Conveyors.
|
||||
- DEBUG collapsed width reduced to 58 px.
|
||||
- Facility sprite scale aligned to grid-object pixel density.
|
||||
- Scanner ducts, blocked-cell coverage, chick sex colors/symbols, metal/grime decoration, and card texture icons verified in browser.
|
||||
|
||||
## v27.18 UI and routing patch
|
||||
|
||||
- Moved event, belt speed, combo, and truck target indicators to a non-overlapping lower-left group.
|
||||
- Reworked T-junction branch mode as explicit output selection: random or a valid fixed exit.
|
||||
- Lightened floor tiles, added deterministic green-fluid stains, increased conveyor arrow contrast, and centered blocked-cell X marks.
|
||||
- Converted card icons into translucent background watermarks and normalized modal button styling.
|
||||
- Replaced Maintenance Room art with storage/break-room pixel art.
|
||||
|
||||
|
||||
## v27.19 land/UI/dynamite patch
|
||||
|
||||
- Starting blocked ratio: 3%.
|
||||
- Purchased-lot blocked ratio: base 3% + 0.5 percentage points per purchase, max 10%.
|
||||
- Dynamite target selection is map-driven and consumes the card correctly after any blast.
|
||||
- Duplicate event/status information removed.
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
# v27.19 verification
|
||||
|
||||
## Automated checks
|
||||
|
||||
- All source JavaScript files pass `node --check`.
|
||||
- Production bundle `dist/game-v27.19.js` builds successfully.
|
||||
- Initial blocked-cell generation: 6 / 200 cells = 3%.
|
||||
- Purchased-lot ratios: purchase 1 = 3.5%, purchase 2 = 4.0%, purchase 14+ = 10% cap.
|
||||
- A 10x10 lot at the cap generates 10 blocked cells.
|
||||
- Dynamite empty-space click leaves target mode active.
|
||||
- Clicking a chosen blocked cell removes that exact cell and consumes one charge.
|
||||
- Escape after at least one blast finishes and consumes the card, preventing reuse.
|
||||
- Browser check: no console/page errors, duplicate right-side event panel absent, Status contains no event/belt/combo/truck-target duplicates.
|
||||
- Browser check: NEXT DAY Fairies fee remains inside the button.
|
||||
|
||||
## Visual checks
|
||||
|
||||
- Green floor residue is sparse and splattered.
|
||||
- Blocked cells use subdued black/yellow hazard striping.
|
||||
- Initial Status reports 6 blocked cells.
|
||||
14
README.md
14
README.md
|
|
@ -1,19 +1,17 @@
|
|||
# Chick Sorter v27.19 — Land and UI Patch
|
||||
# Chick Sorter
|
||||
|
||||
Serve this folder with a static HTTP server and open `index.html`.
|
||||
|
||||
The launch page uses cache-busted files:
|
||||
The launch page uses the unversioned app assets:
|
||||
|
||||
- `styles-v27.19.css?build=2719`
|
||||
- `dist/game-v27.19.js?build=2719`
|
||||
|
||||
A `v27.19 VERIFIED` badge appears near the upper-right when the current build is loaded.
|
||||
- `styles.css`
|
||||
- `dist/game.js`
|
||||
|
||||
## Build
|
||||
|
||||
Run `npm install` once, then `npm run build` after editing JavaScript or `styles.css`.
|
||||
Run `npm install` once, then `npm run build` after editing JavaScript or `styles.css`. Without a local Node/npm toolchain, `dist/game.js` remains a source-module wrapper for local static serving.
|
||||
|
||||
## v27.19 changes
|
||||
## Current changes
|
||||
|
||||
- Initial no-build tiles are 3% of the starting 20x10 land.
|
||||
- Each purchased 10x10 lot increases its own no-build ratio by 0.5 percentage points, capped at 10%.
|
||||
|
|
|
|||
|
|
@ -5,10 +5,7 @@ This patch adds a **dark factory** art set without changing runtime references y
|
|||
## Folder guide
|
||||
- `assets/dark_factory/facilities/` : new factory equipment sprites
|
||||
- `assets/dark_factory/previews/` : visual overview sheet
|
||||
- `assets/ui/` : future UI asset buckets (buttons / panels / icons / debug)
|
||||
- `assets/chicks/` : reserved for user-supplied chick sprites
|
||||
- `assets/poop/` : reserved for user-supplied poop sprites
|
||||
- `assets/_legacy/` : copied backup of older loose files
|
||||
- `assets/images/` : user-supplied chick and poop sprites used by the renderer
|
||||
- `assets/images/` : left untouched for compatibility with the current build
|
||||
|
||||
## Notes
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
# Image assets
|
||||
|
||||
`poop.png` is bundled and loaded by default. Other equipment/chick art is optional and currently falls back to canvas drawing unless external art loading is enabled in `src/render/draw.js`.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB |
|
|
@ -1 +0,0 @@
|
|||
Reserved for user-supplied chick sprites. Recommended base: 16x16 world sprites / 32x32 UI icons.\n
|
||||
|
|
@ -1 +0,0 @@
|
|||
Reserved for user-supplied poop sprites. Recommended base: 16x16 world sprites / 32x32 UI icons.\n
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 378 B |
|
|
@ -1 +0,0 @@
|
|||
Future UI asset buckets. Nothing is wired yet. Use 32x32 as the base size for non-grid UI objects.\n
|
||||
59
dist/game-v27.17.js
vendored
59
dist/game-v27.17.js
vendored
File diff suppressed because one or more lines are too long
59
dist/game-v27.18.js
vendored
59
dist/game-v27.18.js
vendored
File diff suppressed because one or more lines are too long
51
dist/game-v27.19.js
vendored
51
dist/game-v27.19.js
vendored
File diff suppressed because one or more lines are too long
61
dist/game.js
vendored
61
dist/game.js
vendored
File diff suppressed because one or more lines are too long
12
index.html
12
index.html
|
|
@ -6,13 +6,13 @@
|
|||
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
<title>Chick Sorter v27.19 — Land and UI Patch</title>
|
||||
<link rel="stylesheet" href="./styles-v27.19.css?build=2719" />
|
||||
<title>Chick Sorter v27.20 — Card and Expansion Patch</title>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main id="app">
|
||||
<section id="gameShell" class="game-shell" data-build="v27.19" aria-label="Game screen">
|
||||
<div class="build-stamp" aria-label="loaded build">v27.19 VERIFIED</div>
|
||||
<section id="gameShell" class="game-shell" data-build="v27.20" aria-label="Game screen">
|
||||
<div class="build-stamp" aria-label="loaded build">v27.20 VERIFIED</div>
|
||||
<canvas id="gameCanvas" width="1440" height="900" aria-label="Chick Sorter game canvas"></canvas>
|
||||
|
||||
<div class="hud hud-top-left" aria-label="Run status">
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
<div id="buildPanel" class="build-panel" aria-label="Build and facility upgrades">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h1>CHICK SORTER <span>v27.19</span></h1>
|
||||
<h1>CHICK SORTER <span>v27.20</span></h1>
|
||||
<p>Build, expand, connect at least one valid EGG route, then start the next day.</p>
|
||||
</div>
|
||||
<button id="nextTurnButton" class="primary-button" type="button">Next Day</button>
|
||||
|
|
@ -110,6 +110,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="./dist/game-v27.19.js?build=2719"></script>
|
||||
<script type="module" src="./dist/game.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "zunda-shiwake",
|
||||
"version": "27.19.0",
|
||||
"version": "27.20.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "zunda-shiwake",
|
||||
"version": "27.19.0",
|
||||
"version": "27.20.0",
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "zunda-shiwake",
|
||||
"version": "27.19.0",
|
||||
"version": "27.20.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
|
||||
"build": "npm run clean && node -e \"require('fs').copyFileSync('styles.css', 'styles-v27.19.css')\" && esbuild src/game.js --bundle --format=esm --target=es2020 --minify --outfile=dist/game-v27.19.js"
|
||||
"build": "npm run clean && esbuild src/game.js --bundle --format=esm --target=es2020 --minify --outfile=dist/game.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.0"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Central balance sheet for tuning gameplay.
|
||||
// Edit this file first when adjusting prices, income, penalties, timing, card rates, or caps.
|
||||
export const BALANCE = {
|
||||
version: 'v27.19 LAND + UI PATCH',
|
||||
version: 'v27.20 CARD + EXPANSION PATCH',
|
||||
time: {
|
||||
daySeconds: 60,
|
||||
farmShutdownGraceSeconds: 10
|
||||
|
|
@ -24,9 +24,9 @@ export const BALANCE = {
|
|||
}
|
||||
},
|
||||
map: {
|
||||
blockedRatio: 0.03,
|
||||
blockedExpansionIncrement: 0.005,
|
||||
blockedRatioMax: 0.10
|
||||
blockedRatio: 0.00,
|
||||
blockedExpansionIncrement: 0.0075,
|
||||
blockedRatioMax: 0.12
|
||||
},
|
||||
economy: {
|
||||
income: {
|
||||
|
|
@ -253,7 +253,7 @@ export const BALANCE = {
|
|||
rarity: 'rare',
|
||||
type: 'cellAction',
|
||||
target: 'blockedCell',
|
||||
description: 'Choose and remove up to 3 unbuildable cells.',
|
||||
description: 'Choose and remove up to 10 unbuildable cells.',
|
||||
tags: ['RISK', 'RARE', 'ONE-SHOT']
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export function resetOwnedCells(game) {
|
|||
game.ownedChunks = new Set();
|
||||
game.gridChunkOriginY = 0;
|
||||
game.gridExpansionPurchases = 0;
|
||||
game.gridExpansionPurchasesByDirection = { right: 0, up: 0 };
|
||||
game.gridExpansionPurchasesByDirection = { right: 0, up: 0, down: 0 };
|
||||
for (let chunkY = 0; chunkY < INITIAL_CHUNKS_Y; chunkY += 1) {
|
||||
for (let chunkX = 0; chunkX < INITIAL_CHUNKS_X; chunkX += 1) game.ownedChunks.add(chunkKey(chunkX, chunkY));
|
||||
}
|
||||
|
|
@ -83,15 +83,17 @@ export function expansionLots(game) {
|
|||
const { chunkX, chunkY } = parseChunkKey(k);
|
||||
const right = { chunkX: chunkX + 1, chunkY, direction: 'right' };
|
||||
const up = { chunkX, chunkY: chunkY - 1, direction: 'up' };
|
||||
for (const candidate of [right, up]) {
|
||||
if (candidate.chunkX < 0 || candidate.chunkY > 0) continue;
|
||||
const down = { chunkX, chunkY: chunkY + 1, direction: 'down' };
|
||||
for (const candidate of [right, up, down]) {
|
||||
if (candidate.chunkX < 0) continue;
|
||||
const ck = chunkKey(candidate.chunkX, candidate.chunkY);
|
||||
if (owned.has(ck) || candidates.has(ck)) continue;
|
||||
candidates.set(ck, lotFromChunk(game, candidate.chunkX, candidate.chunkY, candidate.direction));
|
||||
}
|
||||
}
|
||||
const directionOrder = { right: 0, up: 1, down: 2 };
|
||||
return [...candidates.values()].sort((a, b) => {
|
||||
const dir = (a.direction === b.direction) ? 0 : (a.direction === 'right' ? -1 : 1);
|
||||
const dir = (directionOrder[a.direction] ?? 9) - (directionOrder[b.direction] ?? 9);
|
||||
if (dir) return dir;
|
||||
if (a.chunkY !== b.chunkY) return a.chunkY - b.chunkY;
|
||||
return a.chunkX - b.chunkX;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ function mulberry32(seed) {
|
|||
}
|
||||
|
||||
|
||||
export function blockedRatioForExpansionPurchase(purchaseCount, { base = 0.03, increment = 0.005, max = 0.10 } = {}) {
|
||||
export function blockedRatioForExpansionPurchase(purchaseCount, { base = 0.00, increment = 0.0075, max = 0.12 } = {}) {
|
||||
const count = Math.max(0, Math.floor(Number(purchaseCount) || 0));
|
||||
return Number(Math.min(max, base + count * increment).toFixed(6));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ function refreshDebugTargetSelect() {
|
|||
}
|
||||
const auto = document.createElement('option');
|
||||
auto.value = '__auto__';
|
||||
auto.textContent = card?.id === 'dynamite' ? 'Auto: first 3 blocked cells' : 'Auto: first valid target';
|
||||
auto.textContent = card?.id === 'dynamite' ? 'Auto: first 10 blocked cells' : 'Auto: first valid target';
|
||||
ui.debug.targetSelect.appendChild(auto);
|
||||
for (const target of targets) {
|
||||
const option = document.createElement('option');
|
||||
|
|
|
|||
|
|
@ -150,15 +150,15 @@ function floorCellHash(col, row) {
|
|||
}
|
||||
function drawFloorSlime(ctx, x, y, col, row) {
|
||||
const h = floorCellHash(col, row);
|
||||
// Sparse, deterministic splashes: about 3% of owned floor cells.
|
||||
if (h % 31 !== 0) return;
|
||||
// Sparse, deterministic splashes: roughly 1.5% of owned floor cells.
|
||||
if (h % 67 !== 0) return;
|
||||
const ox = 10 + ((h >>> 4) % Math.max(1, GRID.cell - 22));
|
||||
const oy = 10 + ((h >>> 9) % Math.max(1, GRID.cell - 22));
|
||||
const angle = ((h >>> 14) % 16) * Math.PI / 16;
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.46;
|
||||
ctx.fillStyle = '#4d9638';
|
||||
ctx.strokeStyle = 'rgba(30,66,27,.48)';
|
||||
ctx.globalAlpha = 0.38;
|
||||
ctx.fillStyle = '#4a8d37';
|
||||
ctx.strokeStyle = 'rgba(30,66,27,.34)';
|
||||
ctx.lineWidth = 1;
|
||||
|
||||
// Small irregular core rather than a large puddle.
|
||||
|
|
@ -175,16 +175,16 @@ function drawFloorSlime(ctx, x, y, col, row) {
|
|||
ctx.stroke();
|
||||
|
||||
// Several separated droplets produce a splattered silhouette.
|
||||
const droplets = 3 + ((h >>> 24) % 4);
|
||||
const droplets = 4 + ((h >>> 24) % 5);
|
||||
for (let i = 0; i < droplets; i += 1) {
|
||||
const a = angle + i * 2.17 + ((h >>> (i + 8)) & 3) * .18;
|
||||
const dist = 8 + ((h >>> (i + 11)) & 7);
|
||||
const radius = 1.1 + ((h >>> (i + 17)) & 3) * .45;
|
||||
const dist = 10 + ((h >>> (i + 11)) & 10);
|
||||
const radius = 0.9 + ((h >>> (i + 17)) & 3) * .35;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x + ox + Math.cos(a) * dist, y + oy + Math.sin(a) * dist * .72, radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.fillStyle = 'rgba(151,218,101,.40)';
|
||||
ctx.fillStyle = 'rgba(151,218,101,.26)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(x + ox - 2, y + oy - 2, 1.7, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
|
@ -248,36 +248,32 @@ function drawGrid(ctx, game) {
|
|||
const c = cellCenter(p.col, p.row);
|
||||
const x = c.x - GRID.cell / 2;
|
||||
const y = c.y - GRID.cell / 2;
|
||||
// Muted floor-level hazard striping. It remains readable without dominating machines.
|
||||
// Muted floor-level warning striping that sits close to the floor tone range.
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(Math.round(x + 1), Math.round(y + 1), GRID.cell - 2, GRID.cell - 2);
|
||||
ctx.clip();
|
||||
ctx.fillStyle = 'rgba(42,45,48,.42)';
|
||||
ctx.fillStyle = 'rgba(70,72,76,.16)';
|
||||
ctx.fillRect(Math.round(x), Math.round(y), GRID.cell, GRID.cell);
|
||||
ctx.lineWidth = 8;
|
||||
ctx.strokeStyle = 'rgba(196,163,79,.38)';
|
||||
for (let stripe = -GRID.cell; stripe < GRID.cell * 2; stripe += 16) {
|
||||
ctx.lineWidth = 6;
|
||||
ctx.strokeStyle = 'rgba(165,145,73,.24)';
|
||||
for (let stripe = -GRID.cell; stripe < GRID.cell * 2; stripe += 14) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + stripe, y + GRID.cell);
|
||||
ctx.lineTo(x + stripe + GRID.cell, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.lineWidth = 8;
|
||||
ctx.strokeStyle = 'rgba(20,23,26,.32)';
|
||||
for (let stripe = -GRID.cell + 8; stripe < GRID.cell * 2; stripe += 16) {
|
||||
ctx.strokeStyle = 'rgba(27,29,33,.22)';
|
||||
for (let stripe = -GRID.cell + 7; stripe < GRID.cell * 2; stripe += 14) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + stripe, y + GRID.cell);
|
||||
ctx.lineTo(x + stripe + GRID.cell, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
ctx.strokeStyle = 'rgba(17,19,22,.72)';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeRect(Math.round(x) + 1, Math.round(y) + 1, GRID.cell - 2, GRID.cell - 2);
|
||||
ctx.strokeStyle = 'rgba(224,194,112,.24)';
|
||||
ctx.strokeStyle = 'rgba(20,23,26,.42)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(Math.round(x) + 4, Math.round(y) + 4, GRID.cell - 8, GRID.cell - 8);
|
||||
ctx.strokeRect(Math.round(x) + 1.5, Math.round(y) + 1.5, GRID.cell - 3, GRID.cell - 3);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
|
@ -304,7 +300,8 @@ function drawExpansionOffers(ctx, game) {
|
|||
ctx.fillStyle = affordable ? THEME.green : THEME.danger;
|
||||
ctx.font = '900 16px ui-monospace, monospace';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(lot.direction === 'right' ? 'BUY RIGHT LOT' : 'BUY UPPER LOT', cx, cy - 12);
|
||||
const lotLabel = lot.direction === 'right' ? 'BUY RIGHT LOT' : (lot.direction === 'down' ? 'BUY LOWER LOT' : 'BUY UPPER LOT');
|
||||
ctx.fillText(lotLabel, cx, cy - 12);
|
||||
ctx.fillStyle = THEME.ink;
|
||||
ctx.font = '900 16px ui-monospace, monospace';
|
||||
ctx.fillText(`${yen(cost)} / 10×10`, cx, cy + 15);
|
||||
|
|
@ -663,27 +660,47 @@ function drawEggFarms(ctx, game) { for (const farm of game.eggFarms) drawEggFarm
|
|||
function drawEggFarm(ctx, farm, game) {
|
||||
const c = cellCenter(farm.col, farm.row);
|
||||
ctx.save();
|
||||
if (drawImageIfLoaded(ctx, assets.eggFarm, c.x - 29, c.y - 29, 58, 58)) {
|
||||
drawDirtOverlay(ctx, c.x - 29, c.y - 29, 58, 58, farm);
|
||||
const outlets = 1 + Math.max(0, Number(farm.extraEggOutlet) || 0);
|
||||
ctx.fillStyle = 'rgba(255,255,255,.92)'; ctx.strokeStyle = THEME.ink; ctx.lineWidth = 2;
|
||||
rect(ctx, c.x + 9, c.y + 12, 30, 18, true, true);
|
||||
ctx.fillStyle = THEME.ink; ctx.font = '900 10px ui-monospace, monospace'; ctx.textAlign = 'center'; ctx.fillText(`O${outlets}`, c.x + 24, c.y + 24);
|
||||
drawSelection(ctx, game, 'eggFarm', farm.id, c.x, c.y, 68, 68);
|
||||
ctx.restore(); return;
|
||||
ctx.fillStyle = '#5a6470';
|
||||
ctx.strokeStyle = THEME.ink;
|
||||
ctx.lineWidth = 3;
|
||||
rect(ctx, c.x - 27, c.y - 27, 54, 54, true, true);
|
||||
ctx.fillStyle = '#6f5130';
|
||||
ctx.strokeStyle = '#2f2112';
|
||||
ctx.lineWidth = 3;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(c.x, c.y + 3, 19, 12, 0, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.strokeStyle = '#a77b4b';
|
||||
ctx.lineWidth = 2;
|
||||
for (let i = -16; i <= 16; i += 6) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(c.x - 18, c.y + i * 0.1);
|
||||
ctx.quadraticCurveTo(c.x + i, c.y - 10 + Math.abs(i) * 0.2, c.x + 18, c.y + i * 0.08);
|
||||
ctx.stroke();
|
||||
}
|
||||
const eggs = [{ x: -8, y: 1 }, { x: 0, y: -3 }, { x: 8, y: 2 }];
|
||||
for (const egg of eggs) {
|
||||
ctx.fillStyle = '#e8e2cf';
|
||||
ctx.strokeStyle = '#8d8475';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(c.x + egg.x, c.y + egg.y, 4.8, 6.2, 0.2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.fillStyle = THEME.white; ctx.strokeStyle = THEME.ink; ctx.lineWidth = 4;
|
||||
rect(ctx, c.x - 28, c.y - 28, 56, 56, true, true);
|
||||
ctx.fillStyle = THEME.green; ctx.font = '900 19px ui-monospace, monospace'; ctx.textAlign = 'center'; ctx.fillText('EGG', c.x, c.y - 4);
|
||||
const outlets = 1 + Math.max(0, Number(farm.extraEggOutlet) || 0);
|
||||
ctx.font = '900 11px ui-monospace, monospace'; ctx.fillStyle = THEME.ink; ctx.fillText(`L${farm.level} O${outlets}`, c.x, c.y + 13); ctx.fillText(`${farm.nextSpawn.toFixed(1)}s`, c.x, c.y + 26);
|
||||
ctx.fillStyle = 'rgba(255,255,255,.92)'; ctx.strokeStyle = THEME.ink; ctx.lineWidth = 2;
|
||||
rect(ctx, c.x + 8, c.y + 11, 30, 18, true, true);
|
||||
ctx.fillStyle = THEME.ink; ctx.font = '900 10px ui-monospace, monospace'; ctx.textAlign = 'center'; ctx.fillText(`O${outlets}`, c.x + 23, c.y + 23);
|
||||
ctx.fillStyle = '#d5e35a'; ctx.font = '900 9px ui-monospace, monospace'; ctx.fillText(`L${farm.level}`, c.x - 13, c.y - 15);
|
||||
const shutter = Math.max(0, Math.min(1, farm.shutterProgress || 0));
|
||||
if (shutter > 0) {
|
||||
const h = 56 * shutter;
|
||||
ctx.fillStyle = THEME.ink;
|
||||
rect(ctx, c.x - 28, c.y - 28, 56, h, true, false);
|
||||
ctx.strokeStyle = THEME.white; ctx.lineWidth = 2;
|
||||
for (let yy = c.y - 24; yy < c.y - 28 + h; yy += 8) { ctx.beginPath(); ctx.moveTo(c.x - 24, yy); ctx.lineTo(c.x + 24, yy); ctx.stroke(); }
|
||||
const h = 54 * shutter;
|
||||
ctx.fillStyle = 'rgba(10,12,15,.86)';
|
||||
rect(ctx, c.x - 27, c.y - 27, 54, h, true, false);
|
||||
ctx.strokeStyle = 'rgba(230,237,242,.64)'; ctx.lineWidth = 2;
|
||||
for (let yy = c.y - 23; yy < c.y - 27 + h; yy += 8) { ctx.beginPath(); ctx.moveTo(c.x - 23, yy); ctx.lineTo(c.x + 23, yy); ctx.stroke(); }
|
||||
if (shutter >= 1) { ctx.fillStyle = THEME.white; ctx.font = '900 12px ui-monospace, monospace'; ctx.fillText('SHUT', c.x, c.y + 4); }
|
||||
}
|
||||
drawDirtOverlay(ctx, c.x - 28, c.y - 28, 56, 56, farm);
|
||||
|
|
@ -952,11 +969,16 @@ function drawMixer(ctx, game, m = game.facilities.mixer) {
|
|||
ctx.fillText(`MIXER L${m.level}`, m.x + 14, m.y + 18);
|
||||
ctx.fillStyle = THEME.muted; ctx.font = '900 10px ui-monospace, monospace';
|
||||
ctx.fillText(`PAY ${yen(upgradedMixerPrice(game))}`, m.x + 14, m.y + 34);
|
||||
const drew = drawFacilityTexture(ctx, assets.mixerCompact.loaded ? assets.mixerCompact : assets.mixer, m, 2.05);
|
||||
if (!drew) {
|
||||
ctx.strokeStyle = THEME.green; ctx.lineWidth = 5;
|
||||
for (let i = 0; i < 3; i += 1) { ctx.beginPath(); ctx.arc(m.x + m.w / 2, m.y + m.h / 2 + 6, 11 + i * 8, 0, Math.PI * 1.5); ctx.stroke(); }
|
||||
}
|
||||
ctx.fillStyle = '#1a2028'; ctx.strokeStyle = '#87bfff'; ctx.lineWidth = 4;
|
||||
rect(ctx, m.x + 44, m.y + 34, 76, 50, true, true);
|
||||
ctx.strokeStyle = '#4aa3ff'; ctx.lineWidth = 3;
|
||||
ctx.beginPath(); ctx.arc(m.x + 82, m.y + 58, 15, 0, Math.PI * 2); ctx.stroke();
|
||||
ctx.beginPath(); ctx.moveTo(m.x + 82, m.y + 43); ctx.lineTo(m.x + 82, m.y + 73); ctx.stroke();
|
||||
ctx.beginPath(); ctx.moveTo(m.x + 69, m.y + 56); ctx.lineTo(m.x + 95, m.y + 60); ctx.stroke();
|
||||
ctx.fillStyle = '#59687a'; ctx.strokeStyle = '#0d1014';
|
||||
rect(ctx, m.x + 56, m.y + 24, 12, 10, true, true);
|
||||
rect(ctx, m.x + 96, m.y + 24, 12, 10, true, true);
|
||||
ctx.fillStyle = '#7ea9dc'; ctx.fillRect(m.x + 48, m.y + 42, 68, 5);
|
||||
drawTargetBadge(ctx, m.x + 14, m.y + m.h - 40, m.w - 28, 'SEND MALE', 'male', 'safe meat route');
|
||||
drawDirtOverlay(ctx, m.x, m.y, m.w, m.h, m);
|
||||
drawSelection(ctx, game, 'facility', m.id, m.x + m.w / 2, m.y + m.h / 2, m.w + 12, m.h + 12);
|
||||
|
|
@ -969,11 +991,23 @@ function drawTrash(ctx, game, t = game.facilities.trash) {
|
|||
drawFacilityDetails(ctx, t, THEME.wasteGreen);
|
||||
ctx.fillStyle = THEME.white; ctx.font = '900 14px ui-monospace, monospace'; ctx.textAlign = 'left';
|
||||
ctx.fillText(`SHREDDER L${t.level}`, t.x + 14, t.y + 18);
|
||||
const drew = drawFacilityTexture(ctx, assets.shredderCompact.loaded ? assets.shredderCompact : assets.shredder, t, 1.85);
|
||||
if (!drew) {
|
||||
ctx.fillStyle = '#0a0b0e';
|
||||
for (let i = 0; i < 7; i += 1) ctx.fillRect(t.x + 40 + i * 12, t.y + 42, 6, Math.max(20, t.h - 72));
|
||||
ctx.fillStyle = '#181d20'; ctx.strokeStyle = '#9fd473'; ctx.lineWidth = 4;
|
||||
rect(ctx, t.x + 38, t.y + 36, 92, 22, true, true);
|
||||
ctx.fillStyle = '#262d30'; ctx.strokeStyle = '#0d1014';
|
||||
rect(ctx, t.x + 46, t.y + 58, 76, 18, true, true);
|
||||
ctx.fillStyle = '#111418';
|
||||
for (let i = 0; i < 7; i += 1) {
|
||||
const tx = t.x + 51 + i * 10;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(tx, t.y + 58);
|
||||
ctx.lineTo(tx + 5, t.y + 68);
|
||||
ctx.lineTo(tx + 10, t.y + 58);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.fillStyle = '#6f7e8d';
|
||||
rect(ctx, t.x + 54, t.y + 27, 16, 9, true, true);
|
||||
rect(ctx, t.x + 98, t.y + 27, 16, 9, true, true);
|
||||
drawTargetBadge(ctx, t.x + 14, t.y + t.h - 42, t.w - 28, 'SEND POOP', 'poop', 'shredder');
|
||||
drawDirtOverlay(ctx, t.x, t.y, t.w, t.h, t);
|
||||
drawSelection(ctx, game, 'facility', t.id, t.x + t.w / 2, t.y + t.h / 2, t.w + 12, t.h + 12);
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel
|
|||
|
||||
function resolveExpansionRequest(request = 'right') {
|
||||
if (request && typeof request === 'object' && Number.isFinite(request.chunkX) && Number.isFinite(request.chunkY)) return request;
|
||||
const direction = ['right', 'up'].includes(request) ? request : 'right';
|
||||
const direction = ['right', 'up', 'down'].includes(request) ? request : 'right';
|
||||
return expansionLot(game, direction);
|
||||
}
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel
|
|||
}
|
||||
record(game);
|
||||
spendCash(game, cost);
|
||||
if (!game.gridExpansionPurchasesByDirection) game.gridExpansionPurchasesByDirection = { right: 0, up: 0 };
|
||||
if (!game.gridExpansionPurchasesByDirection) game.gridExpansionPurchasesByDirection = { right: 0, up: 0, down: 0 };
|
||||
const originY = game.gridChunkOriginY || 0;
|
||||
if (offer.chunkY < originY) {
|
||||
const chunksToAdd = originY - offer.chunkY;
|
||||
|
|
@ -93,9 +93,9 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel
|
|||
game.gridExpansionPurchasesByDirection[direction] = (game.gridExpansionPurchasesByDirection[direction] || 0) + 1;
|
||||
syncGridSizeToGame();
|
||||
const blockedRatio = blockedRatioForExpansionPurchase(game.gridExpansionPurchases, {
|
||||
base: BALANCE.map.blockedRatio ?? 0.03,
|
||||
increment: BALANCE.map.blockedExpansionIncrement ?? 0.005,
|
||||
max: BALANCE.map.blockedRatioMax ?? 0.10
|
||||
base: BALANCE.map.blockedRatio ?? 0.00,
|
||||
increment: BALANCE.map.blockedExpansionIncrement ?? 0.0075,
|
||||
max: BALANCE.map.blockedRatioMax ?? 0.12
|
||||
});
|
||||
const blocked = generateBlockedCellsInRect({
|
||||
seed: `${game.runSeed}:grid-expansion:${lot.chunkX},${lot.chunkY}:${game.gridExpansionPurchases}`,
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ export function debugGrantCard(game, cardOrId, options = {}) {
|
|||
.map(String));
|
||||
const selected = requestedKeys.size
|
||||
? targets.filter(t => requestedKeys.has(targetKey(t)))
|
||||
: targets.slice(0, 3);
|
||||
: targets.slice(0, 10);
|
||||
if (!selected.length) return { ok: false, reason: `Selected target is no longer valid for ${card.title}.` };
|
||||
for (const target of selected.slice(0, 3)) {
|
||||
game.blockedCells.delete(key(target.col, target.row));
|
||||
|
|
@ -129,7 +129,7 @@ export function debugGrantCard(game, cardOrId, options = {}) {
|
|||
sparkBurst(game, c.x, c.y, 8, THEME.warn);
|
||||
}
|
||||
refreshRoutingAfterEdit(game);
|
||||
return { ok: true, reason: `${card.title} removed ${Math.min(3, selected.length)} blocked cell(s).` };
|
||||
return { ok: true, reason: `${card.title} removed ${Math.min(10, selected.length)} blocked cell(s).` };
|
||||
}
|
||||
return { ok: false, reason: `${card.title} cannot be debug-granted.` };
|
||||
}
|
||||
|
|
@ -507,7 +507,7 @@ function cardDescription(game, card) {
|
|||
if (card.id === 'newMachine') return 'Cancel Used Machines. Future equipment returns to normal price, refund, and durability.';
|
||||
if (card.id === 'laborExploitation') return 'Repairman dispatch paperwork is streamlined. Repair rooms keep operating; one time only.';
|
||||
if (card.id === 'rescueLoan') return `At day-end bankruptcy check, gain ${yen(500)} if cash is below ${yen(0)}. Held: ${e.rescueLoanCharges || 0}.`;
|
||||
if (card.id === 'dynamite') return 'Choose any blocked tiles on the map and destroy up to 3. Empty-space clicks are ignored; press Esc to finish after at least one blast.';
|
||||
if (card.id === 'dynamite') return 'Choose any blocked tiles on the map and destroy up to 10. Empty-space clicks are ignored; press Esc to finish after at least one blast.';
|
||||
return card.description;
|
||||
}
|
||||
|
||||
|
|
@ -610,7 +610,7 @@ export function createCardSystem({ game, ui, onUpdatePanels }) {
|
|||
cardId: card.id,
|
||||
mode: card.target === 'blockedCell' ? 'blockedCell' : 'equipment',
|
||||
targetKeys: targets.map(targetKey),
|
||||
remaining: card.id === 'dynamite' ? Math.min(3, targets.length) : 1,
|
||||
remaining: card.id === 'dynamite' ? Math.min(10, targets.length) : 1,
|
||||
used: 0
|
||||
};
|
||||
ui.modal.classList.remove('visible', 'equipment-popover');
|
||||
|
|
@ -721,8 +721,10 @@ export function createCardSystem({ game, ui, onUpdatePanels }) {
|
|||
|
||||
function cardIconHtml(card) {
|
||||
const icon = cardIconFor(card);
|
||||
if (icon.type === 'image') return `<div class="card-icon image"><img src="${icon.src}" alt="${icon.alt || ''}" /></div>`;
|
||||
return `<div class="card-icon glyph" aria-hidden="true">${icon.text || '◆'}</div>`;
|
||||
if (icon.type === 'image') {
|
||||
return `<div class="card-badge-icon image"><img src="${icon.src}" alt="${icon.alt || ''}" /></div><div class="card-icon image" aria-hidden="true"><img src="${icon.src}" alt="" /></div>`;
|
||||
}
|
||||
return `<div class="card-badge-icon glyph" aria-hidden="true">${icon.text || '◆'}</div><div class="card-icon glyph" aria-hidden="true">${icon.text || '◆'}</div>`;
|
||||
}
|
||||
|
||||
function cardButton(card, index) {
|
||||
|
|
|
|||
|
|
@ -1,143 +0,0 @@
|
|||
import { TURN_SECONDS, POOP_RATE, CONTRACT_EVENT_CHANCE, CONTRACT_EVENT_FIRST_TURN } from '../core/config.js';
|
||||
import { getSpawnRange } from '../core/state.js';
|
||||
import { applyRevenue } from './economy.js';
|
||||
|
||||
const CONTRACT_TARGETS = [
|
||||
{
|
||||
target: 'female',
|
||||
title: 'Premium Female Shipment',
|
||||
route: 'Female -> Truck',
|
||||
note: 'Only female chicks are valid truck cargo this turn.'
|
||||
},
|
||||
{
|
||||
target: 'male',
|
||||
title: 'Male Export Trial',
|
||||
route: 'Male -> Truck',
|
||||
note: 'Male chicks are temporarily valid truck cargo. Male truck fines are suspended for valid male cargo.'
|
||||
},
|
||||
{
|
||||
target: 'poop',
|
||||
title: 'Contaminant Sample Export',
|
||||
route: 'Poop -> Truck',
|
||||
note: 'Poop is temporarily valid truck cargo. Shipped poop does not soil this contract shipment.'
|
||||
}
|
||||
];
|
||||
|
||||
export function factoryValue(game) {
|
||||
return [
|
||||
...game.eggFarms.map(f => f.price || 0),
|
||||
...game.scanners.map(s => s.price || 0),
|
||||
...Object.values(game.facilities).map(f => f.price || 0),
|
||||
...[...game.conveyorMeta.values()].map(m => m.price || 0)
|
||||
].reduce((a, b) => a + b, 0);
|
||||
}
|
||||
|
||||
export function estimatedChicksPerTurn(game, productionMultiplier = 1) {
|
||||
return game.eggFarms.reduce((sum, farm) => {
|
||||
const [min, max] = getSpawnRange(farm);
|
||||
const avg = Math.max(0.1, (min + max) / 2);
|
||||
return sum + (TURN_SECONDS / avg) * productionMultiplier;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
export function rollContractOffer(game) {
|
||||
// No irregular events during the first seven running turns.
|
||||
// Offers created after Turn 7 apply to Turn 8 or later.
|
||||
const nextTurn = game.turn + 1;
|
||||
if (nextTurn < CONTRACT_EVENT_FIRST_TURN) return null;
|
||||
if (Math.random() > CONTRACT_EVENT_CHANCE) return null;
|
||||
const template = CONTRACT_TARGETS[Math.floor(Math.random() * CONTRACT_TARGETS.length)];
|
||||
const productionMultiplier = 2;
|
||||
const poopMultiplier = 3;
|
||||
const estimate = estimatedChicksPerTurn(game, productionMultiplier);
|
||||
const targetAmount = Math.max(1, Math.ceil(estimate * 0.9));
|
||||
const rate = Math.round((0.08 + Math.random() * 0.08) * 100) / 100;
|
||||
const rewardByFactory = Math.floor(factoryValue(game) * rate);
|
||||
const rewardByCash = Math.floor(Math.max(0, game.cash) * 0.25);
|
||||
const reward = Math.max(rewardByFactory, rewardByCash);
|
||||
return {
|
||||
id: `contract-${game.turn}-${Date.now()}-${Math.floor(Math.random() * 9999)}`,
|
||||
...template,
|
||||
forced: true,
|
||||
productionMultiplier,
|
||||
poopMultiplier,
|
||||
targetAmount,
|
||||
estimatedOutput: Math.round(estimate),
|
||||
rewardRate: rate,
|
||||
reward,
|
||||
rewardByFactory,
|
||||
rewardByCash,
|
||||
createdTurn: game.turn,
|
||||
oneTurnOnly: true
|
||||
};
|
||||
}
|
||||
|
||||
export function activateAcceptedContract(game) {
|
||||
// v10.1: limited events are forced. If an event is shown during Build phase,
|
||||
// it automatically applies to the next turn; there is no accept/skip step.
|
||||
game.contractActive = game.contractOffer ? { ...game.contractOffer } : null;
|
||||
game.contractOffer = null;
|
||||
}
|
||||
|
||||
export function clearActiveContract(game) {
|
||||
game.contractActive = null;
|
||||
}
|
||||
|
||||
export function productionMultiplier(game) {
|
||||
return game.contractActive?.productionMultiplier || 1;
|
||||
}
|
||||
|
||||
export function currentPoopRate(game) {
|
||||
const feedCards = Math.max(0, Number(game?.cardEffects?.hatchingFeed) || 0);
|
||||
const feedMultiplier = Math.pow(1.03, feedCards);
|
||||
return Math.min(0.95, POOP_RATE * (game.contractActive?.poopMultiplier || 1) * feedMultiplier);
|
||||
}
|
||||
|
||||
export function truckTarget(game) {
|
||||
return game.contractActive?.target || 'female';
|
||||
}
|
||||
|
||||
export function targetTruckCount(stats, target) {
|
||||
if (target === 'male') return stats.truckMale || 0;
|
||||
if (target === 'poop') return stats.poopTruck || 0;
|
||||
return stats.truckFemale || 0;
|
||||
}
|
||||
|
||||
export function isTargetTruckCargo(game, sex) {
|
||||
return truckTarget(game) === sex;
|
||||
}
|
||||
|
||||
export function shouldFineMaleTruck(game) {
|
||||
return truckTarget(game) !== 'male';
|
||||
}
|
||||
|
||||
export function resolveContract(game) {
|
||||
const contract = game.contractActive;
|
||||
if (!contract) return null;
|
||||
const count = targetTruckCount(game.stats, contract.target);
|
||||
const success = count >= contract.targetAmount;
|
||||
const bonus = success ? contract.reward : 0;
|
||||
if (bonus > 0) {
|
||||
applyRevenue(game, bonus);
|
||||
game.stats.contractBonus = bonus;
|
||||
game.totals.contractBonus += bonus;
|
||||
game.totals.contractSuccess += 1;
|
||||
} else {
|
||||
game.stats.contractBonus = 0;
|
||||
game.totals.contractFailed += 1;
|
||||
}
|
||||
const result = {
|
||||
title: contract.title,
|
||||
target: contract.target,
|
||||
route: contract.route,
|
||||
targetAmount: contract.targetAmount,
|
||||
count,
|
||||
success,
|
||||
bonus,
|
||||
reward: contract.reward,
|
||||
productionMultiplier: contract.productionMultiplier,
|
||||
poopMultiplier: contract.poopMultiplier
|
||||
};
|
||||
game.stats.contractResult = result;
|
||||
return result;
|
||||
}
|
||||
1130
styles-v27.17.css
1130
styles-v27.17.css
File diff suppressed because it is too large
Load diff
1253
styles-v27.18.css
1253
styles-v27.18.css
File diff suppressed because it is too large
Load diff
1279
styles-v27.19.css
1279
styles-v27.19.css
File diff suppressed because it is too large
Load diff
48
styles.css
48
styles.css
|
|
@ -263,7 +263,7 @@ h1, h2, p { margin: 0; }
|
|||
/* v15 pre-build card draft */
|
||||
.card-choices {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(190px, 220px));
|
||||
grid-template-columns: repeat(auto-fit, minmax(190px, 220px));
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
|
|
@ -532,7 +532,7 @@ body { font-size: 18px; }
|
|||
from { box-shadow: inset 0 0 0 3px var(--danger), 4px 4px 0 rgba(16,32,21,.18); filter: brightness(1); }
|
||||
to { box-shadow: inset 0 0 0 5px #fff0d1, 4px 4px 0 rgba(16,32,21,.20); filter: brightness(1.08); }
|
||||
}
|
||||
.card-choices { grid-template-columns: repeat(auto-fill, minmax(230px, 270px)); gap: 14px; }
|
||||
.card-choices { grid-template-columns: repeat(auto-fit, minmax(230px, 270px)); gap: 14px; }
|
||||
.card-choice { min-height: 164px; padding: 15px; }
|
||||
.card-choice strong { font-size: 18px; }
|
||||
.card-choice span { font-size: 11px; }
|
||||
|
|
@ -613,9 +613,10 @@ body { font-size: 20px; }
|
|||
display: none;
|
||||
position: absolute;
|
||||
z-index: 90;
|
||||
right: 0;
|
||||
top: calc(100% + 8px);
|
||||
width: min(320px, calc(100vw - 40px));
|
||||
right: calc(100% + 10px);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: min(320px, 34vw);
|
||||
max-height: none;
|
||||
opacity: 1;
|
||||
overflow: visible;
|
||||
|
|
@ -636,11 +637,11 @@ body { font-size: 20px; }
|
|||
.large-tools .tool-button .tool-flavor::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 18px;
|
||||
top: -10px;
|
||||
right: -10px;
|
||||
top: calc(50% - 8px);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-left: 3px solid var(--line);
|
||||
border-right: 3px solid var(--line);
|
||||
border-top: 3px solid var(--line);
|
||||
background: rgba(255,255,255,.98);
|
||||
transform: rotate(45deg);
|
||||
|
|
@ -1201,6 +1202,35 @@ body {
|
|||
.facility-action:disabled,
|
||||
.card-target-button:disabled { opacity: .42; filter: grayscale(.85); box-shadow: 3px 3px 0 rgba(0,0,0,.45); }
|
||||
|
||||
/* Keep the translucent watermark, but also show a readable foreground icon badge. */
|
||||
.card-badge-icon {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
left: 14px;
|
||||
top: 14px;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 3px solid rgba(7,8,10,.95);
|
||||
background: rgba(16,19,25,.88);
|
||||
box-shadow: inset 0 0 0 1px rgba(255,184,39,.18), 3px 3px 0 rgba(0,0,0,.48);
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
.card-badge-icon img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
object-fit: contain;
|
||||
image-rendering: pixelated;
|
||||
filter: drop-shadow(1px 1px 0 rgba(0,0,0,.7));
|
||||
}
|
||||
.card-badge-icon.glyph {
|
||||
color: #ffcf68;
|
||||
font-size: 22px;
|
||||
line-height: 1;
|
||||
text-shadow: 2px 2px 0 rgba(0,0,0,.55);
|
||||
}
|
||||
|
||||
/* Icons are now translucent background art rather than separate foreground badges. */
|
||||
.card-choice {
|
||||
display: block;
|
||||
|
|
@ -1209,7 +1239,7 @@ body {
|
|||
isolation: isolate;
|
||||
}
|
||||
.card-choice::before { z-index: 1; }
|
||||
.card-copy { position: relative; z-index: 3; padding-right: 6px; }
|
||||
.card-copy { position: relative; z-index: 3; padding-right: 6px; padding-left: 54px; }
|
||||
.card-icon {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue