diff --git a/modules/block-interactions/build.gradle.kts b/modules/block-interactions/build.gradle.kts index 2275e45..6bc20ff 100644 --- a/modules/block-interactions/build.gradle.kts +++ b/modules/block-interactions/build.gradle.kts @@ -9,4 +9,6 @@ dependencies { implementation(project(":modules:player")) compileOnly(project(":modules:quest")) + + implementation("io.github.jglrxavpok.hephaistos:common:2.2.0") } \ No newline at end of file diff --git a/modules/block-interactions/src/main/java/net/hollowcube/blocks/ore/item/PickaxeHandler.java b/modules/block-interactions/src/main/java/net/hollowcube/blocks/ore/item/PickaxeHandler.java index 96ac5d9..c27c8c8 100644 --- a/modules/block-interactions/src/main/java/net/hollowcube/blocks/ore/item/PickaxeHandler.java +++ b/modules/block-interactions/src/main/java/net/hollowcube/blocks/ore/item/PickaxeHandler.java @@ -44,20 +44,21 @@ public PickaxeHandler() { } private void handleLongDiggingStart(PlayerLongDiggingStartEvent event) { - var ore = Ore.fromBlock(event.getBlock()); - if (ore == null || event.getBlock().compare(OreBlockHandler.REPLACEMENT_BLOCK, Block.Comparator.STATE)) return; - - // Ensure they have a pickaxe in hand and get the pickaxe - var item = Item.fromItemStack(event.getPlayer().getItemInMainHand()); - //todo will currently fail on any non-custom item - - var pickaxe = item.getComponent(Pickaxe.class); - if (pickaxe == null) return; // Not holding a pickaxe - - // Start mining the block - event.setDiggingBlock( - ore.health(), - pickaxe::miningSpeed - ); +// var ore = Ore.fromBlock(event.getBlock()); +// if (ore == null || event.getBlock().compare(OreBlockHandler.REPLACEMENT_BLOCK, Block.Comparator.STATE)) return; +// +// // Ensure they have a pickaxe in hand and get the pickaxe +// var item = Item.fromItemStack(event.getPlayer().getItemInMainHand()); +// //todo will currently fail on any non-custom item +// +// var pickaxe = item.getComponent(Pickaxe.class); +// if (pickaxe == null) return; // Not holding a pickaxe +// +// // Start mining the block +// event.setDiggingBlock( +// ore.health(), +// pickaxe::miningSpeed +// ); } + } diff --git a/modules/block-interactions/src/main/java/net/hollowcube/blocks/resource/BlockResource.java b/modules/block-interactions/src/main/java/net/hollowcube/blocks/resource/BlockResource.java new file mode 100644 index 0000000..c485882 --- /dev/null +++ b/modules/block-interactions/src/main/java/net/hollowcube/blocks/resource/BlockResource.java @@ -0,0 +1,4 @@ +package net.hollowcube.blocks.resource; + +public class BlockResource { +} diff --git a/modules/block-interactions/src/main/java/net/hollowcube/blocks/resource/MultiBlockResource.java b/modules/block-interactions/src/main/java/net/hollowcube/blocks/resource/MultiBlockResource.java new file mode 100644 index 0000000..0c6b1b6 --- /dev/null +++ b/modules/block-interactions/src/main/java/net/hollowcube/blocks/resource/MultiBlockResource.java @@ -0,0 +1,48 @@ +package net.hollowcube.blocks.resource; + +//{ +// "namespace": "starlight:oak_tree", +// "loot_table": "starlight:oak_tree", +// "type": "schematic", +// // size: {x: 3, y: 3, z: 3}, // Would prevent a mistake of accidentally adding a big one, im a fan of this +// "schematics": [ +// "tree/foresttree1", +// "tree/foresttree2", +// "tree/foresttree3", +// "tree/foresttree4", +// "tree/foresttree5" +// ] +//} + +import com.google.auto.service.AutoService; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.hollowcube.dfu.ExtraCodecs; +import net.minestom.server.coordinate.Vec; +import net.minestom.server.utils.NamespaceID; + +import java.util.List; + +public record MultiBlockResource( + NamespaceID namespace, + NamespaceID lootTable, + Vec size, + List schematics +) implements Resource { + + public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( + ExtraCodecs.NAMESPACE_ID.fieldOf("namespace").forGetter(MultiBlockResource::namespace), + ExtraCodecs.NAMESPACE_ID.optionalFieldOf("loot_table", NamespaceID.from("starlight:empty")).forGetter(MultiBlockResource::lootTable), + ExtraCodecs.VEC.fieldOf("size").forGetter(MultiBlockResource::size), + Codec.STRING.listOf().fieldOf("schematics").forGetter(MultiBlockResource::schematics) + ).apply(i, MultiBlockResource::new)); + + + @AutoService(Resource.Factory.class) + public static class Factory extends Resource.Factory { + public Factory() { + super("multiblock", MultiBlockResource.class, MultiBlockResource.CODEC); + } + } + +} diff --git a/modules/block-interactions/src/main/java/net/hollowcube/blocks/resource/Resource.java b/modules/block-interactions/src/main/java/net/hollowcube/blocks/resource/Resource.java new file mode 100644 index 0000000..8080f30 --- /dev/null +++ b/modules/block-interactions/src/main/java/net/hollowcube/blocks/resource/Resource.java @@ -0,0 +1,83 @@ +package net.hollowcube.blocks.resource; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.hollowcube.dfu.ExtraCodecs; +import net.hollowcube.lang.LanguageProvider; +import net.hollowcube.registry.Registry; +import net.hollowcube.registry.ResourceFactory; +import net.kyori.adventure.text.Component; +import net.minestom.server.tag.Tag; +import net.minestom.server.utils.NamespaceID; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.UnknownNullability; + +/* + +{ + "namespace": "starlight:oak_tree", + "loot_table": "starlight:oak_tree", + "type": "schematic", + // size: {x: 3, y: 3, z: 3}, // Would prevent a mistake of accidentally adding a big one, im a fan of this + "schematics": [ + "tree/foresttree1", + "tree/foresttree2", + "tree/foresttree3", + "tree/foresttree4", + "tree/foresttree5" + ] +} + +{ + "namespace": "starlight:gold_ore", + "loot_table": "starlight:gold_ore", + "type": "block", + "block": "minecraft:gold_ore", + "replacement": "minecraft:bedrock" +} + + */ + +public interface Resource extends net.hollowcube.registry.Resource { + + Tag TAG = Tag.String("resource"); + + /** + * @return The translation key for this item + * @see LanguageProvider#get(Component) + */ + @Contract(pure = true) + default @NotNull String translationKey() { + return String.format("resource.%s.%s.name", namespace().namespace(), namespace().path()); + } + + Codec CODEC = ExtraCodecs.lazy(() -> Factory.CODEC).dispatch(Factory::from, Factory::codec); + + Registry REGISTRY = Registry.lazy(() -> Registry.codec("resource", CODEC)); + + static @UnknownNullability Resource fromNamespaceId(@NotNull NamespaceID namespace) { + return REGISTRY.get(namespace); + } + + static @UnknownNullability Resource fromNamespaceId(@NotNull String namespace) { + return REGISTRY.get(namespace); + } + + + class Factory extends ResourceFactory { + static Registry REGISTRY = Registry.service("resource_type", Resource.Factory.class); + static Registry.Index, Factory> TYPE_REGISTRY = REGISTRY.index(Factory::type); + + static Codec CODEC = Codec.STRING.xmap(ns -> REGISTRY.required(ns), Factory::name); + + public Factory(String id, Class type, Codec codec) { + super(NamespaceID.from("starlight", id), type, codec); + } + + public static @NotNull Factory from(@NotNull Resource resource) { + return TYPE_REGISTRY.get(resource.getClass()); + } + } + +} diff --git a/modules/block-interactions/src/main/java/net/hollowcube/blocks/resource/ResourceFacet.java b/modules/block-interactions/src/main/java/net/hollowcube/blocks/resource/ResourceFacet.java new file mode 100644 index 0000000..b21725b --- /dev/null +++ b/modules/block-interactions/src/main/java/net/hollowcube/blocks/resource/ResourceFacet.java @@ -0,0 +1,122 @@ +package net.hollowcube.blocks.resource; + +import com.google.auto.service.AutoService; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import net.hollowcube.blocks.resource.handler.ResourceSpawner; +import net.hollowcube.blocks.schem.SchematicManager; +import net.hollowcube.player.event.PlayerLongDiggingStartEvent; +import net.hollowcube.server.Facet; +import net.hollowcube.server.ServerWrapper; +import net.minestom.server.coordinate.Point; +import net.minestom.server.event.EventNode; +import net.minestom.server.event.player.PlayerBlockBreakEvent; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockHandler; +import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.particle.Particle; +import net.minestom.server.particle.ParticleCreator; +import net.minestom.server.utils.Rotation; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.atomic.AtomicInteger; + +@AutoService(Facet.class) +public class ResourceFacet implements Facet { + + // Globally increasing ID for resources in the world + private final AtomicInteger nextId = new AtomicInteger(0); + + // All resources currently in the server + // todo probably would make more sense to do this per instance rather than per server + private final Int2ObjectMap resources = Int2ObjectMaps.synchronize(new Int2ObjectArrayMap<>()); + + private final BlockHandler resourceSpawnerHandler = new ResourceSpawner(this); + + //todo ResourceSpawner block handler which will delay and then spawn a resource in a given position + // added to the world when a resource is removed. + + + @Override + public void hook(@NotNull ServerWrapper server) { + var node = EventNode.all("awghauwghuiawg"); + node.addListener(PlayerLongDiggingStartEvent.class, this::handleDigging); + node.addListener(PlayerBlockBreakEvent.class, this::blockBroken); + server.addEventNode(node); + + server.registerBlockHandler(() -> resourceSpawnerHandler); + } + + public void addResourceSpawner(@NotNull Resource resource, @NotNull Instance instance, @NotNull Point pos) { + var block = instance.getBlock(pos) + .withTag(Resource.TAG, resource.name()) + .withHandler(resourceSpawnerHandler); + instance.setBlock(pos, block); + } + + public void spawnResource(@NotNull Resource resource, @NotNull Instance instance, @NotNull Point pos) { + var worldResource = new WorldResource(nextId.getAndIncrement(), resource, instance, pos); + resources.put(worldResource.id(), worldResource); + + //todo i guess this logic could exist in MultiBlockResource? This is not very generic + if (resource instanceof MultiBlockResource res) { + var schematicName = res.schematics().get(worldResource.id() % res.schematics().size()); + var schematic = SchematicManager.get(schematicName); + var rotation = Rotation.values()[(worldResource.id() % 4) * 2]; + + schematic.applyManual(rotation, (blockPos, block) -> { + var taggedBlock = block.withTag(WorldResource.ID_TAG, worldResource.id()); + instance.setBlock(pos.add(blockPos), taggedBlock); + }); + } + + } + + private void handleDigging(@NotNull PlayerLongDiggingStartEvent event) { + final var block = event.getBlock(); + final var resourceId = block.getTag(WorldResource.ID_TAG); + if (resourceId == null) return; + + //todo get health from resource + event.setDiggingBlock(5, () -> 1); + } + + private void blockBroken(@NotNull PlayerBlockBreakEvent event) { + final var instance = event.getInstance(); + final var block = event.getBlock(); + final var resourceId = block.getTag(WorldResource.ID_TAG); + if (resourceId == null) return; + + var worldResource = resources.get((int) resourceId); + resources.remove((int) resourceId); + + final var pos = worldResource.pos(); + final var res = worldResource.resource(); + if (res instanceof MultiBlockResource resource) { + //todo this might be problematic for "randomness" (getting both schematic and rotation from this number) + var schematicName = resource.schematics().get(resourceId % resource.schematics().size()); + var schematic = SchematicManager.get(schematicName); + var rotation = Rotation.values()[(resourceId % 4) * 2]; + + schematic.applyManual(rotation, (relBlockPos, b) -> { + final var blockPos = pos.add(relBlockPos); + + // Spawn particle + ServerPacket packet = ParticleCreator.createParticlePacket( + Particle.BLOCK, false, blockPos.x(), blockPos.y(), blockPos.z(), 0.51f, 0.51f, 0.51f, + 0.15f, 25, binaryWriter -> binaryWriter.writeVarInt(b.stateId())); + event.getPlayer().sendPacketToViewersAndSelf(packet); + + // Remove the block + instance.setBlock(blockPos, Block.AIR); + }); + + //todo add the resource back to the world after a delay + } + + // Add a resource spawner block at the position + addResourceSpawner(res, instance, pos); + } +} diff --git a/modules/block-interactions/src/main/java/net/hollowcube/blocks/resource/WorldResource.java b/modules/block-interactions/src/main/java/net/hollowcube/blocks/resource/WorldResource.java new file mode 100644 index 0000000..e64357a --- /dev/null +++ b/modules/block-interactions/src/main/java/net/hollowcube/blocks/resource/WorldResource.java @@ -0,0 +1,16 @@ +package net.hollowcube.blocks.resource; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.Instance; +import net.minestom.server.tag.Tag; +import org.jetbrains.annotations.NotNull; + +public record WorldResource( + int id, + @NotNull Resource resource, + @NotNull Instance instance, + @NotNull Point pos +) { + public static final Tag ID_TAG = Tag.Integer("resource_id"); + +} diff --git a/modules/block-interactions/src/main/java/net/hollowcube/blocks/resource/handler/ResourceSpawner.java b/modules/block-interactions/src/main/java/net/hollowcube/blocks/resource/handler/ResourceSpawner.java new file mode 100644 index 0000000..69b4ce3 --- /dev/null +++ b/modules/block-interactions/src/main/java/net/hollowcube/blocks/resource/handler/ResourceSpawner.java @@ -0,0 +1,69 @@ +package net.hollowcube.blocks.resource.handler; + +import net.hollowcube.blocks.resource.Resource; +import net.hollowcube.blocks.resource.ResourceFacet; +import net.hollowcube.debug.Simulation; +import net.minestom.server.instance.block.BlockHandler; +import net.minestom.server.thread.TickThread; +import net.minestom.server.utils.NamespaceID; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ThreadLocalRandom; + +public class ResourceSpawner implements BlockHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(ResourceSpawner.class); + + private final ResourceFacet manager; + + public ResourceSpawner(ResourceFacet manager) { + this.manager = manager; + } + + @Override + public @NotNull NamespaceID getNamespaceId() { + return NamespaceID.from("starlight:resource_spawner"); + } + + @Override + public boolean isTickable() { + return true; + } + + @Override + public void onPlace(@NotNull Placement placement) { + if (!placement.getBlock().hasTag(Resource.TAG)) { + var instance = placement.getInstance(); + var block = placement.getBlock(); + var pos = placement.getBlockPosition(); + + // Block handler should not be present if the resource tag is not present. Remove it and log error. + LOGGER.error("Resource spawner present on a block without resource tag: {} at {} in {}", block, pos, instance); + instance.setBlock(pos, block.withHandler(null)); + + //todo ensure the tag is valid + } + } + + @Override + public void tick(@NotNull Tick tick) { + if (!Simulation.isRunning()) return; + + var thread = TickThread.current(); + if (thread == null) return; + + // Tick once per second + if (thread.getTick() % 20 != 0) return; + // 25% chance to spawn each tick + if (ThreadLocalRandom.current().nextInt(4) != 0) return; + + var block = tick.getBlock(); + var resource = Resource.fromNamespaceId(block.getTag(Resource.TAG)); + assert resource != null; // Checked in placement handler + + var instance = tick.getInstance(); + var pos = tick.getBlockPosition(); + manager.spawnResource(resource, instance, pos); + } +} diff --git a/modules/block-interactions/src/main/java/net/hollowcube/blocks/schem/Schematic.java b/modules/block-interactions/src/main/java/net/hollowcube/blocks/schem/Schematic.java new file mode 100644 index 0000000..590dbad --- /dev/null +++ b/modules/block-interactions/src/main/java/net/hollowcube/blocks/schem/Schematic.java @@ -0,0 +1,150 @@ +package net.hollowcube.blocks.schem; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.coordinate.Vec; +import net.minestom.server.instance.batch.BatchOption; +import net.minestom.server.instance.batch.RelativeBlockBatch; +import net.minestom.server.instance.block.Block; +import net.minestom.server.utils.Rotation; +import net.minestom.server.utils.Utils; +import org.jetbrains.annotations.NotNull; + +import java.nio.ByteBuffer; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +public class Schematic { + private final Point size; + private final Point offset; + + private final Block[] palette; + private final byte[] blocks; + + public Schematic(Point size, Point offset, Block[] palette, byte[] blocks) { + this.size = size; + this.offset = offset; + this.palette = palette; + this.blocks = blocks; + } + + public Point size() { + return size; + } + + public Point offset() { + return offset; + } + + public @NotNull RelativeBlockBatch build(@NotNull Rotation rawRotation, Function blockModifier) { + Rotation rotation = switch (rawRotation) { + case NONE -> Rotation.NONE; + case CLOCKWISE -> Rotation.CLOCKWISE; + case FLIPPED -> Rotation.FLIPPED; + case COUNTER_CLOCKWISE -> Rotation.COUNTER_CLOCKWISE; + default -> throw new IllegalArgumentException("Unexpected value: " + rawRotation); + }; + + ByteBuffer blocks = ByteBuffer.wrap(this.blocks); + RelativeBlockBatch batch = new RelativeBlockBatch(new BatchOption().setCalculateInverse(true)); + for (int y = 0; y < size().y(); y++) { + for (int z = 0; z < size.z(); z++) { + for (int x = 0; x < size.x(); x++) { + int blockVal = Utils.readVarInt(blocks); + Block b = palette[blockVal]; + + if (b == null || b.isAir()) { + continue; + } + + Vec pos = new Vec(x + offset.x(), y + offset.y(), z + offset.z()); + batch.setBlock(rotatePos(pos, rotation), blockModifier.apply(rotateBlock(b, rotation))); + } + } + } + + return batch; + } + + public void applyManual(Rotation rawRotation, BiConsumer applicator) { + Rotation rotation = switch (rawRotation) { + case NONE -> Rotation.NONE; + case CLOCKWISE -> Rotation.CLOCKWISE; + case FLIPPED -> Rotation.FLIPPED; + case COUNTER_CLOCKWISE -> Rotation.COUNTER_CLOCKWISE; + default -> throw new IllegalArgumentException("Unexpected value: " + rawRotation); + }; + + ByteBuffer blocks = ByteBuffer.wrap(this.blocks); + for (int y = 0; y < size().y(); y++) { + for (int z = 0; z < size.z(); z++) { + for (int x = 0; x < size.x(); x++) { + int blockVal = Utils.readVarInt(blocks); + Block b = palette[blockVal]; + + if (b == null || b.isAir()) { + continue; + } + + Vec blockPos = new Vec(x + offset.x(), y + offset.y(), z + offset.z()); + applicator.accept(rotatePos(blockPos, rotation), rotateBlock(b, rotation)); + } + } + } + } + + public void forEachPoint(Point base, Consumer pointConsumer) { + ByteBuffer blocks = ByteBuffer.wrap(this.blocks); + for (int y = 0; y < size().y(); y++) { + for (int z = 0; z < size.z(); z++) { + for (int x = 0; x < size.x(); x++) { + int blockVal = Utils.readVarInt(blocks); + Block b = palette[blockVal]; + + if (b == null || b.isAir()) { + continue; + } + + pointConsumer.accept(base.add(x + offset.x(), y + offset.y(), z + offset.z())); + } + } + } + } + + private @NotNull Point rotatePos(@NotNull Point point, @NotNull Rotation rotation) { + return switch (rotation) { + case NONE -> point; + case CLOCKWISE -> new Vec(-point.z(), point.y(), point.x()); + case FLIPPED -> new Vec(-point.x(), point.y(), -point.z()); + case COUNTER_CLOCKWISE -> new Vec(point.z(), point.y(), -point.x()); + default -> throw new IllegalArgumentException("Unexpected value: " + rotation); + }; + } + + private @NotNull Block rotateBlock(@NotNull Block block, @NotNull Rotation rotation) { + if (block.name().contains("stair")) { + return rotateStair(block, rotation); + } else { + return block; + } + } + + private static Block rotateStair(Block block, Rotation rotation) { + return switch (rotation) { + case NONE -> block; + case CLOCKWISE -> block.withProperty("facing", rotate90(block.getProperty("facing"))); + case FLIPPED -> block.withProperty("facing", rotate90(rotate90(block.getProperty("facing")))); + case COUNTER_CLOCKWISE -> block.withProperty("facing", rotate90(rotate90(rotate90(block.getProperty("facing"))))); + default -> throw new IllegalArgumentException("Unexpected value: " + rotation); + }; + } + + private static String rotate90(String in) { + return switch (in) { + case "north" -> "east"; + case "east" -> "south"; + case "south" -> "west"; + default -> "north"; + }; + } +} diff --git a/modules/block-interactions/src/main/java/net/hollowcube/blocks/schem/SchematicManager.java b/modules/block-interactions/src/main/java/net/hollowcube/blocks/schem/SchematicManager.java new file mode 100644 index 0000000..79e9eca --- /dev/null +++ b/modules/block-interactions/src/main/java/net/hollowcube/blocks/schem/SchematicManager.java @@ -0,0 +1,26 @@ +package net.hollowcube.blocks.schem; + +import net.hollowcube.registry.Resource; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.UnknownNullability; + +import java.nio.file.Path; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class SchematicManager { + private static final Path BASE_DIR = Resource.DATA_PATH.resolve("schem"); + private static final Map cache = new ConcurrentHashMap<>(); + + public static @UnknownNullability Schematic get(@NotNull String name) { + return cache.computeIfAbsent(name, unused -> { + try { + var path = BASE_DIR.resolve(name + ".schem"); + return SchematicReader.read(path); + } catch (Exception e) { + //todo better error handling here as well as in the reader + throw new RuntimeException(e); + } + }); + } +} diff --git a/modules/block-interactions/src/main/java/net/hollowcube/blocks/schem/SchematicReader.java b/modules/block-interactions/src/main/java/net/hollowcube/blocks/schem/SchematicReader.java new file mode 100644 index 0000000..a9d5e91 --- /dev/null +++ b/modules/block-interactions/src/main/java/net/hollowcube/blocks/schem/SchematicReader.java @@ -0,0 +1,55 @@ +package net.hollowcube.blocks.schem; + +import net.minestom.server.command.builder.arguments.minecraft.ArgumentBlockState; +import net.minestom.server.coordinate.Vec; +import net.minestom.server.instance.block.Block; +import org.jetbrains.annotations.NotNull; +import org.jglrxavpok.hephaistos.collections.ImmutableByteArray; +import org.jglrxavpok.hephaistos.nbt.CompressedProcesser; +import org.jglrxavpok.hephaistos.nbt.NBTCompound; +import org.jglrxavpok.hephaistos.nbt.NBTInt; +import org.jglrxavpok.hephaistos.nbt.NBTReader; + +import java.nio.file.Path; + +public class SchematicReader { + + public static Schematic read(@NotNull Path path) throws Exception { + var reader = new NBTReader(path, CompressedProcesser.GZIP); + + NBTCompound tag = (NBTCompound) reader.read(); + + short width = tag.getShort("Width"); + short height = tag.getShort("Height"); + short length = tag.getShort("Length"); + + NBTCompound metadata = tag.getCompound("Metadata"); + + int offsetX = metadata.getInt("WEOffsetX"); + int offsetY = metadata.getInt("WEOffsetY"); + int offsetZ = metadata.getInt("WEOffsetZ"); + + NBTCompound palette = tag.getCompound("Palette"); + ImmutableByteArray blockArray = tag.getByteArray("BlockData"); + + int paletteSize = tag.getInt("PaletteMax"); + + Block[] paletteBlocks = new Block[paletteSize]; + + ArgumentBlockState state = new ArgumentBlockState(""); + + palette.forEach((key, value) -> { + int assigned = ((NBTInt) value).getValue(); + Block block = state.parse(key); + paletteBlocks[assigned] = block; + }); + + return new Schematic( + new Vec(width, height, length), + new Vec(offsetX, offsetY, offsetZ), + paletteBlocks, + blockArray.copyArray() + ); + } + +} diff --git a/modules/block-interactions/src/test/resources/twobytes.schem b/modules/block-interactions/src/test/resources/twobytes.schem new file mode 100644 index 0000000..d351ee2 Binary files /dev/null and b/modules/block-interactions/src/test/resources/twobytes.schem differ diff --git a/modules/common/src/main/java/net/hollowcube/debug/Simulation.java b/modules/common/src/main/java/net/hollowcube/debug/Simulation.java new file mode 100644 index 0000000..6e08a55 --- /dev/null +++ b/modules/common/src/main/java/net/hollowcube/debug/Simulation.java @@ -0,0 +1,21 @@ +package net.hollowcube.debug; + +import java.util.concurrent.atomic.AtomicBoolean; + +public final class Simulation { + private static final AtomicBoolean running = new AtomicBoolean(true); + + private Simulation() {} + + public static void play() { + running.set(true); + } + + public static void pause() { + running.set(false); + } + + public static boolean isRunning() { + return running.get(); + } +} diff --git a/modules/common/src/main/java/net/hollowcube/dfu/ExtraCodecs.java b/modules/common/src/main/java/net/hollowcube/dfu/ExtraCodecs.java index 1e42a46..906d002 100644 --- a/modules/common/src/main/java/net/hollowcube/dfu/ExtraCodecs.java +++ b/modules/common/src/main/java/net/hollowcube/dfu/ExtraCodecs.java @@ -6,6 +6,8 @@ import com.mojang.serialization.DynamicOps; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.PrimitiveCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minestom.server.coordinate.Vec; import net.minestom.server.instance.block.Block; import net.minestom.server.item.Material; import net.minestom.server.potion.PotionEffect; @@ -39,6 +41,12 @@ public T write(DynamicOps ops, Number value) { public static final Codec BLOCK = Codec.STRING.xmap(Block::fromNamespaceId, Block::name); + public static final Codec VEC = RecordCodecBuilder.create(i -> i.group( + Codec.DOUBLE.fieldOf("x").forGetter(Vec::x), + Codec.DOUBLE.fieldOf("y").forGetter(Vec::y), + Codec.DOUBLE.fieldOf("z").forGetter(Vec::z) + ).apply(i, Vec::new)); + public static @NotNull MapCodec string(@NotNull String name, @Nullable String defaultValue) { return Codec.STRING.optionalFieldOf(name, defaultValue); } diff --git a/modules/common/src/main/java/net/hollowcube/lang/LanguageProvider.java b/modules/common/src/main/java/net/hollowcube/lang/LanguageProvider.java index 11c39f8..2a16f3a 100644 --- a/modules/common/src/main/java/net/hollowcube/lang/LanguageProvider.java +++ b/modules/common/src/main/java/net/hollowcube/lang/LanguageProvider.java @@ -32,6 +32,10 @@ public class LanguageProvider { private static final Pattern ARG_PATTERN = Pattern.compile("\\{[0-9]+}"); + public static @NotNull Component get(@NotNull String key) { + return get(Component.translatable(key)); + } + /** * Translates a component (if possible, see below). *

diff --git a/modules/common/src/main/java/net/hollowcube/registry/LazyRegistry.java b/modules/common/src/main/java/net/hollowcube/registry/LazyRegistry.java new file mode 100644 index 0000000..99b57c3 --- /dev/null +++ b/modules/common/src/main/java/net/hollowcube/registry/LazyRegistry.java @@ -0,0 +1,47 @@ +package net.hollowcube.registry; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.function.Function; +import java.util.function.Supplier; + +public class LazyRegistry implements Registry { + private final Supplier> registrySupplier; + private Registry registry = null; + + public LazyRegistry(Supplier> registrySupplier) { + this.registrySupplier = registrySupplier; + } + + @Override + public @Nullable T getRaw(String namespace) { + return getRegistry().getRaw(namespace); + } + + @Override + public @NotNull Collection keys() { + return getRegistry().keys(); + } + + @Override + public @NotNull Collection values() { + return getRegistry().values(); + } + + @Override + public int size() { + return getRegistry().size(); + } + + @Override + public @NotNull Index index(Function mapper) { + return getRegistry().index(mapper); + } + + private Registry getRegistry() { + if (registry == null) registry = registrySupplier.get(); + return registry; + } +} diff --git a/modules/common/src/main/java/net/hollowcube/registry/Registry.java b/modules/common/src/main/java/net/hollowcube/registry/Registry.java index 18c6091..d17509a 100644 --- a/modules/common/src/main/java/net/hollowcube/registry/Registry.java +++ b/modules/common/src/main/java/net/hollowcube/registry/Registry.java @@ -147,6 +147,10 @@ static Registry codec(@NotNull String name, @NotNull Cod return new MapRegistry<>(registry); } + static Registry lazy(Supplier> supplier) { + return new LazyRegistry<>(supplier); + } + // Impl