diff --git a/package-lock.json b/package-lock.json index 20b0167c..e1d5025c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "brace": "^0.11.1", "buffer": "^6.0.3", "comment-json": "^4.1.1", - "deepslate": "^0.17.5", + "deepslate": "^0.18.0", "deepslate-1.18": "npm:deepslate@^0.9.0-beta.9", "deepslate-1.18.2": "npm:deepslate@^0.9.0-beta.13", "highlight.js": "^11.5.1", @@ -1998,9 +1998,9 @@ "dev": true }, "node_modules/deepslate": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/deepslate/-/deepslate-0.17.5.tgz", - "integrity": "sha512-kS0Hn1RbAZ/6WS2DDeFKYkcQXd0VC9PrahYfec96bA6RY0+s0jTpEejY9m3og/qZ5hNq+NgHu5qQxvcaN0MZ1g==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/deepslate/-/deepslate-0.18.0.tgz", + "integrity": "sha512-mip9lv9ka0ksdaQ6OrfwQHFyr171vtTrmy1FAIWWy+owWbGnYiXioIS6uX4jEeQ9MMaWrBXwKHuPu5Hv+H7E3w==", "dependencies": { "gl-matrix": "^3.3.0", "md5": "^2.3.0", @@ -6773,9 +6773,9 @@ "dev": true }, "deepslate": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/deepslate/-/deepslate-0.17.5.tgz", - "integrity": "sha512-kS0Hn1RbAZ/6WS2DDeFKYkcQXd0VC9PrahYfec96bA6RY0+s0jTpEejY9m3og/qZ5hNq+NgHu5qQxvcaN0MZ1g==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/deepslate/-/deepslate-0.18.0.tgz", + "integrity": "sha512-mip9lv9ka0ksdaQ6OrfwQHFyr171vtTrmy1FAIWWy+owWbGnYiXioIS6uX4jEeQ9MMaWrBXwKHuPu5Hv+H7E3w==", "requires": { "gl-matrix": "^3.3.0", "md5": "^2.3.0", diff --git a/package.json b/package.json index e49f1363..4e3c7bc3 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "brace": "^0.11.1", "buffer": "^6.0.3", "comment-json": "^4.1.1", - "deepslate": "^0.17.5", + "deepslate": "^0.18.0", "deepslate-1.18": "npm:deepslate@^0.9.0-beta.9", "deepslate-1.18.2": "npm:deepslate@^0.9.0-beta.13", "highlight.js": "^11.5.1", diff --git a/src/app/components/generator/PreviewPanel.tsx b/src/app/components/generator/PreviewPanel.tsx index cb21905c..b5e7da81 100644 --- a/src/app/components/generator/PreviewPanel.tsx +++ b/src/app/components/generator/PreviewPanel.tsx @@ -4,9 +4,9 @@ import { useState } from 'preact/hooks' import { useModel } from '../../hooks/index.js' import type { VersionId } from '../../services/index.js' import { checkVersion } from '../../services/index.js' -import { BiomeSourcePreview, BlockStatePreview, DecoratorPreview, DensityFunctionPreview, LootTablePreview, ModelPreview, NoisePreview, NoiseSettingsPreview } from '../previews/index.js' +import { BiomeSourcePreview, BlockStatePreview, DecoratorPreview, DensityFunctionPreview, LootTablePreview, ModelPreview, NoisePreview, NoiseSettingsPreview, StructureSetPreview } from '../previews/index.js' -export const HasPreview = ['loot_table', 'dimension', 'worldgen/density_function', 'worldgen/noise', 'worldgen/noise_settings', 'worldgen/configured_feature', 'worldgen/placed_feature', 'block_definition', 'model'] +export const HasPreview = ['loot_table', 'dimension', 'worldgen/density_function', 'worldgen/noise', 'worldgen/noise_settings', 'worldgen/configured_feature', 'worldgen/placed_feature', 'worldgen/structure_set', 'block_definition', 'model'] type PreviewPanelProps = { model: DataModel | undefined, @@ -50,6 +50,10 @@ export function PreviewPanel({ model, version, id, shown }: PreviewPanelProps) { return } + if (id === 'worldgen/structure_set' && checkVersion(version, '1.19')) { + return + } + if (id === 'block_definition') { return } diff --git a/src/app/components/previews/Decorator.ts b/src/app/components/previews/Decorator.ts index b18b8ed4..23a26de4 100644 --- a/src/app/components/previews/Decorator.ts +++ b/src/app/components/previews/Decorator.ts @@ -1,9 +1,9 @@ import { DataModel } from '@mcschema/core' import type { BlockPos, ChunkPos, PerlinNoise, Random } from 'deepslate/worldgen' -import type { VersionId } from '../../services/index.js' -import { checkVersion } from '../../services/index.js' import type { Color } from '../../Utils.js' import { clamp, isObject, stringToColor } from '../../Utils.js' +import type { VersionId } from '../../services/index.js' +import { checkVersion } from '../../services/index.js' export type Placement = [BlockPos, number] @@ -27,7 +27,7 @@ export type PlacedFeature = { const terrain = [50, 50, 51, 51, 52, 52, 53, 54, 56, 57, 57, 58, 58, 59, 60, 60, 60, 59, 59, 59, 60, 61, 61, 62, 63, 63, 64, 64, 64, 65, 65, 66, 66, 65, 65, 66, 66, 67, 67, 67, 68, 69, 71, 73, 74, 76, 79, 80, 81, 81, 82, 83, 83, 82, 82, 81, 81, 80, 80, 80, 81, 81, 82, 82] -const featureColors: Color[] = [ +export const featureColors: Color[] = [ [255, 77, 54], // red [59, 118, 255], // blue [91, 207, 25], // green diff --git a/src/app/components/previews/Deepslate.ts b/src/app/components/previews/Deepslate.ts index 98c456df..13f8b29f 100644 --- a/src/app/components/previews/Deepslate.ts +++ b/src/app/components/previews/Deepslate.ts @@ -25,6 +25,7 @@ export class Deepslate { private generatorCache: ChunkGenerator | undefined private biomeSourceCache: BiomeSource | undefined private randomStateCache: deepslate19.RandomState | undefined + private structureContextCache: deepslate19.WorldgenStructure.GenerationContext | undefined private chunksCache: Chunk[] = [] private biomeCache: Map = new Map() private readonly presetCache: Map = new Map() @@ -296,16 +297,72 @@ export class Deepslate { } } + public loadStructureSet(state: unknown, seed: bigint) { + if (!this.isVersion('1.19')) { + throw new Error('Cannot load structure set prior to 1.19') + } + const settings = this.d.NoiseGeneratorSettings.create({ + noise: { + minY: 0, + height: 256, + xzSize: 1, + ySize: 2, + }, + noiseRouter: this.d.NoiseRouter.create({ + finalDensity: this.d.DensityFunction.fromJson(state), + }), + }) + const unknownBiome = this.d.Identifier.create('unknown') + const randomState = new this.d.RandomState(settings, seed) + const biomeSource = new this.d.FixedBiomeSource(unknownBiome) + const chunkGenerator = new this.d.NoiseChunkGenerator(biomeSource, settings) + this.structureContextCache = { seed, settings, randomState, biomeSource, chunkGenerator } + + class SimpleStructure extends this.d.WorldgenStructure { + constructor(settings: deepslate19.WorldgenStructure.StructureSettings) { + super(settings) + } + findGenerationPoint(chunkX: number, chunkZ: number, _random: deepslate19.Random, _context: deepslate19.WorldgenStructure.GenerationContext): deepslate19.BlockPos { + return [(chunkX << 4) + 8, 0, (chunkZ << 4) + 8] + } + } + const structureSet = this.d.StructureSet.fromJson(state) + for (const e of structureSet.structures) { + const id = e.structure.key() + if (id === undefined) { + continue + } + const structure = new SimpleStructure({ validBiomes: new this.d.HolderSet([this.d.Holder.reference(this.d.WorldgenRegistries.BIOME, unknownBiome)])}) + this.d.WorldgenStructure.REGISTRY.register(id, structure) + this.d.WorldgenRegistries.BIOME.register(unknownBiome, {}) + } + if (structureSet.placement instanceof this.d.StructurePlacement.ConcentricRingsStructurePlacement) { + const biomeTag = structureSet.placement['preferredBiomes'].key() + if (biomeTag instanceof this.d.Identifier) { + this.d.WorldgenRegistries.BIOME.getTagRegistry().register(biomeTag, new this.d.HolderSet([this.d.Holder.reference(this.d.WorldgenRegistries.BIOME, unknownBiome)])) + } + } + + return structureSet + } + + public getWorldgenStructureContext(): deepslate19.WorldgenStructure.GenerationContext { + if (!this.structureContextCache) { + throw new Error('Tried to access structure context when it isn\'t loaded') + } + return this.structureContextCache + } + public getNoiseSettings(): NoiseSettings { if (!this.settingsCache) { - throw new Error('Tried to access noise settings when they are not loaded') + throw new Error('Tried to access noise settings when it isn\'t loaded') } return this.settingsCache } public getNoiseRouter(): NoiseRouter { if (!this.routerCache) { - throw new Error('Tried to access noise router when they are not loaded') + throw new Error('Tried to access noise router when it isn\'t loaded') } return this.routerCache } diff --git a/src/app/components/previews/StructureSetPreview.tsx b/src/app/components/previews/StructureSetPreview.tsx new file mode 100644 index 00000000..451d563c --- /dev/null +++ b/src/app/components/previews/StructureSetPreview.tsx @@ -0,0 +1,93 @@ +import { DataModel } from '@mcschema/core' +import type { Identifier } from 'deepslate' +import { ChunkPos } from 'deepslate' +import type { mat3 } from 'gl-matrix' +import { useCallback, useMemo, useRef, useState } from 'preact/hooks' +import type { Color } from '../../Utils.js' +import { computeIfAbsent, iterateWorld2D, randomSeed, stringToColor } from '../../Utils.js' +import { useLocale } from '../../contexts/index.js' +import { useAsync } from '../../hooks/useAsync.js' +import { Btn } from '../index.js' +import { featureColors } from './Decorator.js' +import { DEEPSLATE } from './Deepslate.js' +import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx' +import type { PreviewProps } from './index.js' + +export const StructureSetPreview = ({ data, version, shown }: PreviewProps) => { + const { locale } = useLocale() + const [seed, setSeed] = useState(randomSeed()) + const state = JSON.stringify(data) + + const { value: structureSet } = useAsync(async () => { + await DEEPSLATE.loadVersion(version) + const structureSet = DEEPSLATE.loadStructureSet(DataModel.unwrapLists(data), seed) + return structureSet + }, [state, version, seed]) + + const { chunkStructures, structureColors } = useMemo(() => { + return { + chunkStructures: new Map(), + structureColors: new Map(), + } + }, [structureSet]) + + const ctx = useRef() + const imageData = useRef() + const [focused, setFocused] = useState([]) + + const onSetup = useCallback(function onSetup(canvas: HTMLCanvasElement) { + const ctx2D = canvas.getContext('2d') + if (!ctx2D) return + ctx.current = ctx2D + }, []) + const onResize = useCallback(function onResize(width: number, height: number) { + if (!ctx.current) return + imageData.current = ctx.current.getImageData(0, 0, width, height) + }, []) + const onDraw = useCallback(function onDraw(transform: mat3) { + if (!ctx.current || !imageData.current || !shown || !structureSet) return + const context = DEEPSLATE.getWorldgenStructureContext() + if (!context) return + + iterateWorld2D(imageData.current, transform, (x, y) => { + const pos = ChunkPos.create(x, y) + const structure = computeIfAbsent(chunkStructures, `${pos[0]} ${pos[1]}`, () => structureSet?.getStructureInChunk(pos[0], pos[1], context)) + return { structure, pos } + }, ({ structure, pos }) => { + if (structure !== undefined) { + const color = computeIfAbsent(structureColors, structure.toString(), () => { + const index = structureColors.size + return index < featureColors.length ? featureColors[index] : stringToColor(structure.toString()) + }) + return [0.8 * color[0], 0.8 * color[1], 0.8 * color[2]] + } + if ((Math.floor(pos[0] / 32) + Math.floor(pos[1] / 32)) % 2 === 0) { + return [0.85 * 256, 0.85 * 256, 0.85 * 256] + } + return [256, 256, 256] + }) + ctx.current.putImageData(imageData.current, 0, 0) + }, [structureSet, chunkStructures, structureColors, shown]) + const onHover = useCallback(function onHover(pos: [number, number] | undefined) { + if (!pos) { + setFocused([]) + } else { + const [x, y] = pos + const context = DEEPSLATE.getWorldgenStructureContext() + if (!context) return + const structure = structureSet?.getStructureInChunk(x, -y, context) + setFocused([...(structure ? [structure.toString().replace(/^minecraft:/, '')] : []), `X=${x << 4} Z=${(-y) << 4}`]) + } + }, [structureSet]) + + return <> +
+ {focused.map(s => )} + setSeed(randomSeed())} /> +
+
+ +
+ +} diff --git a/src/app/components/previews/index.ts b/src/app/components/previews/index.ts index 82ad9d42..e33b5cd4 100644 --- a/src/app/components/previews/index.ts +++ b/src/app/components/previews/index.ts @@ -9,6 +9,7 @@ export * from './LootTablePreview.jsx' export * from './ModelPreview.jsx' export * from './NoisePreview.js' export * from './NoiseSettingsPreview.js' +export * from './StructureSetPreview.jsx' export type PreviewProps = { model: DataModel,