@jjlmoya/utils-games-development 1.56.0 → 1.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-games-development",
3
- "version": "1.56.0",
3
+ "version": "1.57.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
package/src/entries.ts CHANGED
@@ -23,7 +23,8 @@ import { spriteSheetPacker } from './tool/spriteSheetPacker/entry';
23
23
  import { audioLoopPointFinder } from './tool/audioLoopPointFinder/entry';
24
24
  import { saveFileEditor } from './tool/saveFileEditor/entry';
25
25
  import { localizationSanitizer } from './tool/localizationSanitizer/entry';
26
+ import { steamBbcodeTranslator } from './tool/steamBbcodeTranslator/entry';
26
27
  import { retroSfxGenerator } from './tool/retroSfxGenerator/entry';
27
28
  import { pixelArtPaletteSwapper } from './tool/pixelArtPaletteSwapper/entry';
28
29
 
29
- export const ALL_ENTRIES = [steamCapsuleGenerator, itchioGameTester, spriteSheetPacker, audioLoopPointFinder, saveFileEditor, localizationSanitizer, retroSfxGenerator, pixelArtPaletteSwapper];
30
+ export const ALL_ENTRIES = [steamCapsuleGenerator, itchioGameTester, spriteSheetPacker, audioLoopPointFinder, saveFileEditor, localizationSanitizer, steamBbcodeTranslator, retroSfxGenerator, pixelArtPaletteSwapper];
@@ -0,0 +1,27 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { ALL_ENTRIES } from '../entries';
5
+ import { ALL_TOOLS } from '../tools';
6
+
7
+ describe('runtime registry', () => {
8
+ it('keeps every runtime tool represented in the data registry', () => {
9
+ const toolIds = ALL_TOOLS.map(({ entry }) => entry.id).sort();
10
+ const entryIds = ALL_ENTRIES.map(({ id }) => id).sort();
11
+
12
+ expect(entryIds).toEqual(toolIds);
13
+ });
14
+
15
+ it('keeps every runtime directory represented in the data registry', () => {
16
+ const runtimeIds = readdirSync(join(process.cwd(), 'src', 'tool'), { withFileTypes: true })
17
+ .filter((directory) => directory.isDirectory())
18
+ .map((directory) => join(process.cwd(), 'src', 'tool', directory.name))
19
+ .filter((directory) => existsSync(join(directory, 'index.ts')) && existsSync(join(directory, 'entry.ts')))
20
+ .map((directory) => readFileSync(join(directory, 'entry.ts'), 'utf8').match(/\bid\s*:\s*['"]([^'"]+)['"]/)?.[1])
21
+ .filter((id): id is string => Boolean(id))
22
+ .sort();
23
+
24
+ const entryIds = ALL_ENTRIES.map(({ id }) => id).sort();
25
+ expect(entryIds).toEqual(runtimeIds);
26
+ });
27
+ });
@@ -61,7 +61,10 @@ function queryAppElements(root: HTMLElement): AppEls | null {
61
61
 
62
62
  function renderSwatches(root: HTMLElement): void {
63
63
  root.querySelectorAll<HTMLElement>('[data-swatch-palette]').forEach((strip) => {
64
- const palette = PRESET_PALETTES[strip.dataset.swatchPalette ?? ''] ?? [];
64
+ const id = strip.dataset.swatchPalette;
65
+ const palette = id && id in PRESET_PALETTES
66
+ ? PRESET_PALETTES[id as keyof typeof PRESET_PALETTES]
67
+ : [];
65
68
  strip.replaceChildren(...palette.map((c) => {
66
69
  const s = document.createElement('span');
67
70
  s.style.backgroundColor = c.hex;
@@ -151,8 +154,10 @@ function setupFileHandler(els: AppEls, onLoaded: (src: SourceImageState) => void
151
154
  function setupPresetButtons(els: AppEls, onApply: (p: PaletteColor[], id: string) => void): void {
152
155
  els.root.querySelectorAll<HTMLButtonElement>('[data-palette-id]').forEach((b) => b.addEventListener('click', () => {
153
156
  const id = b.dataset.paletteId ?? 'gameBoy';
154
- els.customPaletteInput.value = PRESET_PALETTES[id].map((c) => c.hex).join(', ');
155
- onApply(PRESET_PALETTES[id], id);
157
+ const palette = PRESET_PALETTES[id as keyof typeof PRESET_PALETTES];
158
+ if (!palette) return;
159
+ els.customPaletteInput.value = palette.map((c) => c.hex).join(', ');
160
+ onApply(palette, id);
156
161
  }));
157
162
  }
158
163
 
@@ -8,13 +8,21 @@ export interface PaletteColor extends RGBColor {
8
8
  hex: string;
9
9
  }
10
10
 
11
+ interface PresetPalettes {
12
+ gameBoy: PaletteColor[];
13
+ nes: PaletteColor[];
14
+ pico8: PaletteColor[];
15
+ commodore64: PaletteColor[];
16
+ dawnBringer16: PaletteColor[];
17
+ }
18
+
11
19
  export interface QuantizationResult {
12
20
  data: Uint8ClampedArray;
13
21
  sourceColors: number;
14
22
  mappedColors: number;
15
23
  }
16
24
 
17
- export const PRESET_PALETTES: Record<string, PaletteColor[]> = {
25
+ export const PRESET_PALETTES: PresetPalettes = {
18
26
  gameBoy: ['#0f380f', '#306230', '#8bac0f', '#9bbc0f'].map(createPresetPaletteColor),
19
27
  nes: [
20
28
  '#000000', '#fcfcfc', '#f8f8f8', '#b8b8b8', '#7c7c7c', '#a80000', '#e45c10', '#f8b800',
@@ -94,11 +102,17 @@ export function findNearestColor(source: RGBColor, palette: PaletteColor[]): Pal
94
102
  return null;
95
103
  }
96
104
 
97
- let nearest = palette[0];
105
+ const nearestColor = palette[0];
106
+ if (!nearestColor) {
107
+ return null;
108
+ }
109
+
110
+ let nearest = nearestColor;
98
111
  let distance = colorDistanceSquared(source, nearest);
99
112
 
100
113
  for (let index = 1; index < palette.length; index += 1) {
101
114
  const candidate = palette[index];
115
+ if (!candidate) continue;
102
116
  const candidateDistance = colorDistanceSquared(source, candidate);
103
117
  if (candidateDistance < distance) {
104
118
  nearest = candidate;
@@ -125,16 +139,32 @@ interface QuantizePixelContext {
125
139
  mappedColors: Set<string>;
126
140
  }
127
141
 
142
+ interface PixelData {
143
+ alpha: number;
144
+ color: RGBColor;
145
+ }
146
+
147
+ function readPixel(data: Uint8ClampedArray, index: number): PixelData | null {
148
+ const alpha = data[index + 3];
149
+ const red = data[index];
150
+ const green = data[index + 1];
151
+ const blue = data[index + 2];
152
+ if (alpha === undefined) return null;
153
+ if (red === undefined) return null;
154
+ if (green === undefined) return null;
155
+ if (blue === undefined) return null;
156
+ return { alpha, color: { r: red, g: green, b: blue } };
157
+ }
158
+
128
159
  function processPixel(ctx: QuantizePixelContext): void {
129
160
  const { data, index, palette, preserveTransparency, sourceColors, mappedColors } = ctx;
130
- const alpha = data[index + 3];
131
- if (alpha === 0 && preserveTransparency) return;
161
+ const pixel = readPixel(data, index);
162
+ if (!pixel || (pixel.alpha === 0 && preserveTransparency)) return;
132
163
 
133
- const sourceColor = { r: data[index], g: data[index + 1], b: data[index + 2] };
134
- const mapped = findNearestColor(sourceColor, palette);
164
+ const mapped = findNearestColor(pixel.color, palette);
135
165
  if (!mapped) return;
136
166
 
137
- sourceColors.add(rgbToHex(sourceColor));
167
+ sourceColors.add(rgbToHex(pixel.color));
138
168
  mappedColors.add(mapped.hex);
139
169
  data[index] = mapped.r;
140
170
  data[index + 1] = mapped.g;