mirror of
https://github.com/isledecomp/isle.pizza.git
synced 2026-02-28 05:47:39 +00:00
Some checks are pending
Build / build (push) Waiting to run
* WIP * stuff * WIP: Interactive 3D score cube for save editor * Conditionally render ScoreCube to properly clean up WebGL canvas Only mount the ScoreCube component when on the save-editor page. This ensures onDestroy is called when navigating away, properly disposing of the WebGL renderer and removing the canvas from DOM. * Refactor: Consolidate formats and genericize WDB rendering - Move parsing/serialization code to src/core/formats/: - BinaryReader.js, BinaryWriter.js (shared utilities) - SaveGameParser.js, SaveGameSerializer.js - PlayersParser.js, PlayersSerializer.js - Create formats/index.js as barrel export - Extract generic WdbModelRenderer from ScoreCubeRenderer: - WdbModelRenderer handles D3DRM geometry and paletted textures - ScoreCubeRenderer extends it with score-specific logic - Prepares for rendering other WDB models in the future - Keep savegame/constants.js for domain-specific constants - savegame/index.js remains as high-level API facade * Save editor UI improvements - Make save slot cards fixed width (85px) to prevent resizing with name length - Make save slot cards more compact (smaller icons, padding, font) - Remove Act selection from Character section - Remove box-shadow from selected character to fix collapsed section bleed * Improve score cube lighting to match in-game appearance - Use flat, even lighting (high ambient + soft front light) - Remove harsh directional shadows from edges - Adjust camera position slightly for better framing * Add spacing below score cube canvas * Add spinning loader while loading score cube * Update three.js to 0.182.0 and fix npm audit issues - Update three.js from 0.170.0 to 0.182.0 - Fix npm audit vulnerabilities (devalue, lodash, svelte) - Remaining vulns are in dev dependencies (vite, workbox-cli) * Fix score cube overflow on mobile Add max-width constraints to prevent the score cube from expanding its container on narrow viewports while preserving its natural 200x200 size on desktop. * Add save slot carousel and improve empty states - Add reusable Carousel component with arrow navigation, drag-to-scroll, and click-to-scroll-into-view functionality - Replace static save slot list with horizontal carousel - Add empty state with image when no save files exist - Add prompt state when saves exist but none is selected - Reset selected slot when entering Save Editor page * Add February 2026 changelog entry for Save Editor * Add missing January 2026 changelog entries for Safari and mobile fixes * Remove unused mission images * Refactor opfs.js to reduce code duplication - Consolidate getFileHandle to use getOpfsRoot internally - Add writeTextFile helper that uses writeBinaryFile - Extract showToast helper for toast notifications - Simplify saveConfig to use writeTextFile instead of duplicate worker - Simplify fileExists and readBinaryFile to use getFileHandle * Remove unused ScoreColorButton component * Remove unused UI components
85 lines
3.3 KiB
JavaScript
85 lines
3.3 KiB
JavaScript
import { WdbModelRenderer } from './WdbModelRenderer.js';
|
|
|
|
/**
|
|
* Specialized renderer for the LEGO Island score cube
|
|
* Extends WdbModelRenderer with score-specific functionality
|
|
*/
|
|
export class ScoreCubeRenderer extends WdbModelRenderer {
|
|
// Score grid layout constants (from score.cpp)
|
|
static AREA_Y_OFFSETS = [0x2b, 0x57, 0x80, 0xab, 0xd6]; // per actor row
|
|
static AREA_HEIGHTS = [0x2a, 0x27, 0x29, 0x29, 0x2a];
|
|
static AREA_X_OFFSETS = [0x2f, 0x56, 0x81, 0xaa, 0xd4]; // per activity column
|
|
static AREA_WIDTHS = [0x25, 0x29, 0x27, 0x28, 0x28];
|
|
static COLOR_INDICES = [0x11, 0x0f, 0x08, 0x05]; // grey, yellow, blue, red
|
|
|
|
/**
|
|
* Update score colors on texture
|
|
* Score layout on cube (left to right, top to bottom):
|
|
* - Activities (columns): carRace, jetskiRace, pizza, towTrack, ambulance (0-4)
|
|
* - Actors (rows): pepper, mama, papa, nick, laura (0-4)
|
|
* @param {Array<Array<number>>} scores - 2D array [actor][activity] with values 0-3
|
|
*/
|
|
updateScores(scores) {
|
|
if (!this.textureCanvas || !this.baseImageData || !this.palette) return;
|
|
|
|
const ctx = this.textureCanvas.getContext('2d');
|
|
ctx.putImageData(this.baseImageData, 0, 0);
|
|
|
|
for (let actor = 0; actor < 5; actor++) {
|
|
for (let activity = 0; activity < 5; activity++) {
|
|
const score = scores?.[actor]?.[activity] ?? 0;
|
|
const clampedScore = Math.max(0, Math.min(3, score));
|
|
const colorIdx = ScoreCubeRenderer.COLOR_INDICES[clampedScore];
|
|
const color = this.palette[colorIdx];
|
|
|
|
if (color) {
|
|
ctx.fillStyle = `rgb(${color.r}, ${color.g}, ${color.b})`;
|
|
ctx.fillRect(
|
|
ScoreCubeRenderer.AREA_X_OFFSETS[activity],
|
|
ScoreCubeRenderer.AREA_Y_OFFSETS[actor],
|
|
ScoreCubeRenderer.AREA_WIDTHS[activity],
|
|
ScoreCubeRenderer.AREA_HEIGHTS[actor]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.texture) {
|
|
this.texture.needsUpdate = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Raycast to find clicked score cell
|
|
* @param {MouseEvent} event - Click event
|
|
* @returns {{ actor: number, activity: number } | null}
|
|
*/
|
|
raycast(event) {
|
|
const hit = this.raycastUV(event);
|
|
if (!hit) return null;
|
|
return this.uvToScoreCell(hit.x, hit.y);
|
|
}
|
|
|
|
/**
|
|
* Convert texture pixel coordinates to score cell
|
|
* @param {number} x - X coordinate (0-256)
|
|
* @param {number} y - Y coordinate (0-256)
|
|
* @returns {{ actor: number, activity: number } | null}
|
|
*/
|
|
uvToScoreCell(x, y) {
|
|
for (let activity = 0; activity < 5; activity++) {
|
|
for (let actor = 0; actor < 5; actor++) {
|
|
if (
|
|
x >= ScoreCubeRenderer.AREA_X_OFFSETS[activity] &&
|
|
x < ScoreCubeRenderer.AREA_X_OFFSETS[activity] + ScoreCubeRenderer.AREA_WIDTHS[activity] &&
|
|
y >= ScoreCubeRenderer.AREA_Y_OFFSETS[actor] &&
|
|
y < ScoreCubeRenderer.AREA_Y_OFFSETS[actor] + ScoreCubeRenderer.AREA_HEIGHTS[actor]
|
|
) {
|
|
return { actor, activity };
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|