Basic crops system
This commit is contained in:
+111
-5
@@ -7,6 +7,7 @@ import {
|
||||
RENDER_LAYERS,
|
||||
type RenderLayer,
|
||||
} from "./everything_registry.ts";
|
||||
import { type BlockVariant, MODEL_FACES, type ModelFace } from "./block_models.ts";
|
||||
|
||||
export const FORMAT_VERSION = 1;
|
||||
export const ID_PATTERN = /^[a-z0-9_]+:[a-z0-9_]+$/;
|
||||
@@ -31,6 +32,8 @@ export interface ManifestJson {
|
||||
export interface BlockJson {
|
||||
id: string;
|
||||
textures: BlockTextures;
|
||||
model?: string;
|
||||
variants?: Record<string, BlockVariant>;
|
||||
render_layer?: RenderLayer;
|
||||
cull_same?: boolean;
|
||||
light_emission?: number;
|
||||
@@ -89,6 +92,8 @@ export interface GridRecipe {
|
||||
|
||||
export function block_to_json(block: BlockRegistry, has_item: boolean): BlockJson {
|
||||
const json: BlockJson = { id: block.id, textures: block.textures };
|
||||
if (block.model !== undefined) json.model = block.model;
|
||||
if (block.variants !== undefined) json.variants = block.variants;
|
||||
if (block.render_layer && block.render_layer !== "solid") json.render_layer = block.render_layer;
|
||||
if (block.cull_same !== undefined) json.cull_same = block.cull_same;
|
||||
if (block.light_emission !== undefined) json.light_emission = block.light_emission;
|
||||
@@ -115,6 +120,8 @@ export function block_from_json(json: BlockJson): { block: BlockRegistry; has_it
|
||||
textures: json.textures,
|
||||
has_collision: json.collision ?? true,
|
||||
};
|
||||
if (json.model !== undefined) block.model = json.model;
|
||||
if (json.variants !== undefined) block.variants = json.variants;
|
||||
const render_layer = json.render_layer ?? (json.transparent ? "translucent" : undefined);
|
||||
if (render_layer && render_layer !== "solid") block.render_layer = render_layer;
|
||||
if (json.cull_same !== undefined) block.cull_same = json.cull_same;
|
||||
@@ -255,12 +262,45 @@ export function validate_manifest(json: unknown, folder_name: string): Problems
|
||||
export function validate_block(json: unknown): Problems {
|
||||
if (!is_object(json)) return ["block must be an object"];
|
||||
const problems = validate_id(json.id, "id");
|
||||
if (json.model !== undefined) problems.push(...validate_id(json.model, "model"));
|
||||
const is_cube = json.model === undefined || json.model === "engine:cube";
|
||||
const textures = json.textures;
|
||||
const texture_ok = typeof textures === "string" ||
|
||||
(is_object(textures) &&
|
||||
(["top", "bottom", "side"].every((k) => typeof textures[k] === "string") ||
|
||||
["front", "side"].every((k) => typeof textures[k] === "string")));
|
||||
if (!texture_ok) problems.push("textures must be a texture id, { top, bottom, side } or { front, side }");
|
||||
if (is_cube) {
|
||||
const texture_ok = typeof textures === "string" ||
|
||||
(is_object(textures) &&
|
||||
(["top", "bottom", "side"].every((k) => typeof textures[k] === "string") ||
|
||||
["front", "side"].every((k) => typeof textures[k] === "string")));
|
||||
if (!texture_ok) problems.push("textures must be a texture id, { top, bottom, side } or { front, side }");
|
||||
} else {
|
||||
problems.push(...validate_textures(textures, "textures"));
|
||||
}
|
||||
if (json.variants !== undefined) {
|
||||
if (!is_object(json.variants)) {
|
||||
problems.push('variants must map conditions like "age=3" to { model, textures, y }');
|
||||
} else {
|
||||
const names = new Set(Array.isArray(json.states) ? json.states.map((s) => is_object(s) ? s.name : "") : []);
|
||||
for (const [condition, variant] of Object.entries(json.variants)) {
|
||||
const where = `variants["${condition}"]`;
|
||||
for (const part of condition === "" ? [] : condition.split(",")) {
|
||||
const [name, value] = part.split("=").map((p) => p.trim());
|
||||
if (!names.has(name) || !/^\d+$/.test(value ?? "")) {
|
||||
problems.push(`${where}: "${part}" must be a state of this block and a number, like age=3`);
|
||||
}
|
||||
}
|
||||
if (!is_object(variant)) {
|
||||
problems.push(`${where} must be { model, textures, y }`);
|
||||
continue;
|
||||
}
|
||||
if (variant.model !== undefined) problems.push(...validate_id(variant.model, `${where}.model`));
|
||||
if (variant.textures !== undefined) {
|
||||
problems.push(...validate_textures(variant.textures, `${where}.textures`));
|
||||
}
|
||||
if (variant.y !== undefined && ![0, 90, 180, 270].includes(variant.y as number)) {
|
||||
problems.push(`${where}.y must be 0, 90, 180 or 270`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (json.render_layer !== undefined && !RENDER_LAYERS.includes(json.render_layer as RenderLayer)) {
|
||||
problems.push(`render_layer must be one of ${RENDER_LAYERS.join(", ")}`);
|
||||
}
|
||||
@@ -294,6 +334,72 @@ export function validate_block(json: unknown): Problems {
|
||||
return problems;
|
||||
}
|
||||
|
||||
export function validate_model(json: unknown): Problems {
|
||||
if (!is_object(json)) return ["model must be an object"];
|
||||
const problems = validate_id(json.id, "id");
|
||||
if (json.textures !== undefined) problems.push(...validate_textures(json.textures, "textures", true));
|
||||
if (!Array.isArray(json.elements) || json.elements.length === 0) {
|
||||
problems.push("elements must be a list of boxes");
|
||||
return problems;
|
||||
}
|
||||
json.elements.forEach((element, i) => {
|
||||
const where = `elements[${i}]`;
|
||||
if (!is_object(element)) {
|
||||
problems.push(`${where} must be { from, to, faces }`);
|
||||
return;
|
||||
}
|
||||
for (const key of ["from", "to"]) {
|
||||
const point = element[key];
|
||||
if (!Array.isArray(point) || point.length !== 3 || !point.every((v) => typeof v === "number")) {
|
||||
problems.push(`${where}.${key} must be [x, y, z] in pixels`);
|
||||
}
|
||||
}
|
||||
const rotation = element.rotation;
|
||||
if (rotation !== undefined) {
|
||||
if (
|
||||
!is_object(rotation) || !["x", "y", "z"].includes(rotation.axis as string) ||
|
||||
![-45, -22.5, 0, 22.5, 45].includes(rotation.angle as number) || !Array.isArray(rotation.origin)
|
||||
) {
|
||||
problems.push(
|
||||
`${where}.rotation must be { origin, axis: x, y or z, angle: -45, -22.5, 0, 22.5 or 45 }`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (element.shade !== undefined && typeof element.shade !== "boolean") {
|
||||
problems.push(`${where}.shade must be true or false`);
|
||||
}
|
||||
if (!is_object(element.faces)) {
|
||||
problems.push(`${where}.faces must map sides to { texture }`);
|
||||
return;
|
||||
}
|
||||
for (const [side, face] of Object.entries(element.faces)) {
|
||||
if (!MODEL_FACES.includes(side as ModelFace)) {
|
||||
problems.push(`${where}.faces: "${side}" isn't one of ${MODEL_FACES.join(", ")}`);
|
||||
}
|
||||
if (!is_object(face) || typeof face.texture !== "string") {
|
||||
problems.push(`${where}.faces.${side} needs a texture, like "#side"`);
|
||||
continue;
|
||||
}
|
||||
if (face.uv !== undefined && (!Array.isArray(face.uv) || face.uv.length !== 4)) {
|
||||
problems.push(`${where}.faces.${side}.uv must be [u1, v1, u2, v2] in pixels`);
|
||||
}
|
||||
if (face.cullface !== undefined && !MODEL_FACES.includes(face.cullface as ModelFace)) {
|
||||
problems.push(`${where}.faces.${side}.cullface isn't one of ${MODEL_FACES.join(", ")}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
return problems;
|
||||
}
|
||||
|
||||
// a texture id, or texture variables mapped to ids. a model's own defaults may also point at variables ("#side")
|
||||
function validate_textures(value: unknown, field: string, allow_variables = false): Problems {
|
||||
if (typeof value === "string") return validate_id(value, field);
|
||||
if (!is_object(value)) return [`${field} must be a texture id or { variable: texture id }`];
|
||||
return Object.entries(value).flatMap(([name, id]) =>
|
||||
allow_variables && typeof id === "string" && id.startsWith("#") ? [] : validate_id(id, `${field}.${name}`)
|
||||
);
|
||||
}
|
||||
|
||||
export function validate_item(json: unknown): Problems {
|
||||
if (!is_object(json)) return ["item must be an object"];
|
||||
const problems = validate_id(json.id, "id");
|
||||
|
||||
Reference in New Issue
Block a user