diff --git a/src/Inventory.ts b/src/Inventory.ts index d0c4a50..0091954 100644 --- a/src/Inventory.ts +++ b/src/Inventory.ts @@ -46,7 +46,7 @@ export default class Inventory implements HasData { if (amount <= 0) break - if (slot.item.id == "tiny:air") { + if (slot.item.id.matches("tiny:air")) { this.slots[i] = new ItemStack(stack.item.id, amount) return null @@ -75,14 +75,14 @@ export default class Inventory implements HasData { emptySlots(): number { let count = 0 for (const slot of this.slots) { - if (slot.item.id == "tiny:air") count++ + if (slot.item.id.matches("tiny:air")) count++ } return count } firstEmptySlot(): number { - for (const i in this.slots) if (this.slots[i].item.id == "tiny:air") return Number(i) + for (const i in this.slots) if (this.slots[i].item.id.matches("tiny:air")) return Number(i) return -1 } @@ -91,7 +91,7 @@ export default class Inventory implements HasData { const arr: (ReturnType & {slot: number})[] = [] this.slots.forEach((slot, index) => { - if (slot.item.id == "tiny:air") return + if (slot.item.id.matches("tiny:air")) return arr.push({ ...slot.getData(), slot: index }) }) diff --git a/src/ItemStack.ts b/src/ItemStack.ts index e5727c6..573316c 100644 --- a/src/ItemStack.ts +++ b/src/ItemStack.ts @@ -29,7 +29,7 @@ export default class ItemStack implements HasData { } draw(g: Graphics, size: number, hideAmount: boolean = false) { - if (this.item.id == "tiny:air") return + if (this.item.id.matches("tiny:air")) return this.item.texture?.draw(g, size, size, true) diff --git a/src/Registry.ts b/src/Registry.ts index e053f50..17b2e98 100644 --- a/src/Registry.ts +++ b/src/Registry.ts @@ -1,38 +1,45 @@ -import { BaseData, NamespacedId } from "./util/interfaces.js" +import { NamespacedId, RawNamespacedId } from "./util/interfaces.js" import Attribute from "./defs/Attribute.js" import BlockDef from "./defs/BlockDef.js" import EntityDef from "./defs/EntityDef.js" import ItemDef from "./defs/ItemDef.js" import PlayerDef from "./defs/PlayerDef.js" +import Resource from "./defs/Resource.js" +import ResourceLocation from "./util/ResourceLocation.js" import YSON from "https://j0code.github.io/yson/YSON.js" import YSONSyntaxError from "https://j0code.github.io/yson/YSONSyntaxError.js" -export class Registry implements BaseData { +export class Registry extends Resource { - private entries: Map + private entries: Map - constructor(public readonly id: NamespacedId) { + constructor(id: NamespacedId) { + super(id) this.entries = new Map() } register(entry: T) { - if (entry.id == this.id) { + if (entry.id.matches(this.id)) { throw new Error("Entry may not have the same ID as registry") } - if (this.entries.has(entry.id)) { + if (this.entries.has(entry.id.toString())) { throw new Error("Duplicate entry") } - this.entries.set(entry.id, entry) + this.entries.set(entry.id.toString(), entry) } get(id: NamespacedId): T | undefined { + if (id instanceof ResourceLocation) id = id.toString() + return this.entries.get(id) } - query(...ids: NamespacedId[]): BaseData | undefined { - const entry = this.entries.get(ids[0]) + query(...ids: NamespacedId[]): Resource | undefined { + const id = ids[0] instanceof ResourceLocation ? ids[0].toString() : ids[0] + + const entry = this.entries.get(id) if (ids.length == 0) return entry @@ -44,6 +51,7 @@ export class Registry implements BaseData { } exists(id: NamespacedId, ...ids: NamespacedId[]): boolean { + if (id instanceof ResourceLocation) id = id.toString() if (ids.length == 0) return this.entries.has(id) const entry = this.entries.get(id) @@ -74,8 +82,8 @@ export async function createRegistries() { const items = await loadDefs("tiny:item", ItemDef) // hardcoded entries - blocks.register(new BlockDef("tiny", "air", {})) - entities.register(new EntityDef("tiny", "item", { hasFriction: true }, attributes)) + blocks.register(new BlockDef("tiny:air", {})) + entities.register(new EntityDef("tiny:item", { hasFriction: true }, attributes)) entities.register(new PlayerDef(attributes)) rootRegistry.register(attributes) @@ -92,12 +100,13 @@ export async function createRegistries() { return rootRegistry } -type ResourceConstructor = { new(ns: string, id: string, data: unknown, ...other: never[]): T } +type ResourceConstructor = { new(id: NamespacedId, data: unknown, ...other: never[]): T } type DirSummary = { directories: string[], files: string[], recursiveFiles: string[] } -async function loadDefs(regId: NamespacedId, cls: ResourceConstructor): Promise> { +async function loadDefs(regId: NamespacedId, cls: ResourceConstructor): Promise> { + if (typeof regId == "string") regId = new ResourceLocation(regId) + const registry = new Registry(regId) - const [_, idname] = regId.split(":") let dir @@ -110,7 +119,7 @@ async function loadDefs(regId: NamespacedId, cls: ResourceCo const promises: Promise[] = [] for (const namespace of dir.directories) { - promises.push(loadDefsInNamespace(registry, namespace, `./data/${namespace}/${idname}`, cls)) + promises.push(loadDefsInNamespace(registry, namespace, `./data/${namespace}/${regId.path}`, cls)) } await Promise.all(promises) @@ -118,7 +127,7 @@ async function loadDefs(regId: NamespacedId, cls: ResourceCo return registry } -async function loadDefsInNamespace(registry: Registry, namespace: string, path: string, cls: ResourceConstructor): Promise { +async function loadDefsInNamespace(registry: Registry, namespace: string, path: string, cls: ResourceConstructor): Promise { let dir try { @@ -132,16 +141,16 @@ async function loadDefsInNamespace(registry: Registry, na const promises: Promise[] = [] for (const file of dir.recursiveFiles) { - const entryIdname = file.substring(0, file.lastIndexOf(".")) + const idpath = file.substring(0, file.lastIndexOf(".")) const promise = YSON.load(`${path}/${file}`) .then(data => { - registry.register(new cls(namespace, entryIdname, data)) + registry.register(new cls(`${namespace}:${idpath}`, data)) }) .catch(e => { if (e instanceof YSONSyntaxError) { - console.warn(`Malformed def ${registry.id}#${namespace}:${entryIdname}:`, e.message) + console.warn(`Malformed def ${registry.id}#${namespace}:${idpath}:`, e.message) } else { - console.warn(`Error loading def ${registry.id}#${namespace}:${entryIdname}:`, e) + console.warn(`Error loading def ${registry.id}#${namespace}:${idpath}:`, e) } }) diff --git a/src/block/Block.ts b/src/block/Block.ts index a18be98..aae6276 100644 --- a/src/block/Block.ts +++ b/src/block/Block.ts @@ -107,7 +107,7 @@ export default class Block implements HasData { let light = this.lightColor(world) if (z < 0) light = light.scale(12) - if (this.def.id == "tiny:air" && z <= 0) { + if (this.def.id.matches("tiny:air") && z <= 0) { if (debug.showDebugScreen && debug.showAirLightLevel) overlayLight(g, x, y, light) return @@ -230,7 +230,7 @@ function deriveSkyLight(world: World, x: number, y: number, z: number) { const other = world.getBlock(x, y, z) if (!other) return 15 - else if (other.id == "tiny:air" || !other.full) return other.skyLight + else if (other.id.matches("tiny:air") || !other.full) return other.skyLight else if (other.isSolid()) return 0 // Math.floor(other.skyLight * 0.5) return other.skyLight - 1 // water diff --git a/src/defs/Attribute.ts b/src/defs/Attribute.ts index 6d66d8e..2deb364 100644 --- a/src/defs/Attribute.ts +++ b/src/defs/Attribute.ts @@ -1,4 +1,5 @@ import { isInRangeIncl, isObject, validateProperty } from "../util/typecheck.js" +import { NamespacedId } from "../util/interfaces.js" import Resource from "./Resource.js" import TinyError from "../TinyError.js" import YSON from "https://j0code.github.io/yson/YSON.js" @@ -9,13 +10,13 @@ export default class Attribute extends Resource { readonly min: number readonly max: number - constructor(namespace: string, idname: string, data: unknown) { - super(namespace, idname) + constructor(id: NamespacedId, data: unknown) { + super(id) try { - if (!isAttributeDef(data)) throw new Error(`Invalid attribute ${namespace}:${idname}: ${YSON.stringify(data)}`) + if (!isAttributeDef(data)) throw new Error(`Invalid attribute ${id}: ${YSON.stringify(data)}`) } catch (e) { // @ts-expect-error e should be an Error - throw new TinyError(`Invalid attribute ${namespace}:${idname}`, e) + throw new TinyError(`Invalid attribute ${id}`, e) } this.base = data.base diff --git a/src/defs/BlockDef.ts b/src/defs/BlockDef.ts index 4d670b1..e8bce55 100644 --- a/src/defs/BlockDef.ts +++ b/src/defs/BlockDef.ts @@ -1,10 +1,11 @@ import { equalsAny, isIntInRange, isObject, isPosInt, validateArray, validateProperty } from "../util/typecheck.js" -import { type Flatten } from "../util/interfaces.js" +import { type Flatten, NamespacedId } from "../util/interfaces.js" import { getSound } from "../main.js" import LightColor from "../util/LightColor.js" import Sound from "../sound/Sound.js" import TexturedResource from "./TexturedResource.js" import TinyError from "../TinyError.js" +import YSON from "https://j0code.github.io/yson/YSON.js" export default class BlockDef extends TexturedResource { @@ -21,13 +22,13 @@ export default class BlockDef extends TexturedResource { place: Sound } - constructor(namespace: string, idname: string, data: unknown) { - super(namespace, idname) + constructor(id: NamespacedId, data: unknown) { + super(id) try { - if (!validate(data)) throw new Error(`Invalid blockdef for ${namespace}:${idname}: ${JSON.stringify(data)}`) + if (!validate(data)) throw new Error(`Invalid blockdef for ${id}: ${YSON.stringify(data)}`) } catch (e) { // @ts-expect-error e should be an Error - throw new TinyError(`Invalid blockdef for ${namespace}:${idname}`, e) + throw new TinyError(`Invalid blockdef for ${id}`, e) } this.type = data.type @@ -52,7 +53,7 @@ export default class BlockDef extends TexturedResource { } get assetsPath() { - return `${this.namespace}/textures/block/${this.idname}.png` + return `${this.id.namespace}/textures/block/${this.id.path}.png` } hasInventory() { @@ -60,7 +61,7 @@ export default class BlockDef extends TexturedResource { } isSolid() { - return (this.type == "block" || this.type == "container") && this.id != "tiny:air" + return (this.type == "block" || this.type == "container") && !this.id.matches("tiny:air") } mainLayerOnly() { diff --git a/src/defs/EntityDef.ts b/src/defs/EntityDef.ts index f0df01b..1a5c3dd 100644 --- a/src/defs/EntityDef.ts +++ b/src/defs/EntityDef.ts @@ -1,6 +1,6 @@ import { isInRangeIncl, isObject, validateProperty } from "../util/typecheck.js" +import { type NamespacedId, type RawNamespacedId } from "../util/interfaces.js" import Attribute from "./Attribute.js" -import { NamespacedId } from "../util/interfaces.js" import { recordToMap } from "../util/util.js" import { Registry } from "../Registry.js" import TexturedResource from "./TexturedResource.js" @@ -13,13 +13,13 @@ export default class EntityDef extends TexturedResource { readonly attributes: Map readonly eyeHeight: number - constructor(namespace: string, idname: string, data: unknown, attributes: Registry) { - super(namespace, idname) + constructor(id: NamespacedId, data: unknown, attributes: Registry) { + super(id) try { - if (!validate(data, attributes)) throw new Error(`Invalid entitydef for ${namespace}:${idname}: ${JSON.stringify(data)}`) + if (!validate(data, attributes)) throw new Error(`Invalid entitydef for ${id}: ${YSON.stringify(data)}`) } catch (e) { // @ts-expect-error e should be an Error - throw new TinyError(`Invalid entitydef for ${namespace}:${idname}`, e) + throw new TinyError(`Invalid entitydef for ${id}`, e) } this.hasFriction = data.hasFriction @@ -28,14 +28,14 @@ export default class EntityDef extends TexturedResource { } get assetsPath() { - return `${this.namespace}/textures/entity/${this.idname}.png` + return `${this.id.namespace}/textures/entity/${this.id.path}.png` } } export type EntityDefData = { hasFriction: boolean, - attributes: Record, + attributes: Record, eyeHeight: number } @@ -47,7 +47,7 @@ function validate(data: unknown, attributes: Registry): data is Entit if (!isObject(rec)) throw new Error(`Expected a Record at ${path} but got ${YSON.stringify(rec)}`) for (const attr of attributes.values()) { - validateProperty(rec, attr.id, isInRangeIncl(attr.min, attr.max), attr.base, `${path}.${attr.id}`) + validateProperty(rec, attr.id.toString(), isInRangeIncl(attr.min, attr.max), attr.base, `${path}.${attr.id}`) } return true diff --git a/src/defs/ItemDef.ts b/src/defs/ItemDef.ts index db5d04d..64b7d72 100644 --- a/src/defs/ItemDef.ts +++ b/src/defs/ItemDef.ts @@ -1,21 +1,23 @@ import { isIntInRange, isObject, safeValidateProperty } from "../util/typecheck.js" +import { NamespacedId } from "../util/interfaces.js" import TexturedResource from "./TexturedResource.js" +import YSON from "https://j0code.github.io/yson/YSON.js" export default class ItemDef extends TexturedResource { readonly maxItemStack: number - constructor(namespace: string, idname: string, data: unknown) { - super(namespace, idname) + constructor(id: NamespacedId, data: unknown) { + super(id) if (!isObject(data) || !safeValidateProperty(data, "maxItemStack", isIntInRange(1, 9999), 128)) { - throw new Error(`Invalid itemdef for ${namespace}:${idname}: ${JSON.stringify(data)}`) + throw new Error(`Invalid itemdef for ${id}: ${YSON.stringify(data)}`) } this.maxItemStack = data.maxItemStack as number || 128 } get assetsPath() { - return `${this.namespace}/textures/item/${this.idname}.png` + return `${this.id.namespace}/textures/item/${this.id.path}.png` } } diff --git a/src/defs/PlayerDef.ts b/src/defs/PlayerDef.ts index b4eb54b..85ccea5 100644 --- a/src/defs/PlayerDef.ts +++ b/src/defs/PlayerDef.ts @@ -5,7 +5,7 @@ import { Registry } from "../Registry.js" export default class PlayerDef extends EntityDef { constructor(attributes: Registry) { - super("tiny", "player", { + super("tiny:player", { attributes: { "tiny:movement_speed": 1, "tiny:jump_strength": 0.35, @@ -18,11 +18,11 @@ export default class PlayerDef extends EntityDef { } get assetsPath() { - return `${this.namespace}/textures/entity/${this.idname}/${this.idname}.png` + return `${this.id.namespace}/textures/entity/${this.id.path}/${this.id.path}.png` } skinAssetsPath(skin: string, pose: string = skin) { - return `${this.namespace}/textures/skin/${skin}/${pose}.png` + return `${this.id.namespace}/textures/skin/${skin}/${pose}.png` } } diff --git a/src/defs/Resource.ts b/src/defs/Resource.ts index 31cfd7c..acdaaec 100644 --- a/src/defs/Resource.ts +++ b/src/defs/Resource.ts @@ -1,17 +1,12 @@ -import { type NamespacedId } from "../util/interfaces.js" +import { NamespacedId } from "../util/interfaces.js" +import ResourceLocation from "../util/ResourceLocation.js" export default class Resource { - readonly namespace: string - readonly idname: string + readonly id: ResourceLocation - constructor(namespace: string, idname: string) { - this.namespace = namespace - this.idname = idname - } - - get id(): NamespacedId { - return `${this.namespace}:${this.idname}` + constructor(id: NamespacedId) { + this.id = new ResourceLocation(id) } } diff --git a/src/defs/TexturedResource.ts b/src/defs/TexturedResource.ts index ede5bc5..ec9ea82 100644 --- a/src/defs/TexturedResource.ts +++ b/src/defs/TexturedResource.ts @@ -1,15 +1,16 @@ import Base from "./Resource.js" import { getTexture } from "../main.js" +import { NamespacedId } from "../util/interfaces.js" import Texture from "../texture/Texture.js" export default class TexturedResource extends Base { readonly texture: Texture | null - constructor(namespace: string, idname: string) { - super(namespace, idname) + constructor(id: NamespacedId) { + super(id) - if (["tiny:air", "tiny:player", "tiny:item"].includes(this.id)) this.texture = null + if (this.id.matches("tiny:air", "tiny:player", "tiny:item")) this.texture = null else this.texture = getTexture(this.assetsPath) } diff --git a/src/entity/ItemEntity.ts b/src/entity/ItemEntity.ts index 4882d5f..bb26043 100644 --- a/src/entity/ItemEntity.ts +++ b/src/entity/ItemEntity.ts @@ -1,8 +1,9 @@ import Entity, { type EntityData } from "./Entity.js" +import { type Flatten, NamespacedId } from "../util/interfaces.js" import ItemStack, { type ItemStackData } from "../ItemStack.js" import { entitydefs } from "../main.js" -import { type Flatten } from "../util/interfaces.js" import Player from "./Player.js" +import ResourceLocation from "../util/ResourceLocation.js" import World from "../world/World.js" export default class ItemEntity extends Entity { @@ -50,7 +51,7 @@ export default class ItemEntity extends Entity { } // combine item entities - const items = world.getAllEntities(entity => entity.id == "tiny:item" && (entity as ItemEntity).itemstack.match(this.itemstack)) + const items = world.getAllEntities(entity => entity.id.matches("tiny:item") && (entity as ItemEntity).itemstack.match(this.itemstack)) for (const item of items) { if (item == this) continue @@ -84,6 +85,8 @@ export type ItemEntityData = Flatten -export function isItemEntityData(data: Partial, id: string = data.id || ""): data is ItemEntityData { - return id == "tiny:item" +const itemEntityId = new ResourceLocation("tiny:item") + +export function isItemEntityData(data: Partial, id: NamespacedId = data.id || ":"): data is ItemEntityData { + return itemEntityId.matches(id) } diff --git a/src/entity/Player.ts b/src/entity/Player.ts index 08556f5..449ef05 100644 --- a/src/entity/Player.ts +++ b/src/entity/Player.ts @@ -55,7 +55,7 @@ export default class Player extends Entity { pickBlock(block: Block) { const blockItem = new Item(block.id) - if (this.selectedItem.item.id == block.id || block.id == "tiny:air") return + if (this.selectedItem.item.id == block.id || block.id.matches("tiny:air")) return let index = this.hotbar.find(blockItem) if (index >= 0) this.#selectedItemSlot = index diff --git a/src/gui/state/ingame.ts b/src/gui/state/ingame.ts index 665c808..ae1e9dc 100644 --- a/src/gui/state/ingame.ts +++ b/src/gui/state/ingame.ts @@ -103,7 +103,7 @@ export function draw(g: Graphics) { } else if (floatingStack) { g.ctx.translate(-size/2, -size/2) floatingStack.draw(g, size) - } else if (player.selectedItem.item.id != "tiny:air" && !inaccessible && targetBlock?.id == "tiny:air") { + } else if (!player.selectedItem.item.id.matches("tiny:air") && !inaccessible && targetBlock?.id.matches("tiny:air")) { g.ctx.translate(-size/2, -size/2) g.globalAlpha = 0.8 player.selectedItem.item.texture?.draw(g, size, size, true) @@ -267,7 +267,7 @@ export function whileKey(key: string) { const stack = player.selectedItem const index = player.selectedItemSlot - if (stack.item.id == "tiny:air") return + if (stack.item.id.matches("tiny:air")) return const entityData = { position: player.eyes.asArray(), @@ -304,7 +304,7 @@ export function onClick(button: number) { break case 1: - if (!frontBlock || frontBlock.id == "tiny:air") break + if (!frontBlock || frontBlock.id.matches("tiny:air")) break player.pickBlock(frontBlock) @@ -401,7 +401,7 @@ export function getFirstFluid(w: World, x: number, y: number, startZ: number = w for (let z = startZ; z >= w.minZ; z--) { const block = w.getBlock(x, y, z) - if (!block || block.id == "tiny:air") continue + if (!block || block.id.matches("tiny:air")) continue return { block, z } } diff --git a/src/util/Container.ts b/src/util/Container.ts index fd7ada8..98589db 100644 --- a/src/util/Container.ts +++ b/src/util/Container.ts @@ -35,7 +35,7 @@ export default class Container { if ((floatingStackIndex != undefined && inventory)) { const stack = inventory.get(floatingStackIndex) - if (stack.item.id != "tiny:air") return stack + if (!stack.item.id.matches("tiny:air")) return stack // shouldn't happen, but just in case @@ -64,7 +64,7 @@ export default class Container { drawSlots(g, inventory, (inv, i) => { const stack = inv.get(i) - if (stack.item.id != "tiny:air" && i != floatingStackIndex) { + if (!stack.item.id.matches("tiny:air") && i != floatingStackIndex) { ctx.save() ctx.translate(inset, inset) stack.draw(g, itemSize, inv instanceof CreativeInventory) @@ -121,7 +121,7 @@ export default class Container { if (input.keyPressed("ShiftLeft")) { // move stack to hotbar const stack = inventory.get(mouseSlot.slotIndex) - if (stack.item.id == "tiny:air") return false + if (stack.item.id.matches("tiny:air")) return false const leftOver = player.hotbar.addItems(stack) @@ -136,8 +136,8 @@ export default class Container { inventory.set(floatingStackIndex, stack || new ItemStack("tiny:air")) } - if (floatingStackIndex == undefined && stack.item.id != "tiny:air") floatingStackIndex = mouseSlot.slotIndex - else if (stack.item.id == "tiny:air" || floatingStackIndex == mouseSlot.slotIndex) floatingStackIndex = undefined + if (floatingStackIndex == undefined && !stack.item.id.matches("tiny:air")) floatingStackIndex = mouseSlot.slotIndex + else if (stack.item.id.matches("tiny:air") || floatingStackIndex == mouseSlot.slotIndex) floatingStackIndex = undefined } } else if (button == 2) { if (floatingStackIndex == undefined) { @@ -146,7 +146,7 @@ export default class Container { const stack = inventory.get(mouseSlot.slotIndex) const floating = Container.floatingStack()! - if (stack.item.id == "tiny:air") { + if (stack.item.id.matches("tiny:air")) { inventory.set(mouseSlot.slotIndex, new ItemStack(floating.item, 1)) if (floating.amount > 1) floating.amount-- else inventory.set(floatingStackIndex, new ItemStack("tiny:air", 1)) diff --git a/src/util/Hotbar.ts b/src/util/Hotbar.ts index 23a5bd5..e256afd 100644 --- a/src/util/Hotbar.ts +++ b/src/util/Hotbar.ts @@ -69,7 +69,7 @@ export default class Hotbar { for (let i = 0; i < hotbar.size; i++) { const stack = hotbar.get(i) - if (stack.item.id == "tiny:air") continue + if (stack.item.id.matches("tiny:air")) continue if (!stack.item.texture) continue const cx = 22/2 * scale - itemSize/2 + i * 20 * scale diff --git a/src/util/ResourceLocation.ts b/src/util/ResourceLocation.ts new file mode 100644 index 0000000..29df255 --- /dev/null +++ b/src/util/ResourceLocation.ts @@ -0,0 +1,52 @@ +import { type NamespacedId, RawNamespacedId } from "./interfaces" + +const namespaceRegex = /^[0-9a-z_.-]+$/ +const pathRegex = /^(?:[0-9a-z_.-]+\/)*[0-9a-z_.-]+$/ + +export default class ResourceLocation { + + readonly namespace: string + readonly path: string + + constructor(raw: NamespacedId) { + if (raw instanceof ResourceLocation) { // copy + this.namespace = raw.namespace + this.path = raw.path + + return + } + + if (!raw.includes(":")) throw new Error(`Invalid ResourceLocation [${raw}]`) + + const colonIndex = raw.indexOf(":") + const namespace = raw.substring(0, colonIndex) + const path = raw.substring(colonIndex + 1) + + if (!namespaceRegex.test(namespace)) throw new Error(`Invalid namespace [${namespace}] in [${raw}]`) + if (!pathRegex.test(path)) throw new Error(`Invalid path [${path}] in [${raw}]`) + + this.namespace = namespace + this.path = path + } + + getPathEntries(): string[] { + return this.path.split("/") + } + + matches(...ids: NamespacedId[] | string[]): boolean { + for (const id of ids) { + if (this.toString() == id) return true + } + + return false + } + + toString(): RawNamespacedId { + return `${this.namespace}:${this.path}` + } + + toYSON() { + return this.toString() + } + +} diff --git a/src/util/interfaces.ts b/src/util/interfaces.ts index 81c04c7..4ceb8eb 100644 --- a/src/util/interfaces.ts +++ b/src/util/interfaces.ts @@ -1,4 +1,5 @@ -import Inventory from "../Inventory" +import type Inventory from "../Inventory" +import type ResourceLocation from "./ResourceLocation" export type ArrayElement = A extends readonly (infer T)[] ? T : never @@ -20,7 +21,8 @@ export interface HasData { export type DataOf = ReturnType -export type NamespacedId = `${string}:${string}` +export type RawNamespacedId = `${string}:${string}` +export type NamespacedId = RawNamespacedId | ResourceLocation export type SoundDef = { sounds: Array this.maxX || y < this.minY || y > this.maxY || z < this.minZ || z > this.maxZ) { @@ -154,7 +156,7 @@ export default class World { } this.blocks.set(`${x},${y},${z}`, block) - if (!silent && block.id != "tiny:air") block.playSound("place") + if (!silent && !block.id.matches("tiny:air")) block.playSound("place") this.scheduleBlockUpdate(x, y, z) this.scheduleBlockUpdate(x-1, y, z) @@ -174,7 +176,7 @@ export default class World { const oldBlock = this.getBlock(x, y, z) - if (!oldBlock || oldBlock.id == "tiny:air") return + if (!oldBlock || oldBlock.id.matches("tiny:air")) return if (!silent) oldBlock?.playSound("break") const block = new Block("tiny:air") @@ -192,15 +194,15 @@ export default class World { getAllEntities(filter?: string | ((entity: Entity, index: number) => boolean)): E[] { let entities = Array.from(this.entities.values()) - if (typeof filter == "string") entities = entities.filter(entity => entity.id == filter) + if (typeof filter == "string") entities = entities.filter(entity => entity.id.matches(filter)) else if (typeof filter == "function") entities = entities.filter(filter) return entities as E[] } spawn(entity: Entity | NamespacedId, data?: Partial) { - if (typeof entity == "string") this.entities.add(createEntity(entity, this.tickCount, data)) - else this.entities.add(entity) + if (typeof entity == "string" || entity instanceof ResourceLocation) this.entities.add(createEntity(entity, this.tickCount, data)) + else this.entities.add(entity) } removeEntity(entity: Entity) { @@ -255,7 +257,7 @@ export default class World { for (let x = this.minX; x <= this.maxX; x++) { const block = this.getBlock(x, y, z) - if (!block || block.id == "tiny:air") continue + if (!block || block.id.matches("tiny:air")) continue block.getBoundingBox(x, y).draw(g, "blue") } @@ -313,11 +315,11 @@ export default class World { } if (block.type == "container") blockData.push(block.getData(x, y, z)) - const idIndex = blockIds.indexOf(block.id) + const idIndex = blockIds.indexOf(block.id.toString()) if (idIndex >= 0) blocks[i] = idIndex else { - blockIds.push(block.id) + blockIds.push(block.id.toString()) blocks[i] = blockIds.length -1 }