80 lines
3.8 KiB
JavaScript
Executable File
80 lines
3.8 KiB
JavaScript
Executable File
// Original deterministic assets, generated without libraries or downloaded art.
|
|
import { deflateSync } from 'node:zlib';
|
|
import * as fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
|
|
function crc32(data) {
|
|
let crc = 0xffffffff;
|
|
for (const byte of data) {
|
|
crc ^= byte;
|
|
for (let bit = 0; bit < 8; bit++) crc = (crc >>> 1) ^ ((crc & 1) ? 0xedb88320 : 0);
|
|
}
|
|
return (crc ^ 0xffffffff) >>> 0;
|
|
}
|
|
function chunk(type, data) {
|
|
const body = Buffer.concat([Buffer.from(type), data]);
|
|
const size = Buffer.alloc(4), crc = Buffer.alloc(4);
|
|
size.writeUInt32BE(data.length); crc.writeUInt32BE(crc32(body));
|
|
return Buffer.concat([size, body, crc]);
|
|
}
|
|
function png(size, pixel) {
|
|
const rows = Buffer.alloc(size * (size * 4 + 1));
|
|
for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) {
|
|
const color = pixel(x, y), offset = y * (size * 4 + 1) + 1 + x * 4;
|
|
rows.set(color, offset);
|
|
}
|
|
const header = Buffer.alloc(13);
|
|
header.writeUInt32BE(size); header.writeUInt32BE(size, 4); header[8] = 8; header[9] = 6;
|
|
return Buffer.concat([Buffer.from([137,80,78,71,13,10,26,10]), chunk('IHDR', header),
|
|
chunk('IDAT', deflateSync(rows)), chunk('IEND', Buffer.alloc(0))]);
|
|
}
|
|
const icons = {
|
|
knife: (x,y) => (Math.abs(x + y - 28) < 3 && x > 9 && x < 24) ? [180,185,173,255]
|
|
: (Math.abs(x + y - 28) < 2 && x >= 23) ? [119,86,57,255] : null,
|
|
axe: (x,y) => Math.abs(x-y) < 2 && x > 7 && x < 27 ? [139,103,65,255]
|
|
: x > 5 && x < 20 && y > 4 && y < 14 && x+y < 29 ? [157,164,151,255] : null,
|
|
pick: (x,y) => Math.abs(x-y) < 2 && x > 10 && x < 28 ? [139,103,65,255]
|
|
: y > 5 && y < 10 && x > 4 && x < 25 ? [157,164,151,255] : null,
|
|
spear: (x,y) => Math.abs(x+y-31) < 2 && x > 5 && x < 27 ? [157,124,80,255]
|
|
: x > 22 && y < 10 && x-y > 18 ? [185,188,177,255] : null,
|
|
};
|
|
|
|
export async function generateAssets(destination, source) {
|
|
const textures = path.join(destination, 'mods/hearth/textures');
|
|
await fs.mkdir(textures, {recursive:true});
|
|
const files = ['content.lua','encounters.lua','runtime.lua'];
|
|
const colors = new Set(['c7aa83','947853']);
|
|
for (const file of files) {
|
|
const text = await fs.readFile(path.join(source, 'mods/hearth', file), 'utf8');
|
|
for (const match of text.matchAll(/#[0-9a-fA-F]{6}\b/g)) colors.add(match[0].slice(1).toLowerCase());
|
|
}
|
|
for (const color of colors) {
|
|
const rgb = [0,2,4].map(i=>parseInt(color.slice(i,i+2),16));
|
|
const image = png(32, (x,y)=> {
|
|
const noise = ((x*73+y*151+x*y*7) % 19)-9;
|
|
const grain = (y % 8 === 0) ? -12 : (x % 13 === 0 ? 5 : 0);
|
|
return [...rgb.map(c=>Math.max(0,Math.min(255,c+noise+grain))),255];
|
|
});
|
|
await fs.writeFile(path.join(textures, `hearth_${color}.png`), image);
|
|
}
|
|
for (const [name, shape] of Object.entries(icons)) {
|
|
await fs.writeFile(path.join(textures, `hearth_icon_${name}.png`), png(32,(x,y)=>shape(x,y)||[0,0,0,0]));
|
|
}
|
|
// A low-poly original player silhouette; stable engine mesh instead of a billboard.
|
|
const models = path.join(destination, 'mods/hearth/models');
|
|
await fs.mkdir(models, {recursive:true});
|
|
let vertices = 0;
|
|
const obj = ['# Original Last Hearth block character', 'vt 0 0','vt 1 0','vt 1 1','vt 0 1'];
|
|
function box(x0,y0,z0,x1,y1,z1) {
|
|
for(const v of [[x0,y0,z0],[x1,y0,z0],[x1,y1,z0],[x0,y1,z0],
|
|
[x0,y0,z1],[x1,y0,z1],[x1,y1,z1],[x0,y1,z1]]) obj.push(`v ${v.join(' ')}`);
|
|
for(const f of [[1,4,3,2],[5,6,7,8],[1,2,6,5],[4,8,7,3],[1,5,8,4],[2,3,7,6]])
|
|
obj.push(`f ${f.map((n,i)=>`${vertices+n}/${i+1}`).join(' ')}`);
|
|
vertices += 8;
|
|
}
|
|
box(-2.3,8,-1.3,2.3,14,1.3); box(-2,14,-2,2,18,2);
|
|
box(-4,8,-1,-2.3,14,1); box(2.3,8,-1,4,14,1);
|
|
box(-2.2,0,-1.2,-0.2,8,1.2); box(0.2,0,-1.2,2.2,8,1.2);
|
|
await fs.writeFile(path.join(models,'hearth_person.obj'), obj.join('\n')+'\n');
|
|
}
|