feat: open schematic (.schem)
todo: fix exporting .schem
This commit is contained in:
parent
b89111d3c3
commit
d078bae747
3 changed files with 78 additions and 22 deletions
|
|
@ -13,7 +13,7 @@ import { Button } from "@/components/ui/button";
|
|||
import _blockData from "@/data/blocks/data.json";
|
||||
const blockData: BlockData = _blockData;
|
||||
|
||||
interface BlockPalette extends nbt.CompoundTagLike {
|
||||
interface LitematicaBlockPalette extends nbt.CompoundTagLike {
|
||||
Name: string;
|
||||
Properties?: Record<string, string>;
|
||||
}
|
||||
|
|
@ -21,13 +21,24 @@ interface BlockPalette extends nbt.CompoundTagLike {
|
|||
interface LitematicNBT extends nbt.ListTagLike {
|
||||
Regions: {
|
||||
Image: {
|
||||
BlockStatePalette: BlockPalette[];
|
||||
BlockStatePalette: LitematicaBlockPalette[];
|
||||
BlockStates: BigInt64Array;
|
||||
Size: { x: number; y: number; z: number };
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface SpongeNBT extends nbt.ListTagLike {
|
||||
Schematic: {
|
||||
Blocks: {
|
||||
Data: Int8Array;
|
||||
Palette: Record<string, number>;
|
||||
};
|
||||
Width: number;
|
||||
Height: number;
|
||||
};
|
||||
}
|
||||
|
||||
function OpenSchematic({ close }: DialogProps) {
|
||||
const { setBlocks } = useContext(CanvasContext);
|
||||
const { setLoading } = useContext(LoadingContext);
|
||||
|
|
@ -50,17 +61,16 @@ function OpenSchematic({ close }: DialogProps) {
|
|||
const data = await nbt.read(bytes);
|
||||
|
||||
if (fileExtension == "litematic") {
|
||||
const litematicData = (data as nbt.NBTData<LitematicNBT>).data;
|
||||
const imageRegion = litematicData.Regions.Image;
|
||||
const litematicData = (data as nbt.NBTData<LitematicNBT>).data.Regions.Image;
|
||||
|
||||
// todo: set version
|
||||
const requiredBits = Math.max(Math.ceil(Math.log2(imageRegion.BlockStatePalette.length)), 2);
|
||||
const requiredBits = Math.max(Math.ceil(Math.log2(litematicData.BlockStatePalette.length)), 2);
|
||||
|
||||
const getPaletteIndex = (index: number): bigint => {
|
||||
const originalY = Math.floor(index / imageRegion.Size.x);
|
||||
const originalX = index % imageRegion.Size.x;
|
||||
const reversedY = imageRegion.Size.y - 1 - originalY;
|
||||
const reversedIndex = reversedY * imageRegion.Size.x + originalX;
|
||||
const originalY = Math.floor(index / litematicData.Size.x);
|
||||
const originalX = index % litematicData.Size.x;
|
||||
const reversedY = litematicData.Size.y - 1 - originalY;
|
||||
const reversedIndex = reversedY * litematicData.Size.x + originalX;
|
||||
|
||||
// getAt() implementation - LitematicaBitArray.java
|
||||
const startOffset = reversedIndex * requiredBits;
|
||||
|
|
@ -70,11 +80,11 @@ function OpenSchematic({ close }: DialogProps) {
|
|||
const mask = (1 << requiredBits) - 1;
|
||||
|
||||
if (startArrayIndex === endArrayIndex) {
|
||||
return (imageRegion.BlockStates[startArrayIndex] >> BigInt(bitOffset)) & BigInt(mask);
|
||||
return (litematicData.BlockStates[startArrayIndex] >> BigInt(bitOffset)) & BigInt(mask);
|
||||
} else {
|
||||
const endOffset = 64 - bitOffset;
|
||||
return (
|
||||
((imageRegion.BlockStates[startArrayIndex] >> BigInt(bitOffset)) | (imageRegion.BlockStates[endArrayIndex] << BigInt(endOffset))) &
|
||||
((litematicData.BlockStates[startArrayIndex] >> BigInt(bitOffset)) | (litematicData.BlockStates[endArrayIndex] << BigInt(endOffset))) &
|
||||
BigInt(mask)
|
||||
);
|
||||
}
|
||||
|
|
@ -84,10 +94,11 @@ function OpenSchematic({ close }: DialogProps) {
|
|||
const blocks: Block[] = [];
|
||||
let index = 0;
|
||||
|
||||
for (let y = 0; y < imageRegion.Size.y; y++) {
|
||||
for (let x = 0; x < imageRegion.Size.x; x++) {
|
||||
for (let y = 0; y < litematicData.Size.y; y++) {
|
||||
for (let x = 0; x < litematicData.Size.x; x++) {
|
||||
const paletteIndex = Number(getPaletteIndex(index));
|
||||
const paletteBlock = imageRegion.BlockStatePalette.at(paletteIndex);
|
||||
console.log(paletteIndex);
|
||||
const paletteBlock = litematicData.BlockStatePalette[paletteIndex];
|
||||
|
||||
index++;
|
||||
if (!paletteBlock) continue;
|
||||
|
|
@ -101,9 +112,14 @@ function OpenSchematic({ close }: DialogProps) {
|
|||
const paletteProperties = paletteBlock.Properties;
|
||||
const dataProperties = blockData[name].properties;
|
||||
|
||||
// The schematic doesn't explicitly provide the texture name, thus we have to figure it out by checking the block's properties
|
||||
if (paletteProperties) {
|
||||
// Check if the properties in the block data exist
|
||||
// If not, that means the block we're looking for has an extra word
|
||||
// For example, pale_oak_log can have no properties but pale_oak_log_top does
|
||||
if (!dataProperties) continue;
|
||||
|
||||
// Check if the schematic and data properties are the same
|
||||
if (!Object.keys(paletteProperties).every((key) => paletteProperties[key] === dataProperties[key])) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -115,6 +131,50 @@ function OpenSchematic({ close }: DialogProps) {
|
|||
}
|
||||
}
|
||||
|
||||
setBlocks(blocks);
|
||||
} else if (fileExtension == "schem") {
|
||||
const spongeData = (data as nbt.NBTData<SpongeNBT>).data.Schematic;
|
||||
// todo: set version
|
||||
|
||||
// Add every block to the canvas
|
||||
const blocks: Block[] = [];
|
||||
let index = 0;
|
||||
|
||||
for (let y = spongeData.Height; y > 0; y--) {
|
||||
for (let x = 0; x < spongeData.Width; x++) {
|
||||
const paletteValue = spongeData.Blocks.Data[index];
|
||||
const paletteBlock = Object.keys(spongeData.Blocks.Palette).find((key) => spongeData.Blocks.Palette[key] == paletteValue);
|
||||
|
||||
index++;
|
||||
if (!paletteBlock) continue;
|
||||
|
||||
const blockIdWithProperties = paletteBlock.replace("minecraft:", "");
|
||||
if (blockIdWithProperties == "air") continue;
|
||||
|
||||
// Split up block ID and properties
|
||||
const [blockId, propertiesString] = blockIdWithProperties.split(/\[(.+)\]/);
|
||||
const properties = propertiesString ? Object.fromEntries(propertiesString.split(",").map((pair) => pair.split("="))) : null;
|
||||
|
||||
for (const name in blockData) {
|
||||
if (blockData[name].id !== blockId) continue;
|
||||
|
||||
const dataProperties = blockData[name].properties;
|
||||
|
||||
// See .litematic function above for how this works
|
||||
if (properties) {
|
||||
if (!dataProperties) continue;
|
||||
|
||||
if (!Object.keys(properties).every((key) => properties[key] === dataProperties[key])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
blocks.push({ x, y, name });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setBlocks(blocks);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,10 +133,8 @@ function SaveLitematic({ close }: DialogProps) {
|
|||
};
|
||||
|
||||
// Write to file
|
||||
const bytes = await nbt.write(data);
|
||||
const compressed = await nbt.compress(bytes, "gzip");
|
||||
|
||||
const blob = new Blob([compressed], { type: "application/x-gzip" });
|
||||
const bytes = await nbt.write(data, { compression: "gzip" });
|
||||
const blob = new Blob([bytes], { type: "application/x-gzip" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
setLoading(false);
|
||||
|
|
|
|||
|
|
@ -102,10 +102,8 @@ function SaveLitematic({ close }: DialogProps) {
|
|||
};
|
||||
|
||||
// Write to file
|
||||
const bytes = await nbt.write(data);
|
||||
const compressed = await nbt.compress(bytes, "gzip");
|
||||
|
||||
const blob = new Blob([compressed], { type: "application/x-gzip" });
|
||||
const bytes = await nbt.write(data, { compression: "gzip" });
|
||||
const blob = new Blob([bytes], { type: "application/x-gzip" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
setLoading(false);
|
||||
|
|
|
|||
Loading…
Reference in a new issue