diff --git a/src/main/java/ru/nanit/limbo/protocol/ByteMessage.java b/src/main/java/ru/nanit/limbo/protocol/ByteMessage.java index e3550c3..49facd1 100644 --- a/src/main/java/ru/nanit/limbo/protocol/ByteMessage.java +++ b/src/main/java/ru/nanit/limbo/protocol/ByteMessage.java @@ -160,9 +160,7 @@ public class ByteMessage extends ByteBuf { } public void writeCompoundTagArray(CompoundBinaryTag[] compoundTags) { - try { - ByteBufOutputStream stream = new ByteBufOutputStream(buf); - + try (ByteBufOutputStream stream = new ByteBufOutputStream(buf)) { writeVarInt(compoundTags.length); for (CompoundBinaryTag tag : compoundTags) { @@ -174,16 +172,16 @@ public class ByteMessage extends ByteBuf { } public CompoundBinaryTag readCompoundTag() { - try { - return BinaryTagIO.reader().read((InputStream) new ByteBufInputStream(buf)); + try (ByteBufInputStream stream = new ByteBufInputStream(buf)) { + return BinaryTagIO.reader().read((InputStream) stream); } catch (IOException thrown) { throw new DecoderException("Cannot read NBT CompoundTag"); } } public void writeCompoundTag(CompoundBinaryTag compoundTag) { - try { - BinaryTagIO.writer().write(compoundTag, (OutputStream) new ByteBufOutputStream(buf)); + try (ByteBufOutputStream stream = new ByteBufOutputStream(buf)) { + BinaryTagIO.writer().write(compoundTag, (OutputStream) stream); } catch (IOException e) { throw new EncoderException("Cannot write NBT CompoundTag"); } diff --git a/src/main/java/ru/nanit/limbo/protocol/PacketSnapshot.java b/src/main/java/ru/nanit/limbo/protocol/PacketSnapshot.java index 8034c16..602e908 100644 --- a/src/main/java/ru/nanit/limbo/protocol/PacketSnapshot.java +++ b/src/main/java/ru/nanit/limbo/protocol/PacketSnapshot.java @@ -22,14 +22,19 @@ import ru.nanit.limbo.protocol.registry.Version; import java.util.HashMap; import java.util.Map; +/** + * PacketSnapshot encodes packet to byt array for each MC version. + * Some versions have same bytes snapshot, so there are mappings + * to avoid storing same byte array for different versions + */ public class PacketSnapshot implements PacketOut { private final PacketOut packet; - private final Map versionMessages; + private final Map versionMessages = new HashMap<>(); + private final Map mappings = new HashMap<>(); public PacketSnapshot(PacketOut packet) { this.packet = packet; - this.versionMessages = new HashMap<>(); } public PacketOut getWrappedPacket() { @@ -37,11 +42,24 @@ public class PacketSnapshot implements PacketOut { } public PacketSnapshot encodePacket() { + Map hashes = new HashMap<>(); + for (Version version : Version.values()) { + if (version.equals(Version.UNDEFINED)) continue; + ByteMessage encodedMessage = ByteMessage.create(); packet.encode(encodedMessage, version); - byte[] message = encodedMessage.toByteArray(); - versionMessages.put(version, message); + + int hash = encodedMessage.hashCode(); + Version hashed = hashes.get(hash); + + if (hashed != null) { + mappings.put(version, hashed); + continue; + } + + hashes.put(hash, version); + versionMessages.put(version, encodedMessage.toByteArray()); encodedMessage.release(); } @@ -50,11 +68,11 @@ public class PacketSnapshot implements PacketOut { @Override public void encode(ByteMessage msg, Version version) { - byte[] message = versionMessages.get(version); + Version mapped = mappings.get(version); + byte[] message = versionMessages.get(mapped); - if (message != null) { + if (message != null) msg.writeBytes(message); - } } @Override diff --git a/src/main/java/ru/nanit/limbo/world/BlockMap.java b/src/main/java/ru/nanit/limbo/world/BlockMap.java deleted file mode 100644 index e69a981..0000000 --- a/src/main/java/ru/nanit/limbo/world/BlockMap.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2020 Nan1t - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package ru.nanit.limbo.world; - -import ru.nanit.limbo.protocol.registry.Version; - -public final class BlockMap { - - public BlockData convert(int id, byte data, Version version) { - // TODO - return null; - } - - public BlockData convert(String state, Version version) { - // TODO - return null; - } - -} diff --git a/src/main/java/ru/nanit/limbo/world/BlockMappings.java b/src/main/java/ru/nanit/limbo/world/BlockMappings.java new file mode 100644 index 0000000..0f5360e --- /dev/null +++ b/src/main/java/ru/nanit/limbo/world/BlockMappings.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 Nan1t + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package ru.nanit.limbo.world; + +import ru.nanit.limbo.protocol.registry.Version; + +import java.util.HashMap; +import java.util.Map; + +public final class BlockMappings { + + private final Map idToState = new HashMap<>(); + private final Map stateToId = new HashMap<>(); + + public BlockData convert(int id, byte data, Version version) { + if (version.less(Version.V1_13)) + return new BlockData(id, data); + + String state = idToState.get(toId(id, data)); + + return state != null ? new BlockData(state) : null; + } + + public BlockData convert(String state, Version version) { + if (state == null) return null; + + if (version.moreOrEqual(Version.V1_13)) { + return new BlockData(state); + } + + String id = stateToId.get(state); + + if (id != null) { + String[] arr = id.split(":"); + int blockId = Integer.parseInt(arr[0]); + byte data = Byte.parseByte(arr[1]); + + return new BlockData(blockId, data); + } + + return null; + } + + public void register(int id, byte data, String state) { + String strId = toId(id, data); + idToState.put(strId, state); + stateToId.put(state, strId); + } + + private String toId(int id, byte data) { + return id + ":" + data; + } +} diff --git a/src/main/java/ru/nanit/limbo/world/dimension/DimensionRegistry.java b/src/main/java/ru/nanit/limbo/world/dimension/DimensionRegistry.java index e428032..60c07b3 100644 --- a/src/main/java/ru/nanit/limbo/world/dimension/DimensionRegistry.java +++ b/src/main/java/ru/nanit/limbo/world/dimension/DimensionRegistry.java @@ -89,9 +89,15 @@ public final class DimensionRegistry { return TagStringIO.get().asCompound(streamToString(in)); } - private String streamToString(InputStream in) { - return new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)) - .lines() + private String streamToString(InputStream in) throws IOException { + InputStreamReader isReader = new InputStreamReader(in, StandardCharsets.UTF_8); + BufferedReader bufReader = new BufferedReader(isReader); + String content = bufReader.lines() .collect(Collectors.joining("\n")); + + isReader.close(); + bufReader.close(); + + return content; } } diff --git a/src/main/java/ru/nanit/limbo/world/schematic/AbstractSchematic.java b/src/main/java/ru/nanit/limbo/world/schematic/AbstractSchematic.java new file mode 100644 index 0000000..24ccf1e --- /dev/null +++ b/src/main/java/ru/nanit/limbo/world/schematic/AbstractSchematic.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2020 Nan1t + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package ru.nanit.limbo.world.schematic; + +import ru.nanit.limbo.world.BlockEntity; +import ru.nanit.limbo.world.BlockMappings; + +import java.util.List; + +public abstract class AbstractSchematic implements Schematic { + + protected final BlockMappings mappings; + + private int width; + private int height; + private int length; + private List blockEntities; + + public AbstractSchematic(BlockMappings mappings) { + this.mappings = mappings; + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight() { + return height; + } + + @Override + public int getLength() { + return length; + } + + @Override + public List getBlockEntities() { + return blockEntities; + } + + public void setWidth(int width) { + this.width = width; + } + + public void setHeight(int height) { + this.height = height; + } + + public void setLength(int length) { + this.length = length; + } + + public void setBlockEntities(List blockEntities) { + this.blockEntities = blockEntities; + } +} diff --git a/src/main/java/ru/nanit/limbo/world/schematic/Schematic.java b/src/main/java/ru/nanit/limbo/world/schematic/Schematic.java index 548bbdf..24d0102 100644 --- a/src/main/java/ru/nanit/limbo/world/schematic/Schematic.java +++ b/src/main/java/ru/nanit/limbo/world/schematic/Schematic.java @@ -20,7 +20,6 @@ package ru.nanit.limbo.world.schematic; import ru.nanit.limbo.protocol.registry.Version; import ru.nanit.limbo.world.BlockData; import ru.nanit.limbo.world.BlockEntity; -import ru.nanit.limbo.world.BlockMap; import ru.nanit.limbo.world.Location; import java.util.List; @@ -35,6 +34,6 @@ public interface Schematic { List getBlockEntities(); - BlockData getBlock(Location loc, Version version, BlockMap mappings); + BlockData getBlock(Location loc, Version version); } diff --git a/src/main/java/ru/nanit/limbo/world/schematic/SchematicLoader.java b/src/main/java/ru/nanit/limbo/world/schematic/SchematicLoader.java index 591f58f..70596a0 100644 --- a/src/main/java/ru/nanit/limbo/world/schematic/SchematicLoader.java +++ b/src/main/java/ru/nanit/limbo/world/schematic/SchematicLoader.java @@ -21,6 +21,7 @@ import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.nbt.BinaryTagIO; import net.kyori.adventure.nbt.CompoundBinaryTag; import ru.nanit.limbo.world.BlockEntity; +import ru.nanit.limbo.world.BlockMappings; import ru.nanit.limbo.world.Location; import ru.nanit.limbo.world.schematic.versions.LegacySchematic; import ru.nanit.limbo.world.schematic.versions.SpongeSchematic; @@ -36,6 +37,12 @@ import java.util.Map; public class SchematicLoader { + private final BlockMappings mappings; + + public SchematicLoader(BlockMappings mappings) { + this.mappings = mappings; + } + public Schematic load(Path file) throws IOException { return load(Files.newInputStream(file)); } @@ -43,16 +50,16 @@ public class SchematicLoader { public Schematic load(InputStream stream) throws IOException { CompoundBinaryTag nbt = BinaryTagIO.unlimitedReader().read(stream, BinaryTagIO.Compression.GZIP); - if (nbt.getCompound("BlockData") == CompoundBinaryTag.empty()) { - return loadLegacy(nbt); - } else { + if (nbt.keySet().contains("BlockData")) { return loadSponge(nbt); + } else { + return loadLegacy(nbt); } } // Specification: https://github.com/SpongePowered/Schematic-Specification/blob/master/versions/schematic-2.md private Schematic loadSponge(CompoundBinaryTag nbt) { - SpongeSchematic schematic = new SpongeSchematic(); + SpongeSchematic schematic = new SpongeSchematic(mappings); schematic.setDataVersion(nbt.getInt("DataVersion")); @@ -93,7 +100,7 @@ public class SchematicLoader { } private Schematic loadLegacy(CompoundBinaryTag nbt) { - LegacySchematic schematic = new LegacySchematic(); + LegacySchematic schematic = new LegacySchematic(mappings); schematic.setWidth(nbt.getShort("Width")); schematic.setHeight(nbt.getShort("Height")); diff --git a/src/main/java/ru/nanit/limbo/world/schematic/versions/LegacySchematic.java b/src/main/java/ru/nanit/limbo/world/schematic/versions/LegacySchematic.java index 9a35a8e..70614b6 100644 --- a/src/main/java/ru/nanit/limbo/world/schematic/versions/LegacySchematic.java +++ b/src/main/java/ru/nanit/limbo/world/schematic/versions/LegacySchematic.java @@ -19,68 +19,33 @@ package ru.nanit.limbo.world.schematic.versions; import ru.nanit.limbo.protocol.registry.Version; import ru.nanit.limbo.world.BlockData; -import ru.nanit.limbo.world.BlockEntity; -import ru.nanit.limbo.world.BlockMap; +import ru.nanit.limbo.world.BlockMappings; import ru.nanit.limbo.world.Location; -import ru.nanit.limbo.world.schematic.Schematic; - -import java.util.List; +import ru.nanit.limbo.world.schematic.AbstractSchematic; /** * Legacy schematic format (1.12-) */ -public class LegacySchematic implements Schematic { +public class LegacySchematic extends AbstractSchematic { - private short width; - private short height; - private short length; private Materials materials; private byte[] blocks; private byte[] addBlocks; private byte[] data; - private List blockEntities; - @Override - public int getWidth() { - return width; + public LegacySchematic(BlockMappings mappings) { + super(mappings); } @Override - public int getHeight() { - return height; - } - - @Override - public int getLength() { - return length; - } - - @Override - public List getBlockEntities() { - return blockEntities; - } - - @Override - public BlockData getBlock(Location loc, Version version, BlockMap mappings) { - int index = (loc.getBlockY() * length + loc.getBlockZ()) * width + loc.getBlockX(); + public BlockData getBlock(Location loc, Version version) { + int index = (loc.getBlockY() * getLength() + loc.getBlockZ()) * getWidth() + loc.getBlockX(); byte id = this.blocks[index]; byte data = this.data[index]; return mappings.convert(id, data, version); } - public void setWidth(short width) { - this.width = width; - } - - public void setHeight(short height) { - this.height = height; - } - - public void setLength(short length) { - this.length = length; - } - public void setMaterials(Materials materials) { this.materials = materials; } @@ -97,21 +62,17 @@ public class LegacySchematic implements Schematic { this.data = data; } - public void setBlockEntities(List blockEntities) { - this.blockEntities = blockEntities; - } - @Override public String toString() { return "Schematic{" + - "width=" + width + - ", height=" + height + - ", length=" + length + + "width=" + getWidth() + + ", height=" + getHeight() + + ", length=" + getLength() + ", materials=" + materials + ", blocks length=" + blocks.length + ", addBlocks length=" + addBlocks.length + ", data length=" + data.length + - ", blockEntities=" + blockEntities + + ", blockEntities=" + getBlockEntities() + '}'; } diff --git a/src/main/java/ru/nanit/limbo/world/schematic/versions/SpongeSchematic.java b/src/main/java/ru/nanit/limbo/world/schematic/versions/SpongeSchematic.java index 5fc7b89..bc6010c 100644 --- a/src/main/java/ru/nanit/limbo/world/schematic/versions/SpongeSchematic.java +++ b/src/main/java/ru/nanit/limbo/world/schematic/versions/SpongeSchematic.java @@ -19,51 +19,29 @@ package ru.nanit.limbo.world.schematic.versions; import ru.nanit.limbo.protocol.registry.Version; import ru.nanit.limbo.world.BlockData; -import ru.nanit.limbo.world.BlockEntity; -import ru.nanit.limbo.world.BlockMap; +import ru.nanit.limbo.world.BlockMappings; import ru.nanit.limbo.world.Location; -import ru.nanit.limbo.world.schematic.Schematic; +import ru.nanit.limbo.world.schematic.AbstractSchematic; -import java.util.List; import java.util.Map; /** * Modern schematic format (Sponge second specification) */ -public class SpongeSchematic implements Schematic { +public class SpongeSchematic extends AbstractSchematic { private int dataVersion; - private int width; - private int height; - private int length; private int paletteMax; private Map palette; private byte[] blockData; - private List blockEntities; - @Override - public int getWidth() { - return width; + public SpongeSchematic(BlockMappings mappings) { + super(mappings); } @Override - public int getHeight() { - return height; - } - - @Override - public int getLength() { - return length; - } - - @Override - public List getBlockEntities() { - return blockEntities; - } - - @Override - public BlockData getBlock(Location loc, Version version, BlockMap mappings) { - int index = loc.getBlockX() + loc.getBlockZ() * width + loc.getBlockY() * width * length; + public BlockData getBlock(Location loc, Version version) { + int index = loc.getBlockX() + loc.getBlockZ() * getWidth() + loc.getBlockY() * getWidth() * getLength(); int id = blockData[index]; String state = palette.get(id); return mappings.convert(state, version); @@ -73,18 +51,6 @@ public class SpongeSchematic implements Schematic { this.dataVersion = dataVersion; } - public void setWidth(int width) { - this.width = width; - } - - public void setHeight(int height) { - this.height = height; - } - - public void setLength(int length) { - this.length = length; - } - public void setPaletteMax(int paletteMax) { this.paletteMax = paletteMax; } @@ -97,7 +63,17 @@ public class SpongeSchematic implements Schematic { this.blockData = blockData; } - public void setBlockEntities(List blockEntities) { - this.blockEntities = blockEntities; + @Override + public String toString() { + return "SpongeSchematic{" + + "dataVersion=" + dataVersion + + ", width=" + getWidth() + + ", height=" + getHeight() + + ", length=" + getLength() + + ", paletteMax=" + paletteMax + + ", palette=" + palette + + ", blockData bytes=" + blockData.length + + ", blockEntities=" + getBlockEntities() + + '}'; } } diff --git a/src/test/java/SchematicTest.java b/src/test/java/SchematicTest.java index a23a054..157e434 100644 --- a/src/test/java/SchematicTest.java +++ b/src/test/java/SchematicTest.java @@ -16,6 +16,7 @@ */ import org.junit.jupiter.api.Test; +import ru.nanit.limbo.world.BlockMappings; import ru.nanit.limbo.world.schematic.SchematicLoader; import java.io.IOException; @@ -25,10 +26,10 @@ public class SchematicTest { @Test public void testLoading() throws IOException { - SchematicLoader loader = new SchematicLoader(); - InputStream stream = getClass().getResourceAsStream("test.schematic"); + BlockMappings mappings = new BlockMappings(); + SchematicLoader loader = new SchematicLoader(mappings); + InputStream stream = getClass().getResourceAsStream("spawn.schem"); System.out.println(loader.load(stream)); } - } diff --git a/src/test/resources/spawn.schem b/src/test/resources/spawn.schem new file mode 100644 index 0000000..0a800b5 Binary files /dev/null and b/src/test/resources/spawn.schem differ