Honey Village: six model builds + hub frontend, Dockerized for Dokploy
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
import { mkdir, writeFile } from 'node:fs/promises';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
const root = resolve(import.meta.dirname, '..');
|
||||
const sourceDir = resolve(root, 'public/maps/source');
|
||||
await mkdir(sourceDir, { recursive: true });
|
||||
|
||||
const maps = [
|
||||
['HouseScene', '小满小屋', 12, 8, 4, [
|
||||
['spawn', 96, 88, { id: 'default' }], ['door', 96, 112, { target: 'PlazaScene', spawn: 'house' }],
|
||||
['interact', 42, 34, { id: 'calendar', label: '床头日历' }], ['interact', 64, 42, { id: 'bed', label: '床' }],
|
||||
]],
|
||||
['PlazaScene', '村广场', 30, 20, 0, [
|
||||
['spawn', 240, 260, { id: 'default' }], ['spawn', 240, 280, { id: 'house' }], ['spawn', 32, 160, { id: 'bakery' }], ['spawn', 448, 160, { id: 'shop' }], ['spawn', 240, 32, { id: 'clinic' }], ['spawn', 32, 48, { id: 'lake' }], ['spawn', 448, 48, { id: 'field' }], ['spawn', 32, 280, { id: 'well' }], ['spawn', 448, 280, { id: 'mist' }],
|
||||
['door', 240, 304, { target: 'HouseScene', spawn: 'default' }], ['door', 0, 160, { target: 'BakeryScene', spawn: 'default' }], ['door', 464, 160, { target: 'ShopScene', spawn: 'default' }], ['door', 240, 0, { target: 'ClinicScene', spawn: 'default' }], ['door', 0, 48, { target: 'LakeScene', spawn: 'default' }], ['door', 464, 48, { target: 'FieldScene', spawn: 'default' }], ['door', 0, 280, { target: 'WellScene', spawn: 'default' }], ['door', 464, 280, { target: 'MistScene', spawn: 'default' }],
|
||||
['npc', 240, 132, { id: 'afu', label: '阿福' }], ['npc', 180, 162, { id: 'dabao', label: '大宝' }],
|
||||
['npc', 232, 76, { id: 'birds', label: '双子鸟' }], ['npc', 124, 210, { id: 'extra1', label: '村民' }], ['npc', 352, 224, { id: 'extra2', label: '村民' }],
|
||||
['interact', 240, 146, { id: 'board', label: '公告板' }], ['interact', 92, 148, { id: 'mail', label: '散落信件' }], ['interact', 200, 184, { id: 'flower1', label: '花坛' }], ['interact', 240, 200, { id: 'flower2', label: '花坛' }], ['interact', 280, 184, { id: 'flower3', label: '花坛' }],
|
||||
['interact', 160, 100, { id: 'ribbon1', label: '彩带架' }], ['interact', 224, 92, { id: 'ribbon2', label: '彩带架' }], ['interact', 288, 92, { id: 'ribbon3', label: '彩带架' }], ['interact', 352, 100, { id: 'ribbon4', label: '彩带架' }], ['interact', 240, 224, { id: 'festival', label: '庆典场地' }],
|
||||
]],
|
||||
['BakeryScene', '面包房', 15, 10, 3, [
|
||||
['spawn', 120, 128, { id: 'default' }], ['door', 120, 144, { target: 'PlazaScene', spawn: 'bakery' }], ['npc', 120, 72, { id: 'yuanyuan', label: '圆圆' }], ['interact', 48, 46, { id: 'recipe', label: '配方纸' }], ['interact', 184, 50, { id: 'oven', label: '烤炉' }],
|
||||
]],
|
||||
['ShopScene', '杂货店', 15, 10, 3, [
|
||||
['spawn', 120, 128, { id: 'default' }], ['door', 120, 144, { target: 'PlazaScene', spawn: 'shop' }], ['npc', 120, 72, { id: 'mimi', label: '咪咪' }], ['interact', 48, 52, { id: 'shelf', label: '货架' }],
|
||||
]],
|
||||
['ClinicScene', '诊所', 12, 8, 5, [
|
||||
['spawn', 96, 96, { id: 'default' }], ['door', 96, 112, { target: 'PlazaScene', spawn: 'clinic' }], ['npc', 96, 50, { id: 'doctor', label: '芙医生' }], ['interact', 48, 42, { id: 'form', label: '桌上表格' }],
|
||||
]],
|
||||
['LakeScene', '湖畔', 24, 16, 2, [
|
||||
['spawn', 368, 128, { id: 'default' }], ['door', 368, 128, { target: 'PlazaScene', spawn: 'lake' }], ['npc', 142, 132, { id: 'turtle', label: '龟爷爷' }], ['npc', 284, 68, { id: 'extra4', label: '村民' }], ['interact', 190, 148, { id: 'picnic', label: '野餐布' }], ['interact', 110, 68, { id: 'lake', label: '湖面' }],
|
||||
]],
|
||||
['FieldScene', '花田', 20, 14, 0, [
|
||||
['spawn', 16, 112, { id: 'default' }], ['door', 0, 112, { target: 'PlazaScene', spawn: 'field' }], ['npc', 244, 148, { id: 'extra3', label: '村民' }],
|
||||
['interact', 80, 72, { id: 'berry1', label: '莓果丛' }], ['interact', 128, 56, { id: 'berry2', label: '莓果丛' }], ['interact', 176, 88, { id: 'berry3', label: '莓果丛' }], ['interact', 224, 64, { id: 'berry4', label: '莓果丛' }], ['interact', 272, 96, { id: 'berry5', label: '莓果丛' }],
|
||||
]],
|
||||
['WellScene', '古井空地', 14, 10, 0, [
|
||||
['spawn', 208, 80, { id: 'default' }], ['door', 208, 80, { target: 'PlazaScene', spawn: 'well' }], ['npc', 46, 116, { id: 'extra5', label: '村民' }], ['interact', 104, 72, { id: 'well', label: '古井' }],
|
||||
]],
|
||||
['MistScene', '雾墙边界', 20, 8, 0, [
|
||||
['spawn', 16, 64, { id: 'default' }], ['door', 0, 64, { target: 'PlazaScene', spawn: 'mist' }], ['interact', 288, 64, { id: 'mist', label: '雾墙' }],
|
||||
]],
|
||||
['WhiteScene', '白色层', 30, 8, 5, [
|
||||
['spawn', 24, 64, { id: 'default' }], ['interact', 400, 64, { id: 'record', label: '病历' }],
|
||||
]],
|
||||
];
|
||||
|
||||
function createTileData(key, width, height, ground) {
|
||||
const data = Array(width * height).fill(ground + 1);
|
||||
const set = (x, y, gid) => { if (x >= 0 && y >= 0 && x < width && y < height) data[y * width + x] = gid; };
|
||||
const rect = (x, y, w, h, gid) => { for (let yy = y; yy < y + h; yy += 1) for (let xx = x; xx < x + w; xx += 1) set(xx, yy, gid); };
|
||||
const building = (x, y, w, h) => {
|
||||
rect(x, y, w, 2, 9); rect(x, y + 2, w, h - 2, 4);
|
||||
set(x + Math.floor(w / 2), y + h - 1, 10);
|
||||
for (let xx = x + 1; xx < x + w - 1; xx += 3) set(xx, y + 3, 15);
|
||||
};
|
||||
|
||||
if (key === 'PlazaScene') {
|
||||
rect(13, 0, 4, height, 2); rect(0, 8, width, 4, 2);
|
||||
building(2, 1, 7, 6); building(21, 1, 7, 6); building(2, 14, 7, 5); building(21, 14, 7, 5);
|
||||
rect(10, 2, 3, 3, 12); set(15, 8, 11);
|
||||
set(5, 9, 17);
|
||||
[[12, 11], [13, 12], [14, 11], [16, 11], [17, 12], [18, 11]].forEach(([x, y]) => set(x, y, 8));
|
||||
[[1, 3], [9, 6], [19, 5], [28, 3], [4, 12], [25, 12], [10, 16], [19, 16]].forEach(([x, y]) => set(x, y, 13));
|
||||
} else if (key === 'HouseScene') {
|
||||
rect(0, 0, width, height, 4); rect(0, 0, width, 1, 5); rect(0, height - 1, width, 1, 5);
|
||||
rect(2, 2, 3, 2, 5); set(8, 2, 11); rect(6, 5, 2, 1, 8); set(6, 7, 10);
|
||||
} else if (key === 'BakeryScene') {
|
||||
rect(0, 0, width, height, 4); rect(1, 1, width - 2, 2, 5); set(11, 2, 13); rect(2, 5, 11, 1, 5); set(7, 9, 10);
|
||||
} else if (key === 'ShopScene') {
|
||||
rect(0, 0, width, height, 4); for (let x = 1; x < width - 1; x += 3) rect(x, 1, 2, 4, 5); set(7, 9, 10);
|
||||
for (let y = 2; y <= 4; y += 1) for (const x of [2, 5, 8]) set(x, y, 7);
|
||||
} else if (key === 'ClinicScene') {
|
||||
rect(0, 0, width, height, 6); rect(0, 0, width, 1, 16); rect(2, 2, 5, 2, 5); set(6, 7, 10); set(9, 2, 15);
|
||||
} else if (key === 'LakeScene') {
|
||||
rect(0, 0, 10, height, 3); rect(10, 0, width - 10, height, 1); rect(10, 7, width - 10, 3, 2);
|
||||
rect(11, 9, 3, 2, 5); rect(14, 9, 3, 2, 8); for (let y = 2; y < height - 2; y += 4) set(20, y, 12);
|
||||
} else if (key === 'FieldScene') {
|
||||
rect(0, 6, width, 3, 2);
|
||||
for (let y = 1; y < height - 1; y += 3) for (let x = 2; x < width - 1; x += 3) set(x, y, (x + y) % 2 ? 7 : 8);
|
||||
} else if (key === 'WellScene') {
|
||||
rect(3, 2, 8, 6, 2); rect(5, 3, 4, 4, 14); set(13, 5, 10);
|
||||
} else if (key === 'MistScene') {
|
||||
rect(0, 3, width - 3, 3, 2); rect(width - 4, 0, 4, height, 15);
|
||||
} else if (key === 'WhiteScene') {
|
||||
rect(0, 0, width, height, 6); rect(0, 0, width, 1, 16); rect(0, height - 1, width, 1, 16); rect(19, 1, 1, height - 2, 16); set(25, 4, 5);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
const collisionSpecs = {
|
||||
PlazaScene: [[32, 16, 112, 96], [336, 16, 112, 96], [32, 224, 112, 76], [336, 224, 112, 76], [160, 32, 48, 48], [232, 128, 32, 24]],
|
||||
BakeryScene: [[16, 16, 208, 32], [176, 32, 32, 30], [32, 80, 176, 12]],
|
||||
ShopScene: [[16, 16, 32, 64], [64, 16, 32, 64], [112, 16, 32, 64], [160, 16, 32, 64], [208, 16, 16, 64]],
|
||||
ClinicScene: [[32, 32, 80, 32]],
|
||||
LakeScene: [[0, 0, 150, 256]],
|
||||
WellScene: [[80, 48, 64, 64]],
|
||||
WhiteScene: [[304, 16, 16, 96]],
|
||||
};
|
||||
|
||||
const propertyXml = (properties) => Object.entries(properties).map(([name, value]) => ` <property name="${name}" value="${String(value).replaceAll('&', '&').replaceAll('"', '"')}"/>`).join('\n');
|
||||
|
||||
for (const [key, displayName, width, height, ground, objects] of maps) {
|
||||
const data = createTileData(key, width, height, ground);
|
||||
const borders = [
|
||||
`<object id="900" x="0" y="0" width="${width * 16}" height="4"><properties><property name="collides" type="bool" value="true"/></properties></object>`,
|
||||
`<object id="901" x="0" y="${height * 16 - 4}" width="${width * 16}" height="4"><properties><property name="collides" type="bool" value="true"/></properties></object>`,
|
||||
`<object id="902" x="0" y="0" width="4" height="${height * 16}"><properties><property name="collides" type="bool" value="true"/></properties></object>`,
|
||||
`<object id="903" x="${width * 16 - 4}" y="0" width="4" height="${height * 16}"><properties><property name="collides" type="bool" value="true"/></properties></object>`,
|
||||
];
|
||||
(collisionSpecs[key] ?? []).forEach(([x, y, w, h], index) => borders.push(`<object id="${910 + index}" x="${x}" y="${y}" width="${w}" height="${h}"><properties><property name="collides" type="bool" value="true"/></properties></object>`));
|
||||
const objectXml = objects.map(([type, x, y, properties], index) => ` <object id="${index + 1}" name="${properties.label ?? properties.id}" type="${type}" x="${x}" y="${y}" width="16" height="16">\n <properties>\n${propertyXml(properties)}\n </properties>\n </object>`).join('\n');
|
||||
const tmx = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.12.2" orientation="orthogonal" renderorder="right-down" width="${width}" height="${height}" tilewidth="16" tileheight="16" infinite="0" nextlayerid="4" nextobjectid="1000">
|
||||
<properties><property name="sceneKey" value="${key}"/><property name="displayName" value="${displayName}"/></properties>
|
||||
<tileset firstgid="1" name="honey-village" tilewidth="16" tileheight="16" tilecount="64" columns="16"><image source="../../assets/tileset.png" width="256" height="64"/></tileset>
|
||||
<layer id="1" name="ground" width="${width}" height="${height}"><data encoding="csv">${data.join(',')}</data></layer>
|
||||
<objectgroup id="2" name="obstacles">${borders.join('\n')}</objectgroup>
|
||||
<objectgroup id="3" name="objects">\n${objectXml}\n </objectgroup>
|
||||
</map>`;
|
||||
await writeFile(resolve(sourceDir, `${key}.tmx`), tmx);
|
||||
}
|
||||
|
||||
await writeFile(resolve(sourceDir, 'tileset.tsx'), `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<tileset version="1.10" tiledversion="1.12.2" name="honey-village" tilewidth="16" tileheight="16" tilecount="64" columns="16">
|
||||
<image source="../../assets/tileset.png" width="256" height="64"/>
|
||||
</tileset>`);
|
||||
|
||||
console.log(`Generated ${maps.length} original Tiled source maps in ${sourceDir}`);
|
||||
Reference in New Issue
Block a user