diff --git a/src/main/java/com/portingdeadmods/modjam/api/blockentities/MultiblockEntity.java b/src/main/java/com/portingdeadmods/modjam/api/blockentities/MultiblockEntity.java new file mode 100644 index 00000000..405ee064 --- /dev/null +++ b/src/main/java/com/portingdeadmods/modjam/api/blockentities/MultiblockEntity.java @@ -0,0 +1,4 @@ +package com.portingdeadmods.modjam.api.blockentities; + +public interface MultiblockEntity { +} diff --git a/src/main/java/com/portingdeadmods/modjam/api/multiblocks/Multiblock.java b/src/main/java/com/portingdeadmods/modjam/api/multiblocks/Multiblock.java new file mode 100644 index 00000000..297e26f1 --- /dev/null +++ b/src/main/java/com/portingdeadmods/modjam/api/multiblocks/Multiblock.java @@ -0,0 +1,208 @@ +package com.portingdeadmods.modjam.api.multiblocks; + +import com.portingdeadmods.modjam.api.blockentities.MultiblockEntity; +import com.portingdeadmods.modjam.api.utils.HorizontalDirection; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.IntIntPair; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import org.apache.commons.lang3.IntegerRange; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public interface Multiblock { + /** + * This method provides the controller block of your unformed multiblock. + * Your multiblock needs at least one of these in its structure. + *
+ *
+ * Example: {@link com.indref.industrial_reforged.registries.multiblocks.BlastFurnaceMultiblock#getUnformedController() BlastFurnaceMultiblock.getUnformedController()} + *
+ * @return The controller block of your unformed multiblock + */ + Block getUnformedController(); + + /** + * This method provides the controller block of your formed multiblock. + * Your multiblock needs at least one of these in its structure. + *
+ *
+ * Example: {@link com.indref.industrial_reforged.registries.multiblocks.BlastFurnaceMultiblock#getUnformedController() BlastFurnaceMultiblock.getFormedController()} + *
+ * @return The controller block of your formed multiblock + */ + Block getFormedController(); + + /** + * This method provides the layout of your unformed multiblock. + *
+ * It consists of an array of multiblock layers. Each layer + * is constructed with a method call. + *
+ * For this, you can use {@link Multiblock#layer(int...)} + *
+ * Each of these methods ask you to provide you a list of integers. + * These integers represent the actual blocks used. + * Nonetheless, you still need to provide the actual blocks using + * the {@link Multiblock#getDefinition()} method. + * This provides the minimum and maximum height for this multiblock. + *
+ * Example: {@code IntegerRange.of(1, 3)} + *
+ *
+ * Note: The first layer in this array also represents the bottom layer of the multiblock + *
+ *
+ * Example: {@link com.indref.industrial_reforged.registries.multiblocks.BlastFurnaceMultiblock#getLayout() BlastFurnaceMultiblock.getLayout()}. + * @return An array of multiblock layers that describes the layout of the multiblock + */ + MultiblockLayer[] getLayout(); + + /** + * This method provides a definition map that can be used to look up + * an integer key in {@link Multiblock#getLayout()} and will return a block. + *
+ *
+ * The keyset of this map needs to include + * every key that is used in {@link Multiblock#getLayout()}. + *
+ *
+ * The values of this map need to contain the block for each + * integer key. If you do not care about a block you can use {@code null} + * instead of a value. + *
+ *
+ * Example: {@link com.indref.industrial_reforged.registries.multiblocks.BlastFurnaceMultiblock#getDefinition() BlastFurnaceMultiblock.getDefintion()} + * @return The integer to block map that provides the integer keys and their block values + */ + Int2ObjectMap getDefinition(); + + /** + * This method provides the block entity type for the controller of your multiblock. + * @return the blockentity type of your controllers blockentity + */ + BlockEntityType getMultiBlockEntityType(); + + /** + * This method provides a list of widths for every layer + * of your multiblock. + *
+ *
+ * This method has a default implementation meaning that + * you do not have to override it, unless one of your + * multiblock layers is not quadratic. (And it's width + * can therefore not be determined by getting the + * square root of the integer arrays length) + *
+ *
+ * The size of this list needs to be {@link Multiblock#getMaxSize()} + * and needs to contain the widths for every possible layer, this also + * includes dynamic layers. + * + * @return a list of integer pairs where left is the x- and right is the z-width + */ + default List getWidths() { + List widths = new ArrayList<>(getMaxSize()); + for (MultiblockLayer layer : getLayout()) { + if (layer.dynamic()) { + for (int i = 0; i < layer.range().getMaximum(); i++) { + widths.add(layer.getWidths()); + } + } else { + widths.add(layer.getWidths()); + } + } + return widths; + } + + /** + * This method is used to form a block. It is called for that block and also when unforming the multi. + * This is why this should only return the blockState, not perform any interactions on the level/player.... + * For interactions with the world/player..., use {@link Multiblock#afterFormBlock(Level, BlockPos, BlockPos, int, int, MultiblockData, Player)} + * @param level Level of the multiblock, should only be used for reading things, not setting new things. + * @param blockPos BlockPos of the block that is being formed + * @param controllerPos BlockPos of this multiblocks controller + * @param layerIndex index of the current layers block (array of integer) + * @param layoutIndex index of the current multiblock layer (array of multiblock layer) + * @param multiblockData Information about the unformed multiblock, like the layers of the concrete multiblock and the direction it is formed in. + * @param player Player that is trying to form this multiblock. Note that there does not necessarily have to be a player that is responsible for forming the multiblock + * @return Formed BlockState. This will replace the unformed block in the multiblock. Return {@code null} if you do not want to change the block. + */ + @Nullable BlockState formBlock(Level level, BlockPos blockPos, BlockPos controllerPos, int layerIndex, int layoutIndex, MultiblockData multiblockData, @Nullable Player player); + + /** + * This method is called after the block is formed. It can be used to interact with the level/player... + * as it is only called, when the multiblock is formed. + * @param level Level of the multiblock + * @param blockPos BlockPos of the block that is being formed + * @param controllerPos BlockPos of this multiblocks controller + * @param layerIndex index of the current layers block (array of integer) + * @param layoutIndex index of the current multiblock layer (array of multiblock layer) + * @param multiblockData Information about the unformed multiblock, like the layers of the concrete multiblock and the direction it is formed in. + * @param player Player that is trying to form this multiblock. Note that there does not necessarily have to be a player that is responsible for forming the multiblock + */ + default void afterFormBlock(Level level, BlockPos blockPos, BlockPos controllerPos, int layerIndex, int layoutIndex, MultiblockData multiblockData, @Nullable Player player) { + } + + /** + * This method is called after the block is unformed. It can be used to interact with the level/player... + * as it is only called, when the multiblock is unformed. + * @param level Level of the multiblock + * @param direction Direction of the multiblock + * @param blockPos BlockPos of the block that is being unformed + * @param controllerPos BlockPos of this multiblocks controller + * @param layerIndex index of the current layers block (array of integer) + * @param layoutIndex index of the current multiblock layer (array of multiblock layer) + * @param player Player that is trying to unform this multiblock. Note that there does not necessarily have to be a player that is responsible for unforming the multiblock + */ + default void afterUnformBlock(Level level, BlockPos blockPos, BlockPos controllerPos, int layerIndex, int layoutIndex, HorizontalDirection direction, @Nullable Player player) { + } + + /** + * This method determines whether the block at the specified position + * is a formed part of this multiblock. + * @param level Level of the multiblock + * @param blockPos BlockPos that needs to be checked if it is formed. + * @return Whether the block at this position is formed + */ + boolean isFormed(Level level, BlockPos blockPos); + + /** + * This method can make the direction of this multiblock fixed. This only works, + * if the multiblock cannot be rotated, like the crucible or firebox. + * Providing a fixed direction can improve performance while forming the multiblock + * by a bit. + * @return a horizontal direction, if the direction can be fixed. + */ + default @Nullable HorizontalDirection getFixedDirection() { + return null; + } + + /** + * This method provides the maximum possible + * size for this multiblock. + * @return the maximum possible size + */ + default int getMaxSize() { + int maxSize = 0; + for (MultiblockLayer layer : getLayout()) { + maxSize += layer.range().getMaximum(); + } + return maxSize; + } + + /** + * Create a new layer for your multiblock + * @param layer The block indices for your multiblock layer + * @return the newly created layer + */ + default MultiblockLayer layer(int... layer) { + return new MultiblockLayer(false, IntegerRange.of(1, 1), layer); + } +} \ No newline at end of file diff --git a/src/main/java/com/portingdeadmods/modjam/api/multiblocks/MultiblockData.java b/src/main/java/com/portingdeadmods/modjam/api/multiblocks/MultiblockData.java new file mode 100644 index 00000000..a09aa20f --- /dev/null +++ b/src/main/java/com/portingdeadmods/modjam/api/multiblocks/MultiblockData.java @@ -0,0 +1,34 @@ +package com.portingdeadmods.modjam.api.multiblocks; + +import com.portingdeadmods.modjam.api.utils.HorizontalDirection; +import net.minecraft.nbt.CompoundTag; + +public record MultiblockData(boolean valid, HorizontalDirection direction, MultiblockLayer[] layers) { + public static final MultiblockData EMPTY = new MultiblockData(false, HorizontalDirection.NORTH, new MultiblockLayer[0]); + + public CompoundTag serializeNBT() { + CompoundTag tag = new CompoundTag(); + tag.putInt("layersLength", layers.length); + CompoundTag listTag = new CompoundTag(); + for (int i = 0, expandedLayersLength = layers.length; i < expandedLayersLength; i++) { + MultiblockLayer layer = layers[i]; + listTag.put(String.valueOf(i), layer.save()); + } + tag.put("layersList", listTag); + tag.putInt("direction", this.direction.ordinal()); + tag.putBoolean("valid", this.valid); + return tag; + } + + public static MultiblockData deserializeNBT(CompoundTag nbt) { + int layersLength = nbt.getInt("layersLength"); + CompoundTag listTag = nbt.getCompound("layersList"); + MultiblockLayer[] layers = new MultiblockLayer[layersLength]; + for (int i = 0; i < layers.length; i++) { + layers[i] = MultiblockLayer.load(listTag.getCompound(String.valueOf(i))); + } + HorizontalDirection direction = HorizontalDirection.values()[nbt.getInt("direction")]; + boolean valid = nbt.getBoolean("valid"); + return new MultiblockData(valid, direction, layers); + } +} \ No newline at end of file diff --git a/src/main/java/com/portingdeadmods/modjam/api/multiblocks/MultiblockLayer.java b/src/main/java/com/portingdeadmods/modjam/api/multiblocks/MultiblockLayer.java new file mode 100644 index 00000000..8c09fb8b --- /dev/null +++ b/src/main/java/com/portingdeadmods/modjam/api/multiblocks/MultiblockLayer.java @@ -0,0 +1,54 @@ +package com.portingdeadmods.modjam.api.multiblocks; + +import it.unimi.dsi.fastutil.ints.IntIntPair; +import net.minecraft.nbt.CompoundTag; +import org.apache.commons.lang3.IntegerRange; + +import java.util.Arrays; + +public record MultiblockLayer(boolean dynamic, IntegerRange range, int[] layer, IntIntPair widths) { + public MultiblockLayer(boolean dynamic, IntegerRange range, int[] layer) { + this(dynamic, range, layer, IntIntPair.of((int) Math.sqrt(layer.length), (int) Math.sqrt(layer.length))); + } + + public MultiblockLayer setDynamic(IntegerRange size) { + return new MultiblockLayer(true, size, layer, widths); + } + + public MultiblockLayer setWidths(int xWidth, int zWidth) { + return new MultiblockLayer(dynamic, range, layer, IntIntPair.of(xWidth, zWidth)); + } + + public static MultiblockLayer load(CompoundTag tag) { + return new MultiblockLayer( + tag.getBoolean("dynamic"), + IntegerRange.of(tag.getInt("rangeMin"), tag.getInt("rangeMax")), + tag.getIntArray("layer"), + IntIntPair.of(tag.getInt("widthsX"), tag.getInt("widthsZ")) + ); + } + + public CompoundTag save() { + CompoundTag tag = new CompoundTag(); + tag.putBoolean("dynamic", dynamic); + tag.putInt("rangeMin", range.getMinimum()); + tag.putInt("rangeMax", range.getMaximum()); + tag.putIntArray("layer", layer); + tag.putInt("widthsX", widths.leftInt()); + tag.putInt("widthsZ", widths.rightInt()); + return tag; + } + + public IntIntPair getWidths() { + return widths; + } + + @Override + public String toString() { + return "MultiblockLayer{" + + "dynamic=" + dynamic + + ", range=" + range + + ", layer=" + Arrays.toString(layer) + + '}'; + } +} diff --git a/src/main/java/com/portingdeadmods/modjam/api/utils/HorizontalDirection.java b/src/main/java/com/portingdeadmods/modjam/api/utils/HorizontalDirection.java new file mode 100644 index 00000000..9dfd6887 --- /dev/null +++ b/src/main/java/com/portingdeadmods/modjam/api/utils/HorizontalDirection.java @@ -0,0 +1,30 @@ +package com.portingdeadmods.modjam.api.utils; + +import net.minecraft.core.Direction; +import org.jetbrains.annotations.Nullable; + +public enum HorizontalDirection { + NORTH, + EAST, + SOUTH, + WEST; + + public Direction toRegularDirection() { + return switch (this) { + case NORTH -> Direction.NORTH; + case EAST -> Direction.EAST; + case SOUTH -> Direction.SOUTH; + case WEST -> Direction.WEST; + }; + } + + public static @Nullable HorizontalDirection fromRegularDirection(Direction direction) { + return switch (direction) { + case NORTH -> NORTH; + case EAST -> EAST; + case SOUTH -> SOUTH; + case WEST -> WEST; + default -> null; + }; + } +} \ No newline at end of file