Skip to content

Commit

Permalink
add ResourceLocation
Browse files Browse the repository at this point in the history
- regex checking
- Registries are now also a Resource
  • Loading branch information
j0code committed Nov 30, 2024
1 parent c5e7e02 commit 6af8422
Show file tree
Hide file tree
Showing 20 changed files with 162 additions and 94 deletions.
8 changes: 4 additions & 4 deletions src/Inventory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -91,7 +91,7 @@ export default class Inventory implements HasData {
const arr: (ReturnType<ItemStack["getData"]> & {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 })
})
Expand Down
2 changes: 1 addition & 1 deletion src/ItemStack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
49 changes: 29 additions & 20 deletions src/Registry.ts
Original file line number Diff line number Diff line change
@@ -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<T extends BaseData = BaseData> implements BaseData {
export class Registry<T extends Resource = Resource> extends Resource {

private entries: Map<NamespacedId, T>
private entries: Map<RawNamespacedId, T>

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

Expand All @@ -44,6 +51,7 @@ export class Registry<T extends BaseData = BaseData> 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)
Expand Down Expand Up @@ -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)
Expand All @@ -92,12 +100,13 @@ export async function createRegistries() {
return rootRegistry
}

type ResourceConstructor<T> = { new(ns: string, id: string, data: unknown, ...other: never[]): T }
type ResourceConstructor<T> = { new(id: NamespacedId, data: unknown, ...other: never[]): T }
type DirSummary = { directories: string[], files: string[], recursiveFiles: string[] }

async function loadDefs<T extends BaseData>(regId: NamespacedId, cls: ResourceConstructor<T>): Promise<Registry<T>> {
async function loadDefs<T extends Resource>(regId: NamespacedId, cls: ResourceConstructor<T>): Promise<Registry<T>> {
if (typeof regId == "string") regId = new ResourceLocation(regId)

const registry = new Registry<T>(regId)
const [_, idname] = regId.split(":")

let dir

Expand All @@ -110,15 +119,15 @@ async function loadDefs<T extends BaseData>(regId: NamespacedId, cls: ResourceCo
const promises: Promise<void>[] = []

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)

return registry
}

async function loadDefsInNamespace<T extends BaseData>(registry: Registry<T>, namespace: string, path: string, cls: ResourceConstructor<T>): Promise<void> {
async function loadDefsInNamespace<T extends Resource>(registry: Registry<T>, namespace: string, path: string, cls: ResourceConstructor<T>): Promise<void> {
let dir

try {
Expand All @@ -132,16 +141,16 @@ async function loadDefsInNamespace<T extends BaseData>(registry: Registry<T>, na
const promises: Promise<void>[] = []

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)
}
})

Expand Down
4 changes: 2 additions & 2 deletions src/block/Block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions src/defs/Attribute.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
Expand Down
15 changes: 8 additions & 7 deletions src/defs/BlockDef.ts
Original file line number Diff line number Diff line change
@@ -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 {

Expand All @@ -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
Expand All @@ -52,15 +53,15 @@ 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() {
return this.type == "container"
}

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() {
Expand Down
16 changes: 8 additions & 8 deletions src/defs/EntityDef.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -13,13 +13,13 @@ export default class EntityDef extends TexturedResource {
readonly attributes: Map<NamespacedId, number>
readonly eyeHeight: number

constructor(namespace: string, idname: string, data: unknown, attributes: Registry<Attribute>) {
super(namespace, idname)
constructor(id: NamespacedId, data: unknown, attributes: Registry<Attribute>) {
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
Expand All @@ -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<NamespacedId, number>,
attributes: Record<RawNamespacedId, number>,
eyeHeight: number
}

Expand All @@ -47,7 +47,7 @@ function validate(data: unknown, attributes: Registry<Attribute>): 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
Expand Down
10 changes: 6 additions & 4 deletions src/defs/ItemDef.ts
Original file line number Diff line number Diff line change
@@ -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`
}

}
6 changes: 3 additions & 3 deletions src/defs/PlayerDef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Registry } from "../Registry.js"
export default class PlayerDef extends EntityDef {

constructor(attributes: Registry<Attribute>) {
super("tiny", "player", {
super("tiny:player", {
attributes: {
"tiny:movement_speed": 1,
"tiny:jump_strength": 0.35,
Expand All @@ -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`
}

}
15 changes: 5 additions & 10 deletions src/defs/Resource.ts
Original file line number Diff line number Diff line change
@@ -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)
}

}
Loading

0 comments on commit 6af8422

Please sign in to comment.