diff --git a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java index bc5fe5e..7a8271e 100644 --- a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java +++ b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java @@ -3,32 +3,39 @@ package dev.zontreck.libzontreck; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.sql.SQLException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; -import dev.zontreck.ariaslib.util.DelayedExecutorService; -import dev.zontreck.eventsbus.Bus; import dev.zontreck.libzontreck.chestgui.ChestGUIRegistry; +import dev.zontreck.libzontreck.config.ServerConfig; import dev.zontreck.libzontreck.currency.Bank; import dev.zontreck.libzontreck.currency.CurrencyHelper; +import dev.zontreck.libzontreck.events.BlockRestoreQueueRegistrationEvent; import dev.zontreck.libzontreck.items.ModItems; +import dev.zontreck.libzontreck.memory.world.BlockRestoreQueue; +import dev.zontreck.libzontreck.memory.world.BlockRestoreQueueRegistry; +import dev.zontreck.libzontreck.memory.world.DatabaseMigrations; +import dev.zontreck.libzontreck.memory.world.DatabaseWrapper; import dev.zontreck.libzontreck.menus.ChestGUIScreen; import dev.zontreck.libzontreck.types.ModMenuTypes; import dev.zontreck.libzontreck.networking.NetworkEvents; import net.minecraft.client.gui.screens.MenuScreens; +import net.minecraft.server.level.ServerLevel; import org.slf4j.Logger; import com.mojang.logging.LogUtils; import dev.zontreck.libzontreck.commands.Commands; import dev.zontreck.libzontreck.events.ForgeEventHandlers; -import dev.zontreck.libzontreck.memory.VolatilePlayerStorage; +import dev.zontreck.libzontreck.memory.player.VolatilePlayerStorage; import dev.zontreck.libzontreck.networking.ModMessages; import dev.zontreck.libzontreck.profiles.Profile; import dev.zontreck.libzontreck.util.FileTreeDatastore; -import net.minecraft.server.MinecraftServer; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.server.ServerStartedEvent; import net.minecraftforge.event.server.ServerStoppingEvent; @@ -45,7 +52,6 @@ public class LibZontreck { public static final Logger LOGGER = LogUtils.getLogger(); public static final String MOD_ID = "libzontreck"; public static final Map PROFILES; - public static MinecraftServer THE_SERVER; public static VolatilePlayerStorage playerStorage; public static boolean ALIVE=true; public static final String FILESTORE = FileTreeDatastore.get(); @@ -55,6 +61,7 @@ public class LibZontreck { public static final UUID NULL_ID; public static boolean LIBZONTRECK_SERVER_AVAILABLE=false; + public static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); public static LogicalSide CURRENT_SIDE; @@ -81,6 +88,8 @@ public class LibZontreck { IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus(); // Register the setup method for modloading bus.addListener(this::setup); + + ServerConfig.init(); MinecraftForge.EVENT_BUS.register(this); @@ -89,14 +98,14 @@ public class LibZontreck { MinecraftForge.EVENT_BUS.register(new NetworkEvents()); MinecraftForge.EVENT_BUS.register(ChestGUIRegistry.class); - Bus.Reset(); ModMenuTypes.REGISTRY.register(bus); //CreativeModeTabs.register(bus); ModItems.register(bus); - Bus.Register(CurrencyHelper.class, null); - Bus.Register(Bank.class, null); + MinecraftForge.EVENT_BUS.register(CurrencyHelper.class); + MinecraftForge.EVENT_BUS.register(Bank.class); + } private void setup(final FMLCommonSetupEvent event) @@ -108,16 +117,39 @@ public class LibZontreck { @SubscribeEvent public void onServerStarted(final ServerStartedEvent event) { - THE_SERVER = event.getServer(); ALIVE=true; + + ServerConfig.init(); + try { + DatabaseWrapper.start(); + }catch(RuntimeException e) { + LOGGER.warn("Database not configured properly, it will not be available."); + DatabaseWrapper.invalidate(); + } + CURRENT_SIDE = LogicalSide.SERVER; + + MinecraftForge.EVENT_BUS.post(new BlockRestoreQueueRegistrationEvent()); + + for(ServerLevel level : event.getServer().getAllLevels()) + { + // Queues have been registered, but we now need to initialize the queue's data from saveddata + BlockRestoreQueueRegistry.init(level); + } + + if(!DatabaseWrapper.hasDB)return; + + try { + DatabaseMigrations.initMigrations(); + } catch (SQLException e) { + e.printStackTrace(); + } } @SubscribeEvent public void onServerStopping(final ServerStoppingEvent ev) { ALIVE=false; - DelayedExecutorService.stop(); Iterator iProfile = PROFILES.values().iterator(); while(iProfile.hasNext()) diff --git a/src/main/java/dev/zontreck/libzontreck/api/Vector2.java b/src/main/java/dev/zontreck/libzontreck/api/Vector2.java new file mode 100644 index 0000000..e251c81 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/api/Vector2.java @@ -0,0 +1,138 @@ +package dev.zontreck.libzontreck.api; + +import dev.zontreck.libzontreck.vectors.Vector2f; +import dev.zontreck.libzontreck.vectors.Vector2i; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.phys.Vec2; + +public interface Vector2 extends Cloneable, Comparable> +{ + /** + * Converts the current Vector2 representation into a minecraft Vec2 + * @return Minecraft equivalent Vec2 + */ + Vec2 asMinecraftVector(); + + /** + * Parses a string in serialized format. + * @param vector2 Expects it in the same format returned by Vector2#toString + * @return New Vector2, or a null Vector2 initialized with zeros if invalid data + */ + static Vector2 parseString(String vector2){ + throw new UnsupportedOperationException("This method is not implemented by this implementation"); + } + + /** + * Copies the values to a new and detached instance + * @return New Vector2 + */ + Vector2 Clone(); + + /** + * Saves the X and Y positions to a NBT tag + * @return NBT compound tag + */ + CompoundTag serialize(); + + /** + * Loads a Vector2 from a NBT tag + * @param tag The NBT tag to load + */ + static Vector2 deserialize(CompoundTag tag) + { + throw new UnsupportedOperationException("This method is not implemented by this implementation"); + } + + /** + * Compares the two vector2 instances + * @param other The position to check + * @return True if same position + */ + boolean Same(Vector2 other); + + /** + * True if the current position is inside the two points + * @param point1 Lowest point + * @param point2 Hightest Point + * @return True if inside + */ + boolean Inside(Vector2 point1, Vector2 point2); + + /** + * Converts, if necessary, to Vector2d + * @return A vector2d instance + */ + Vector2f asVector2f(); + + /** + * Converts, if necessary, to Vector2i + * @return A vector2i instance + */ + Vector2i asVector2i(); + + /** + * Checks if the current vector is greater than the provided one + * @param other The other vector to check + * @return True if greater + */ + boolean greater(Vector2 other); + + /** + * Checks if the current vector is less than the provided one + * @param other The vector to check + * @return True if less than other + */ + boolean less(Vector2 other); + + /** + * Alias for Vector2#same + * @param other Vector to check + * @return True if same position + */ + boolean equal(Vector2 other); + + /** + * Adds the two vectors together + * @param other Vector to add + * @return New instance after adding the other vector + */ + Vector2 add(Vector2 other); + + /** + * Subtracts the other vector from this one + * @param other Vector to subtract + * @return New instance after subtracting + */ + Vector2 subtract(Vector2 other); + + /** + * Calculates the distance between the two vectors + * @param other + * @return The distance + */ + double distance(Vector2 other); + + /** + * Increments the Y axis by 1 + * @return New instance + */ + Vector2 moveUp(); + + /** + * Decrements the Y axis by 1 + * @return New instance + */ + Vector2 moveDown(); + + /** + * Gets the X value + * @return X + */ + T getX(); + + /** + * Gets the Y value + * @return Y + */ + T getY(); +} diff --git a/src/main/java/dev/zontreck/libzontreck/api/Vector3.java b/src/main/java/dev/zontreck/libzontreck/api/Vector3.java new file mode 100644 index 0000000..ffca2a8 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/api/Vector3.java @@ -0,0 +1,158 @@ +package dev.zontreck.libzontreck.api; + +import dev.zontreck.libzontreck.vectors.Vector3d; +import dev.zontreck.libzontreck.vectors.Vector3i; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.phys.Vec3; + +public interface Vector3 extends Cloneable, Comparable> +{ + /** + * Converts the current Vector3 representation into a minecraft Vec3 + * @return Minecraft equivalent Vec3 + */ + Vec3 asMinecraftVector(); + + /** + * Converts to a vec3i position + * @return Equivalent vec3i + */ + Vec3i asVec3i(); + + /** + * Converts to a block position + * @return Equivalent block position + */ + BlockPos asBlockPos(); + + /** + * Parses a string in serialized format. + * @param vector3 Expects it in the same format returned by Vector3#toString + * @return New Vector3, or a null Vector3 initialized with zeros if invalid data + */ + static Vector3 parseString(String vector3){ + throw new UnsupportedOperationException("This method is not implemented by this implementation"); + } + + /** + * Copies the values to a new and detached instance + * @return New Vector3 + */ + Vector3 Clone(); + + /** + * Saves the X, Y, and Z positions to a NBT tag + * @return NBT compound tag + */ + CompoundTag serialize(); + + /** + * Loads a Vector3 from a NBT tag + * @param tag The NBT tag to load + */ + static Vector3 deserialize(CompoundTag tag) + { + throw new UnsupportedOperationException("This method is not implemented by this implementation"); + } + + /** + * Compares the two vector3 instances + * @param other The position to check + * @return True if same position + */ + boolean Same(Vector3 other); + + /** + * True if the current position is inside the two points + * @param point1 Lowest point + * @param point2 Hightest Point + * @return True if inside + */ + boolean Inside(Vector3 point1, Vector3 point2); + + /** + * Converts, if necessary, to Vector3d + * @return A vector2d instance + */ + Vector3d asVector3d(); + + /** + * Converts, if necessary, to Vector3i + * @return A vector3i instance + */ + Vector3i asVector3i(); + + /** + * Checks if the current vector is greater than the provided one + * @param other The other vector to check + * @return True if greater + */ + boolean greater(Vector3 other); + + /** + * Checks if the current vector is less than the provided one + * @param other The vector to check + * @return True if less than other + */ + boolean less(Vector3 other); + + /** + * Alias for Vector3#same + * @param other Vector to check + * @return True if same position + */ + boolean equal(Vector3 other); + + /** + * Adds the two vectors together + * @param other Vector to add + * @return New instance after adding the other vector + */ + Vector3 add(Vector3 other); + + /** + * Subtracts the other vector from this one + * @param other Vector to subtract + * @return New instance after subtracting + */ + Vector3 subtract(Vector3 other); + + /** + * Calculates the distance between the two vectors + * @param other + * @return The distance + */ + double distance(Vector3 other); + + /** + * Increments the Y axis by 1 + * @return New instance + */ + Vector3 moveUp(); + + /** + * Decrements the Y axis by 1 + * @return New instance + */ + Vector3 moveDown(); + + /** + * Gets the X value + * @return X + */ + T getX(); + + /** + * Gets the Y value + * @return Y + */ + T getY(); + + /** + * Gets the Z value + * @return Z + */ + T getZ(); +} diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/BlockCustomVoxels.java b/src/main/java/dev/zontreck/libzontreck/blocks/BlockCustomVoxels.java new file mode 100644 index 0000000..f567242 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/BlockCustomVoxels.java @@ -0,0 +1,22 @@ +package dev.zontreck.libzontreck.blocks; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +public class BlockCustomVoxels extends PartialTransparentBlock +{ + private VoxelShape superShape; + public BlockCustomVoxels(Properties p_54120_, VoxelShape shape) { + super(p_54120_); + this.superShape = shape; + } + + @Override + public VoxelShape getShape(BlockState p_60555_, BlockGetter p_60556_, BlockPos p_60557_, CollisionContext p_60558_) { + return superShape; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/BlockImitation.java b/src/main/java/dev/zontreck/libzontreck/blocks/BlockImitation.java new file mode 100644 index 0000000..ab23bd1 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/BlockImitation.java @@ -0,0 +1,95 @@ +package dev.zontreck.libzontreck.blocks; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +/** + * Code partially taken from HT's TreeChop + *

+ * Source Material + */ +public abstract class BlockImitation extends Block +{ + public BlockImitation(Properties pProperties) { + super(pProperties); + } + + + public abstract BlockState getImitatedBlockState(BlockGetter level, BlockPos pos); + + public abstract void updateImitation(BlockState newState, BlockGetter level, BlockPos pos); + + @Override + public void animateTick(BlockState blockState, Level level, BlockPos pos, RandomSource random) { + BlockState imitatedBlockState = getImitatedBlockState(level, pos); + imitatedBlockState.getBlock().animateTick(imitatedBlockState, level, pos, random); + } + + @Override + public void stepOn(Level level, BlockPos pos, BlockState blockState, Entity entity) { + BlockState imitatedBlockState = getImitatedBlockState(level, pos); + imitatedBlockState.getBlock().stepOn(level, pos, imitatedBlockState, entity); + } + + @Override + public void fallOn(Level level, BlockState blockState, BlockPos pos, Entity entity, float speed) { + BlockState imitatedBlockState = getImitatedBlockState(level, pos); + imitatedBlockState.getBlock().fallOn(level, imitatedBlockState, pos, entity, speed); + } + + @Override + public ItemStack getCloneItemStack(BlockGetter level, BlockPos pos, BlockState blockState) { + BlockState imitatedBlockState = getImitatedBlockState(level, pos); + return imitatedBlockState.getBlock().getCloneItemStack(level, pos, imitatedBlockState); + } + + @Override + public void handlePrecipitation(BlockState blockState, Level level, BlockPos pos, Biome.Precipitation precipitation) { + BlockState imitatedBlockState = getImitatedBlockState(level, pos); + imitatedBlockState.getBlock().handlePrecipitation(imitatedBlockState, level, pos, precipitation); + } + + @Override + public int getLightBlock(BlockState blockState, BlockGetter level, BlockPos pos) { + return super.getLightBlock(blockState, level, pos); + } + + @Override + public float getShadeBrightness(BlockState blockState, BlockGetter level, BlockPos pos) { + return super.getShadeBrightness(blockState, level, pos); + } + + @Override + public int getAnalogOutputSignal(BlockState blockState, Level level, BlockPos pos) { + return getImitatedBlockState(level, pos).getAnalogOutputSignal(level, pos); + } + + @Override + public void randomTick(BlockState blockState, ServerLevel level, BlockPos pos, RandomSource random) { + getImitatedBlockState(level, pos).randomTick(level, pos, random); + } + + @Override + public void tick(BlockState blockState, ServerLevel level, BlockPos pos, RandomSource random) { + getImitatedBlockState(level, pos).tick(level, pos, random); + } + + @Override + public int getSignal(BlockState blockState, BlockGetter level, BlockPos pos, Direction direction) { + return getImitatedBlockState(level, pos).getSignal(level, pos, direction); + } + + @Override + public int getDirectSignal(BlockState blockState, BlockGetter level, BlockPos pos, Direction direction) { + return getImitatedBlockState(level, pos).getDirectSignal(level, pos, direction); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentBlock.java new file mode 100644 index 0000000..0ee305d --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentBlock.java @@ -0,0 +1,18 @@ +package dev.zontreck.libzontreck.blocks; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.AbstractGlassBlock; +import net.minecraft.world.level.block.state.BlockState; + +public class PartialTransparentBlock extends AbstractGlassBlock +{ + public PartialTransparentBlock(Properties p_48729_) { + super(p_48729_); + } + + @Override + public boolean propagatesSkylightDown(BlockState p_48740_, BlockGetter p_48741_, BlockPos p_48742_) { + return true; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentSlabBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentSlabBlock.java new file mode 100644 index 0000000..ff74562 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentSlabBlock.java @@ -0,0 +1,19 @@ +package dev.zontreck.libzontreck.blocks; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.AbstractGlassBlock; +import net.minecraft.world.level.block.SlabBlock; +import net.minecraft.world.level.block.state.BlockState; + +public class PartialTransparentSlabBlock extends SlabBlock +{ + public PartialTransparentSlabBlock(Properties p_48729_) { + super(p_48729_); + } + + @Override + public boolean propagatesSkylightDown(BlockState p_48740_, BlockGetter p_48741_, BlockPos p_48742_) { + return true; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java new file mode 100644 index 0000000..6b498b6 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java @@ -0,0 +1,98 @@ +package dev.zontreck.libzontreck.blocks; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public abstract class RedstoneBlock extends RotatableBlock +{ + public static final BooleanProperty INPUT_POWER = BooleanProperty.create("inputpower"); + public static final BooleanProperty POWERED = BlockStateProperties.POWERED; + private List sides; + + protected RedstoneBlock(Properties pProperties, Direction... validSides) { + super(pProperties); + sides=List.of(validSides); + } + + @Override + public BlockState getStateForPlacement(BlockPlaceContext pContext) { + return super.getStateForPlacement(pContext).setValue(INPUT_POWER, false).setValue(POWERED, false); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { + super.createBlockStateDefinition(pBuilder); + pBuilder.add(INPUT_POWER, POWERED); + } + + @Override + public boolean canConnectRedstone(BlockState state, BlockGetter level, BlockPos pos, @Nullable Direction direction) { + if(sides.contains(direction)) return true; + else return false; + } + + private boolean redstoneIsActivated(LevelReader level, BlockPos pos) + { + if(level.hasNeighborSignal(pos)) + return true; + else return false; + } + + @Override + public int getSignal(BlockState p_60483_, BlockGetter p_60484_, BlockPos p_60485_, Direction p_60486_) { + if(!sides.contains(p_60486_)) return 0; + + if(p_60483_.getValue(POWERED)) + return 15; + else return 0; + } + + protected abstract void onRedstone(LevelReader level, BlockPos pos, boolean on); + protected abstract void onRedstoneInputChanged(LevelReader level, BlockPos pos, boolean on); + + @Override + public void onNeighborChange(BlockState state, LevelReader level, BlockPos pos, BlockPos neighbor) { + boolean rs = redstoneIsActivated(level, pos); + onRedstone(level, pos, rs); + boolean inp = state.getValue(INPUT_POWER); + state.setValue(INPUT_POWER, rs); + + if(inp != rs) + onRedstoneInputChanged(level, pos, rs); + } + + + @Override + public void neighborChanged(BlockState state, Level level, BlockPos pos, Block block, BlockPos other, boolean unknown) { + boolean rs = redstoneIsActivated(level, pos); + onRedstone(level, pos, rs); + boolean inp = state.getValue(INPUT_POWER); + state.setValue(INPUT_POWER, rs); + + if(inp != rs) + onRedstoneInputChanged(level, pos, rs); + } + + @Override + public void onPlace(BlockState state, Level level, BlockPos pos, BlockState p_60569_, boolean p_60570_) { + boolean rs = redstoneIsActivated(level, pos); + onRedstone(level, pos, rs); + boolean inp = state.getValue(INPUT_POWER); + state.setValue(INPUT_POWER, rs); + + if(inp != rs) + onRedstoneInputChanged(level, pos, rs); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java new file mode 100644 index 0000000..8526891 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java @@ -0,0 +1,39 @@ +package dev.zontreck.libzontreck.blocks; + +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.HorizontalDirectionalBlock; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; + +public class RotatableBlock extends HorizontalDirectionalBlock +{ + public RotatableBlock(Properties pProperties) { + super(pProperties); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { + super.createBlockStateDefinition(pBuilder); + pBuilder.add(FACING); + } + + + @Override + public BlockState rotate(BlockState p_55115_, Rotation p_55116_) { + return p_55115_.setValue(FACING, p_55116_.rotate(p_55115_.getValue(FACING))); + } + + @Override + public BlockState mirror(BlockState p_55112_, Mirror p_55113_) { + return p_55112_.rotate(p_55113_.getRotation(p_55112_.getValue(FACING))); + } + + + @Override + public BlockState getStateForPlacement(BlockPlaceContext pContext) { + return defaultBlockState().setValue(FACING, pContext.getHorizontalDirection()); + } +} \ No newline at end of file diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlockCustomVoxels.java b/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlockCustomVoxels.java new file mode 100644 index 0000000..6bf6ac4 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlockCustomVoxels.java @@ -0,0 +1,35 @@ +package dev.zontreck.libzontreck.blocks; + +import dev.zontreck.ariaslib.util.Maps; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; + +import java.util.HashMap; +import java.util.Map; + + +public class RotatableBlockCustomVoxels extends RotatableBlock +{ + private Map rotatedShapes = new HashMap<>(); + + public RotatableBlockCustomVoxels(Properties properties, VoxelShape north, VoxelShape south, VoxelShape east, VoxelShape west) { + super(properties); + rotatedShapes = Maps.of(new Maps.Entry<>(Direction.NORTH, north), new Maps.Entry<>(Direction.SOUTH, south), new Maps.Entry<>(Direction.WEST, west), new Maps.Entry<>(Direction.EAST, east), new Maps.Entry<>(Direction.NORTH, north), new Maps.Entry<>(Direction.DOWN, north)); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) { + Direction facing = state.getValue(FACING); + return rotatedShapes.get(facing); + } + + @Override + public boolean propagatesSkylightDown(BlockState p_49928_, BlockGetter p_49929_, BlockPos p_49930_) { + return true; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java index 826bc44..d41046b 100644 --- a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java +++ b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java @@ -9,10 +9,8 @@ import dev.zontreck.libzontreck.networking.ModMessages; import dev.zontreck.libzontreck.networking.packets.S2CCloseChestGUI; import dev.zontreck.libzontreck.util.ChatHelpers; import dev.zontreck.libzontreck.util.ServerUtilities; -import dev.zontreck.libzontreck.vectors.Vector2; +import dev.zontreck.libzontreck.vectors.Vector2f; import dev.zontreck.libzontreck.vectors.Vector2i; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.SimpleMenuProvider; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; @@ -24,8 +22,6 @@ import net.minecraftforge.network.NetworkHooks; import java.util.ArrayList; import java.util.List; import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.function.Function; /** * Zontreck's ChestGUI Interface @@ -199,7 +195,7 @@ public class ChestGUI { updateUtilityButtons(); MinecraftForge.EVENT_BUS.post(new OpenGUIEvent(id, player, this)); - NetworkHooks.openScreen(ServerUtilities.getPlayerByID(player.toString()), new SimpleMenuProvider(ChestGUIMenu.getServerMenu(this), ChatHelpers.macro(((MenuTitle != "") ? MenuTitle : "No Title")))); + NetworkHooks.openScreen(ServerUtilities.getPlayerByID(player.toString()), new SimpleMenuProvider(ChestGUIMenu.getServerMenu(this), ChatHelpers.macro((MenuTitle != "") ? MenuTitle : "No Title"))); } } @@ -232,7 +228,7 @@ public class ChestGUI return this.id.equals(id); } - public void handleButtonClicked(int slot, Vector2 pos, Item item) { + public void handleButtonClicked(int slot, Vector2f pos, Item item) { for(ChestGUIButton button : buttons) { if(button.getSlotNum() == slot) diff --git a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUIButton.java b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUIButton.java index 4368831..0ed5b46 100644 --- a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUIButton.java +++ b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUIButton.java @@ -3,10 +3,8 @@ package dev.zontreck.libzontreck.chestgui; import dev.zontreck.libzontreck.lore.LoreContainer; import dev.zontreck.libzontreck.lore.LoreEntry; import dev.zontreck.libzontreck.util.ChatHelpers; -import dev.zontreck.libzontreck.vectors.Vector2; import dev.zontreck.libzontreck.vectors.Vector2i; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtUtils; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraftforge.items.ItemStackHandler; @@ -144,7 +142,7 @@ public class ChestGUIButton */ public boolean matchesSlot(Vector2i slot) { - return position.same(slot); + return position.Same(slot); } /** diff --git a/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java b/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java new file mode 100644 index 0000000..404f834 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java @@ -0,0 +1,39 @@ +package dev.zontreck.libzontreck.config; + +import dev.zontreck.libzontreck.LibZontreck; +import dev.zontreck.libzontreck.config.sections.DatabaseSection; +import dev.zontreck.libzontreck.util.SNbtIo; +import net.minecraft.nbt.CompoundTag; + +import java.nio.file.Path; + +public class ServerConfig +{ + public static final Path BASE = LibZontreck.BASE_CONFIG.resolve("server.snbt"); + + public static DatabaseSection database; + + public static void init() + { + if(BASE.toFile().exists()) + { + var config = SNbtIo.loadSnbt(BASE); + + database = DatabaseSection.deserialize(config.getCompound(DatabaseSection.TAG_NAME)); + + commit(); + } else { + database = new DatabaseSection(); + + commit(); + } + } + + public static void commit() + { + CompoundTag tag = new CompoundTag(); + tag.put(DatabaseSection.TAG_NAME, database.serialize()); + + SNbtIo.writeSnbt(BASE, tag); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java b/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java new file mode 100644 index 0000000..774b5a5 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java @@ -0,0 +1,76 @@ +package dev.zontreck.libzontreck.config.sections; + +import dev.zontreck.libzontreck.config.ServerConfig; +import net.minecraft.nbt.CompoundTag; + +public class DatabaseSection +{ + public static final String TAG_NAME = "database"; + public static final String TAG_USER = "username"; + public static final String TAG_PASSWORD = "password"; + public static final String TAG_HOST = "host"; + public static final String TAG_DATABASE = "database"; + public static final String TAG_VERSION = "rev"; + + + public String user = "root"; + public String password = ""; + public String host = "localhost:3306"; // IP:port + public String database = ""; + public int version; + + public static final int VERSION = 1; + + public static DatabaseSection deserialize(CompoundTag tag) + { + DatabaseSection ret = new DatabaseSection(); + ret.version = tag.getInt(TAG_VERSION); + + if(ret.version == 0) + { + ret.version = VERSION; + return ret; + } + + if(ret.version >= 1) + { + ret.user = tag.getString(TAG_USER); + ret.password = tag.getString(TAG_PASSWORD); + ret.host = tag.getString(TAG_HOST); + ret.database = tag.getString(TAG_DATABASE); + } + + + ret.version = VERSION; + return ret; + } + + public DatabaseSection(){ + + } + + public DatabaseSection(String user, String password, String host, String database) + { + this.user = user; + this.password = password; + this.host = host; + this.database = database; + } + + public String getAsSQLFileName() + { + return database + ".sql"; + } + + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + tag.putString(TAG_USER, user); + tag.putString(TAG_PASSWORD, password); + tag.putString(TAG_HOST, host); + tag.putString(TAG_DATABASE, database); + tag.putInt(TAG_VERSION, version); + + return tag; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/currency/Account.java b/src/main/java/dev/zontreck/libzontreck/currency/Account.java index bef02ff..394be28 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/Account.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/Account.java @@ -1,15 +1,16 @@ package dev.zontreck.libzontreck.currency; -import dev.zontreck.eventsbus.Bus; import dev.zontreck.libzontreck.chat.ChatColor; import dev.zontreck.libzontreck.currency.events.TransactionHistoryFlushEvent; import dev.zontreck.libzontreck.profiles.Profile; import dev.zontreck.libzontreck.profiles.UserProfileNotYetExistsException; +import net.minecraft.core.UUIDUtil; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtUtils; import net.minecraft.nbt.Tag; +import net.minecraftforge.common.MinecraftForge; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; @@ -93,7 +94,7 @@ public class Account LongTermTransactionHistoryRecord rec = LongTermTransactionHistoryRecord.of(player_id); rec.addHistory(history); rec.commit(); - Bus.Post(new TransactionHistoryFlushEvent(this, rec, history)); + MinecraftForge.EVENT_BUS.post(new TransactionHistoryFlushEvent(this, rec, history)); rec = null; history = new ArrayList<>(); } diff --git a/src/main/java/dev/zontreck/libzontreck/currency/Bank.java b/src/main/java/dev/zontreck/libzontreck/currency/Bank.java index a94b4c2..4473d88 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/Bank.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/Bank.java @@ -1,11 +1,6 @@ package dev.zontreck.libzontreck.currency; -import com.google.common.collect.Lists; -import dev.zontreck.eventsbus.Bus; -import dev.zontreck.eventsbus.Subscribe; import dev.zontreck.libzontreck.LibZontreck; -import dev.zontreck.libzontreck.chat.ChatColor; -import dev.zontreck.libzontreck.chat.ChatColorFactory; import dev.zontreck.libzontreck.currency.events.BankAccountCreatedEvent; import dev.zontreck.libzontreck.currency.events.BankReadyEvent; import dev.zontreck.libzontreck.currency.events.TransactionEvent; @@ -21,7 +16,10 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtIo; import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.server.ServerLifecycleHooks; import java.io.IOException; import java.lang.reflect.InvocationTargetException; @@ -81,7 +79,7 @@ public class Bank accounts.add(new Account((CompoundTag) t)); } - Bus.Post(new BankReadyEvent()); + MinecraftForge.EVENT_BUS.post(new BankReadyEvent()); } catch (IOException e) { throw new RuntimeException(e); } @@ -128,7 +126,7 @@ public class Bank instance.accounts.add(new Account(ID)); instance.commit(); - Bus.Post(new BankAccountCreatedEvent(getAccount(ID))); + MinecraftForge.EVENT_BUS.post(new BankAccountCreatedEvent(getAccount(ID))); }else { } } @@ -143,7 +141,10 @@ public class Bank protected static boolean postTx(Transaction tx) throws InvalidSideException, InvocationTargetException, IllegalAccessException { if(ServerUtilities.isClient())return false; TransactionEvent ev = new TransactionEvent(tx); - if(Bus.Post(ev)) + + MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); + + if(MinecraftForge.EVENT_BUS.post(ev)) { // Send the list of reasons to the user String reasonStr = String.join("\n", ev.reasons); @@ -151,13 +152,14 @@ public class Bank Account from = ev.tx.from.get(); Account to = ev.tx.to.get(); + if(from.isValidPlayer()) { - ChatHelpers.broadcastTo(from.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] !Dark_Red!The transaction could not be completed because of the following reasons: " + reasonStr), LibZontreck.THE_SERVER); + ChatHelpers.broadcastTo(from.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] !Dark_Red!The transaction could not be completed because of the following reasons: " + reasonStr), server); } if(to.isValidPlayer()) { - ChatHelpers.broadcastTo(to.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] !Dark_Red!The transaction could not be completed because of the following reasons: " + reasonStr), LibZontreck.THE_SERVER); + ChatHelpers.broadcastTo(to.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] !Dark_Red!The transaction could not be completed because of the following reasons: " + reasonStr), server); } return false; @@ -198,19 +200,19 @@ public class Bank } if(from.isValidPlayer()) - ChatHelpers.broadcastTo(from.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] !Dark_Green!You sent !White!${0} !Dark_green!to {1}", String.valueOf(tx.amount), toProf.name_color+toProf.nickname), LibZontreck.THE_SERVER); + ChatHelpers.broadcastTo(from.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] !Dark_Green!You sent !White!${0} !Dark_green!to {1}", String.valueOf(tx.amount), toProf.name_color+toProf.nickname), server); if(to.isValidPlayer()) - ChatHelpers.broadcastTo(from.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] {0} !Dark_Green!paid you ${1}", String.valueOf(tx.amount), toProf.name_color+toProf.nickname), LibZontreck.THE_SERVER); + ChatHelpers.broadcastTo(from.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] {0} !Dark_Green!paid you ${1}", String.valueOf(tx.amount), toProf.name_color+toProf.nickname), server); if(to.isValidPlayer() && ServerUtilities.playerIsOffline(to.player_id)) Profile.unload(toProf); if(from.isValidPlayer() && ServerUtilities.playerIsOffline(from.player_id)) Profile.unload(fromProf); - Bus.Post(new WalletUpdatedEvent(from.player_id, fromOld, from.balance, tx)); + MinecraftForge.EVENT_BUS.post(new WalletUpdatedEvent(from.player_id, fromOld, from.balance, tx)); - Bus.Post(new WalletUpdatedEvent(to.player_id, toOld, to.balance, tx)); + MinecraftForge.EVENT_BUS.post(new WalletUpdatedEvent(to.player_id, toOld, to.balance, tx)); if(from.isValidPlayer() && !ServerUtilities.playerIsOffline(from.player_id)) { @@ -232,7 +234,7 @@ public class Bank * This event is fired when wallets get updated. It cannot be cancelled * @param ev The event containing the player ID and new+old wallet data */ - @Subscribe + @SubscribeEvent public static void onWalletUpdate(WalletUpdatedEvent ev) { diff --git a/src/main/java/dev/zontreck/libzontreck/currency/events/BankAccountCreatedEvent.java b/src/main/java/dev/zontreck/libzontreck/currency/events/BankAccountCreatedEvent.java index 1961f97..6ff98b0 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/events/BankAccountCreatedEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/events/BankAccountCreatedEvent.java @@ -1,8 +1,8 @@ package dev.zontreck.libzontreck.currency.events; -import dev.zontreck.eventsbus.Event; import dev.zontreck.libzontreck.currency.Account; +import net.minecraftforge.eventbus.api.Event; public class BankAccountCreatedEvent extends Event { diff --git a/src/main/java/dev/zontreck/libzontreck/currency/events/BankReadyEvent.java b/src/main/java/dev/zontreck/libzontreck/currency/events/BankReadyEvent.java index c7fcfc0..5dcf914 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/events/BankReadyEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/events/BankReadyEvent.java @@ -1,7 +1,7 @@ package dev.zontreck.libzontreck.currency.events; -import dev.zontreck.eventsbus.Event; +import net.minecraftforge.eventbus.api.Event; /** * Contains no information by itself, it only signals that the Bank is open for business diff --git a/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionEvent.java b/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionEvent.java index 95e2bc9..d0786ea 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionEvent.java @@ -1,15 +1,13 @@ package dev.zontreck.libzontreck.currency.events; -import dev.zontreck.eventsbus.Cancellable; -import dev.zontreck.eventsbus.Event; import dev.zontreck.libzontreck.currency.Transaction; +import net.minecraftforge.eventbus.api.Cancelable; +import net.minecraftforge.eventbus.api.Event; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.List; -@Cancellable +@Cancelable public class TransactionEvent extends Event { public Transaction tx; diff --git a/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionHistoryFlushEvent.java b/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionHistoryFlushEvent.java index ef1d1dc..34bb7a8 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionHistoryFlushEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionHistoryFlushEvent.java @@ -1,10 +1,10 @@ package dev.zontreck.libzontreck.currency.events; -import dev.zontreck.eventsbus.Event; import dev.zontreck.libzontreck.currency.Account; import dev.zontreck.libzontreck.currency.LongTermTransactionHistoryRecord; import dev.zontreck.libzontreck.currency.Transaction; +import net.minecraftforge.eventbus.api.Event; import java.util.List; diff --git a/src/main/java/dev/zontreck/libzontreck/currency/events/WalletUpdatedEvent.java b/src/main/java/dev/zontreck/libzontreck/currency/events/WalletUpdatedEvent.java index 24cdf15..511ec7d 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/events/WalletUpdatedEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/events/WalletUpdatedEvent.java @@ -1,8 +1,8 @@ package dev.zontreck.libzontreck.currency.events; -import dev.zontreck.eventsbus.Event; import dev.zontreck.libzontreck.currency.Transaction; import net.minecraft.world.entity.player.Player; +import net.minecraftforge.eventbus.api.Event; import java.util.UUID; diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Auxiliaries.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Auxiliaries.java new file mode 100644 index 0000000..eb57f30 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Auxiliaries.java @@ -0,0 +1,590 @@ +/* + * @file Auxiliaries.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * General commonly used functionality. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.ChatFormatting; +import net.minecraft.SharedConstants; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Registry; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentUtils; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.shapes.BooleanOp; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.registries.ForgeRegistries; +import org.slf4j.Logger; +import org.lwjgl.glfw.GLFW; + +import javax.annotation.Nullable; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +public class Auxiliaries { + private static String modid; + private static Logger logger; + private static Supplier server_config_supplier = CompoundTag::new; + + public static void init(String modid, Logger logger, Supplier server_config_supplier) { + Auxiliaries.modid = modid; + Auxiliaries.logger = logger; + Auxiliaries.server_config_supplier = server_config_supplier; + } + + // ------------------------------------------------------------------------------------------------------------------- + // Mod specific exports + // ------------------------------------------------------------------------------------------------------------------- + + public static String modid() { + return modid; + } + + public static Logger logger() { + return logger; + } + + // ------------------------------------------------------------------------------------------------------------------- + // Sidedness, system/environment, tagging interfaces + // ------------------------------------------------------------------------------------------------------------------- + + public interface IExperimentalFeature { + } + + public static boolean isModLoaded(final String registry_name) { + return ModList.get().isLoaded(registry_name); + } + + public static boolean isDevelopmentMode() { + return SharedConstants.IS_RUNNING_IN_IDE; + } + + @OnlyIn(Dist.CLIENT) + public static boolean isShiftDown() { + return (InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_LEFT_SHIFT) || + InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_RIGHT_SHIFT)); + } + + @OnlyIn(Dist.CLIENT) + public static boolean isCtrlDown() { + return (InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_LEFT_CONTROL) || + InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_RIGHT_CONTROL)); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Logging + // ------------------------------------------------------------------------------------------------------------------- + + public static void logInfo(final String msg) { + logger.info(msg); + } + + public static void logWarn(final String msg) { + logger.warn(msg); + } + + public static void logError(final String msg) { + logger.error(msg); + } + + public static void logDebug(final String msg) { /*logger.debug(msg);*/ } + + // ------------------------------------------------------------------------------------------------------------------- + // Localization, text formatting + // ------------------------------------------------------------------------------------------------------------------- + + /** + * Text localization wrapper, implicitly prepends `MODID` to the + * translation keys. Forces formatting argument, nullable if no special formatting shall be applied.. + */ + public static MutableComponent localizable(String modtrkey, Object... args) { + return Component.translatable((modtrkey.startsWith("block.") || (modtrkey.startsWith("item."))) ? (modtrkey) : (modid + "." + modtrkey), args); + } + + public static MutableComponent localizable(String modtrkey, @Nullable ChatFormatting color, Object... args) { + final MutableComponent tr = Component.translatable(modid + "." + modtrkey, args); + if (color != null) tr.getStyle().applyFormat(color); + return tr; + } + + public static Component localizable(String modtrkey) { + return localizable(modtrkey, new Object[]{}); + } + + public static Component localizable_block_key(String blocksubkey) { + return Component.translatable("block." + modid + "." + blocksubkey); + } + + @OnlyIn(Dist.CLIENT) + public static String localize(String translationKey, Object... args) { + Component tr = Component.translatable(translationKey, args); + tr.getStyle().applyFormat(ChatFormatting.RESET); + final String ft = tr.getString(); + if (ft.contains("${")) { + // Non-recursive, non-argument lang file entry cross referencing. + Pattern pt = Pattern.compile("\\$\\{([^}]+)\\}"); + Matcher mt = pt.matcher(ft); + StringBuffer sb = new StringBuffer(); + while (mt.find()) { + String m = mt.group(1); + if (m.contains("?")) { + String[] kv = m.split("\\?", 2); + String key = kv[0].trim(); + boolean not = key.startsWith("!"); + if (not) key = key.replaceFirst("!", ""); + m = kv[1].trim(); + if (!server_config_supplier.get().contains(key)) { + m = ""; + } else { + boolean r = server_config_supplier.get().getBoolean(key); + if (not) r = !r; + if (!r) m = ""; + } + } + mt.appendReplacement(sb, Matcher.quoteReplacement((Component.translatable(m)).getString().trim())); + } + mt.appendTail(sb); + return sb.toString(); + } else { + return ft; + } + } + + /** + * Returns true if a given key is translated for the current language. + */ + @OnlyIn(Dist.CLIENT) + public static boolean hasTranslation(String key) { + return net.minecraft.client.resources.language.I18n.exists(key); + } + + public static MutableComponent join(Collection components, String separator) { + return ComponentUtils.formatList(components, Component.literal(separator), Function.identity()); + } + + public static MutableComponent join(Component... components) { + final MutableComponent tc = Component.empty(); + for (Component c : components) { + tc.append(c); + } + return tc; + } + + public static boolean isEmpty(Component component) { + return component.getSiblings().isEmpty() && component.getString().isEmpty(); + } + + public static final class Tooltip { + @OnlyIn(Dist.CLIENT) + public static boolean extendedTipCondition() { + return isShiftDown(); + } + + @OnlyIn(Dist.CLIENT) + public static boolean helpCondition() { + return isShiftDown() && isCtrlDown(); + } + + /** + * Adds an extended tooltip or help tooltip depending on the key states of CTRL and SHIFT. + * Returns true if the localisable help/tip was added, false if not (either not CTL/SHIFT or + * no translation found). + */ + @OnlyIn(Dist.CLIENT) + public static boolean addInformation(@Nullable String advancedTooltipTranslationKey, @Nullable String helpTranslationKey, List tooltip, TooltipFlag flag, boolean addAdvancedTooltipHints) { + // Note: intentionally not using keybinding here, this must be `control` or `shift`. + final boolean help_available = (helpTranslationKey != null) && Auxiliaries.hasTranslation(helpTranslationKey + ".help"); + final boolean tip_available = (advancedTooltipTranslationKey != null) && Auxiliaries.hasTranslation(helpTranslationKey + ".tip"); + if ((!help_available) && (!tip_available)) return false; + String tip_text = ""; + if (helpCondition()) { + if (help_available) tip_text = localize(helpTranslationKey + ".help"); + } else if (extendedTipCondition()) { + if (tip_available) tip_text = localize(advancedTooltipTranslationKey + ".tip"); + } else if (addAdvancedTooltipHints) { + if (tip_available) tip_text += localize(modid + ".tooltip.hint.extended") + (help_available ? " " : ""); + if (help_available) tip_text += localize(modid + ".tooltip.hint.help"); + } + if (tip_text.isEmpty()) return false; + String[] tip_list = tip_text.split("\\r?\\n"); + for (String tip : tip_list) { + tooltip.add(Component.literal(tip.replaceAll("\\s+$", "").replaceAll("^\\s+", "")).withStyle(ChatFormatting.GRAY)); + } + return true; + } + + /** + * Adds an extended tooltip or help tooltip for a given stack depending on the key states of CTRL and SHIFT. + * Format in the lang file is (e.g. for items): "item.MODID.REGISTRYNAME.tip" and "item.MODID.REGISTRYNAME.help". + * Return value see method pattern above. + */ + @OnlyIn(Dist.CLIENT) + public static boolean addInformation(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag, boolean addAdvancedTooltipHints) { + return addInformation(stack.getDescriptionId(), stack.getDescriptionId(), tooltip, flag, addAdvancedTooltipHints); + } + + @OnlyIn(Dist.CLIENT) + public static boolean addInformation(String translation_key, List tooltip) { + if (!Auxiliaries.hasTranslation(translation_key)) return false; + tooltip.add(Component.literal(localize(translation_key).replaceAll("\\s+$", "").replaceAll("^\\s+", "")).withStyle(ChatFormatting.GRAY)); + return true; + } + + } + + @SuppressWarnings("unused") + public static void playerChatMessage(final Player player, final String message) { + player.displayClientMessage(Component.translatable(message.trim()), true); + } + + public static @Nullable Component unserializeTextComponent(String serialized) { + return Component.Serializer.fromJson(serialized); + } + + public static String serializeTextComponent(Component tc) { + return (tc == null) ? ("") : (Component.Serializer.toJson(tc)); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Tag Handling + // ------------------------------------------------------------------------------------------------------------------- + + @SuppressWarnings("deprecation") + public static boolean isInItemTag(Item item, ResourceLocation tag) { + return ForgeRegistries.ITEMS.tags().stream().filter(tg -> tg.getKey().location().equals(tag)).anyMatch(tk -> tk.contains(item)); + } + + @SuppressWarnings("deprecation") + public static boolean isInBlockTag(Block block, ResourceLocation tag) { + return ForgeRegistries.BLOCKS.tags().stream().filter(tg -> tg.getKey().location().equals(tag)).anyMatch(tk -> tk.contains(block)); + } + + @SuppressWarnings("deprecation") + public static ResourceLocation getResourceLocation(Item item) { + return ForgeRegistries.ITEMS.getKey(item); + } + + @SuppressWarnings("deprecation") + public static ResourceLocation getResourceLocation(Block block) { + return ForgeRegistries.BLOCKS.getKey(block); + } + + @SuppressWarnings("deprecation") + public static ResourceLocation getResourceLocation(net.minecraft.world.inventory.MenuType menu) { + return ForgeRegistries.MENU_TYPES.getKey(menu); + } + + @SuppressWarnings("deprecation") + public static ResourceLocation getResourceLocation(net.minecraft.world.level.material.Fluid fluid) { + return ForgeRegistries.FLUIDS.getKey(fluid); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Item NBT data + // ------------------------------------------------------------------------------------------------------------------- + + /** + * Equivalent to getDisplayName(), returns null if no custom name is set. + */ + public static @Nullable Component getItemLabel(ItemStack stack) { + CompoundTag nbt = stack.getTagElement("display"); + if (nbt != null && nbt.contains("Name", 8)) { + try { + Component tc = unserializeTextComponent(nbt.getString("Name")); + if (tc != null) return tc; + nbt.remove("Name"); + } catch (Exception e) { + nbt.remove("Name"); + } + } + return null; + } + + public static ItemStack setItemLabel(ItemStack stack, @Nullable Component name) { + if (name != null) { + CompoundTag nbt = stack.getOrCreateTagElement("display"); + nbt.putString("Name", serializeTextComponent(name)); + } else { + if (stack.hasTag()) stack.removeTagKey("display"); + } + return stack; + } + + // ------------------------------------------------------------------------------------------------------------------- + // Block handling + // ------------------------------------------------------------------------------------------------------------------- + + public static boolean isWaterLogged(BlockState state) { + return state.hasProperty(BlockStateProperties.WATERLOGGED) && state.getValue(BlockStateProperties.WATERLOGGED); + } + + public static AABB getPixeledAABB(double x0, double y0, double z0, double x1, double y1, double z1) { + return new AABB(x0 / 16.0, y0 / 16.0, z0 / 16.0, x1 / 16.0, y1 / 16.0, z1 / 16.0); + } + + public static AABB getRotatedAABB(AABB bb, Direction new_facing) { + return getRotatedAABB(bb, new_facing, false); + } + + public static AABB[] getRotatedAABB(AABB[] bb, Direction new_facing) { + return getRotatedAABB(bb, new_facing, false); + } + + public static AABB getRotatedAABB(AABB bb, Direction new_facing, boolean horizontal_rotation) { + if (!horizontal_rotation) { + switch (new_facing.get3DDataValue()) { + case 0: + return new AABB(1 - bb.maxX, bb.minZ, bb.minY, 1 - bb.minX, bb.maxZ, bb.maxY); // D + case 1: + return new AABB(1 - bb.maxX, 1 - bb.maxZ, 1 - bb.maxY, 1 - bb.minX, 1 - bb.minZ, 1 - bb.minY); // U + case 2: + return new AABB(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // N --> bb + case 3: + return new AABB(1 - bb.maxX, bb.minY, 1 - bb.maxZ, 1 - bb.minX, bb.maxY, 1 - bb.minZ); // S + case 4: + return new AABB(bb.minZ, bb.minY, 1 - bb.maxX, bb.maxZ, bb.maxY, 1 - bb.minX); // W + case 5: + return new AABB(1 - bb.maxZ, bb.minY, bb.minX, 1 - bb.minZ, bb.maxY, bb.maxX); // E + } + } else { + switch (new_facing.get3DDataValue()) { + case 0: + return new AABB(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // D --> bb + case 1: + return new AABB(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // U --> bb + case 2: + return new AABB(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // N --> bb + case 3: + return new AABB(1 - bb.maxX, bb.minY, 1 - bb.maxZ, 1 - bb.minX, bb.maxY, 1 - bb.minZ); // S + case 4: + return new AABB(bb.minZ, bb.minY, 1 - bb.maxX, bb.maxZ, bb.maxY, 1 - bb.minX); // W + case 5: + return new AABB(1 - bb.maxZ, bb.minY, bb.minX, 1 - bb.minZ, bb.maxY, bb.maxX); // E + } + } + return bb; + } + + public static AABB[] getRotatedAABB(AABB[] bbs, Direction new_facing, boolean horizontal_rotation) { + final AABB[] transformed = new AABB[bbs.length]; + for (int i = 0; i < bbs.length; ++i) transformed[i] = getRotatedAABB(bbs[i], new_facing, horizontal_rotation); + return transformed; + } + + public static AABB getYRotatedAABB(AABB bb, int clockwise_90deg_steps) { + final Direction[] direction_map = new Direction[]{Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST}; + return getRotatedAABB(bb, direction_map[(clockwise_90deg_steps + 4096) & 0x03], true); + } + + public static AABB[] getYRotatedAABB(AABB[] bbs, int clockwise_90deg_steps) { + final AABB[] transformed = new AABB[bbs.length]; + for (int i = 0; i < bbs.length; ++i) transformed[i] = getYRotatedAABB(bbs[i], clockwise_90deg_steps); + return transformed; + } + + public static AABB getMirroredAABB(AABB bb, Direction.Axis axis) { + return switch (axis) { + case X -> new AABB(1 - bb.maxX, bb.minY, bb.minZ, 1 - bb.minX, bb.maxY, bb.maxZ); + case Y -> new AABB(bb.minX, 1 - bb.maxY, bb.minZ, bb.maxX, 1 - bb.minY, bb.maxZ); + case Z -> new AABB(bb.minX, bb.minY, 1 - bb.maxZ, bb.maxX, bb.maxY, 1 - bb.minZ); + }; + } + + public static AABB[] getMirroredAABB(AABB[] bbs, Direction.Axis axis) { + final AABB[] transformed = new AABB[bbs.length]; + for (int i = 0; i < bbs.length; ++i) transformed[i] = getMirroredAABB(bbs[i], axis); + return transformed; + } + + public static VoxelShape getUnionShape(AABB... aabbs) { + VoxelShape shape = Shapes.empty(); + for (AABB aabb : aabbs) shape = Shapes.joinUnoptimized(shape, Shapes.create(aabb), BooleanOp.OR); + return shape; + } + + public static VoxelShape getUnionShape(AABB[]... aabb_list) { + VoxelShape shape = Shapes.empty(); + for (AABB[] aabbs : aabb_list) { + for (AABB aabb : aabbs) shape = Shapes.joinUnoptimized(shape, Shapes.create(aabb), BooleanOp.OR); + } + return shape; + } + + public static AABB[] getMappedAABB(AABB[] bbs, Function mapper) { + final AABB[] transformed = new AABB[bbs.length]; + for (int i = 0; i < bbs.length; ++i) transformed[i] = mapper.apply(bbs[i]); + return transformed; + } + + public static final class BlockPosRange implements Iterable { + private final int x0, x1, y0, y1, z0, z1; + + public BlockPosRange(int x0, int y0, int z0, int x1, int y1, int z1) { + this.x0 = Math.min(x0, x1); + this.x1 = Math.max(x0, x1); + this.y0 = Math.min(y0, y1); + this.y1 = Math.max(y0, y1); + this.z0 = Math.min(z0, z1); + this.z1 = Math.max(z0, z1); + } + + public static BlockPosRange of(AABB range) { + return new BlockPosRange( + (int) Math.floor(range.minX), + (int) Math.floor(range.minY), + (int) Math.floor(range.minZ), + (int) Math.floor(range.maxX - .0625), + (int) Math.floor(range.maxY - .0625), + (int) Math.floor(range.maxZ - .0625) + ); + } + + public int getXSize() { + return x1 - x0 + 1; + } + + public int getYSize() { + return y1 - y0 + 1; + } + + public int getZSize() { + return z1 - z0 + 1; + } + + public int getArea() { + return getXSize() * getZSize(); + } + + public int getHeight() { + return getYSize(); + } + + public int getVolume() { + return getXSize() * getYSize() * getZSize(); + } + + public BlockPos byXZYIndex(int xyz_index) { + final int xsz = getXSize(), ysz = getYSize(), zsz = getZSize(); + xyz_index = xyz_index % (xsz * ysz * zsz); + final int y = xyz_index / (xsz * zsz); + xyz_index -= y * (xsz * zsz); + final int z = xyz_index / xsz; + xyz_index -= z * xsz; + final int x = xyz_index; + return new BlockPos(x0 + x, y0 + y, z0 + z); + } + + public BlockPos byXZIndex(int xz_index, int y_offset) { + final int xsz = getXSize(), zsz = getZSize(); + xz_index = xz_index % (xsz * zsz); + final int z = xz_index / xsz; + xz_index -= z * xsz; + final int x = xz_index; + return new BlockPos(x0 + x, y0 + y_offset, z0 + z); + } + + public static final class BlockRangeIterator implements Iterator { + private final BlockPosRange range_; + private int x, y, z; + + public BlockRangeIterator(BlockPosRange range) { + range_ = range; + x = range.x0; + y = range.y0; + z = range.z0; + } + + @Override + public boolean hasNext() { + return (z <= range_.z1); + } + + @Override + public BlockPos next() { + if (!hasNext()) throw new NoSuchElementException(); + final BlockPos pos = new BlockPos(x, y, z); + ++x; + if (x > range_.x1) { + x = range_.x0; + ++y; + if (y > range_.y1) { + y = range_.y0; + ++z; + } + } + return pos; + } + } + + @Override + public BlockRangeIterator iterator() { + return new BlockRangeIterator(this); + } + + public Stream stream() { + return java.util.stream.StreamSupport.stream(spliterator(), false); + } + } + + // ------------------------------------------------------------------------------------------------------------------- + // JAR resource related + // ------------------------------------------------------------------------------------------------------------------- + + public static String loadResourceText(InputStream is) { + try { + if (is == null) return ""; + BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); + return br.lines().collect(Collectors.joining("\n")); + } catch (Throwable e) { + return ""; + } + } + + public static String loadResourceText(String path) { + return loadResourceText(Auxiliaries.class.getResourceAsStream(path)); + } + + public static void logGitVersion(String mod_name) { + try { + // Done during construction to have an exact version in case of a crash while registering. + String version = Auxiliaries.loadResourceText("/.gitversion-" + modid).trim(); + logInfo(mod_name + ((version.isEmpty()) ? (" (dev build)") : (" GIT id #" + version)) + "."); + } catch (Throwable e) { + // (void)e; well, then not. Priority is not to get unneeded crashes because of version logging. + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Containers.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Containers.java new file mode 100644 index 0000000..37a1814 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Containers.java @@ -0,0 +1,138 @@ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.util.Mth; +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import java.util.function.BiConsumer; + +public class Containers { + // ------------------------------------------------------------------------------------------------------------------- + // Slots + // ------------------------------------------------------------------------------------------------------------------- + + public static class StorageSlot extends Slot { + protected BiConsumer slot_change_action_ = (oldStack, newStack) -> { + }; + protected int stack_limit_ = 64; + public boolean enabled = true; + + public StorageSlot(Container inventory, int index, int x, int y) { + super(inventory, index, x, y); + } + + public StorageSlot setSlotStackLimit(int limit) { + stack_limit_ = Mth.clamp(limit, 1, 64); + return this; + } + + public int getMaxStackSize() { + return stack_limit_; + } + + public StorageSlot setSlotChangeNotifier(BiConsumer action) { + slot_change_action_ = action; + return this; + } + + @Override + public void onQuickCraft(ItemStack oldStack, ItemStack newStack) { + slot_change_action_.accept(oldStack, newStack); + } // no crafting trigger + + @Override + public void set(ItemStack stack) { + if (stack.is(getItem().getItem())) { + super.set(stack); + } else { + final ItemStack before = getItem().copy(); + super.set(stack); // whatever this does else next to setting inventory. + slot_change_action_.accept(before, getItem()); + } + } + + @Override + public boolean mayPlace(ItemStack stack) { + return enabled && this.container.canPlaceItem(this.getSlotIndex(), stack); + } + + @Override + public int getMaxStackSize(ItemStack stack) { + return Math.min(getMaxStackSize(), stack_limit_); + } + + @OnlyIn(Dist.CLIENT) + public boolean isActive() { + return enabled; + } + } + + public static class LockedSlot extends Slot { + protected int stack_limit_ = 64; + public boolean enabled = true; + + public LockedSlot(Container inventory, int index, int x, int y) { + super(inventory, index, x, y); + } + + public LockedSlot setSlotStackLimit(int limit) { + stack_limit_ = Mth.clamp(limit, 1, 64); + return this; + } + + public int getMaxStackSize() { + return stack_limit_; + } + + @Override + public int getMaxStackSize(ItemStack stack) { + return Math.min(getMaxStackSize(), stack_limit_); + } + + @Override + public boolean mayPlace(ItemStack stack) { + return false; + } + + @Override + public boolean mayPickup(Player player) { + return false; + } + + @OnlyIn(Dist.CLIENT) + public boolean isActive() { + return enabled; + } + } + + public static class HiddenSlot extends Slot { + public HiddenSlot(Container inventory, int index) { + super(inventory, index, 0, 0); + } + + @Override + public int getMaxStackSize(ItemStack stack) { + return getMaxStackSize(); + } + + @Override + public boolean mayPlace(ItemStack stack) { + return false; + } + + @Override + public boolean mayPickup(Player player) { + return false; + } + + @OnlyIn(Dist.CLIENT) + public boolean isActive() { + return false; + } + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Crafting.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Crafting.java new file mode 100644 index 0000000..2708733 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Crafting.java @@ -0,0 +1,440 @@ +/* + * @file Recipes.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Recipe utility functionality. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.NonNullList; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.util.Tuple; +import net.minecraft.world.Container; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.player.StackedContents; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CraftingContainer; +import net.minecraft.world.item.EnchantedBookItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.*; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.EnchantmentInstance; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.ComposterBlock; +import net.minecraftforge.common.ForgeHooks; +import net.minecraftforge.common.brewing.BrewingRecipeRegistry; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.BiPredicate; + + +public class Crafting { + // ------------------------------------------------------------------------------------------------------------------- + + /** + * Returns a Crafting recipe by registry name. + */ + public static Optional getCraftingRecipe(Level world, ResourceLocation recipe_id) { + Recipe recipe = world.getRecipeManager().byKey(recipe_id).orElse(null); + return (recipe instanceof CraftingRecipe) ? Optional.of((CraftingRecipe) recipe) : Optional.empty(); + } + + /** + * Returns a list of matching recipes by the first N slots (crafting grid slots) of the given inventory. + */ + public static List get3x3CraftingRecipes(Level world, Container crafting_grid_slots) { + return CraftingGrid.instance3x3.getRecipes(world, crafting_grid_slots); + } + + /** + * Returns a recipe by the first N slots (crafting grid slots). + */ + public static Optional get3x3CraftingRecipe(Level world, Container crafting_grid_slots) { + return get3x3CraftingRecipes(world, crafting_grid_slots).stream().findFirst(); + } + + /** + * Returns the result item of the recipe with the given grid layout. + */ + public static ItemStack get3x3CraftingResult(Level world, Container grid, CraftingRecipe recipe) { + return CraftingGrid.instance3x3.getCraftingResult(world, grid, recipe); + } + + /** + * Returns the items remaining in the grid after crafting 3x3. + */ + public static List get3x3RemainingItems(Level world, Container grid, CraftingRecipe recipe) { + return CraftingGrid.instance3x3.getRemainingItems(world, grid, recipe); + } + + public static List get3x3Placement(Level world, CraftingRecipe recipe, Container item_inventory, @Nullable Container crafting_grid) { + final int width = 3; + final int height = 3; + if (!recipe.canCraftInDimensions(width, height)) return Collections.emptyList(); + List used = new ArrayList<>(); //NonNullList.withSize(width*height); + for (int i = width * height; i > 0; --i) used.add(ItemStack.EMPTY); + Container check_inventory = Inventories.copyOf(item_inventory); + Inventories.InventoryRange source = new Inventories.InventoryRange(check_inventory); + final List ingredients = recipe.getIngredients(); + final List preferred = new ArrayList<>(width * height); + if (crafting_grid != null) { + for (int i = 0; i < crafting_grid.getContainerSize(); ++i) { + ItemStack stack = crafting_grid.getItem(i); + if (stack.isEmpty()) continue; + stack = stack.copy(); + stack.setCount(1); + if (!source.extract(stack).isEmpty()) preferred.add(stack); + } + } + for (int i = 0; i < ingredients.size(); ++i) { + final Ingredient ingredient = ingredients.get(i); + if (ingredient == Ingredient.EMPTY) continue; + ItemStack stack = preferred.stream().filter(ingredient).findFirst().orElse(ItemStack.EMPTY); + if (!stack.isEmpty()) { + preferred.remove(stack); + } else { + stack = source.stream().filter(ingredient).findFirst().orElse(ItemStack.EMPTY); + if (stack.isEmpty()) return Collections.emptyList(); + stack = stack.copy(); + stack.setCount(1); + if (source.extract(stack).isEmpty()) return Collections.emptyList(); + } + used.set(i, stack); + } + if (recipe instanceof ShapedRecipe shaped) { + List placement = NonNullList.withSize(width * height, ItemStack.EMPTY); + for (int row = 0; row < shaped.getRecipeHeight(); ++row) { + for (int col = 0; col < shaped.getRecipeWidth(); ++col) { + placement.set(width * row + col, used.get(row * shaped.getRecipeWidth() + col)); + } + } + return placement; + } else { + return used; + } + } + + /** + * Returns the recipe for a given input stack to smelt, null if there is no recipe + * for the given type (SMELTING,BLASTING,SMOKING, etc). + */ + public static > Optional getFurnaceRecipe(RecipeType recipe_type, Level world, ItemStack input_stack) { + if (input_stack.isEmpty()) { + return Optional.empty(); + } else if (recipe_type == RecipeType.SMELTING) { + SimpleContainer inventory = new SimpleContainer(3); + inventory.setItem(0, input_stack); + SmeltingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.SMELTING, inventory, world).orElse(null); + return (recipe == null) ? Optional.empty() : Optional.of(recipe); + } else if (recipe_type == RecipeType.BLASTING) { + SimpleContainer inventory = new SimpleContainer(3); + inventory.setItem(0, input_stack); + BlastingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.BLASTING, inventory, world).orElse(null); + return (recipe == null) ? Optional.empty() : Optional.of(recipe); + } else if (recipe_type == RecipeType.SMOKING) { + SimpleContainer inventory = new SimpleContainer(3); + inventory.setItem(0, input_stack); + SmokingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.SMOKING, inventory, world).orElse(null); + return (recipe == null) ? Optional.empty() : Optional.of(recipe); + } else { + return Optional.empty(); + } + } + + // ------------------------------------------------------------------------------------------------------------------- + + public static > int getSmeltingTimeNeeded(RecipeType recipe_type, Level world, ItemStack stack) { + if (stack.isEmpty()) return 0; + final int t = getFurnaceRecipe(recipe_type, world, stack).map((AbstractCookingRecipe::getCookingTime)).orElse(0); + return (t <= 0) ? 200 : t; + } + + /** + * Returns the burn time of an item when used as fuel, 0 if it is no fuel. + */ + public static int getFuelBurntime(Level world, ItemStack stack) { + if (stack.isEmpty()) return 0; + int t = ForgeHooks.getBurnTime(stack, null); + return Math.max(t, 0); + } + + /** + * Returns true if an item can be used as fuel. + */ + public static boolean isFuel(Level world, ItemStack stack) { + return (getFuelBurntime(world, stack) > 0) || (stack.getItem() == Items.LAVA_BUCKET); + } + + /** + * Returns burntime and remaining stack then the item shall be used as fuel. + */ + public static Tuple consumeFuel(Level world, ItemStack stack) { + if (stack.isEmpty()) return new Tuple<>(0, stack); + int burnime = getFuelBurntime(world, stack); + if ((stack.getItem() == Items.LAVA_BUCKET)) { + if (burnime <= 0) burnime = 1000 * 20; + return new Tuple<>(burnime, new ItemStack(Items.BUCKET)); + } else if (burnime <= 0) { + return new Tuple<>(0, stack); + } else { + ItemStack left_over = stack.copy(); + left_over.shrink(1); + return new Tuple<>(burnime, left_over); + } + } + + /** + * Returns true if the item can be used as brewing fuel. + */ + public static boolean isBrewingFuel(Level world, ItemStack stack) { + return (stack.getItem() == Items.BLAZE_POWDER) || (stack.getItem() == Items.BLAZE_ROD); + } + + // ------------------------------------------------------------------------------------------------------------------- + + /** + * Returns true if the item can be used as brewing ingredient. + */ + public static boolean isBrewingIngredient(Level world, ItemStack stack) { + return BrewingRecipeRegistry.isValidIngredient(stack); + } + + /** + * Returns true if the item can be used as brewing bottle. + */ + public static boolean isBrewingInput(Level world, ItemStack stack) { + return BrewingRecipeRegistry.isValidInput(stack); + } + + /** + * Returns the burn time for brewing of the given stack. + */ + public static int getBrewingFuelBurntime(Level world, ItemStack stack) { + if (stack.isEmpty()) return 0; + if (stack.getItem() == Items.BLAZE_POWDER) return (400 * 20); + if (stack.getItem() == Items.BLAZE_ROD) return (400 * 40); + return 0; + } + + /** + * Returns brewing burn time and remaining stack if the item shall be used as fuel. + */ + public static Tuple consumeBrewingFuel(Level world, ItemStack stack) { + int burntime = getBrewingFuelBurntime(world, stack); + if (burntime <= 0) return new Tuple<>(0, stack.copy()); + stack = stack.copy(); + stack.shrink(1); + return new Tuple<>(burntime, stack.isEmpty() ? ItemStack.EMPTY : stack); + } + + public static double getCompostingChance(ItemStack stack) { + return ComposterBlock.COMPOSTABLES.getOrDefault(stack.getItem(), 0); + } + + /** + * Returns the enchtments bound to the given stack. + */ + public static Map getEnchantmentsOnItem(Level world, ItemStack stack) { + return (stack.isEmpty() || (stack.getTag() == null)) ? Collections.emptyMap() : EnchantmentHelper.getEnchantments(stack); + } + + // ------------------------------------------------------------------------------------------------------------------- + + /** + * Returns an enchanted book with the given enchantment, emtpy stack if not applicable. + */ + public static ItemStack getEnchantmentBook(Level world, Enchantment enchantment, int level) { + return ((!enchantment.isAllowedOnBooks()) || (level <= 0)) ? ItemStack.EMPTY : EnchantedBookItem.createForEnchantment(new EnchantmentInstance(enchantment, level)); + } + + // ------------------------------------------------------------------------------------------------------------------- + + /** + * Returns the accumulated repair cost for the given enchantments. + */ + public static int getEnchantmentRepairCost(Level world, Map enchantments) { + int repair_cost = 0; + for (Map.Entry e : enchantments.entrySet()) + repair_cost = repair_cost * 2 + 1; // @see: RepairContainer.getNewRepairCost() + return repair_cost; + } + + /** + * Trys to add an enchtment to the given stack, returns boolean success. + */ + public static boolean addEnchantmentOnItem(Level world, ItemStack stack, Enchantment enchantment, int level) { + if (stack.isEmpty() || (level <= 0) || (!stack.isEnchantable()) || (level >= enchantment.getMaxLevel())) + return false; + final Map on_item = getEnchantmentsOnItem(world, stack); + if (on_item.keySet().stream().anyMatch(ench -> ench.isCompatibleWith(enchantment))) return false; + final ItemStack book = EnchantedBookItem.createForEnchantment(new EnchantmentInstance(enchantment, level)); + if ((!(stack.isBookEnchantable(book) && enchantment.isAllowedOnBooks())) && (!stack.canApplyAtEnchantingTable(enchantment)) && (!enchantment.canEnchant(stack))) + return false; + final int existing_level = on_item.getOrDefault(enchantment, 0); + if (existing_level > 0) level = Mth.clamp(level + existing_level, 1, enchantment.getMaxLevel()); + on_item.put(enchantment, level); + EnchantmentHelper.setEnchantments(on_item, stack); + stack.setRepairCost(getEnchantmentRepairCost(world, on_item)); + return true; + } + + /** + * Removes enchantments from a stack, returns the removed enchantments. + */ + public static Map removeEnchantmentsOnItem(Level world, ItemStack stack, BiPredicate filter) { + if (stack.isEmpty()) return Collections.emptyMap(); + final Map on_item = getEnchantmentsOnItem(world, stack); + final Map removed = new HashMap<>(); + for (Map.Entry e : on_item.entrySet()) { + if (filter.test(e.getKey(), e.getValue())) { + removed.put(e.getKey(), e.getValue()); + } + } + for (Enchantment e : removed.keySet()) { + on_item.remove(e); + } + EnchantmentHelper.setEnchantments(on_item, stack); + stack.setRepairCost(getEnchantmentRepairCost(world, on_item)); + return removed; + } + + public static final class CraftingGrid implements CraftingContainer { + private static final CraftingGrid instance3x3 = new CraftingGrid(3, 3); + + final int _width; + + private CraftingGrid(int width, int height) { + _width=width; + } + + private void fill(Container grid) { + for (int i = 0; i < getContainerSize(); ++i) + setItem(i, i >= grid.getContainerSize() ? ItemStack.EMPTY : grid.getItem(i)); + } + + public List getRecipes(Level world, Container grid) { + fill(grid); + return world.getRecipeManager().getRecipesFor(RecipeType.CRAFTING, this, world); + } + + public List getRemainingItems(Level world, Container grid, CraftingRecipe recipe) { + fill(grid); + return recipe.getRemainingItems(this); + } + + public ItemStack getCraftingResult(Level world, Container grid, CraftingRecipe recipe) { + fill(grid); + return recipe.assemble(this, world.registryAccess()); + } + + @Override + public int getWidth() { + return _width; + } + + @Override + public int getHeight() { + return 0; + } + + @Override + public List getItems() { + return null; + } + + @Override + public int getContainerSize() { + return 0; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public ItemStack getItem(int i) { + return null; + } + + @Override + public ItemStack removeItem(int i, int i1) { + return null; + } + + @Override + public ItemStack removeItemNoUpdate(int i) { + return null; + } + + @Override + public void setItem(int i, ItemStack itemStack) { + + } + + @Override + public void setChanged() { + + } + + @Override + public boolean stillValid(Player player) { + return false; + } + + @Override + public void clearContent() { + + } + + @Override + public void fillStackedContents(StackedContents stackedContents) { + + } + } + + public static final class BrewingOutput { + public static final int DEFAULT_BREWING_TIME = 400; + public static final BrewingOutput EMPTY = new BrewingOutput(ItemStack.EMPTY, new SimpleContainer(1), new SimpleContainer(1), 0, 0, DEFAULT_BREWING_TIME); + public final ItemStack item; + public final Container potionInventory; + public final Container ingredientInventory; + public final int potionSlot; + public final int ingredientSlot; + public final int brewTime; + + public BrewingOutput(ItemStack output_potion, Container potion_inventory, Container ingredient_inventory, int potion_slot, int ingredient_slot, int time_needed) { + item = output_potion; + potionInventory = potion_inventory; + ingredientInventory = ingredient_inventory; + potionSlot = potion_slot; + ingredientSlot = ingredient_slot; + brewTime = time_needed; + } + + public static BrewingOutput find(Level world, Container potion_inventory, Container ingredient_inventory) { + for (int potion_slot = 0; potion_slot < potion_inventory.getContainerSize(); ++potion_slot) { + final ItemStack pstack = potion_inventory.getItem(potion_slot); + if (!isBrewingInput(world, pstack)) continue; + for (int ingredient_slot = 0; ingredient_slot < ingredient_inventory.getContainerSize(); ++ingredient_slot) { + final ItemStack istack = ingredient_inventory.getItem(ingredient_slot); + if ((!isBrewingIngredient(world, istack)) || (ingredient_slot == potion_slot) || (isBrewingFuel(world, istack))) + continue; + final ItemStack result = BrewingRecipeRegistry.getOutput(pstack, istack); + if (result.isEmpty()) continue; + return new BrewingOutput(result, potion_inventory, ingredient_inventory, potion_slot, ingredient_slot, DEFAULT_BREWING_TIME); + } + } + return BrewingOutput.EMPTY; + } + } + + +} \ No newline at end of file diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Fluidics.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Fluidics.java new file mode 100644 index 0000000..6ac9dbd --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Fluidics.java @@ -0,0 +1,485 @@ +/* + * @file Fluidics.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * General fluid handling functionality. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.Mth; +import net.minecraft.util.Tuple; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.material.Fluid; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ForgeCapabilities; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraft.nbt.Tag; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.fluids.FluidActionResult; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.FluidUtil; +import net.minecraftforge.fluids.IFluidTank; +import net.minecraftforge.fluids.capability.IFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction; +import net.minecraftforge.fluids.capability.IFluidHandlerItem; +import net.minecraftforge.items.IItemHandler; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Predicate; + + +public class Fluidics { + public static class SingleTankFluidHandler implements IFluidHandler { + private final IFluidTank tank_; + + public SingleTankFluidHandler(IFluidTank tank) { + tank_ = tank; + } + + @Override + public int getTanks() { + return 1; + } + + @Override + public FluidStack getFluidInTank(int tank) { + return tank_.getFluid(); + } + + @Override + public int getTankCapacity(int tank) { + return tank_.getCapacity(); + } + + @Override + public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { + return tank_.isFluidValid(stack); + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + return tank_.fill(resource, action); + } + + @Override + public FluidStack drain(FluidStack resource, FluidAction action) { + return tank_.drain(resource, action); + } + + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + return tank_.drain(maxDrain, action); + } + } + + private static class SingleTankOutputFluidHandler implements IFluidHandler { + private final IFluidTank tank_; + + public SingleTankOutputFluidHandler(IFluidTank tank) { + tank_ = tank; + } + + @Override + public int getTanks() { + return 1; + } + + @Override + public FluidStack getFluidInTank(int tank) { + return tank_.getFluid().copy(); + } + + @Override + public int getTankCapacity(int tank) { + return tank_.getCapacity(); + } + + @Override + public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { + return true; + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + return 0; + } + + @Override + public FluidStack drain(FluidStack resource, FluidAction action) { + return tank_.drain(resource, action); + } + + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + return tank_.drain(maxDrain, action); + } + } + + public static class Tank implements IFluidTank { + private Predicate validator_ = ((e) -> true); + private BiConsumer interaction_notifier_ = ((tank, diff) -> { + }); + private FluidStack fluid_ = FluidStack.EMPTY; + private int capacity_; + private int fill_rate_; + private int drain_rate_; + + public Tank(int capacity) { + this(capacity, capacity, capacity); + } + + public Tank(int capacity, int fill_rate, int drain_rate) { + this(capacity, fill_rate, drain_rate, e -> true); + } + + public Tank(int capacity, int fill_rate, int drain_rate, Predicate validator) { + capacity_ = capacity; + setMaxFillRate(fill_rate); + setMaxDrainRate(drain_rate); + setValidator(validator); + } + + public Tank load(CompoundTag nbt) { + if (nbt.contains("tank", Tag.TAG_COMPOUND)) { + setFluid(FluidStack.loadFluidStackFromNBT(nbt.getCompound("tank"))); + } else { + clear(); + } + return this; + } + + public CompoundTag save(CompoundTag nbt) { + if (!isEmpty()) { + nbt.put("tank", fluid_.writeToNBT(new CompoundTag())); + } + return nbt; + } + + public void reset() { + clear(); + } + + public Tank clear() { + setFluid(null); + return this; + } + + public int getCapacity() { + return capacity_; + } + + public Tank setCapacity(int capacity) { + capacity_ = capacity; + return this; + } + + public int getMaxDrainRate() { + return drain_rate_; + } + + public Tank setMaxDrainRate(int rate) { + drain_rate_ = Mth.clamp(rate, 0, capacity_); + return this; + } + + public int getMaxFillRate() { + return fill_rate_; + } + + public Tank setMaxFillRate(int rate) { + fill_rate_ = Mth.clamp(rate, 0, capacity_); + return this; + } + + public Tank setValidator(Predicate validator) { + validator_ = (validator != null) ? validator : ((e) -> true); + return this; + } + + public Tank setInteractionNotifier(BiConsumer notifier) { + interaction_notifier_ = (notifier != null) ? notifier : ((tank, diff) -> { + }); + return this; + } + + public LazyOptional createFluidHandler() { + return LazyOptional.of(() -> new Fluidics.SingleTankFluidHandler(this)); + } + + public LazyOptional createOutputFluidHandler() { + return LazyOptional.of(() -> new Fluidics.SingleTankOutputFluidHandler(this)); + } + + // IFluidTank ------------------------------------------------------------------------------------ + + @Nonnull + public FluidStack getFluid() { + return fluid_; + } + + public void setFluid(@Nullable FluidStack stack) { + fluid_ = (stack == null) ? FluidStack.EMPTY : stack; + } + + public int getFluidAmount() { + return fluid_.getAmount(); + } + + public boolean isEmpty() { + return fluid_.isEmpty(); + } + + public boolean isFull() { + return getFluidAmount() >= getCapacity(); + } + + public boolean isFluidValid(FluidStack stack) { + return validator_.test(stack); + } + + public boolean isFluidEqual(FluidStack stack) { + return (stack == null) ? (fluid_.isEmpty()) : fluid_.isFluidEqual(stack); + } + + @Override + public int fill(FluidStack fs, FluidAction action) { + if ((fs == null) || fs.isEmpty() || (!isFluidValid(fs))) { + return 0; + } else if (action.simulate()) { + if (fluid_.isEmpty()) return Math.min(capacity_, fs.getAmount()); + if (!fluid_.isFluidEqual(fs)) return 0; + return Math.min(capacity_ - fluid_.getAmount(), fs.getAmount()); + } else if (fluid_.isEmpty()) { + fluid_ = new FluidStack(fs, Math.min(capacity_, fs.getAmount())); + return fluid_.getAmount(); + } else if (!fluid_.isFluidEqual(fs)) { + return 0; + } else { + int amount = capacity_ - fluid_.getAmount(); + if (fs.getAmount() < amount) { + fluid_.grow(fs.getAmount()); + amount = fs.getAmount(); + } else { + fluid_.setAmount(capacity_); + } + if (amount != 0) interaction_notifier_.accept(this, amount); + return amount; + } + } + + @Nonnull + public FluidStack drain(int maxDrain) { + return drain(maxDrain, FluidAction.EXECUTE); + } + + @Nonnull + @Override + public FluidStack drain(FluidStack fs, FluidAction action) { + return ((fs.isEmpty()) || (!fs.isFluidEqual(fluid_))) ? FluidStack.EMPTY : drain(fs.getAmount(), action); + } + + @Nonnull + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + final int amount = Math.min(fluid_.getAmount(), maxDrain); + final FluidStack stack = new FluidStack(fluid_, amount); + if ((amount > 0) && action.execute()) { + fluid_.shrink(amount); + if (fluid_.isEmpty()) fluid_ = FluidStack.EMPTY; + if (amount != 0) interaction_notifier_.accept(this, -amount); + } + return stack; + } + } + + // ------------------------------------------------------------------------------------------------------------------- + + public static @Nullable IFluidHandler handler(Level world, BlockPos pos, @Nullable Direction side) { + return FluidUtil.getFluidHandler(world, pos, side).orElse(null); + } + + /** + * Fills or drains items with fluid handlers from or into tile blocks with fluid handlers. + */ + public static boolean manualFluidHandlerInteraction(Level world, BlockPos pos, @Nullable Direction side, Player player, InteractionHand hand) { + return manualTrackedFluidHandlerInteraction(world, pos, side, player, hand) != null; + } + + public static boolean manualFluidHandlerInteraction(Player player, InteractionHand hand, IFluidHandler handler) { + return FluidUtil.interactWithFluidHandler(player, hand, handler); + } + + /** + * Fills or drains items with fluid handlers from or into tile blocks with fluid handlers. + * Returns the fluid and (possibly negative) amount that transferred from the item into the block. + */ + public static @Nullable Tuple manualTrackedFluidHandlerInteraction(Level world, BlockPos pos, @Nullable Direction side, Player player, InteractionHand hand) { + if (world.isClientSide()) return null; + final ItemStack held = player.getItemInHand(hand); + if (held.isEmpty()) return null; + final IFluidHandler fh = handler(world, pos, side); + if (fh == null) return null; + final IItemHandler ih = player.getCapability(ForgeCapabilities.ITEM_HANDLER).orElse(null); + if (ih == null) return null; + FluidActionResult far = FluidUtil.tryFillContainerAndStow(held, fh, ih, Integer.MAX_VALUE, player, true); + if (!far.isSuccess()) far = FluidUtil.tryEmptyContainerAndStow(held, fh, ih, Integer.MAX_VALUE, player, true); + if (!far.isSuccess()) return null; + final ItemStack rstack = far.getResult().copy(); + player.setItemInHand(hand, far.getResult()); + final IFluidHandler fh_before = FluidUtil.getFluidHandler(held).orElse(null); + final IFluidHandler fh_after = FluidUtil.getFluidHandler(rstack).orElse(null); + if ((fh_before == null) || (fh_after == null) || (fh_after.getTanks() != fh_before.getTanks())) + return null; // should not be, but y'never know. + for (int i = 0; i < fh_before.getTanks(); ++i) { + final int vol_before = fh_before.getFluidInTank(i).getAmount(); + final int vol_after = fh_after.getFluidInTank(i).getAmount(); + if (vol_before != vol_after) { + return new Tuple<>( + (vol_before > 0) ? (fh_before.getFluidInTank(i).getFluid()) : (fh_after.getFluidInTank(i).getFluid()), + (vol_before - vol_after) + ); + } + } + return null; + } + + public static boolean manualFluidHandlerInteraction(Player player, InteractionHand hand, Level world, BlockPos pos, @Nullable Direction side) { + return FluidUtil.interactWithFluidHandler(player, hand, world, pos, side); + } + + public static int fill(Level world, BlockPos pos, Direction side, FluidStack fs, FluidAction action) { + IFluidHandler fh = FluidUtil.getFluidHandler(world, pos, side).orElse(null); + return (fh == null) ? (0) : (fh.fill(fs, action)); + } + + public static int fill(Level world, BlockPos pos, Direction side, FluidStack fs) { + return fill(world, pos, side, fs, FluidAction.EXECUTE); + } + + /** + * Fluid tank access when itemized. + */ + public static class FluidContainerItemCapabilityWrapper implements IFluidHandlerItem, ICapabilityProvider { + private final LazyOptional handler_ = LazyOptional.of(() -> this); + private final Function nbt_getter_; + private final BiConsumer nbt_setter_; + private final Predicate validator_; + private final ItemStack container_; + private final int capacity_; + private final int transfer_rate_; + + public FluidContainerItemCapabilityWrapper(ItemStack container, int capacity, int transfer_rate, + Function nbt_getter, + BiConsumer nbt_setter, + Predicate validator) { + container_ = container; + capacity_ = capacity; + transfer_rate_ = transfer_rate; + nbt_getter_ = nbt_getter; + nbt_setter_ = nbt_setter; + validator_ = (validator != null) ? validator : (e -> true); + } + + @Override + public LazyOptional getCapability(Capability capability, @Nullable Direction side) { + return (capability == ForgeCapabilities.FLUID_HANDLER) ? handler_.cast() : LazyOptional.empty(); + } + + protected FluidStack readnbt() { + final CompoundTag nbt = nbt_getter_.apply(container_); + return ((nbt == null) || (nbt.isEmpty())) ? FluidStack.EMPTY : FluidStack.loadFluidStackFromNBT(nbt); + } + + protected void writenbt(FluidStack fs) { + CompoundTag nbt = new CompoundTag(); + if (!fs.isEmpty()) fs.writeToNBT(nbt); + nbt_setter_.accept(container_, nbt); + } + + @Override + public ItemStack getContainer() { + return container_; + } + + @Override + public int getTanks() { + return 1; + } + + @Override + public FluidStack getFluidInTank(int tank) { + return readnbt(); + } + + @Override + public int getTankCapacity(int tank) { + return capacity_; + } + + @Override + public boolean isFluidValid(int tank, FluidStack fs) { + return isFluidValid(fs); + } + + public boolean isFluidValid(FluidStack fs) { + return validator_.test(fs); + } + + @Override + public int fill(FluidStack fs, FluidAction action) { + if ((fs.isEmpty()) || (!isFluidValid(fs) || (container_.getCount() != 1))) return 0; + FluidStack tank = readnbt(); + final int amount = Math.min(Math.min(fs.getAmount(), transfer_rate_), capacity_ - tank.getAmount()); + if (amount <= 0) return 0; + if (tank.isEmpty()) { + if (action.execute()) { + tank = new FluidStack(fs.getFluid(), amount, fs.getTag()); + writenbt(tank); + } + } else { + if (!tank.isFluidEqual(fs)) { + return 0; + } else if (action.execute()) { + tank.grow(amount); + writenbt(tank); + } + } + return amount; + } + + @Override + public FluidStack drain(FluidStack fs, FluidAction action) { + if ((fs.isEmpty()) || (container_.getCount() != 1)) return FluidStack.EMPTY; + final FluidStack tank = readnbt(); + if ((!tank.isEmpty()) && (!tank.isFluidEqual(fs))) return FluidStack.EMPTY; + return drain(fs.getAmount(), action); + } + + @Override + public FluidStack drain(int max, FluidAction action) { + if ((max <= 0) || (container_.getCount() != 1)) return FluidStack.EMPTY; + FluidStack tank = readnbt(); + if (tank.isEmpty()) return FluidStack.EMPTY; + final int amount = Math.min(Math.min(tank.getAmount(), max), transfer_rate_); + final FluidStack fs = tank.copy(); + fs.setAmount(amount); + if (action.execute()) { + tank.shrink(amount); + writenbt(tank); + } + return fs; + } + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Guis.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Guis.java new file mode 100644 index 0000000..1374a19 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Guis.java @@ -0,0 +1,466 @@ +/* + * @file Guis.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Gui Wrappers and Widgets. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.mojang.blaze3d.platform.Window; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.entity.ItemRenderer; +import net.minecraft.client.sounds.SoundManager; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import java.util.Arrays; +import java.util.function.Consumer; +import java.util.function.Function; + + +public class Guis { + // ------------------------------------------------------------------------------------------------------------------- + // Gui base + // ------------------------------------------------------------------------------------------------------------------- + + @OnlyIn(Dist.CLIENT) + public static abstract class ContainerGui extends AbstractContainerScreen { + protected final ResourceLocation background_image_; + protected final Player player_; + protected final Guis.BackgroundImage gui_background_; + protected final TooltipDisplay tooltip_ = new TooltipDisplay(); + + public ContainerGui(T menu, Inventory player_inv, Component title, String background_image, int width, int height) { + super(menu, player_inv, title); + this.background_image_ = new ResourceLocation(Auxiliaries.modid(), background_image); + this.player_ = player_inv.player; + this.imageWidth = width; + this.imageHeight = height; + gui_background_ = new Guis.BackgroundImage(background_image_, width, height, Coord2d.ORIGIN); + } + + public ContainerGui(T menu, Inventory player_inv, Component title, String background_image) { + super(menu, player_inv, title); + this.background_image_ = new ResourceLocation(Auxiliaries.modid(), background_image); + this.player_ = player_inv.player; + gui_background_ = new Guis.BackgroundImage(background_image_, imageWidth, imageHeight, Coord2d.ORIGIN); + } + + @Override + public void init() { + super.init(); + gui_background_.init(this, Coord2d.ORIGIN).show(); + } + + @Override + public void render(GuiGraphics mx, int mouseX, int mouseY, float partialTicks) { + renderBackground(mx); + super.render(mx, mouseX, mouseY, partialTicks); + if (!tooltip_.render(mx, this, mouseX, mouseY)) renderTooltip(mx, mouseX, mouseY); + } + + @Override + protected void renderLabels(GuiGraphics mx, int x, int y) { + } + + @Override + @SuppressWarnings("deprecation") + protected final void renderBg(GuiGraphics mx, float partialTicks, int mouseX, int mouseY) { + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + gui_background_.draw(mx, this); + renderBgWidgets(mx, partialTicks, mouseX, mouseY); + RenderSystem.disableBlend(); + } + + public final ResourceLocation getBackgroundImage() { + return background_image_; + } + + protected void renderBgWidgets(GuiGraphics mx, float partialTicks, int mouseX, int mouseY) { + } + + protected void renderItemTemplate(GuiGraphics mx, ItemStack stack, int x, int y) { + final int x0 = getGuiLeft(); + final int y0 = getGuiTop(); + + mx.renderFakeItem(stack, x0 + x, y0 + y); + RenderSystem.disableColorLogicOp(); //RenderSystem.disableColorMaterial(); + RenderSystem.enableDepthTest(); //RenderSystem.enableAlphaTest(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableBlend(); + RenderSystem.colorMask(true, true, true, true); + RenderSystem.setShaderColor(0.7f, 0.7f, 0.7f, 0.8f); + RenderSystem.setShaderTexture(0, background_image_); + mx.blit(background_image_,x0 + x, y0 + y, x, y, 16, 16); + RenderSystem.setShaderColor(1f, 1f, 1f, 1f); + } + } + + // ------------------------------------------------------------------------------------------------------------------- + // Gui elements + // ------------------------------------------------------------------------------------------------------------------- + + @OnlyIn(Dist.CLIENT) + public static class Coord2d { + public static final Coord2d ORIGIN = new Coord2d(0, 0); + public final int x, y; + + public Coord2d(int x, int y) { + this.x = x; + this.y = y; + } + + public static Coord2d of(int x, int y) { + return new Coord2d(x, y); + } + + public String toString() { + return "[" + x + "," + y + "]"; + } + } + + @OnlyIn(Dist.CLIENT) + public static class UiWidget extends AbstractWidget { + protected static final Component EMPTY_TEXT = Component.literal(""); + protected static final Function NO_TOOLTIP = (uiw) -> EMPTY_TEXT; + + private final Minecraft mc_; + private Function tooltip_ = NO_TOOLTIP; + private Screen parent_; + + public UiWidget(int x, int y, int width, int height, Component title) { + super(x, y, width, height, title); + mc_ = Minecraft.getInstance(); + } + + public UiWidget init(Screen parent) { + this.parent_ = parent; + this.setX(((parent instanceof AbstractContainerScreen) ? ((AbstractContainerScreen) parent).getGuiLeft() : 0)); + this.setY(((parent instanceof AbstractContainerScreen) ? ((AbstractContainerScreen) parent).getGuiTop() : 0)); + return this; + } + + public UiWidget init(Screen parent, Coord2d position) { + this.parent_ = parent; + this.setX(position.x + ((parent instanceof AbstractContainerScreen) ? ((AbstractContainerScreen) parent).getGuiLeft() : 0)); + this.setY(position.y + ((parent instanceof AbstractContainerScreen) ? ((AbstractContainerScreen) parent).getGuiTop() : 0)); + return this; + } + + public final UiWidget tooltip(Function tip) { + tooltip_ = tip; + return this; + } + + public final UiWidget tooltip(Component tip) { + tooltip_ = (o) -> tip; + return this; + } + + public final int getWidth() { + return this.width; + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { + + } + + public final int getHeight() { + return this.height; + } + + public Coord2d getMousePosition() { + final Window win = mc_.getWindow(); + return Coord2d.of( + Mth.clamp(((int) (mc_.mouseHandler.xpos() * (double) win.getGuiScaledWidth() / (double) win.getScreenWidth())) - this.getX(), -1, this.width + 1), + Mth.clamp(((int) (mc_.mouseHandler.ypos() * (double) win.getGuiScaledHeight() / (double) win.getScreenHeight())) - this.getY(), -1, this.height + 1) + ); + } + + protected final Coord2d screenCoordinates(Coord2d xy, boolean reverse) { + return (reverse) ? (Coord2d.of(xy.x + getX(), xy.y + getY())) : (Coord2d.of(xy.x - getX(), xy.y - getY())); + } + + public UiWidget show() { + visible = true; + return this; + } + + public UiWidget hide() { + visible = false; + return this; + } + + @Override + public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { + //super.renderWidget(mxs, mouseX, mouseY, partialTicks); + if (isHovered) renderToolTip(mxs, mouseX, mouseY); + } + + @SuppressWarnings("all") + public void renderToolTip(GuiGraphics mx, int mouseX, int mouseY) { + if (!visible || (!active) || (tooltip_ == NO_TOOLTIP)) return; + final Component tip = tooltip_.apply(this); + if (tip.getString().trim().isEmpty()) return; + mx.renderTooltip(mc_.font, Arrays.asList(tip.getVisualOrderText()), mouseX, mouseY); + } + } + + @OnlyIn(Dist.CLIENT) + public static class HorizontalProgressBar extends UiWidget { + private final Coord2d texture_position_base_; + private final Coord2d texture_position_filled_; + private final ResourceLocation atlas_; + private double progress_max_ = 100; + private double progress_ = 0; + + public HorizontalProgressBar(ResourceLocation atlas, int width, int height, Coord2d base_texture_xy, Coord2d filled_texture_xy) { + super(0, 0, width, height, EMPTY_TEXT); + atlas_ = atlas; + texture_position_base_ = base_texture_xy; + texture_position_filled_ = filled_texture_xy; + } + + public HorizontalProgressBar setProgress(double progress) { + progress_ = Mth.clamp(progress, 0, progress_max_); + return this; + } + + public double getProgress() { + return progress_; + } + + public HorizontalProgressBar setMaxProgress(double progress) { + progress_max_ = Math.max(progress, 0); + return this; + } + + public double getMaxProgress() { + return progress_max_; + } + + public HorizontalProgressBar show() { + visible = true; + return this; + } + + public HorizontalProgressBar hide() { + visible = false; + return this; + } + + @Override + public void playDownSound(SoundManager handler) { + } + + @Override + public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { + RenderSystem.setShaderTexture(0, atlas_); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + mxs.blit(atlas_, getX(), getY(), texture_position_base_.x, texture_position_base_.y, width, height); + if ((progress_max_ > 0) && (progress_ > 0)) { + int w = Mth.clamp((int) Math.round((progress_ * width) / progress_max_), 0, width); + mxs.blit(atlas_, getX(), getY(), texture_position_filled_.x, texture_position_filled_.y, w, height); + } + if (isHovered) renderToolTip(mxs, mouseX, mouseY); + } + } + + @OnlyIn(Dist.CLIENT) + public static class BackgroundImage extends UiWidget { + private final ResourceLocation atlas_; + private final Coord2d atlas_position_; + public boolean visible; + + public BackgroundImage(ResourceLocation atlas, int width, int height, Coord2d atlas_position) { + super(0, 0, width, height, EMPTY_TEXT); + atlas_ = atlas; + atlas_position_ = atlas_position; + this.width = width; + this.height = height; + visible = true; + } + + public void draw(GuiGraphics mx, Screen parent) { + if (!visible) return; + RenderSystem.setShaderTexture(0, atlas_); + mx.blit(atlas_, getX(), getY(), atlas_position_.x, atlas_position_.y, width, height); + } + } + + @OnlyIn(Dist.CLIENT) + public static class CheckBox extends UiWidget { + private final Coord2d texture_position_off_; + private final Coord2d texture_position_on_; + private final ResourceLocation atlas_; + private boolean checked_ = false; + private Consumer on_click_ = (checkbox) -> { + }; + + public CheckBox(ResourceLocation atlas, int width, int height, Coord2d atlas_texture_position_off, Coord2d atlas_texture_position_on) { + super(0, 0, width, height, EMPTY_TEXT); + texture_position_off_ = atlas_texture_position_off; + texture_position_on_ = atlas_texture_position_on; + atlas_ = atlas; + } + + public boolean checked() { + return checked_; + } + + public CheckBox checked(boolean on) { + checked_ = on; + return this; + } + + public CheckBox onclick(Consumer action) { + on_click_ = action; + return this; + } + + @Override + public void onClick(double mouseX, double mouseY) { + checked_ = !checked_; + on_click_.accept(this); + } + + @Override + public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, atlas_); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + Coord2d pos = checked_ ? texture_position_on_ : texture_position_off_; + mxs.blit(atlas_, getX(), getY(), pos.x, pos.y, width, height); + if (isHovered) renderToolTip(mxs, mouseX, mouseY); + } + } + + @OnlyIn(Dist.CLIENT) + public static class ImageButton extends UiWidget { + private final Coord2d texture_position_; + private final ResourceLocation atlas_; + private Consumer on_click_ = (bt) -> { + }; + + + public ImageButton(ResourceLocation atlas, int width, int height, Coord2d atlas_texture_position) { + super(0, 0, width, height, Component.empty()); + texture_position_ = atlas_texture_position; + atlas_ = atlas; + } + + public ImageButton onclick(Consumer action) { + on_click_ = action; + return this; + } + + @Override + public void onClick(double mouseX, double mouseY) { + on_click_.accept(this); + } + + @Override + public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, atlas_); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + Coord2d pos = texture_position_; + mxs.blit(atlas_, getX(), getY(), pos.x, pos.y, width, height); + if (isHovered) renderToolTip(mxs, mouseX, mouseY); + } + } + + @OnlyIn(Dist.CLIENT) + public static class Image extends UiWidget { + private final Coord2d texture_position_; + private final ResourceLocation atlas_; + + public Image(ResourceLocation atlas, int width, int height, Coord2d atlas_texture_position) { + super(0, 0, width, height, Component.empty()); + texture_position_ = atlas_texture_position; + atlas_ = atlas; + } + + @Override + public void onClick(double mouseX, double mouseY) { + } + + @Override + public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, atlas_); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + Coord2d pos = texture_position_; + mxs.blit(atlas_, getX(), getY(), pos.x, pos.y, width, height); + if (isHovered) renderToolTip(mxs, mouseX, mouseY); + } + } + + @OnlyIn(Dist.CLIENT) + public static class TextBox extends net.minecraft.client.gui.components.EditBox { + public TextBox(int x, int y, int width, int height, Component title, Font font) { + super(font, x, y, width, height, title); + setBordered(false); + } + + public TextBox withMaxLength(int len) { + super.setMaxLength(len); + return this; + } + + public TextBox withBordered(boolean b) { + super.setBordered(b); + return this; + } + + public TextBox withValue(String s) { + super.setValue(s); + return this; + } + + public TextBox withEditable(boolean e) { + super.setEditable(e); + return this; + } + + public TextBox withResponder(Consumer r) { + super.setResponder(r); + return this; + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Inventories.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Inventories.java new file mode 100644 index 0000000..e7442e0 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Inventories.java @@ -0,0 +1,1134 @@ +/* + * @file Inventories.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * General inventory item handling functionality. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.NonNullList; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.Mth; +import net.minecraft.world.*; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import net.minecraft.nbt.Tag; +import net.minecraftforge.common.capabilities.ForgeCapabilities; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.ItemHandlerHelper; +import net.minecraftforge.items.wrapper.InvWrapper; +import net.minecraftforge.items.wrapper.PlayerMainInvWrapper; +import net.minecraftforge.items.wrapper.SidedInvWrapper; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + + +public class Inventories { + public static boolean areItemStacksIdentical(ItemStack a, ItemStack b) { + return (a.getItem() == b.getItem()) && ItemStack.isSameItemSameTags(a, b); + } + + public static boolean areItemStacksDifferent(ItemStack a, ItemStack b) { + return (a.getItem() != b.getItem()) || (!ItemStack.isSameItemSameTags(a, b)); + } + + public static IItemHandler itemhandler(Level world, BlockPos pos, @Nullable Direction side) { + BlockEntity te = world.getBlockEntity(pos); + if (te == null) return null; + IItemHandler ih = te.getCapability(ForgeCapabilities.ITEM_HANDLER, side).orElse(null); + if (ih != null) return ih; + if ((side != null) && (te instanceof WorldlyContainer)) return new SidedInvWrapper((WorldlyContainer) te, side); + if (te instanceof Container) return new InvWrapper((Container) te); + return null; + } + + public static IItemHandler itemhandler(Level world, BlockPos pos, @Nullable Direction side, boolean including_entities) { + IItemHandler ih = itemhandler(world, pos, side); + if (ih != null) return ih; + if (!including_entities) return null; + Entity entity = world.getEntitiesOfClass(Entity.class, new AABB(pos), (e) -> (e instanceof Container)).stream().findFirst().orElse(null); + return (entity == null) ? (null) : (itemhandler(entity, side)); + } + + public static IItemHandler itemhandler(Player player) { + return new PlayerMainInvWrapper(player.getInventory()); + } + + public static IItemHandler itemhandler(Entity entity) { + return itemhandler(entity, null); + } + + public static IItemHandler itemhandler(Entity entity, @Nullable Direction side) { + if (entity == null) return null; + final IItemHandler ih = entity.getCapability(ForgeCapabilities.ITEM_HANDLER, side).orElse(null); + if (ih != null) return ih; + if (entity instanceof Container container) return (new InvWrapper(container)); + return null; + } + + public static boolean insertionPossible(Level world, BlockPos pos, @Nullable Direction side, boolean including_entities) { + return itemhandler(world, pos, side, including_entities) != null; + } + + public static ItemStack insert(IItemHandler handler, ItemStack stack, boolean simulate) { + return ItemHandlerHelper.insertItemStacked(handler, stack, simulate); + } + + public static ItemStack insert(BlockEntity te, @Nullable Direction side, ItemStack stack, boolean simulate) { + if (te == null) return stack; + IItemHandler hnd = te.getCapability(ForgeCapabilities.ITEM_HANDLER, side).orElse(null); + if (hnd == null) { + if ((side != null) && (te instanceof WorldlyContainer)) { + hnd = new SidedInvWrapper((WorldlyContainer) te, side); + } else if (te instanceof Container) { + hnd = new InvWrapper((Container) te); + } + } + return (hnd == null) ? stack : insert(hnd, stack, simulate); + } + + public static ItemStack insert(Level world, BlockPos pos, @Nullable Direction side, ItemStack stack, boolean simulate, boolean including_entities) { + return insert(itemhandler(world, pos, side, including_entities), stack, simulate); + } + + public static ItemStack insert(Level world, BlockPos pos, @Nullable Direction side, ItemStack stack, boolean simulate) { + return insert(world, pos, side, stack, simulate, false); + } + + public static ItemStack extract(IItemHandler inventory, @Nullable ItemStack match, int amount, boolean simulate) { + if ((inventory == null) || (amount <= 0) || ((match != null) && (match.isEmpty()))) return ItemStack.EMPTY; + final int max = inventory.getSlots(); + ItemStack out_stack = ItemStack.EMPTY; + for (int i = 0; i < max; ++i) { + final ItemStack stack = inventory.getStackInSlot(i); + if (stack.isEmpty()) continue; + if (out_stack.isEmpty()) { + if ((match != null) && areItemStacksDifferent(stack, match)) continue; + out_stack = inventory.extractItem(i, amount, simulate); + } else if (areItemStacksIdentical(stack, out_stack)) { + ItemStack es = inventory.extractItem(i, (amount - out_stack.getCount()), simulate); + out_stack.grow(es.getCount()); + } + if (out_stack.getCount() >= amount) break; + } + return out_stack; + } + + private static ItemStack checked(ItemStack stack) { + return stack.isEmpty() ? ItemStack.EMPTY : stack; + } + + public static Container copyOf(Container src) { + final int size = src.getContainerSize(); + SimpleContainer dst = new SimpleContainer(size); + for (int i = 0; i < size; ++i) dst.setItem(i, src.getItem(i).copy()); + return dst; + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static ItemStack insert(InventoryRange[] to_ranges, ItemStack stack) { + ItemStack remaining = stack.copy(); + for (InventoryRange range : to_ranges) { + remaining = range.insert(remaining, false, 0, false, true); + if (remaining.isEmpty()) return remaining; + } + return remaining; + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static class MappedItemHandler implements IItemHandler { + private final BiPredicate extraction_predicate_; + private final BiPredicate insertion_predicate_; + private final BiConsumer insertion_notifier_; + private final BiConsumer extraction_notifier_; + private final List slot_map_; + private final Container inv_; + + public MappedItemHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate, BiConsumer insertion_notifier, BiConsumer extraction_notifier) { + inv_ = inv; + extraction_predicate_ = extraction_predicate; + insertion_predicate_ = insertion_predicate; + insertion_notifier_ = insertion_notifier; + extraction_notifier_ = extraction_notifier; + slot_map_ = IntStream.range(0, inv.getContainerSize()).boxed().collect(Collectors.toList()); + } + + public MappedItemHandler(Container inv, List slot_map, BiPredicate extraction_predicate, BiPredicate insertion_predicate, BiConsumer insertion_notifier, BiConsumer extraction_notifier) { + inv_ = inv; + extraction_predicate_ = extraction_predicate; + insertion_predicate_ = insertion_predicate; + insertion_notifier_ = insertion_notifier; + extraction_notifier_ = extraction_notifier; + slot_map_ = slot_map; + } + + public MappedItemHandler(Container inv, List slot_map, BiPredicate extraction_predicate, BiPredicate insertion_predicate) { + this(inv, slot_map, extraction_predicate, insertion_predicate, (i, s) -> { + }, (i, s) -> { + }); + } + + public MappedItemHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate) { + this(inv, IntStream.range(0, inv.getContainerSize()).boxed().collect(Collectors.toList()), extraction_predicate, insertion_predicate); + } + + public MappedItemHandler(Container inv) { + this(inv, (i, s) -> true, (i, s) -> true); + } + + @Override + public int hashCode() { + return inv_.hashCode(); + } + + @Override + public boolean equals(Object o) { + return (o == this) || ((o != null) && (getClass() == o.getClass()) && (inv_.equals(((MappedItemHandler) o).inv_))); + } + + // IItemHandler ----------------------------------------------------------------------------------------------- + + @Override + public int getSlots() { + return slot_map_.size(); + } + + @Override + @Nonnull + public ItemStack getStackInSlot(int slot) { + return (slot >= slot_map_.size()) ? ItemStack.EMPTY : inv_.getItem(slot_map_.get(slot)); + } + + @Override + public int getSlotLimit(int slot) { + return inv_.getMaxStackSize(); + } + + @Override + public boolean isItemValid(int slot, @Nonnull ItemStack stack) { + if (slot >= slot_map_.size()) return false; + slot = slot_map_.get(slot); + return insertion_predicate_.test(slot, stack) && inv_.canPlaceItem(slot, stack); + } + + @Override + @Nonnull + public ItemStack insertItem(int slot, @Nonnull ItemStack stack, boolean simulate) { + if (stack.isEmpty()) return ItemStack.EMPTY; + if (slot >= slot_map_.size()) return stack; + slot = slot_map_.get(slot); + if (!insertion_predicate_.test(slot, stack)) return stack; + if (!inv_.canPlaceItem(slot, stack)) return stack; + ItemStack sst = inv_.getItem(slot); + final int slot_limit = inv_.getMaxStackSize(); + if (!sst.isEmpty()) { + if (sst.getCount() >= Math.min(sst.getMaxStackSize(), slot_limit)) return stack; + if (!ItemHandlerHelper.canItemStacksStack(stack, sst)) return stack; + final int limit = Math.min(stack.getMaxStackSize(), slot_limit) - sst.getCount(); + if (stack.getCount() <= limit) { + if (!simulate) { + stack = stack.copy(); + stack.grow(sst.getCount()); + inv_.setItem(slot, stack); + inv_.setChanged(); + insertion_notifier_.accept(slot, sst); + } + return ItemStack.EMPTY; + } else { + stack = stack.copy(); + if (simulate) { + stack.shrink(limit); + } else { + final ItemStack diff = stack.split(limit); + sst.grow(diff.getCount()); + inv_.setItem(slot, sst); + inv_.setChanged(); + insertion_notifier_.accept(slot, diff); + } + return stack; + } + } else { + final int limit = Math.min(slot_limit, stack.getMaxStackSize()); + if (stack.getCount() >= limit) { + stack = stack.copy(); + final ItemStack ins = stack.split(limit); + if (!simulate) { + inv_.setItem(slot, ins); + inv_.setChanged(); + insertion_notifier_.accept(slot, ins.copy()); + } + if (stack.isEmpty()) { + stack = ItemStack.EMPTY; + } + return stack; + } else { + if (!simulate) { + inv_.setItem(slot, stack.copy()); + inv_.setChanged(); + insertion_notifier_.accept(slot, stack.copy()); + } + return ItemStack.EMPTY; + } + } + } + + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + if (amount <= 0) return ItemStack.EMPTY; + if (slot >= slot_map_.size()) return ItemStack.EMPTY; + slot = slot_map_.get(slot); + ItemStack stack = inv_.getItem(slot); + if (!extraction_predicate_.test(slot, stack)) return ItemStack.EMPTY; + if (simulate) { + stack = stack.copy(); + if (amount < stack.getCount()) stack.setCount(amount); + } else { + stack = inv_.removeItem(slot, Math.min(stack.getCount(), amount)); + inv_.setChanged(); + extraction_notifier_.accept(slot, stack.copy()); + } + return stack; + } + + // Factories -------------------------------------------------------------------------------------------- + + public static LazyOptional createGenericHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate, BiConsumer insertion_notifier, BiConsumer extraction_notifier) { + return LazyOptional.of(() -> new MappedItemHandler(inv, extraction_predicate, insertion_predicate, insertion_notifier, extraction_notifier)); + } + + public static LazyOptional createGenericHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate, BiConsumer insertion_notifier, BiConsumer extraction_notifier, List slot_map) { + return LazyOptional.of(() -> new MappedItemHandler(inv, slot_map, extraction_predicate, insertion_predicate, insertion_notifier, extraction_notifier)); + } + + public static LazyOptional createGenericHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate, List slot_map) { + return LazyOptional.of(() -> new MappedItemHandler(inv, slot_map, extraction_predicate, insertion_predicate)); + } + + public static LazyOptional createGenericHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate) { + return LazyOptional.of(() -> new MappedItemHandler(inv, extraction_predicate, insertion_predicate)); + } + + public static LazyOptional createGenericHandler(Container inv) { + return LazyOptional.of(() -> new MappedItemHandler(inv)); + } + + public static LazyOptional createExtractionHandler(Container inv, BiPredicate extraction_predicate, List slot_map) { + return LazyOptional.of(() -> new MappedItemHandler(inv, slot_map, extraction_predicate, (i, s) -> false)); + } + + public static LazyOptional createExtractionHandler(Container inv, BiPredicate extraction_predicate) { + return LazyOptional.of(() -> new MappedItemHandler(inv, extraction_predicate, (i, s) -> false)); + } + + public static LazyOptional createExtractionHandler(Container inv, Integer... slots) { + return LazyOptional.of(() -> new MappedItemHandler(inv, Arrays.asList(slots), (i, s) -> true, (i, s) -> false)); + } + + public static LazyOptional createExtractionHandler(Container inv) { + return LazyOptional.of(() -> new MappedItemHandler(inv, (i, s) -> true, (i, s) -> false)); + } + + public static LazyOptional createInsertionHandler(Container inv, BiPredicate insertion_predicate, List slot_map) { + return LazyOptional.of(() -> new MappedItemHandler(inv, slot_map, (i, s) -> false, insertion_predicate)); + } + + public static LazyOptional createInsertionHandler(Container inv, Integer... slots) { + return LazyOptional.of(() -> new MappedItemHandler(inv, Arrays.asList(slots), (i, s) -> false, (i, s) -> true)); + } + + public static LazyOptional createInsertionHandler(Container inv, BiPredicate insertion_predicate) { + return LazyOptional.of(() -> new MappedItemHandler(inv, (i, s) -> false, insertion_predicate)); + } + + public static LazyOptional createInsertionHandler(Container inv) { + return LazyOptional.of(() -> new MappedItemHandler(inv, (i, s) -> false, (i, s) -> true)); + } + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static class InventoryRange implements Container, Iterable { + protected final Container inventory_; + protected final int offset_, size_, num_rows; + protected int max_stack_size_ = 64; + protected BiPredicate validator_ = (index, stack) -> true; + + public static InventoryRange fromPlayerHotbar(Player player) { + return new InventoryRange(player.getInventory(), 0, 9, 1); + } + + public static InventoryRange fromPlayerStorage(Player player) { + return new InventoryRange(player.getInventory(), 9, 27, 3); + } + + public static InventoryRange fromPlayerInventory(Player player) { + return new InventoryRange(player.getInventory(), 0, 36, 4); + } + + public InventoryRange(Container inventory, int offset, int size, int num_rows) { + this.inventory_ = inventory; + this.offset_ = Mth.clamp(offset, 0, inventory.getContainerSize() - 1); + this.size_ = Mth.clamp(size, 0, inventory.getContainerSize() - this.offset_); + this.num_rows = num_rows; + } + + public InventoryRange(Container inventory, int offset, int size) { + this(inventory, offset, size, 1); + } + + public InventoryRange(Container inventory) { + this(inventory, 0, inventory.getContainerSize(), 1); + } + + public final Container inventory() { + return inventory_; + } + + public final int size() { + return size_; + } + + public final int offset() { + return offset_; + } + + public final ItemStack get(int index) { + return inventory_.getItem(offset_ + index); + } + + public final void set(int index, ItemStack stack) { + inventory_.setItem(offset_ + index, stack); + } + + public final InventoryRange setValidator(BiPredicate validator) { + validator_ = validator; + return this; + } + + public final BiPredicate getValidator() { + return validator_; + } + + public final InventoryRange setMaxStackSize(int count) { + max_stack_size_ = Math.max(count, 1); + return this; + } + + // Container ------------------------------------------------------------------------------------------------------ + + @Override + public void clearContent() { + for (int i = 0; i < size_; ++i) setItem(i, ItemStack.EMPTY); + } + + @Override + public int getContainerSize() { + return size_; + } + + @Override + public boolean isEmpty() { + for (int i = 0; i < size_; ++i) + if (!inventory_.getItem(offset_ + i).isEmpty()) { + return false; + } + return true; + } + + @Override + public ItemStack getItem(int index) { + return inventory_.getItem(offset_ + index); + } + + @Override + public ItemStack removeItem(int index, int count) { + return inventory_.removeItem(offset_ + index, count); + } + + @Override + public ItemStack removeItemNoUpdate(int index) { + return inventory_.removeItemNoUpdate(offset_ + index); + } + + @Override + public void setItem(int index, ItemStack stack) { + inventory_.setItem(offset_ + index, stack); + } + + @Override + public int getMaxStackSize() { + return Math.min(max_stack_size_, inventory_.getMaxStackSize()); + } + + @Override + public void setChanged() { + inventory_.setChanged(); + } + + @Override + public boolean stillValid(Player player) { + return inventory_.stillValid(player); + } + + @Override + public void startOpen(Player player) { + inventory_.startOpen(player); + } + + @Override + public void stopOpen(Player player) { + inventory_.stopOpen(player); + } + + @Override + public boolean canPlaceItem(int index, ItemStack stack) { + return validator_.test(offset_ + index, stack) && inventory_.canPlaceItem(offset_ + index, stack); + } + + //------------------------------------------------------------------------------------------------------------------ + + /** + * Iterates using a function (slot, stack) -> bool until the function matches (returns true). + */ + public boolean iterate(BiPredicate fn) { + for (int i = 0; i < size_; ++i) { + if (fn.test(i, getItem(i))) { + return true; + } + } + return false; + } + + public boolean contains(ItemStack stack) { + for (int i = 0; i < size_; ++i) { + if (areItemStacksIdentical(stack, getItem(i))) { + return true; + } + } + return false; + } + + public int indexOf(ItemStack stack) { + for (int i = 0; i < size_; ++i) { + if (areItemStacksIdentical(stack, getItem(i))) { + return i; + } + } + return -1; + } + + public Optional find(BiFunction> fn) { + for (int i = 0; i < size_; ++i) { + Optional r = fn.apply(i, getItem(i)); + if (r.isPresent()) return r; + } + return Optional.empty(); + } + + public List collect(BiFunction> fn) { + List data = new ArrayList<>(); + for (int i = 0; i < size_; ++i) { + fn.apply(i, getItem(i)).ifPresent(data::add); + } + return data; + } + + public Stream stream() { + return java.util.stream.StreamSupport.stream(this.spliterator(), false); + } + + public Iterator iterator() { + return new InventoryRangeIterator(this); + } + + public static class InventoryRangeIterator implements Iterator { + private final InventoryRange parent_; + private int index = 0; + + public InventoryRangeIterator(InventoryRange range) { + parent_ = range; + } + + public boolean hasNext() { + return index < parent_.size_; + } + + public ItemStack next() { + if (index >= parent_.size_) throw new NoSuchElementException(); + return parent_.getItem(index++); + } + } + + //------------------------------------------------------------------------------------------------------------------ + + /** + * Returns the number of stacks that match the given stack with NBT. + */ + public int stackMatchCount(final ItemStack ref_stack) { + int n = 0; // ... std::accumulate() the old school way. + for (int i = 0; i < size_; ++i) { + if (areItemStacksIdentical(ref_stack, getItem(i))) ++n; + } + return n; + } + + public int totalMatchingItemCount(final ItemStack ref_stack) { + int n = 0; + for (int i = 0; i < size_; ++i) { + ItemStack stack = getItem(i); + if (areItemStacksIdentical(ref_stack, stack)) n += stack.getCount(); + } + return n; + } + + //------------------------------------------------------------------------------------------------------------------ + + /** + * Moves as much items from the stack to the slots in range [offset_, end_slot] of the inventory_, + * filling up existing stacks first, then (player inventory_ only) checks appropriate empty slots next + * to stacks that have that item already, and last uses any empty slot that can be found. + * Returns the stack that is still remaining in the referenced `stack`. + */ + public ItemStack insert(final ItemStack input_stack, boolean only_fillup, int limit, boolean reverse, boolean force_group_stacks) { + final ItemStack mvstack = input_stack.copy(); + //final int end_slot = offset_ + size; + if (mvstack.isEmpty()) return checked(mvstack); + int limit_left = (limit > 0) ? (Math.min(limit, mvstack.getMaxStackSize())) : (mvstack.getMaxStackSize()); + boolean[] matches = new boolean[size_]; + boolean[] empties = new boolean[size_]; + int num_matches = 0; + for (int i = 0; i < size_; ++i) { + final int sno = reverse ? (size_ - 1 - i) : (i); + final ItemStack stack = getItem(sno); + if (stack.isEmpty()) { + empties[sno] = true; + } else if (areItemStacksIdentical(stack, mvstack)) { + matches[sno] = true; + ++num_matches; + } + } + // first iteration: fillup existing stacks + for (int i = 0; i < size_; ++i) { + final int sno = reverse ? (size_ - 1 - i) : (i); + if ((empties[sno]) || (!matches[sno])) continue; + final ItemStack stack = getItem(sno); + int nmax = Math.min(limit_left, stack.getMaxStackSize() - stack.getCount()); + if (mvstack.getCount() <= nmax) { + stack.setCount(stack.getCount() + mvstack.getCount()); + setItem(sno, stack); + return ItemStack.EMPTY; + } else { + stack.grow(nmax); + mvstack.shrink(nmax); + setItem(sno, stack); + limit_left -= nmax; + } + } + if (only_fillup) return checked(mvstack); + if ((num_matches > 0) && ((force_group_stacks) || (inventory_ instanceof Inventory))) { + // second iteration: use appropriate empty slots, + // a) between + { + int insert_start = -1; + int insert_end = -1; + int i = 1; + for (; i < size_ - 1; ++i) { + final int sno = reverse ? (size_ - 1 - i) : (i); + if (insert_start < 0) { + if (matches[sno]) insert_start = sno; + } else if (matches[sno]) { + insert_end = sno; + } + } + for (i = insert_start; i < insert_end; ++i) { + final int sno = reverse ? (size_ - 1 - i) : (i); + if ((!empties[sno]) || (!canPlaceItem(sno, mvstack))) continue; + int nmax = Math.min(limit_left, mvstack.getCount()); + ItemStack moved = mvstack.copy(); + moved.setCount(nmax); + mvstack.shrink(nmax); + setItem(sno, moved); + return checked(mvstack); + } + } + // b) before/after + { + for (int i = 1; i < size_ - 1; ++i) { + final int sno = reverse ? (size_ - 1 - i) : (i); + if (!matches[sno]) continue; + int ii = (empties[sno - 1]) ? (sno - 1) : (empties[sno + 1] ? (sno + 1) : -1); + if ((ii >= 0) && (canPlaceItem(ii, mvstack))) { + int nmax = Math.min(limit_left, mvstack.getCount()); + ItemStack moved = mvstack.copy(); + moved.setCount(nmax); + mvstack.shrink(nmax); + setItem(ii, moved); + return checked(mvstack); + } + } + } + } + // third iteration: use any empty slots + for (int i = 0; i < size_; ++i) { + final int sno = reverse ? (size_ - 1 - i) : (i); + if ((!empties[sno]) || (!canPlaceItem(sno, mvstack))) continue; + int nmax = Math.min(limit_left, mvstack.getCount()); + ItemStack placed = mvstack.copy(); + placed.setCount(nmax); + mvstack.shrink(nmax); + setItem(sno, placed); + return checked(mvstack); + } + return checked(mvstack); + } + + public ItemStack insert(final ItemStack stack_to_move) { + return insert(stack_to_move, false, 0, false, true); + } + + public ItemStack insert(final int index, final ItemStack stack_to_move) { + if (stack_to_move.isEmpty()) return stack_to_move; + final ItemStack stack = getItem(index); + final int limit = Math.min(getMaxStackSize(), stack.getMaxStackSize()); + if (stack.isEmpty()) { + setItem(index, stack_to_move.copy()); + return ItemStack.EMPTY; + } else if ((stack.getCount() >= limit) || !areItemStacksIdentical(stack, stack_to_move)) { + return stack_to_move; + } else { + final int amount = Math.min(limit - stack.getCount(), stack_to_move.getCount()); + ItemStack remaining = stack_to_move.copy(); + remaining.shrink(amount); + stack.grow(amount); + return remaining.isEmpty() ? ItemStack.EMPTY : remaining; + } + } + + //------------------------------------------------------------------------------------------------------------------ + + /** + * Extracts maximum amount of items from the inventory_. + * The first non-empty stack defines the item. + */ + public ItemStack extract(int amount) { + return extract(amount, false); + } + + public ItemStack extract(int amount, boolean random) { + ItemStack out_stack = ItemStack.EMPTY; + int offset = random ? (int) (Math.random() * size_) : 0; + for (int k = 0; k < size_; ++k) { + int i = (offset + k) % size_; + final ItemStack stack = getItem(i); + if (stack.isEmpty()) continue; + if (out_stack.isEmpty()) { + if (stack.getCount() < amount) { + out_stack = stack; + setItem(i, ItemStack.EMPTY); + if (!out_stack.isStackable()) break; + amount -= out_stack.getCount(); + } else { + out_stack = stack.split(amount); + break; + } + } else if (areItemStacksIdentical(stack, out_stack)) { + if (stack.getCount() <= amount) { + out_stack.grow(stack.getCount()); + amount -= stack.getCount(); + setItem(i, ItemStack.EMPTY); + } else { + out_stack.grow(amount); + stack.shrink(amount); + if (stack.isEmpty()) setItem(i, ItemStack.EMPTY); + break; + } + } + } + if (!out_stack.isEmpty()) setChanged(); + return out_stack; + } + + /** + * Moves as much items from the slots in range [offset_, end_slot] of the inventory_ into a new stack. + * Implicitly shrinks the inventory_ stacks and the `request_stack`. + */ + public ItemStack extract(final ItemStack request_stack) { + if (request_stack.isEmpty()) return ItemStack.EMPTY; + List matches = new ArrayList<>(); + for (int i = 0; i < size_; ++i) { + final ItemStack stack = getItem(i); + if ((!stack.isEmpty()) && (areItemStacksIdentical(stack, request_stack))) { + if (stack.hasTag()) { + final CompoundTag nbt = stack.getOrCreateTag(); + int n = nbt.size(); + if ((n > 0) && (nbt.contains("Damage"))) --n; + if (n > 0) continue; + } + matches.add(stack); + } + } + matches.sort(Comparator.comparingInt(ItemStack::getCount)); + if (matches.isEmpty()) return ItemStack.EMPTY; + int n_left = request_stack.getCount(); + ItemStack fetched_stack = matches.get(0).split(n_left); + n_left -= fetched_stack.getCount(); + for (int i = 1; (i < matches.size()) && (n_left > 0); ++i) { + ItemStack stack = matches.get(i).split(n_left); + n_left -= stack.getCount(); + fetched_stack.grow(stack.getCount()); + } + return checked(fetched_stack); + } + + //------------------------------------------------------------------------------------------------------------------ + + /** + * Moves items from this inventory_ range to another. Returns true if something was moved + * (if the inventories should be marked dirty). + */ + public boolean move(int index, final InventoryRange target_range, boolean all_identical_stacks, boolean only_fillup, boolean reverse, boolean force_group_stacks) { + final ItemStack source_stack = getItem(index); + if (source_stack.isEmpty()) return false; + if (!all_identical_stacks) { + ItemStack remaining = target_range.insert(source_stack, only_fillup, 0, reverse, force_group_stacks); + setItem(index, remaining); + return (remaining.getCount() != source_stack.getCount()); + } else { + ItemStack remaining = source_stack.copy(); + setItem(index, ItemStack.EMPTY); + final ItemStack ref_stack = remaining.copy(); + ref_stack.setCount(ref_stack.getMaxStackSize()); + for (int i = size_; (i > 0) && (!remaining.isEmpty()); --i) { + remaining = target_range.insert(remaining, only_fillup, 0, reverse, force_group_stacks); + if (!remaining.isEmpty()) break; + remaining = this.extract(ref_stack); + } + if (!remaining.isEmpty()) { + setItem(index, remaining); // put back + } + return (remaining.getCount() != source_stack.getCount()); + } + } + + public boolean move(int index, final InventoryRange target_range) { + return move(index, target_range, false, false, false, true); + } + + /** + * Moves/clears the complete range to another range if possible. Returns true if something was moved + * (if the inventories should be marked dirty). + */ + public boolean move(final InventoryRange target_range, boolean only_fillup, boolean reverse, boolean force_group_stacks) { + boolean changed = false; + for (int i = 0; i < size_; ++i) + changed |= move(i, target_range, false, only_fillup, reverse, force_group_stacks); + return changed; + } + + public boolean move(final InventoryRange target_range, boolean only_fillup) { + return move(target_range, only_fillup, false, true); + } + + public boolean move(final InventoryRange target_range) { + return move(target_range, false, false, true); + } + + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static class StorageInventory implements Container, Iterable { + protected final NonNullList stacks_; + protected final BlockEntity te_; + protected final int size_; + protected final int num_rows_; + protected int stack_limit_ = 64; + protected BiPredicate validator_ = (index, stack) -> true; + protected Consumer open_action_ = (player) -> { + }; + protected Consumer close_action_ = (player) -> { + }; + protected BiConsumer slot_set_action_ = (index, stack) -> { + }; + + public StorageInventory(BlockEntity te, int size) { + this(te, size, 1); + } + + public StorageInventory(BlockEntity te, int size, int num_rows) { + te_ = te; + size_ = Math.max(size, 1); + stacks_ = NonNullList.withSize(size_, ItemStack.EMPTY); + num_rows_ = Mth.clamp(num_rows, 1, size_); + } + + public CompoundTag save(CompoundTag nbt, String key) { + nbt.put(key, save(new CompoundTag(), false)); + return nbt; + } + + public CompoundTag save(CompoundTag nbt) { + return ContainerHelper.saveAllItems(nbt, stacks_); + } + + public CompoundTag save(CompoundTag nbt, boolean save_empty) { + return ContainerHelper.saveAllItems(nbt, stacks_, save_empty); + } + + public CompoundTag save(boolean save_empty) { + return save(new CompoundTag(), save_empty); + } + + public StorageInventory load(CompoundTag nbt, String key) { + if (!nbt.contains("key", Tag.TAG_COMPOUND)) { + stacks_.clear(); + return this; + } else { + return load(nbt.getCompound(key)); + } + } + + public StorageInventory load(CompoundTag nbt) { + stacks_.clear(); + ContainerHelper.loadAllItems(nbt, stacks_); + return this; + } + + public List stacks() { + return stacks_; + } + + public BlockEntity getBlockEntity() { + return te_; + } + + public StorageInventory setOpenAction(Consumer fn) { + open_action_ = fn; + return this; + } + + public StorageInventory setCloseAction(Consumer fn) { + close_action_ = fn; + return this; + } + + public StorageInventory setSlotChangeAction(BiConsumer fn) { + slot_set_action_ = fn; + return this; + } + + public StorageInventory setStackLimit(int max_slot_stack_size) { + stack_limit_ = Math.max(max_slot_stack_size, 1); + return this; + } + + public StorageInventory setValidator(BiPredicate validator) { + validator_ = validator; + return this; + } + + public BiPredicate getValidator() { + return validator_; + } + + // Iterable --------------------------------------------------------------------- + + public Iterator iterator() { + return stacks_.iterator(); + } + + public Stream stream() { + return stacks_.stream(); + } + + // Container ------------------------------------------------------------------------------ + + @Override + public int getContainerSize() { + return size_; + } + + @Override + public boolean isEmpty() { + for (ItemStack stack : stacks_) { + if (!stack.isEmpty()) return false; + } + return true; + } + + @Override + public ItemStack getItem(int index) { + return (index < size_) ? stacks_.get(index) : ItemStack.EMPTY; + } + + @Override + public ItemStack removeItem(int index, int count) { + return ContainerHelper.removeItem(stacks_, index, count); + } + + @Override + public ItemStack removeItemNoUpdate(int index) { + return ContainerHelper.takeItem(stacks_, index); + } + + @Override + public void setItem(int index, ItemStack stack) { + stacks_.set(index, stack); + if ((stack.getCount() != stacks_.get(index).getCount()) || !areItemStacksDifferent(stacks_.get(index), stack)) { + slot_set_action_.accept(index, stack); + } + } + + @Override + public int getMaxStackSize() { + return stack_limit_; + } + + @Override + public void setChanged() { + te_.setChanged(); + } + + @Override + public boolean stillValid(Player player) { + return ((te_.getLevel().getBlockEntity(te_.getBlockPos()) == te_)) && (te_.getBlockPos().distSqr(player.blockPosition()) < 64); + } + + @Override + public void startOpen(Player player) { + open_action_.accept(player); + } + + @Override + public void stopOpen(Player player) { + setChanged(); + close_action_.accept(player); + } + + @Override + public boolean canPlaceItem(int index, ItemStack stack) { + return validator_.test(index, stack); + } + + @Override + public void clearContent() { + stacks_.clear(); + setChanged(); + } + + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static void give(Player entity, ItemStack stack) { + ItemHandlerHelper.giveItemToPlayer(entity, stack); + } + + public static void setItemInPlayerHand(Player player, InteractionHand hand, ItemStack stack) { + if (stack.isEmpty()) stack = ItemStack.EMPTY; + if (hand == InteractionHand.MAIN_HAND) { + player.getInventory().items.set(player.getInventory().selected, stack); + } else { + player.getInventory().offhand.set(0, stack); + } + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static Container readNbtStacks(CompoundTag nbt, String key, Container target) { + NonNullList stacks = Inventories.readNbtStacks(nbt, key, target.getContainerSize()); + for (int i = 0; i < stacks.size(); ++i) target.setItem(i, stacks.get(i)); + return target; + } + + public static NonNullList readNbtStacks(CompoundTag nbt, String key, int size) { + NonNullList stacks = NonNullList.withSize(size, ItemStack.EMPTY); + if ((nbt == null) || (!nbt.contains(key, Tag.TAG_COMPOUND))) return stacks; + CompoundTag stacknbt = nbt.getCompound(key); + ContainerHelper.loadAllItems(stacknbt, stacks); + return stacks; + } + + public static NonNullList readNbtStacks(CompoundTag nbt, int size) { + NonNullList stacks = NonNullList.withSize(size, ItemStack.EMPTY); + if ((nbt == null) || (!nbt.contains("Items", Tag.TAG_LIST))) return stacks; + ContainerHelper.loadAllItems(nbt, stacks); + return stacks; + } + + public static CompoundTag writeNbtStacks(CompoundTag nbt, String key, NonNullList stacks, boolean omit_trailing_empty) { + CompoundTag stacknbt = new CompoundTag(); + if (omit_trailing_empty) { + for (int i = stacks.size() - 1; i >= 0; --i) { + if (!stacks.get(i).isEmpty()) break; + stacks.remove(i); + } + } + ContainerHelper.saveAllItems(stacknbt, stacks); + if (nbt == null) nbt = new CompoundTag(); + nbt.put(key, stacknbt); + return nbt; + } + + public static CompoundTag writeNbtStacks(CompoundTag nbt, String key, NonNullList stacks) { + return writeNbtStacks(nbt, key, stacks, false); + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static void dropStack(Level world, Vec3 pos, ItemStack stack, Vec3 velocity, double position_noise, double velocity_noise) { + if (stack.isEmpty()) return; + if (position_noise > 0) { + position_noise = Math.min(position_noise, 0.8); + pos = pos.add( + position_noise * (world.getRandom().nextDouble() - .5), + position_noise * (world.getRandom().nextDouble() - .5), + position_noise * (world.getRandom().nextDouble() - .5) + ); + } + if (velocity_noise > 0) { + velocity_noise = Math.min(velocity_noise, 1.0); + velocity = velocity.add( + (velocity_noise) * (world.getRandom().nextDouble() - .5), + (velocity_noise) * (world.getRandom().nextDouble() - .5), + (velocity_noise) * (world.getRandom().nextDouble() - .5) + ); + } + ItemEntity e = new ItemEntity(world, pos.x, pos.y, pos.z, stack); + e.setDeltaMovement((float) velocity.x, (float) velocity.y, (float) velocity.z); + e.setDefaultPickUpDelay(); + world.addFreshEntity(e); + } + + public static void dropStack(Level world, Vec3 pos, ItemStack stack, Vec3 velocity) { + dropStack(world, pos, stack, velocity, 0.3, 0.2); + } + + public static void dropStack(Level world, Vec3 pos, ItemStack stack) { + dropStack(world, pos, stack, Vec3.ZERO, 0.3, 0.2); + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Networking.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Networking.java new file mode 100644 index 0000000..67a3540 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Networking.java @@ -0,0 +1,409 @@ +/* + * @file Networking.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Main client/server message handling. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraftforge.common.util.FakePlayer; +import net.minecraftforge.network.NetworkDirection; +import net.minecraftforge.network.NetworkEvent; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + + +public class Networking { + private static final String PROTOCOL = "1"; + private static SimpleChannel DEFAULT_CHANNEL; + + public static void init(String modid) { + DEFAULT_CHANNEL = NetworkRegistry.ChannelBuilder + .named(new ResourceLocation(modid, "default_ch")) + .clientAcceptedVersions(PROTOCOL::equals).serverAcceptedVersions(PROTOCOL::equals).networkProtocolVersion(() -> PROTOCOL) + .simpleChannel(); + int discr = -1; + DEFAULT_CHANNEL.registerMessage(++discr, PacketTileNotifyClientToServer.class, PacketTileNotifyClientToServer::compose, PacketTileNotifyClientToServer::parse, PacketTileNotifyClientToServer.Handler::handle); + DEFAULT_CHANNEL.registerMessage(++discr, PacketTileNotifyServerToClient.class, PacketTileNotifyServerToClient::compose, PacketTileNotifyServerToClient::parse, PacketTileNotifyServerToClient.Handler::handle); + DEFAULT_CHANNEL.registerMessage(++discr, PacketContainerSyncClientToServer.class, PacketContainerSyncClientToServer::compose, PacketContainerSyncClientToServer::parse, PacketContainerSyncClientToServer.Handler::handle); + DEFAULT_CHANNEL.registerMessage(++discr, PacketContainerSyncServerToClient.class, PacketContainerSyncServerToClient::compose, PacketContainerSyncServerToClient::parse, PacketContainerSyncServerToClient.Handler::handle); + DEFAULT_CHANNEL.registerMessage(++discr, PacketNbtNotifyClientToServer.class, PacketNbtNotifyClientToServer::compose, PacketNbtNotifyClientToServer::parse, PacketNbtNotifyClientToServer.Handler::handle); + DEFAULT_CHANNEL.registerMessage(++discr, PacketNbtNotifyServerToClient.class, PacketNbtNotifyServerToClient::compose, PacketNbtNotifyServerToClient::parse, PacketNbtNotifyServerToClient.Handler::handle); + DEFAULT_CHANNEL.registerMessage(++discr, OverlayTextMessage.class, OverlayTextMessage::compose, OverlayTextMessage::parse, OverlayTextMessage.Handler::handle); + } + + //-------------------------------------------------------------------------------------------------------------------- + // Tile entity notifications + //-------------------------------------------------------------------------------------------------------------------- + + public interface IPacketTileNotifyReceiver { + default void onServerPacketReceived(CompoundTag nbt) { + } + + default void onClientPacketReceived(Player player, CompoundTag nbt) { + } + } + + public static class PacketTileNotifyClientToServer { + CompoundTag nbt = null; + BlockPos pos = BlockPos.ZERO; + + public static void sendToServer(BlockPos pos, CompoundTag nbt) { + if ((pos != null) && (nbt != null)) + DEFAULT_CHANNEL.sendToServer(new PacketTileNotifyClientToServer(pos, nbt)); + } + + public static void sendToServer(BlockEntity te, CompoundTag nbt) { + if ((te != null) && (nbt != null)) + DEFAULT_CHANNEL.sendToServer(new PacketTileNotifyClientToServer(te, nbt)); + } + + public PacketTileNotifyClientToServer() { + } + + public PacketTileNotifyClientToServer(BlockPos pos, CompoundTag nbt) { + this.nbt = nbt; + this.pos = pos; + } + + public PacketTileNotifyClientToServer(BlockEntity te, CompoundTag nbt) { + this.nbt = nbt; + pos = te.getBlockPos(); + } + + public static PacketTileNotifyClientToServer parse(final FriendlyByteBuf buf) { + return new PacketTileNotifyClientToServer(buf.readBlockPos(), buf.readNbt()); + } + + public static void compose(final PacketTileNotifyClientToServer pkt, final FriendlyByteBuf buf) { + buf.writeBlockPos(pkt.pos); + buf.writeNbt(pkt.nbt); + } + + @SuppressWarnings("all") + public static class Handler { + public static void handle(final PacketTileNotifyClientToServer pkt, final Supplier ctx) { + ctx.get().enqueueWork(() -> { + Player player = ctx.get().getSender(); + if (player == null) return; + Level world = player.level(); + final BlockEntity te = world.getBlockEntity(pkt.pos); + if (!(te instanceof IPacketTileNotifyReceiver)) return; + ((IPacketTileNotifyReceiver) te).onClientPacketReceived(ctx.get().getSender(), pkt.nbt); + }); + ctx.get().setPacketHandled(true); + } + } + } + + public static class PacketTileNotifyServerToClient { + CompoundTag nbt = null; + BlockPos pos = BlockPos.ZERO; + + public static void sendToPlayer(Player player, BlockEntity te, CompoundTag nbt) { + if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (te == null) || (nbt == null)) + return; + DEFAULT_CHANNEL.sendTo(new PacketTileNotifyServerToClient(te, nbt), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); + } + + public static void sendToPlayers(BlockEntity te, CompoundTag nbt) { + if (te == null || te.getLevel() == null) return; + for (Player player : te.getLevel().players()) sendToPlayer(player, te, nbt); + } + + public PacketTileNotifyServerToClient() { + } + + public PacketTileNotifyServerToClient(BlockPos pos, CompoundTag nbt) { + this.nbt = nbt; + this.pos = pos; + } + + public PacketTileNotifyServerToClient(BlockEntity te, CompoundTag nbt) { + this.nbt = nbt; + pos = te.getBlockPos(); + } + + public static PacketTileNotifyServerToClient parse(final FriendlyByteBuf buf) { + return new PacketTileNotifyServerToClient(buf.readBlockPos(), buf.readNbt()); + } + + public static void compose(final PacketTileNotifyServerToClient pkt, final FriendlyByteBuf buf) { + buf.writeBlockPos(pkt.pos); + buf.writeNbt(pkt.nbt); + } + + public static class Handler { + public static void handle(final PacketTileNotifyServerToClient pkt, final Supplier ctx) { + ctx.get().enqueueWork(() -> { + Level world = SidedProxy.getWorldClientSide(); + if (world == null) return; + final BlockEntity te = world.getBlockEntity(pkt.pos); + if (!(te instanceof IPacketTileNotifyReceiver)) return; + ((IPacketTileNotifyReceiver) te).onServerPacketReceived(pkt.nbt); + }); + ctx.get().setPacketHandled(true); + } + } + } + + //-------------------------------------------------------------------------------------------------------------------- + // (GUI) Container synchronization + //-------------------------------------------------------------------------------------------------------------------- + + public interface INetworkSynchronisableContainer { + void onServerPacketReceived(int windowId, CompoundTag nbt); + + void onClientPacketReceived(int windowId, Player player, CompoundTag nbt); + } + + public static class PacketContainerSyncClientToServer { + int id = -1; + CompoundTag nbt = null; + + public static void sendToServer(int windowId, CompoundTag nbt) { + if (nbt != null) DEFAULT_CHANNEL.sendToServer(new PacketContainerSyncClientToServer(windowId, nbt)); + } + + public static void sendToServer(AbstractContainerMenu container, CompoundTag nbt) { + if (nbt != null) + DEFAULT_CHANNEL.sendToServer(new PacketContainerSyncClientToServer(container.containerId, nbt)); + } + + public PacketContainerSyncClientToServer() { + } + + public PacketContainerSyncClientToServer(int id, CompoundTag nbt) { + this.nbt = nbt; + this.id = id; + } + + public static PacketContainerSyncClientToServer parse(final FriendlyByteBuf buf) { + return new PacketContainerSyncClientToServer(buf.readInt(), buf.readNbt()); + } + + public static void compose(final PacketContainerSyncClientToServer pkt, final FriendlyByteBuf buf) { + buf.writeInt(pkt.id); + buf.writeNbt(pkt.nbt); + } + + public static class Handler { + public static void handle(final PacketContainerSyncClientToServer pkt, final Supplier ctx) { + ctx.get().enqueueWork(() -> { + Player player = ctx.get().getSender(); + if ((player == null) || !(player.containerMenu instanceof INetworkSynchronisableContainer)) return; + if (player.containerMenu.containerId != pkt.id) return; + ((INetworkSynchronisableContainer) player.containerMenu).onClientPacketReceived(pkt.id, player, pkt.nbt); + }); + ctx.get().setPacketHandled(true); + } + } + } + + public static class PacketContainerSyncServerToClient { + int id = -1; + CompoundTag nbt = null; + + public static void sendToPlayer(Player player, int windowId, CompoundTag nbt) { + if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (nbt == null)) return; + DEFAULT_CHANNEL.sendTo(new PacketContainerSyncServerToClient(windowId, nbt), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); + } + + public static void sendToPlayer(Player player, AbstractContainerMenu container, CompoundTag nbt) { + if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (nbt == null)) return; + DEFAULT_CHANNEL.sendTo(new PacketContainerSyncServerToClient(container.containerId, nbt), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); + } + + public static + void sendToListeners(Level world, C container, CompoundTag nbt) { + for (Player player : world.players()) { + if (player.containerMenu.containerId != container.containerId) continue; + sendToPlayer(player, container.containerId, nbt); + } + } + + public PacketContainerSyncServerToClient() { + } + + public PacketContainerSyncServerToClient(int id, CompoundTag nbt) { + this.nbt = nbt; + this.id = id; + } + + public static PacketContainerSyncServerToClient parse(final FriendlyByteBuf buf) { + return new PacketContainerSyncServerToClient(buf.readInt(), buf.readNbt()); + } + + public static void compose(final PacketContainerSyncServerToClient pkt, final FriendlyByteBuf buf) { + buf.writeInt(pkt.id); + buf.writeNbt(pkt.nbt); + } + + public static class Handler { + public static void handle(final PacketContainerSyncServerToClient pkt, final Supplier ctx) { + ctx.get().enqueueWork(() -> { + Player player = SidedProxy.getPlayerClientSide(); + if ((player == null) || !(player.containerMenu instanceof INetworkSynchronisableContainer)) return; + if (player.containerMenu.containerId != pkt.id) return; + ((INetworkSynchronisableContainer) player.containerMenu).onServerPacketReceived(pkt.id, pkt.nbt); + }); + ctx.get().setPacketHandled(true); + } + } + } + + //-------------------------------------------------------------------------------------------------------------------- + // World notifications + //-------------------------------------------------------------------------------------------------------------------- + + public static class PacketNbtNotifyClientToServer { + public static final Map> handlers = new HashMap<>(); + final CompoundTag nbt; + + public static void sendToServer(CompoundTag nbt) { + if (nbt != null) DEFAULT_CHANNEL.sendToServer(new PacketNbtNotifyClientToServer(nbt)); + } + + public PacketNbtNotifyClientToServer(CompoundTag nbt) { + this.nbt = nbt; + } + + public static PacketNbtNotifyClientToServer parse(final FriendlyByteBuf buf) { + return new PacketNbtNotifyClientToServer(buf.readNbt()); + } + + public static void compose(final PacketNbtNotifyClientToServer pkt, final FriendlyByteBuf buf) { + buf.writeNbt(pkt.nbt); + } + + public static class Handler { + public static void handle(final PacketNbtNotifyClientToServer pkt, final Supplier ctx) { + ctx.get().enqueueWork(() -> { + final ServerPlayer player = ctx.get().getSender(); + if (player == null) return; + final String hnd = pkt.nbt.getString("hnd"); + if (hnd.isEmpty()) return; + if (handlers.containsKey(hnd)) handlers.get(hnd).accept(player, pkt.nbt); + }); + ctx.get().setPacketHandled(true); + } + } + } + + public static class PacketNbtNotifyServerToClient { + public static final Map> handlers = new HashMap<>(); + final CompoundTag nbt; + + public static void sendToPlayer(Player player, CompoundTag nbt) { + if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (nbt == null)) return; + DEFAULT_CHANNEL.sendTo(new PacketNbtNotifyServerToClient(nbt), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); + } + + public static void sendToPlayers(Level world, CompoundTag nbt) { + for (Player player : world.players()) sendToPlayer(player, nbt); + } + + public PacketNbtNotifyServerToClient(CompoundTag nbt) { + this.nbt = nbt; + } + + public static PacketNbtNotifyServerToClient parse(final FriendlyByteBuf buf) { + return new PacketNbtNotifyServerToClient(buf.readNbt()); + } + + public static void compose(final PacketNbtNotifyServerToClient pkt, final FriendlyByteBuf buf) { + buf.writeNbt(pkt.nbt); + } + + public static class Handler { + public static void handle(final PacketNbtNotifyServerToClient pkt, final Supplier ctx) { + ctx.get().enqueueWork(() -> { + final String hnd = pkt.nbt.getString("hnd"); + if (hnd.isEmpty()) return; + if (handlers.containsKey(hnd)) handlers.get(hnd).accept(pkt.nbt); + }); + ctx.get().setPacketHandled(true); + } + } + } + + //-------------------------------------------------------------------------------------------------------------------- + // Main window GUI text message + //-------------------------------------------------------------------------------------------------------------------- + + public static class OverlayTextMessage { + public static final int DISPLAY_TIME_MS = 3000; + private static BiConsumer handler_ = null; + private final Component data_; + private int delay_ = DISPLAY_TIME_MS; + + private Component data() { + return data_; + } + + private int delay() { + return delay_; + } + + public static void setHandler(BiConsumer handler) { + if (handler_ == null) handler_ = handler; + } + + public static void sendToPlayer(Player player, Component message, int delay) { + if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || Auxiliaries.isEmpty(message)) + return; + DEFAULT_CHANNEL.sendTo(new OverlayTextMessage(message, delay), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); + } + + public OverlayTextMessage() { + data_ = Component.translatable("[unset]"); + } + + public OverlayTextMessage(final Component tct, int delay) { + data_ = tct.copy(); + delay_ = delay; + } + + public static OverlayTextMessage parse(final FriendlyByteBuf buf) { + try { + return new OverlayTextMessage(buf.readComponent(), DISPLAY_TIME_MS); + } catch (Throwable e) { + return new OverlayTextMessage(Component.translatable("[incorrect translation]"), DISPLAY_TIME_MS); + } + } + + public static void compose(final OverlayTextMessage pkt, final FriendlyByteBuf buf) { + try { + buf.writeComponent(pkt.data()); + } catch (Throwable e) { + Auxiliaries.logger().error("OverlayTextMessage.toBytes() failed: " + e); + } + } + + public static class Handler { + public static void handle(final OverlayTextMessage pkt, final Supplier ctx) { + if (handler_ != null) ctx.get().enqueueWork(() -> handler_.accept(pkt.data(), pkt.delay())); + ctx.get().setPacketHandled(true); + } + } + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/OptionalRecipeCondition.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/OptionalRecipeCondition.java new file mode 100644 index 0000000..681daff --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/OptionalRecipeCondition.java @@ -0,0 +1,191 @@ +/* + * @file OptionalRecipeCondition.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Recipe condition to enable opt'ing out JSON based recipes. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraftforge.common.crafting.conditions.ICondition; +import net.minecraftforge.common.crafting.conditions.IConditionSerializer; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.IForgeRegistry; +import org.slf4j.Logger; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + + +public class OptionalRecipeCondition implements ICondition { + private static ResourceLocation NAME; + + private final List all_required; + private final List any_missing; + private final List all_required_tags; + private final List any_missing_tags; + private final @Nullable ResourceLocation result; + private final boolean result_is_tag; + private final boolean experimental; + + private static boolean with_experimental = false; + private static boolean without_recipes = false; + private static Predicate block_optouts = (block) -> false; + private static Predicate item_optouts = (item) -> false; + + public static void init(String modid, Logger logger) { + NAME = new ResourceLocation(modid, "optional"); + } + + public static void on_config(boolean enable_experimental, boolean disable_all_recipes, + Predicate block_optout_provider, + Predicate item_optout_provider) { + with_experimental = enable_experimental; + without_recipes = disable_all_recipes; + block_optouts = block_optout_provider; + item_optouts = item_optout_provider; + } + + public OptionalRecipeCondition(ResourceLocation result, List required, List missing, List required_tags, List missing_tags, boolean isexperimental, boolean result_is_tag) { + all_required = required; + any_missing = missing; + all_required_tags = required_tags; + any_missing_tags = missing_tags; + this.result = result; + this.result_is_tag = result_is_tag; + experimental = isexperimental; + } + + @Override + public ResourceLocation getID() { + return NAME; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Optional recipe, all-required: ["); + for (ResourceLocation e : all_required) sb.append(e.toString()).append(","); + for (ResourceLocation e : all_required_tags) sb.append("#").append(e.toString()).append(","); + sb.delete(sb.length() - 1, sb.length()).append("], any-missing: ["); + for (ResourceLocation e : any_missing) sb.append(e.toString()).append(","); + for (ResourceLocation e : any_missing_tags) sb.append("#").append(e.toString()).append(","); + sb.delete(sb.length() - 1, sb.length()).append("]"); + if (experimental) sb.append(" EXPERIMENTAL"); + return sb.toString(); + } + + @Override + public boolean test(IContext context) { + if (without_recipes) return false; + if ((experimental) && (!with_experimental)) return false; + final IForgeRegistry item_registry = ForgeRegistries.ITEMS; + //final Collection item_tags = SerializationTags.getInstance().getOrEmpty(Registry.ITEM_REGISTRY).getAvailableTags(); + if (result != null) { + boolean item_registered = item_registry.containsKey(result); + if (!item_registered) return false; // required result not registered + if (item_optouts.test(item_registry.getValue(result))) return false; + if (ForgeRegistries.BLOCKS.containsKey(result) && block_optouts.test(ForgeRegistries.BLOCKS.getValue(result))) + return false; + } + if (!all_required.isEmpty()) { + for (ResourceLocation rl : all_required) { + if (!item_registry.containsKey(rl)) return false; + } + } + if (!all_required_tags.isEmpty()) { + for (ResourceLocation rl : all_required_tags) { + if (item_registry.tags().getTagNames().noneMatch(tk -> tk.location().equals(rl))) + return false; // if(!item_tags.contains(rl)) return false; + } + } + if (!any_missing.isEmpty()) { + for (ResourceLocation rl : any_missing) { + if (!item_registry.containsKey(rl)) return true; + } + return false; + } + if (!any_missing_tags.isEmpty()) { + for (ResourceLocation rl : any_missing_tags) { + if (item_registry.tags().getTagNames().noneMatch(tk -> tk.location().equals(rl))) + return true; // if(!item_tags.contains(rl)) return true; + } + return false; + } + return true; + } + + public static class Serializer implements IConditionSerializer { + public static final Serializer INSTANCE = new Serializer(); + + @Override + public ResourceLocation getID() { + return OptionalRecipeCondition.NAME; + } + + @Override + public void write(JsonObject json, OptionalRecipeCondition condition) { + JsonArray required = new JsonArray(); + JsonArray missing = new JsonArray(); + for (ResourceLocation e : condition.all_required) required.add(e.toString()); + for (ResourceLocation e : condition.any_missing) missing.add(e.toString()); + json.add("required", required); + json.add("missing", missing); + if (condition.result != null) { + json.addProperty("result", (condition.result_is_tag ? "#" : "") + condition.result); + } + } + + @Override + public OptionalRecipeCondition read(JsonObject json) { + List required = new ArrayList<>(); + List missing = new ArrayList<>(); + List required_tags = new ArrayList<>(); + List missing_tags = new ArrayList<>(); + ResourceLocation result = null; + boolean experimental = false; + boolean result_is_tag = false; + if (json.has("result")) { + String s = json.get("result").getAsString(); + if (s.startsWith("#")) { + result = new ResourceLocation(s.substring(1)); + result_is_tag = true; + } else { + result = new ResourceLocation(s); + } + } + if (json.has("required")) { + for (JsonElement e : GsonHelper.getAsJsonArray(json, "required")) { + String s = e.getAsString(); + if (s.startsWith("#")) { + required_tags.add(new ResourceLocation(s.substring(1))); + } else { + required.add(new ResourceLocation(s)); + } + } + } + if (json.has("missing")) { + for (JsonElement e : GsonHelper.getAsJsonArray(json, "missing")) { + String s = e.getAsString(); + if (s.startsWith("#")) { + missing_tags.add(new ResourceLocation(s.substring(1))); + } else { + missing.add(new ResourceLocation(s)); + } + } + } + if (json.has("experimental")) experimental = json.get("experimental").getAsBoolean(); + return new OptionalRecipeCondition(result, required, missing, required_tags, missing_tags, experimental, result_is_tag); + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Overlay.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Overlay.java new file mode 100644 index 0000000..43f71cc --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Overlay.java @@ -0,0 +1,165 @@ +/* + * @file Overlay.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Renders status messages in one line. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.mojang.blaze3d.platform.Window; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.util.Mth; +import net.minecraft.util.Tuple; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.fml.DistExecutor; + +import javax.annotation.Nullable; +import java.util.Optional; + + +public class Overlay { + public static void show(Player player, final Component message) { + Networking.OverlayTextMessage.sendToPlayer(player, message, 3000); + } + + public static void show(Player player, final Component message, int delay) { + Networking.OverlayTextMessage.sendToPlayer(player, message, delay); + } + + public static void show(BlockState state, BlockPos pos) { + show(state, pos, 100); + } + + public static void show(BlockState state, BlockPos pos, int displayTimeoutMs) { + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> (() -> TextOverlayGui.show(state, pos, displayTimeoutMs))); + } // Only called when client side + + // ----------------------------------------------------------------------------- + // Client side handler + // ----------------------------------------------------------------------------- + + @OnlyIn(Dist.CLIENT) + public static class TextOverlayGui extends Screen { + public static final TextOverlayGui INSTANCE = new TextOverlayGui(); + private static final Component EMPTY_TEXT = Component.literal(""); + private static final BlockState EMPTY_STATE = null; + private static double overlay_y_ = 0.75; + private static int text_color_ = 0x00ffaa00; + private static int border_color_ = 0xaa333333; + private static int background_color1_ = 0xaa333333; + private static int background_color2_ = 0xaa444444; + private static long text_deadline_ = 0; + private static Component text_ = EMPTY_TEXT; + private static long state_deadline_ = 0; + private static @Nullable BlockState state_ = EMPTY_STATE; + private static BlockPos pos_ = BlockPos.ZERO; + + public static void on_config(double overlay_y) { + on_config(overlay_y, 0x00ffaa00, 0xaa333333, 0xaa333333, 0xaa444444); + } + + public static void on_config(double overlay_y, int text_color, int border_color, int background_color1, int background_color2) { + overlay_y_ = overlay_y; + text_color_ = text_color; + border_color_ = border_color; + background_color1_ = background_color1; + background_color2_ = background_color2; + } + + public static synchronized Component text() { + return text_; + } + + public static synchronized long deadline() { + return text_deadline_; + } + + public static synchronized void hide() { + text_deadline_ = 0; + text_ = EMPTY_TEXT; + } + + public static synchronized void show(Component s, int displayTimeoutMs) { + text_ = (s == null) ? (EMPTY_TEXT) : (s.copy()); + text_deadline_ = System.currentTimeMillis() + displayTimeoutMs; + } + + public static synchronized void show(String s, int displayTimeoutMs) { + text_ = ((s == null) || (s.isEmpty())) ? (EMPTY_TEXT) : (Component.literal(s)); + text_deadline_ = System.currentTimeMillis() + displayTimeoutMs; + } + + public static synchronized void show(BlockState state, BlockPos pos, int displayTimeoutMs) { + pos_ = new BlockPos(pos); + state_ = state; + state_deadline_ = System.currentTimeMillis() + displayTimeoutMs; + } + + private static synchronized Optional> state_pos() { + return ((state_deadline_ < System.currentTimeMillis()) || (state_ == EMPTY_STATE)) ? Optional.empty() : Optional.of(new Tuple<>(state_, pos_)); + } + + TextOverlayGui() { + super(Component.literal("")); + } + + public void onRenderGui(final GuiGraphics mxs) { + if (deadline() < System.currentTimeMillis()) return; + if (text() == EMPTY_TEXT) return; + String txt = text().getString(); + if (txt.isEmpty()) return; + final net.minecraft.client.Minecraft mc = net.minecraft.client.Minecraft.getInstance(); + final Window win = mc.getWindow(); + final Font fr = mc.font; + final boolean was_unicode = fr.isBidirectional(); + final int cx = win.getGuiScaledWidth() / 2; + final int cy = (int) (win.getGuiScaledHeight() * overlay_y_); + final int w = fr.width(txt); + final int h = fr.lineHeight; + mxs.fillGradient(cx - (w / 2) - 3, cy - 2, cx + (w / 2) + 2, cy + h + 2, 0xaa333333, 0xaa444444); + mxs.hLine(cx - (w / 2) - 3, cx + (w / 2) + 2, cy - 2, 0xaa333333); + mxs.hLine(cx - (w / 2) - 3, cx + (w / 2) + 2, cy + h + 2, 0xaa333333); + mxs.vLine(cx - (w / 2) - 3, cy - 2, cy + h + 2, 0xaa333333); + mxs.vLine(cx + (w / 2) + 2, cy - 2, cy + h + 2, 0xaa333333); + mxs.drawCenteredString(fr, text(), cx, cy + 1, 0x00ffaa00); + } + + @SuppressWarnings("deprecation") + public void onRenderWorldOverlay(final com.mojang.blaze3d.vertex.PoseStack mxs, final double partialTick) { + final Optional> sp = state_pos(); + if (sp.isEmpty()) return; + final ClientLevel world = Minecraft.getInstance().level; + final LocalPlayer player = Minecraft.getInstance().player; + if ((player == null) || (world == null)) return; + final BlockState state = sp.get().getA(); + final BlockPos pos = sp.get().getB(); + final int light = (world.hasChunkAt(pos)) ? LightTexture.pack(world.getBrightness(LightLayer.BLOCK, pos), world.getBrightness(LightLayer.SKY, pos)) : LightTexture.pack(15, 15); + final MultiBufferSource buffer = Minecraft.getInstance().renderBuffers().bufferSource(); + final double px = Mth.lerp(partialTick, player.xo, player.getX()); + final double py = Mth.lerp(partialTick, player.yo, player.getY()); + final double pz = Mth.lerp(partialTick, player.zo, player.getZ()); + mxs.pushPose(); + mxs.translate((pos.getX() - px), (pos.getY() - py - player.getEyeHeight()), (pos.getZ() - pz)); + Minecraft.getInstance().getBlockRenderer().renderSingleBlock(state, mxs, buffer, light, OverlayTexture.NO_OVERLAY); + mxs.popPose(); + } + + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/README.md b/src/main/java/dev/zontreck/libzontreck/edlibmc/README.md new file mode 100644 index 0000000..f4bf684 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/README.md @@ -0,0 +1,4 @@ +# Engineer's Decor LibMC +____________ + +As Engineer's Decor has been abandoned and discontinued, i've adopted the LibMC, and split the mod up among my various mods. Engineer's Decor blocks, and items, will now reside in Thresholds. \ No newline at end of file diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Registries.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Registries.java new file mode 100644 index 0000000..02542bc --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Registries.java @@ -0,0 +1,263 @@ +/* + * @file Registries.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Common game registry handling. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.flag.FeatureFlagSet; +import net.minecraft.world.flag.FeatureFlags; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; + +import javax.annotation.Nonnull; +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class Registries { + private static String modid = null; + private static String creative_tab_icon = ""; + private static CreativeModeTab creative_tab = null; + private static final Map> registered_block_tag_keys = new HashMap<>(); + private static final Map> registered_item_tag_keys = new HashMap<>(); + + private static final Map> registered_blocks = new HashMap<>(); + private static final Map> registered_items = new HashMap<>(); + private static final Map>> registered_block_entity_types = new HashMap<>(); + private static final Map>> registered_entity_types = new HashMap<>(); + private static final Map>> registered_menu_types = new HashMap<>(); + private static final Map>> recipe_serializers = new HashMap<>(); + private static final Map> registered_tabs = new HashMap<>(); + + public static final List> tabItems = new ArrayList<>(); + + + + private static DeferredRegister BLOCKS; + private static DeferredRegister ITEMS; + private static DeferredRegister TABS; + private static DeferredRegister> BLOCK_ENTITIES; + private static DeferredRegister> MENUS; + private static DeferredRegister> ENTITIES; + private static DeferredRegister> RECIPE_SERIALIZERS; + private static List> MOD_REGISTRIES; + + public static void init(String mod_id, String creative_tab_icon_item_name, IEventBus bus) { + modid = mod_id; + creative_tab_icon = creative_tab_icon_item_name; + BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, modid); + ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, modid); + BLOCK_ENTITIES = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, modid); + MENUS = DeferredRegister.create(ForgeRegistries.MENU_TYPES, modid); + ENTITIES = DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, modid); + RECIPE_SERIALIZERS = DeferredRegister.create(ForgeRegistries.RECIPE_SERIALIZERS, modid); + TABS = DeferredRegister.create(net.minecraft.core.registries.Registries.CREATIVE_MODE_TAB, modid); + + Consumer> consumer = (X)->{ + X.register(bus); + }; + + List.of(BLOCKS, ITEMS, BLOCK_ENTITIES, MENUS, ENTITIES, RECIPE_SERIALIZERS, TABS).forEach(consumer); + } + + // ------------------------------------------------------------------------------------------------------------- + + public static Block getBlock(String block_name) { + return registered_blocks.get(block_name).get(); + } + + public static RegistryObject withTab(RegistryObject item) + { + tabItems.add(item); + + return item; + } + + public static Item getItem(String name) { + return registered_items.get(name).get(); + } + + public static EntityType getEntityType(String name) { + return registered_entity_types.get(name).get(); + } + + public static BlockEntityType getBlockEntityType(String block_name) { + return registered_block_entity_types.get(block_name).get(); + } + + public static MenuType getMenuType(String name) { + return registered_menu_types.get(name).get(); + } + + public static RecipeSerializer getRecipeSerializer(String name) { + return recipe_serializers.get(name).get(); + } + + public static BlockEntityType getBlockEntityTypeOfBlock(String block_name) { + return getBlockEntityType("tet_" + block_name); + } + + public static BlockEntityType getBlockEntityTypeOfBlock(Block block) { + return getBlockEntityTypeOfBlock(ForgeRegistries.BLOCKS.getKey(block).getPath()); + } + + public static MenuType getMenuTypeOfBlock(String name) { + return getMenuType("ct_" + name); + } + + public static MenuType getMenuTypeOfBlock(Block block) { + return getMenuTypeOfBlock(ForgeRegistries.BLOCKS.getKey(block).getPath()); + } + + public static TagKey getBlockTagKey(String name) { + return registered_block_tag_keys.get(name); + } + + public static TagKey getItemTagKey(String name) { + return registered_item_tag_keys.get(name); + } + + // ------------------------------------------------------------------------------------------------------------- + + @Nonnull + public static List getRegisteredBlocks() { + return Collections.unmodifiableList(registered_blocks.values().stream().map(RegistryObject::get).toList()); + } + + @Nonnull + public static List getRegisteredItems() { + return Collections.unmodifiableList(registered_items.values().stream().map(RegistryObject::get).toList()); + } + + @Nonnull + public static List> getRegisteredBlockEntityTypes() { + return Collections.unmodifiableList(registered_block_entity_types.values().stream().map(RegistryObject::get).toList()); + } + + @Nonnull + public static List> getRegisteredEntityTypes() { + return Collections.unmodifiableList(registered_entity_types.values().stream().map(RegistryObject::get).toList()); + } + + // ------------------------------------------------------------------------------------------------------------- + + public static void addItem(String registry_name, Supplier supplier) { + registered_items.put(registry_name, withTab(ITEMS.register(registry_name, supplier))); + + } + + public static void addBlock(String registry_name, Supplier block_supplier) { + registered_blocks.put(registry_name, BLOCKS.register(registry_name, block_supplier)); + registered_items.put(registry_name, withTab(ITEMS.register(registry_name, () -> new BlockItem(registered_blocks.get(registry_name).get(), (new Item.Properties()))))); + } + + public static void addCreativeModeTab(String id, Component title, ItemStack icon) + { + registered_tabs.put(id, TABS.register(id, ()-> CreativeModeTab + .builder() + .icon(()->icon) + .title(title) + .withSearchBar() + + .displayItems((display, output) -> tabItems.forEach(it->output.accept(it.get()))) + + .build() + )); + } + + public static void addBlock(String registry_name, Supplier block_supplier, Supplier item_supplier) { + registered_blocks.put(registry_name, BLOCKS.register(registry_name, block_supplier)); + registered_items.put(registry_name, ITEMS.register(registry_name, item_supplier)); + } + + public static void addBlockEntityType(String registry_name, BlockEntityType.BlockEntitySupplier ctor, String... block_names) { + registered_block_entity_types.put(registry_name, BLOCK_ENTITIES.register(registry_name, () -> { + final Block[] blocks = Arrays.stream(block_names).map(s -> { + Block b = BLOCKS.getEntries().stream().filter((ro) -> ro.getId().getPath().equals(s)).findFirst().map(RegistryObject::get).orElse(null); + if (b == null) Auxiliaries.logError("registered_blocks does not encompass '" + s + "'"); + return b; + }).filter(Objects::nonNull).collect(Collectors.toList()).toArray(new Block[]{}); + return BlockEntityType.Builder.of(ctor, blocks).build(null); + })); + } + + public static > void addEntityType(String registry_name, Supplier> supplier) { + registered_entity_types.put(registry_name, ENTITIES.register(registry_name, supplier)); + } + + public static > void addMenuType(String registry_name, MenuType.MenuSupplier supplier, FeatureFlagSet flags) { + registered_menu_types.put(registry_name, MENUS.register(registry_name, () -> new MenuType<>(supplier, flags))); + } + + public static void addRecipeSerializer(String registry_name, Supplier> serializer_supplier) { + recipe_serializers.put(registry_name, RECIPE_SERIALIZERS.register(registry_name, serializer_supplier)); + } + + public static void addOptionalBlockTag(String tag_name, ResourceLocation... default_blocks) { + final Set> default_suppliers = new HashSet<>(); + for (ResourceLocation rl : default_blocks) default_suppliers.add(() -> ForgeRegistries.BLOCKS.getValue(rl)); + final TagKey key = ForgeRegistries.BLOCKS.tags().createOptionalTagKey(new ResourceLocation(modid, tag_name), default_suppliers); + registered_block_tag_keys.put(tag_name, key); + } + + public static void addOptionaItemTag(String tag_name, ResourceLocation... default_items) { + final Set> default_suppliers = new HashSet<>(); + for (ResourceLocation rl : default_items) default_suppliers.add(() -> ForgeRegistries.ITEMS.getValue(rl)); + final TagKey key = ForgeRegistries.ITEMS.tags().createOptionalTagKey(new ResourceLocation(modid, tag_name), default_suppliers); + registered_item_tag_keys.put(tag_name, key); + } + + // ------------------------------------------------------------------------------------------------------------- + + @Deprecated + /** + * This function is to be removed as it cannot add items to a tab anymore + */ + public static void addBlock(String registry_name, Supplier block_supplier, BiFunction item_builder) { + addBlock(registry_name, block_supplier, () -> item_builder.apply(registered_blocks.get(registry_name).get(), (new Item.Properties()))); + } + + public static void addBlock(String registry_name, Supplier block_supplier, BlockEntityType.BlockEntitySupplier block_entity_ctor) { + addBlock(registry_name, block_supplier); + addBlockEntityType("tet_" + registry_name, block_entity_ctor, registry_name); + } + + public static void addBlock(String registry_name, Supplier block_supplier, BiFunction item_builder, BlockEntityType.BlockEntitySupplier block_entity_ctor) { + addBlock(registry_name, block_supplier, item_builder); + addBlockEntityType("tet_" + registry_name, block_entity_ctor, registry_name); + } + + public static void addBlock(String registry_name, Supplier block_supplier, BiFunction item_builder, BlockEntityType.BlockEntitySupplier block_entity_ctor, MenuType.MenuSupplier menu_type_supplier, FeatureFlagSet menuFlags) { + addBlock(registry_name, block_supplier, item_builder); + addBlockEntityType("tet_" + registry_name, block_entity_ctor, registry_name); + addMenuType("ct_" + registry_name, menu_type_supplier, menuFlags); + } + + public static void addBlock(String registry_name, Supplier block_supplier, BlockEntityType.BlockEntitySupplier block_entity_ctor, MenuType.MenuSupplier menu_type_supplier, FeatureFlagSet menuFlags) { + addBlock(registry_name, block_supplier, block_entity_ctor); + addMenuType("ct_" + registry_name, menu_type_supplier, menuFlags); + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/RfEnergy.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/RfEnergy.java new file mode 100644 index 0000000..8636926 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/RfEnergy.java @@ -0,0 +1,180 @@ +/* + * @file RfEnergy.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * General RF/FE energy handling functionality. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.Mth; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraftforge.common.capabilities.ForgeCapabilities; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.energy.IEnergyStorage; + +import javax.annotation.Nullable; + +public class RfEnergy { + public static int feed(Level world, BlockPos pos, @Nullable Direction side, int rf_energy) { + final BlockEntity te = world.getBlockEntity(pos); + if (te == null) return 0; + final IEnergyStorage es = te.getCapability(ForgeCapabilities.ENERGY, side).orElse(null); + if (es == null) return 0; + return es.receiveEnergy(rf_energy, false); + } + + public static class Battery implements IEnergyStorage { + protected int capacity_; + protected int charge_rate_; + protected int discharge_rate_; + protected int energy_; + + public Battery(int capacity) { + this(capacity, capacity); + } + + public Battery(int capacity, int transfer_rate) { + this(capacity, transfer_rate, transfer_rate, 0); + } + + public Battery(int capacity, int charge_rate, int discharge_rate) { + this(capacity, charge_rate, discharge_rate, 0); + } + + public Battery(int capacity, int charge_rate, int discharge_rate, int energy) { + capacity_ = Math.max(capacity, 1); + charge_rate_ = Mth.clamp(charge_rate, 0, capacity_); + discharge_rate_ = Mth.clamp(discharge_rate, 0, capacity_); + energy_ = Mth.clamp(energy, 0, capacity_); + } + + // --------------------------------------------------------------------------------------------------- + + public Battery setMaxEnergyStored(int capacity) { + capacity_ = Math.max(capacity, 1); + return this; + } + + public Battery setEnergyStored(int energy) { + energy_ = Mth.clamp(energy, 0, capacity_); + return this; + } + + public Battery setChargeRate(int in_rate) { + charge_rate_ = Mth.clamp(in_rate, 0, capacity_); + return this; + } + + public Battery setDischargeRate(int out_rate) { + discharge_rate_ = Mth.clamp(out_rate, 0, capacity_); + return this; + } + + public int getChargeRate() { + return charge_rate_; + } + + public int getDischargeRate() { + return discharge_rate_; + } + + public boolean isEmpty() { + return energy_ <= 0; + } + + public boolean isFull() { + return energy_ >= capacity_; + } + + public int getSOC() { + return (int) Mth.clamp((100.0 * energy_ / capacity_ + .5), 0, 100); + } + + public int getComparatorOutput() { + return (int) Mth.clamp((15.0 * energy_ / capacity_ + .2), 0, 15); + } + + public boolean draw(int energy) { + if (energy_ < energy) return false; + energy_ -= energy; + return true; + } + + public boolean feed(int energy) { + energy_ = Math.min(energy_ + energy, capacity_); + return energy_ >= capacity_; + } + + public Battery clear() { + energy_ = 0; + return this; + } + + public Battery load(CompoundTag nbt, String key) { + setEnergyStored(nbt.getInt(key)); + return this; + } + + public Battery load(CompoundTag nbt) { + return load(nbt, "Energy"); + } + + public CompoundTag save(CompoundTag nbt, String key) { + nbt.putInt(key, energy_); + return nbt; + } + + public CompoundTag save(CompoundTag nbt) { + return save(nbt, "Energy"); + } + + public LazyOptional createEnergyHandler() { + return LazyOptional.of(() -> this); + } + + // IEnergyStorage ------------------------------------------------------------------------------------ + + @Override + public int receiveEnergy(int feed_energy, boolean simulate) { + if (!canReceive()) return 0; + int e = Math.min(Math.min(charge_rate_, feed_energy), capacity_ - energy_); + if (!simulate) energy_ += e; + return e; + } + + @Override + public int extractEnergy(int draw_energy, boolean simulate) { + if (!canExtract()) return 0; + int e = Math.min(Math.min(discharge_rate_, draw_energy), energy_); + if (!simulate) energy_ -= e; + return e; + } + + @Override + public int getEnergyStored() { + return energy_; + } + + @Override + public int getMaxEnergyStored() { + return capacity_; + } + + @Override + public boolean canExtract() { + return discharge_rate_ > 0; + } + + @Override + public boolean canReceive() { + return charge_rate_ > 0; + } + + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/RsSignals.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/RsSignals.java new file mode 100644 index 0000000..70509bd --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/RsSignals.java @@ -0,0 +1,42 @@ +/* + * @file RsSignals.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * General redstone signal related functionality. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.Container; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.state.BlockState; + +import javax.annotation.Nullable; + +public class RsSignals { + + public static boolean hasSignalConnector(BlockState state, BlockGetter world, BlockPos pos, @Nullable Direction realSide) { + return state.isSignalSource(); + } + + public static int fromContainer(@Nullable Container container) { + if (container == null) return 0; + final double max = container.getMaxStackSize(); + if (max <= 0) return 0; + boolean nonempty = false; + double fill_level = 0; + for (int i = 0; i < container.getContainerSize(); ++i) { + ItemStack stack = container.getItem(i); + if (stack.isEmpty() || (stack.getMaxStackSize() <= 0)) continue; + fill_level += ((double) stack.getCount()) / Math.min(max, stack.getMaxStackSize()); + nonempty = true; + } + fill_level /= container.getContainerSize(); + return (int) (Math.floor(fill_level * 14) + (nonempty ? 1 : 0)); // vanilla compliant calculation. + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/SidedProxy.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/SidedProxy.java new file mode 100644 index 0000000..ba5f04b --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/SidedProxy.java @@ -0,0 +1,122 @@ +/* + * @file SidedProxy.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * General client/server sidedness selection proxy. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.client.Minecraft; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraftforge.fml.DistExecutor; + +import javax.annotation.Nullable; +import java.util.Optional; + +public class SidedProxy { + @Nullable + public static Player getPlayerClientSide() { + return proxy.getPlayerClientSide(); + } + + @Nullable + public static Level getWorldClientSide() { + return proxy.getWorldClientSide(); + } + + @Nullable + public static Minecraft mc() { + return proxy.mc(); + } + + public static Optional isCtrlDown() { + return proxy.isCtrlDown(); + } + + public static Optional isShiftDown() { + return proxy.isShiftDown(); + } + + public static Optional getClipboard() { + return proxy.getClipboard(); + } + + public static boolean setClipboard(String text) { + return proxy.setClipboard(text); + } + + // -------------------------------------------------------------------------------------------------------- + + private static final ISidedProxy proxy = DistExecutor.unsafeRunForDist(() -> ClientProxy::new, () -> ServerProxy::new); + + private interface ISidedProxy { + default @Nullable Player getPlayerClientSide() { + return null; + } + + default @Nullable Level getWorldClientSide() { + return null; + } + + default @Nullable Minecraft mc() { + return null; + } + + default Optional isCtrlDown() { + return Optional.empty(); + } + + default Optional isShiftDown() { + return Optional.empty(); + } + + default Optional getClipboard() { + return Optional.empty(); + } + + default boolean setClipboard(String text) { + return false; + } + } + + private static final class ClientProxy implements ISidedProxy { + public @Nullable Player getPlayerClientSide() { + return Minecraft.getInstance().player; + } + + public @Nullable Level getWorldClientSide() { + return Minecraft.getInstance().level; + } + + public @Nullable Minecraft mc() { + return Minecraft.getInstance(); + } + + public Optional isCtrlDown() { + return Optional.of(Auxiliaries.isCtrlDown()); + } + + public Optional isShiftDown() { + return Optional.of(Auxiliaries.isShiftDown()); + } + + public Optional getClipboard() { + return (mc() == null) ? Optional.empty() : Optional.of(net.minecraft.client.gui.font.TextFieldHelper.getClipboardContents(mc())); + } + + public boolean setClipboard(String text) { + if (mc() == null) { + return false; + } + net.minecraft.client.gui.font.TextFieldHelper.setClipboardContents(Minecraft.getInstance(), text); + return true; + } + } + + private static final class ServerProxy implements ISidedProxy { + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/SlabSliceBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/SlabSliceBlock.java new file mode 100644 index 0000000..40e3401 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/SlabSliceBlock.java @@ -0,0 +1,228 @@ +/* + * @file SlabSliceBlock.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Half slab ("slab slices") characteristics class. Actually + * it's now a quarter slab, but who cares. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.IntegerProperty; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SlabSliceBlock extends StandardBlocks.WaterLoggable implements StandardBlocks.IStandardBlock { + public static final IntegerProperty PARTS = IntegerProperty.create("parts", 0, 14); + + protected static final VoxelShape[] AABBs = { + Shapes.create(new AABB(0, 0. / 16, 0, 1, 2. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 4. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 6. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 8. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 10. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 12. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 14. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 2. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 4. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 6. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 8. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 10. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 12. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 14. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 0, 0, 1, 1, 1)) // <- with 4bit fill + }; + + protected static final int[] num_slabs_contained_in_parts_ = {1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1, 0x1}; // <- with 4bit fill + private static boolean with_pickup = false; + + public static void on_config(boolean direct_slab_pickup) { + with_pickup = direct_slab_pickup; + } + + public SlabSliceBlock(long config, BlockBehaviour.Properties builder) { + super(config, builder); + } + + protected boolean is_cube(BlockState state) { + return state.getValue(PARTS) == 0x07; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + if (!Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true)) return; + if (with_pickup) Auxiliaries.Tooltip.addInformation("engineersdecor.tooltip.slabpickup", tooltip); + } + + @Override + public RenderTypeHint getRenderTypeHint() { + return (((config & StandardBlocks.CFG_TRANSLUCENT) != 0) ? (RenderTypeHint.TRANSLUCENT) : (RenderTypeHint.CUTOUT)); + } + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { + return AABBs[state.getValue(PARTS) & 0xf]; + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return getShape(state, world, pos, selectionContext); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(PARTS); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + final BlockPos pos = context.getClickedPos(); + BlockState state = context.getLevel().getBlockState(pos); + if (state.getBlock() == this) { + int parts = state.getValue(PARTS); + if (parts == 7) return null; // -> is already a full block. + parts += (parts < 7) ? 1 : -1; + if (parts == 7) state = state.setValue(WATERLOGGED, false); + return state.setValue(PARTS, parts); + } else { + final Direction face = context.getClickedFace(); + final BlockState placement_state = super.getStateForPlacement(context); // fluid state + if (face == Direction.UP) return placement_state.setValue(PARTS, 0); + if (face == Direction.DOWN) return placement_state.setValue(PARTS, 14); + if (!face.getAxis().isHorizontal()) return placement_state; + final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5); + return placement_state.setValue(PARTS, isupper ? 14 : 0); + } + } + + @Override + @SuppressWarnings("deprecation") + public boolean canBeReplaced(BlockState state, BlockPlaceContext context) { + if (context.getItemInHand().getItem() != this.asItem()) return false; + if (!context.replacingClickedOnBlock()) return true; + final Direction face = context.getClickedFace(); + final int parts = state.getValue(PARTS); + if (parts == 7) return false; + if ((face == Direction.UP) && (parts < 7)) return true; + if ((face == Direction.DOWN) && (parts > 7)) return true; + if (!face.getAxis().isHorizontal()) return false; + final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5); + return isupper ? (parts == 0) : (parts == 1); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState rotate(BlockState state, Rotation rot) { + return state; + } + + @Override + @SuppressWarnings("deprecation") + public BlockState mirror(BlockState state, Mirror mirrorIn) { + return state; + } + + @Override + public boolean hasDynamicDropList() { + return true; + } + + @Override + public List dropList(BlockState state, Level world, BlockEntity te, boolean explosion) { + return new ArrayList<>(Collections.singletonList(new ItemStack(this.asItem(), num_slabs_contained_in_parts_[state.getValue(PARTS) & 0xf]))); + } + + @Override + @SuppressWarnings("deprecation") + public void attack(BlockState state, Level world, BlockPos pos, Player player) { + if ((world.isClientSide) || (!with_pickup)) return; + final ItemStack stack = player.getMainHandItem(); + if (stack.isEmpty() || (Block.byItem(stack.getItem()) != this)) return; + if (stack.getCount() >= stack.getMaxStackSize()) return; + Vec3 lv = player.getLookAngle(); + Direction facing = Direction.getNearest((float) lv.x, (float) lv.y, (float) lv.z); + if ((facing != Direction.UP) && (facing != Direction.DOWN)) return; + if (state.getBlock() != this) return; + int parts = state.getValue(PARTS); + if ((facing == Direction.DOWN) && (parts <= 7)) { + if (parts > 0) { + world.setBlock(pos, state.setValue(PARTS, parts - 1), 3); + } else { + world.removeBlock(pos, false); + } + } else if ((facing == Direction.UP) && (parts >= 7)) { + if (parts < 14) { + world.setBlock(pos, state.setValue(PARTS, parts + 1), 3); + } else { + world.removeBlock(pos, false); + } + } else { + return; + } + if (!player.isCreative()) { + stack.grow(1); + if (player.getInventory() != null) player.getInventory().setChanged(); + } + SoundType st = this.getSoundType(state, world, pos, null); + world.playSound(player, pos, st.getPlaceSound(), SoundSource.BLOCKS, (st.getVolume() + 1f) / 2.5f, 0.9f * st.getPitch()); + } + + @Override + public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { + return (state.getValue(PARTS) != 14) && (super.placeLiquid(world, pos, state, fluidState)); + } + + @Override + public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { + return (state.getValue(PARTS) != 14) && (super.canPlaceLiquid(world, pos, state, fluid)); + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardBlocks.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardBlocks.java new file mode 100644 index 0000000..69343b1 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardBlocks.java @@ -0,0 +1,698 @@ +/* + * @file StandardBlocks.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Common functionality class for decor blocks. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.SimpleWaterloggedBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.block.state.properties.DirectionProperty; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.level.material.PushReaction; +import net.minecraft.world.level.pathfinder.PathComputationType; +import net.minecraft.world.level.storage.loot.LootContext; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.shapes.BooleanOp; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; + + +@SuppressWarnings("deprecation") +public class StandardBlocks { + public static final long CFG_DEFAULT = 0x0000000000000000L; // no special config + public static final long CFG_CUTOUT = 0x0000000000000001L; // cutout rendering + public static final long CFG_MIPPED = 0x0000000000000002L; // cutout mipped rendering + public static final long CFG_TRANSLUCENT = 0x0000000000000004L; // indicates a block/pane is glass like (transparent, etc) + public static final long CFG_WATERLOGGABLE = 0x0000000000000008L; // The derived block extends IWaterLoggable + public static final long CFG_HORIZIONTAL = 0x0000000000000010L; // horizontal block, affects bounding box calculation at construction time and placement + public static final long CFG_LOOK_PLACEMENT = 0x0000000000000020L; // placed in direction the player is looking when placing. + public static final long CFG_FACING_PLACEMENT = 0x0000000000000040L; // placed on the facing the player has clicked. + public static final long CFG_OPPOSITE_PLACEMENT = 0x0000000000000080L; // placed placed in the opposite direction of the face the player clicked. + public static final long CFG_FLIP_PLACEMENT_IF_SAME = 0x0000000000000100L; // placement direction flipped if an instance of the same class was clicked + public static final long CFG_FLIP_PLACEMENT_SHIFTCLICK = 0x0000000000000200L; // placement direction flipped if player is sneaking + public static final long CFG_STRICT_CONNECTIONS = 0x0000000000000400L; // blocks do not connect to similar blocks around (implementation details may vary a bit) + public static final long CFG_AI_PASSABLE = 0x0000000000000800L; // does not block movement path for AI, needed for non-opaque blocks with collision shapes not thin at the bottom or one side. + + public interface IStandardBlock { + default long config() { + return 0; + } + + default boolean hasDynamicDropList() { + return false; + } + + default List dropList(BlockState state, Level world, @Nullable BlockEntity te, boolean explosion) { + return Collections.singletonList((!world.isClientSide()) ? (new ItemStack(state.getBlock().asItem())) : (ItemStack.EMPTY)); + } + + enum RenderTypeHint {SOLID, CUTOUT, CUTOUT_MIPPED, TRANSLUCENT, TRANSLUCENT_NO_CRUMBLING} + + default RenderTypeHint getRenderTypeHint() { + return getRenderTypeHint(config()); + } + + default RenderTypeHint getRenderTypeHint(long config) { + if ((config & CFG_CUTOUT) != 0) return RenderTypeHint.CUTOUT; + if ((config & CFG_MIPPED) != 0) return RenderTypeHint.CUTOUT_MIPPED; + if ((config & CFG_TRANSLUCENT) != 0) return RenderTypeHint.TRANSLUCENT; + return RenderTypeHint.SOLID; + } + } + + public static class BaseBlock extends Block implements IStandardBlock, SimpleWaterloggedBlock { + public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; + public final long config; + + public BaseBlock(long conf, BlockBehaviour.Properties properties) { + super(properties); + config = conf; + BlockState state = getStateDefinition().any(); + if ((conf & CFG_WATERLOGGABLE) != 0) state = state.setValue(WATERLOGGED, false); + registerDefaultState(state); + } + + @Override + public long config() { + return config; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); + } + + @Override + public RenderTypeHint getRenderTypeHint() { + return getRenderTypeHint(config); + } + + @Override + @SuppressWarnings("deprecation") + public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type) { + return ((config & CFG_AI_PASSABLE) != 0) && (super.isPathfindable(state, world, pos, type)); + } + + public boolean hasSignalConnector(BlockState state, BlockGetter world, BlockPos pos, @Nullable Direction side) { + return state.isSignalSource(); + } + + @Override + @SuppressWarnings("deprecation") + public void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean isMoving) { + final boolean rsup = (state.hasBlockEntity() && (state.getBlock() != newState.getBlock())); + super.onRemove(state, world, pos, newState, isMoving); + if (rsup) world.updateNeighbourForOutputSignal(pos, this); + } + + @Override + public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos) { + return (((config & CFG_WATERLOGGABLE) == 0) || (!state.getValue(WATERLOGGED))) && super.propagatesSkylightDown(state, reader, pos); + } + + @Override + @SuppressWarnings("deprecation") + public FluidState getFluidState(BlockState state) { + return (((config & CFG_WATERLOGGABLE) != 0) && state.getValue(WATERLOGGED)) ? Fluids.WATER.getSource(false) : super.getFluidState(state); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos) { + if (((config & CFG_WATERLOGGABLE) != 0) && (state.getValue(WATERLOGGED))) + world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); + return state; + } + + @Override // SimpleWaterloggedBlock + public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { + return ((config & CFG_WATERLOGGABLE) != 0) && SimpleWaterloggedBlock.super.canPlaceLiquid(world, pos, state, fluid); + } + + @Override // SimpleWaterloggedBlock + public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { + return ((config & CFG_WATERLOGGABLE) != 0) && SimpleWaterloggedBlock.super.placeLiquid(world, pos, state, fluidState); + } + + @Override // SimpleWaterloggedBlock + public ItemStack pickupBlock(LevelAccessor world, BlockPos pos, BlockState state) { + return ((config & CFG_WATERLOGGABLE) != 0) ? (SimpleWaterloggedBlock.super.pickupBlock(world, pos, state)) : (ItemStack.EMPTY); + } + + @Override // SimpleWaterloggedBlock + public Optional getPickupSound() { + return ((config & CFG_WATERLOGGABLE) != 0) ? (SimpleWaterloggedBlock.super.getPickupSound()) : Optional.empty(); + } + } + + public static class Cutout extends BaseBlock implements IStandardBlock { + private final VoxelShape vshape; + + public Cutout(long conf, BlockBehaviour.Properties properties) { + this(conf, properties, Auxiliaries.getPixeledAABB(0, 0, 0, 16, 16, 16)); + } + + public Cutout(long conf, BlockBehaviour.Properties properties, AABB aabb) { + this(conf, properties, Shapes.create(aabb)); + } + + public Cutout(long conf, BlockBehaviour.Properties properties, AABB[] aabbs) { + this(conf, properties, Arrays.stream(aabbs).map(Shapes::create).reduce(Shapes.empty(), (shape, aabb) -> Shapes.joinUnoptimized(shape, aabb, BooleanOp.OR))); + } + + public Cutout(long conf, BlockBehaviour.Properties properties, VoxelShape voxel_shape) { + super(conf, properties); + vshape = voxel_shape; + } + + @Override + @SuppressWarnings("deprecation") + public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { + return vshape; + } + + @Override + @SuppressWarnings("deprecation") + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return vshape; + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + BlockState state = super.getStateForPlacement(context); + if ((config & CFG_WATERLOGGABLE) != 0) { + FluidState fs = context.getLevel().getFluidState(context.getClickedPos()); + state = state.setValue(WATERLOGGED, fs.getType() == Fluids.WATER); + } + return state; + } + + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + @SuppressWarnings("deprecation") + public PushReaction getPistonPushReaction(BlockState state) { + return PushReaction.NORMAL; + } + + @Override + public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos) { + if ((config & CFG_WATERLOGGABLE) != 0) { + if (state.getValue(WATERLOGGED)) return false; + } + return super.propagatesSkylightDown(state, reader, pos); + } + + @Override + @SuppressWarnings("deprecation") + public FluidState getFluidState(BlockState state) { + if ((config & CFG_WATERLOGGABLE) != 0) { + return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state); + } + return super.getFluidState(state); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos) { + if ((config & CFG_WATERLOGGABLE) != 0) { + if (state.getValue(WATERLOGGED)) + world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); + } + return state; + } + } + + public static class WaterLoggable extends Cutout implements IStandardBlock { + public WaterLoggable(long config, BlockBehaviour.Properties properties) { + super(config | CFG_WATERLOGGABLE, properties); + } + + public WaterLoggable(long config, BlockBehaviour.Properties properties, AABB aabb) { + super(config | CFG_WATERLOGGABLE, properties, aabb); + } + + public WaterLoggable(long config, BlockBehaviour.Properties properties, VoxelShape voxel_shape) { + super(config | CFG_WATERLOGGABLE, properties, voxel_shape); + } + + public WaterLoggable(long config, BlockBehaviour.Properties properties, AABB[] aabbs) { + super(config | CFG_WATERLOGGABLE, properties, aabbs); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(WATERLOGGED); + } + } + + public static class Directed extends Cutout implements IStandardBlock { + public static final DirectionProperty FACING = BlockStateProperties.FACING; + protected final Map vshapes; + + public Directed(long config, BlockBehaviour.Properties properties, final Function, Map> shape_supplier) { + super(config, properties); + registerDefaultState(super.defaultBlockState().setValue(FACING, Direction.UP)); + vshapes = shape_supplier.apply(getStateDefinition().getPossibleStates()); + } + + public Directed(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { + this(config, properties, (states) -> { + final Map vshapes = new HashMap<>(); + final ArrayList indexed_shapes = shape_supplier.get(); + for (BlockState state : states) + vshapes.put(state, indexed_shapes.get(state.getValue(FACING).get3DDataValue())); + return vshapes; + }); + } + + public Directed(long config, BlockBehaviour.Properties properties, final AABB[] unrotatedAABBs) { + this(config, properties, (states) -> { + final boolean is_horizontal = ((config & CFG_HORIZIONTAL) != 0); + Map vshapes = new HashMap<>(); + for (BlockState state : states) { + vshapes.put(state, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, state.getValue(FACING), is_horizontal))); + } + return vshapes; + }); + } + + public Directed(long config, BlockBehaviour.Properties properties, final AABB unrotatedAABB) { + this(config, properties, new AABB[]{unrotatedAABB}); + } + + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { + return vshapes.get(state); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return getShape(state, world, pos, selectionContext); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(FACING); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + BlockState state = super.getStateForPlacement(context); + if (state == null) return null; + Direction facing = context.getClickedFace(); + if ((config & (CFG_HORIZIONTAL | CFG_LOOK_PLACEMENT)) == (CFG_HORIZIONTAL | CFG_LOOK_PLACEMENT)) { + // horizontal placement in direction the player is looking + facing = context.getHorizontalDirection(); + } else if ((config & (CFG_HORIZIONTAL | CFG_LOOK_PLACEMENT)) == (CFG_HORIZIONTAL)) { + // horizontal placement on a face + if (((facing == Direction.UP) || (facing == Direction.DOWN))) return null; + } else if ((config & CFG_LOOK_PLACEMENT) != 0) { + // placement in direction the player is looking, with up and down + facing = context.getNearestLookingDirection(); + } + if ((config & CFG_OPPOSITE_PLACEMENT) != 0) facing = facing.getOpposite(); + if (((config & CFG_FLIP_PLACEMENT_SHIFTCLICK) != 0) && (context.getPlayer() != null) && (context.getPlayer().isShiftKeyDown())) + facing = facing.getOpposite(); + return state.setValue(FACING, facing); + } + } + + public static class AxisAligned extends Cutout implements IStandardBlock { + public static final EnumProperty AXIS = BlockStateProperties.AXIS; + protected final ArrayList vshapes; + + public AxisAligned(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { + super(config, properties); + registerDefaultState(super.defaultBlockState().setValue(AXIS, Direction.Axis.X)); + vshapes = shape_supplier.get(); + } + + public AxisAligned(long config, BlockBehaviour.Properties properties, final AABB[] unrotatedAABBs) { + this(config, properties, () -> new ArrayList<>(Arrays.asList( + Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, Direction.EAST, false)), + Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, Direction.UP, false)), + Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, Direction.SOUTH, false)), + Shapes.block() + ))); + } + + public AxisAligned(long config, BlockBehaviour.Properties properties, final AABB unrotatedAABB) { + this(config, properties, new AABB[]{unrotatedAABB}); + } + + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { + return vshapes.get((state.getValue(AXIS)).ordinal() & 0x3); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return getShape(state, world, pos, selectionContext); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(AXIS); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + Direction facing; + if ((config & CFG_LOOK_PLACEMENT) != 0) { + facing = context.getNearestLookingDirection(); + } else { + facing = context.getClickedFace(); + } + return super.getStateForPlacement(context).setValue(AXIS, facing.getAxis()); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState rotate(BlockState state, Rotation rotation) { + switch (rotation) { + case CLOCKWISE_90: + case COUNTERCLOCKWISE_90: + switch (state.getValue(AXIS)) { + case X: + return state.setValue(AXIS, Direction.Axis.Z); + case Z: + return state.setValue(AXIS, Direction.Axis.X); + } + } + return state; + } + } + + public static class Horizontal extends Cutout implements IStandardBlock { + public static final DirectionProperty HORIZONTAL_FACING = BlockStateProperties.HORIZONTAL_FACING; + protected final Map vshapes; + protected final Map cshapes; + + public Horizontal(long config, BlockBehaviour.Properties properties, final Function, Map> shape_supplier) { + super(config | CFG_HORIZIONTAL, properties); + registerDefaultState(super.defaultBlockState().setValue(HORIZONTAL_FACING, Direction.NORTH)); + vshapes = shape_supplier.apply(getStateDefinition().getPossibleStates()); + cshapes = shape_supplier.apply(getStateDefinition().getPossibleStates()); + } + + public Horizontal(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { + this(config, properties, (states) -> { + final Map vshapes = new HashMap<>(); + final ArrayList indexed_shapes = shape_supplier.get(); + for (BlockState state : states) + vshapes.put(state, indexed_shapes.get(state.getValue(HORIZONTAL_FACING).get3DDataValue())); + return vshapes; + }); + } + + public Horizontal(long config, BlockBehaviour.Properties properties, final AABB unrotatedAABB) { + this(config, properties, new AABB[]{unrotatedAABB}); + } + + public Horizontal(long config, BlockBehaviour.Properties properties, final AABB[] unrotatedAABBs) { + this(config, properties, (states) -> { + Map vshapes = new HashMap<>(); + for (BlockState state : states) { + vshapes.put(state, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, state.getValue(HORIZONTAL_FACING), true))); + } + return vshapes; + }); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(HORIZONTAL_FACING); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { + return vshapes.get(state); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return cshapes.get(state); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + BlockState state = super.getStateForPlacement(context); + if (state == null) return null; + Direction facing = context.getClickedFace(); + if ((config & CFG_LOOK_PLACEMENT) != 0) { + // horizontal placement in direction the player is looking + facing = context.getHorizontalDirection(); + } else { + // horizontal placement on a face + facing = ((facing == Direction.UP) || (facing == Direction.DOWN)) ? (context.getHorizontalDirection()) : facing; + } + if ((config & CFG_OPPOSITE_PLACEMENT) != 0) facing = facing.getOpposite(); + if (((config & CFG_FLIP_PLACEMENT_SHIFTCLICK) != 0) && (context.getPlayer() != null) && (context.getPlayer().isShiftKeyDown())) + facing = facing.getOpposite(); + return state.setValue(HORIZONTAL_FACING, facing); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState rotate(BlockState state, Rotation rot) { + return state.setValue(HORIZONTAL_FACING, rot.rotate(state.getValue(HORIZONTAL_FACING))); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState mirror(BlockState state, Mirror mirrorIn) { + return state.rotate(mirrorIn.getRotation(state.getValue(HORIZONTAL_FACING))); + } + } + + public static class DirectedWaterLoggable extends Directed implements IStandardBlock { + public DirectedWaterLoggable(long config, BlockBehaviour.Properties properties, AABB aabb) { + super(config | CFG_WATERLOGGABLE, properties, aabb); + } + + public DirectedWaterLoggable(long config, BlockBehaviour.Properties properties, AABB[] aabbs) { + super(config | CFG_WATERLOGGABLE, properties, aabbs); + } + + public DirectedWaterLoggable(long config, BlockBehaviour.Properties properties, final Function, Map> shape_supplier) { + super(config | CFG_WATERLOGGABLE, properties, shape_supplier); + } + + public DirectedWaterLoggable(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { + super(config | CFG_WATERLOGGABLE, properties, shape_supplier); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(WATERLOGGED); + } + } + + public static class AxisAlignedWaterLoggable extends AxisAligned implements IStandardBlock { + public AxisAlignedWaterLoggable(long config, BlockBehaviour.Properties properties, AABB aabb) { + super(config | CFG_WATERLOGGABLE, properties, aabb); + } + + public AxisAlignedWaterLoggable(long config, BlockBehaviour.Properties properties, AABB[] aabbs) { + super(config | CFG_WATERLOGGABLE, properties, aabbs); + } + + public AxisAlignedWaterLoggable(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { + super(config | CFG_WATERLOGGABLE, properties, shape_supplier); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(WATERLOGGED); + } + } + + public static class HorizontalWaterLoggable extends Horizontal implements IStandardBlock { + public HorizontalWaterLoggable(long config, BlockBehaviour.Properties properties, AABB aabb) { + super(config | CFG_WATERLOGGABLE | CFG_HORIZIONTAL, properties, aabb); + } + + public HorizontalWaterLoggable(long config, BlockBehaviour.Properties properties, AABB[] aabbs) { + super(config | CFG_WATERLOGGABLE | CFG_HORIZIONTAL, properties, aabbs); + } + + public HorizontalWaterLoggable(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { + super(config | CFG_WATERLOGGABLE | CFG_HORIZIONTAL, properties, shape_supplier); + } + + public HorizontalWaterLoggable(long config, BlockBehaviour.Properties properties, final Function, Map> shape_supplier) { + super(config | CFG_WATERLOGGABLE | CFG_HORIZIONTAL, properties, shape_supplier); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(WATERLOGGED); + } + } + + static public class HorizontalFourWayWaterLoggable extends WaterLoggable implements IStandardBlock { + public static final BooleanProperty NORTH = BlockStateProperties.NORTH; + public static final BooleanProperty EAST = BlockStateProperties.EAST; + public static final BooleanProperty SOUTH = BlockStateProperties.SOUTH; + public static final BooleanProperty WEST = BlockStateProperties.WEST; + protected final Map shapes; + protected final Map collision_shapes; + + public HorizontalFourWayWaterLoggable(long config, BlockBehaviour.Properties properties, AABB base_aabb, final AABB[] side_aabb, int railing_height_extension) { + super(config, properties, base_aabb); + Map build_shapes = new HashMap<>(); + Map build_collision_shapes = new HashMap<>(); + for (BlockState state : getStateDefinition().getPossibleStates()) { + { + VoxelShape shape = ((base_aabb.getXsize() == 0) || (base_aabb.getYsize() == 0) || (base_aabb.getZsize() == 0)) ? Shapes.empty() : Shapes.create(base_aabb); + if (state.getValue(NORTH)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(side_aabb, Direction.NORTH, true)), BooleanOp.OR); + if (state.getValue(EAST)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(side_aabb, Direction.EAST, true)), BooleanOp.OR); + if (state.getValue(SOUTH)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(side_aabb, Direction.SOUTH, true)), BooleanOp.OR); + if (state.getValue(WEST)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(side_aabb, Direction.WEST, true)), BooleanOp.OR); + if (shape.isEmpty()) shape = Shapes.block(); + build_shapes.put(state.setValue(WATERLOGGED, false), shape); + build_shapes.put(state.setValue(WATERLOGGED, true), shape); + } + { + // how the hack to extend a shape, these are the above with y+4px. + VoxelShape shape = ((base_aabb.getXsize() == 0) || (base_aabb.getYsize() == 0) || (base_aabb.getZsize() == 0)) ? Shapes.empty() : Shapes.create(base_aabb); + if (state.getValue(NORTH)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getMappedAABB(Auxiliaries.getRotatedAABB(side_aabb, + Direction.NORTH, true), bb -> bb.expandTowards(0, railing_height_extension, 0))), BooleanOp.OR); + if (state.getValue(EAST)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getMappedAABB(Auxiliaries.getRotatedAABB(side_aabb, + Direction.EAST, true), bb -> bb.expandTowards(0, railing_height_extension, 0))), BooleanOp.OR); + if (state.getValue(SOUTH)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getMappedAABB(Auxiliaries.getRotatedAABB(side_aabb, + Direction.SOUTH, true), bb -> bb.expandTowards(0, railing_height_extension, 0))), BooleanOp.OR); + if (state.getValue(WEST)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getMappedAABB(Auxiliaries.getRotatedAABB(side_aabb, + Direction.WEST, true), bb -> bb.expandTowards(0, railing_height_extension, 0))), BooleanOp.OR); + if (shape.isEmpty()) shape = Shapes.block(); + build_collision_shapes.put(state.setValue(WATERLOGGED, false), shape); + build_collision_shapes.put(state.setValue(WATERLOGGED, true), shape); + } + } + shapes = build_shapes; + collision_shapes = build_collision_shapes; + registerDefaultState(super.defaultBlockState().setValue(NORTH, false).setValue(EAST, false).setValue(SOUTH, false).setValue(WEST, false).setValue(WATERLOGGED, false)); + } + + public HorizontalFourWayWaterLoggable(long config, BlockBehaviour.Properties properties, AABB base_aabb, final AABB side_aabb, int railing_height_extension) { + this(config, properties, base_aabb, new AABB[]{side_aabb}, railing_height_extension); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(NORTH, EAST, SOUTH, WEST); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + return super.getStateForPlacement(context).setValue(NORTH, false).setValue(EAST, false).setValue(SOUTH, false).setValue(WEST, false); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context) { + return shapes.getOrDefault(state, Shapes.block()); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context) { + return collision_shapes.getOrDefault(state, Shapes.block()); + } + + public static BooleanProperty getDirectionProperty(Direction face) { + return switch (face) { + case EAST -> HorizontalFourWayWaterLoggable.EAST; + case SOUTH -> HorizontalFourWayWaterLoggable.SOUTH; + case WEST -> HorizontalFourWayWaterLoggable.WEST; + default -> HorizontalFourWayWaterLoggable.NORTH; + }; + } + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardDoorBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardDoorBlock.java new file mode 100644 index 0000000..8c7f10b --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardDoorBlock.java @@ -0,0 +1,175 @@ +/* + * @file StandardDoorBlock.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Door blocks, almost entirely based on vanilla. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.DoorBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockSetType; +import net.minecraft.world.level.block.state.properties.DoorHingeSide; +import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.shapes.BooleanOp; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.List; + + +public class StandardDoorBlock extends DoorBlock implements StandardBlocks.IStandardBlock { + private final long config_; + protected final VoxelShape[][][][] shapes_; + protected final SoundEvent open_sound_; + protected final SoundEvent close_sound_; + + public StandardDoorBlock(long config, BlockBehaviour.Properties properties, AABB[] open_aabbs_top, AABB[] open_aabbs_bottom, AABB[] closed_aabbs_top, AABB[] closed_aabbs_bottom, SoundEvent open_sound, SoundEvent close_sound, BlockSetType blockSetType) { + super(properties, blockSetType); + VoxelShape[][][][] shapes = new VoxelShape[Direction.values().length][2][2][2]; + for (Direction facing : Direction.values()) { + for (boolean open : new boolean[]{false, true}) { + for (DoubleBlockHalf half : new DoubleBlockHalf[]{DoubleBlockHalf.UPPER, DoubleBlockHalf.LOWER}) { + for (boolean hinge_right : new boolean[]{false, true}) { + VoxelShape shape = Shapes.empty(); + if (facing.getAxis() == Direction.Axis.Y) { + shape = Shapes.block(); + } else { + final AABB[] aabbs = (open) ? ((half == DoubleBlockHalf.UPPER) ? open_aabbs_top : open_aabbs_bottom) : ((half == DoubleBlockHalf.UPPER) ? closed_aabbs_top : closed_aabbs_bottom); + for (AABB e : aabbs) { + AABB aabb = Auxiliaries.getRotatedAABB(e, facing, true); + if (!hinge_right) + aabb = Auxiliaries.getMirroredAABB(aabb, facing.getClockWise().getAxis()); + shape = Shapes.join(shape, Shapes.create(aabb), BooleanOp.OR); + } + } + shapes[facing.ordinal()][open ? 1 : 0][hinge_right ? 1 : 0][half == DoubleBlockHalf.UPPER ? 0 : 1] = shape; + } + } + } + } + config_ = config; + shapes_ = shapes; + open_sound_ = open_sound; + close_sound_ = close_sound; + } + + public StandardDoorBlock(long config, BlockBehaviour.Properties properties, AABB open_aabb, AABB closed_aabb, SoundEvent open_sound, SoundEvent close_sound, BlockSetType blockSetType) { + this(config, properties, new AABB[]{open_aabb}, new AABB[]{open_aabb}, new AABB[]{closed_aabb}, new AABB[]{closed_aabb}, open_sound, close_sound, blockSetType); + } + + public StandardDoorBlock(long config, BlockBehaviour.Properties properties, SoundEvent open_sound, SoundEvent close_sound, BlockSetType blockSetType) { + this( + config, properties, + Auxiliaries.getPixeledAABB(13, 0, 0, 16, 16, 16), + Auxiliaries.getPixeledAABB(0, 0, 13, 16, 16, 16), + open_sound, + close_sound, + blockSetType + ); + } + + public StandardDoorBlock(long config, BlockBehaviour.Properties properties) { + this( + config, properties, + Auxiliaries.getPixeledAABB(13, 0, 0, 16, 16, 16), + Auxiliaries.getPixeledAABB(0, 0, 13, 16, 16, 16), + SoundEvents.WOODEN_DOOR_OPEN, + SoundEvents.WOODEN_DOOR_CLOSE, + BlockSetType.OAK + ); + } + + @Override + public long config() { + return config_; + } + + protected void sound(BlockGetter world, BlockPos pos, boolean open) { + if (world instanceof Level) + ((Level) world).playSound(null, pos, open ? open_sound_ : close_sound_, SoundSource.BLOCKS, 0.7f, 1f); + } + + protected void actuate_adjacent_wing(BlockState state, BlockGetter world_ro, BlockPos pos, boolean open) { + if (!(world_ro instanceof final Level world)) return; + final BlockPos adjecent_pos = pos.relative((state.getValue(HINGE) == DoorHingeSide.LEFT) ? (state.getValue(FACING).getClockWise()) : (state.getValue(FACING).getCounterClockWise())); + if (!world.isLoaded(adjecent_pos)) return; + BlockState adjacent_state = world.getBlockState(adjecent_pos); + if (adjacent_state.getBlock() != this) return; + if (adjacent_state.getValue(OPEN) == open) return; + world.setBlock(adjecent_pos, adjacent_state.setValue(OPEN, open), 2 | 10); + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); + } + + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) { + return shapes_[state.getValue(FACING).ordinal()][state.getValue(OPEN) ? 1 : 0][state.getValue(HINGE) == DoorHingeSide.RIGHT ? 1 : 0][state.getValue(HALF) == DoubleBlockHalf.UPPER ? 0 : 1]; + } + + @Override + public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + setOpen(player, world, state, pos, !state.getValue(OPEN)); + return InteractionResult.sidedSuccess(world.isClientSide()); + } + + @Override + public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving) { + boolean powered = world.hasNeighborSignal(pos) || world.hasNeighborSignal(pos.relative(state.getValue(HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN)); + if ((block == this) || (powered == state.getValue(POWERED))) return; + world.setBlock(pos, state.setValue(POWERED, powered).setValue(OPEN, powered), 2); + actuate_adjacent_wing(state, world, pos, powered); + if (powered != state.getValue(OPEN)) sound(world, pos, powered); + } + + @Override + public void setOpen(@Nullable Entity entity, Level world, BlockState state, BlockPos pos, boolean open) { + if (!state.is(this) || (state.getValue(OPEN) == open)) return; + state = state.setValue(OPEN, open); + world.setBlock(pos, state, 2 | 8); + sound(world, pos, open); + actuate_adjacent_wing(state, world, pos, open); + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardEntityBlocks.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardEntityBlocks.java new file mode 100644 index 0000000..78bf385 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardEntityBlocks.java @@ -0,0 +1,72 @@ +/* + * @file StandardEntityBlocks.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Common functionality class for blocks with block entities. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.gameevent.GameEventListener; +import net.minecraftforge.common.util.FakePlayer; + +import javax.annotation.Nullable; + + +public class StandardEntityBlocks { + public interface IStandardEntityBlock extends EntityBlock { + + default boolean isBlockEntityTicking(Level world, BlockState state) { + return false; + } + + default InteractionResult useOpenGui(BlockState state, Level world, BlockPos pos, Player player) { + if (world.isClientSide()) return InteractionResult.SUCCESS; + final BlockEntity te = world.getBlockEntity(pos); + if (!(te instanceof MenuProvider) || ((player instanceof FakePlayer))) return InteractionResult.FAIL; + player.openMenu((MenuProvider) te); + return InteractionResult.CONSUME; + } + + @Override + @Nullable + default BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + BlockEntityType tet = Registries.getBlockEntityTypeOfBlock(state.getBlock()); + return (tet == null) ? null : tet.create(pos, state); + } + + @Override + @Nullable + default BlockEntityTicker getTicker(Level world, BlockState state, BlockEntityType te_type) { + return (world.isClientSide || (!isBlockEntityTicking(world, state))) ? (null) : ((Level w, BlockPos p, BlockState s, T te) -> ((StandardBlockEntity) te).tick()); + } + + @Override + @Nullable + default GameEventListener getListener(ServerLevel world, T te) { + return null; + } + } + + public static abstract class StandardBlockEntity extends BlockEntity { + public StandardBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); + } + + public void tick() { + } + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardFenceBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardFenceBlock.java new file mode 100644 index 0000000..0097d76 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardFenceBlock.java @@ -0,0 +1,203 @@ +/* + * @file StandardFenceBlock.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Wall blocks. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.google.common.collect.ImmutableMap; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.FenceGateBlock; +import net.minecraft.world.level.block.WallBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.level.block.state.properties.WallSide; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.level.material.PushReaction; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Map; + + +public class StandardFenceBlock extends WallBlock implements StandardBlocks.IStandardBlock { + public static final BooleanProperty UP = BlockStateProperties.UP; + public static final EnumProperty WALL_EAST = BlockStateProperties.EAST_WALL; + public static final EnumProperty WALL_NORTH = BlockStateProperties.NORTH_WALL; + public static final EnumProperty WALL_SOUTH = BlockStateProperties.SOUTH_WALL; + public static final EnumProperty WALL_WEST = BlockStateProperties.WEST_WALL; + public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; + private final Map shape_voxels; + private final Map collision_shape_voxels; + private final long config; + + public StandardFenceBlock(long config, BlockBehaviour.Properties properties) { + this(config, properties, 1.5, 16, 1.5, 0, 14, 16); + } + + public StandardFenceBlock(long config, BlockBehaviour.Properties properties, double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y) { + super(properties); + shape_voxels = buildShapes(pole_width, pole_height, side_width, side_min_y, side_max_low_y, side_max_tall_y); + collision_shape_voxels = buildShapes(pole_width, 24, pole_width, 0, 24, 24); + this.config = config; + } + + @Override + public long config() { + return config; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); + } + + private static VoxelShape combinedShape(VoxelShape pole, WallSide height, VoxelShape low, VoxelShape high) { + if (height == WallSide.TALL) return Shapes.or(pole, high); + if (height == WallSide.LOW) return Shapes.or(pole, low); + return pole; + } + + protected Map buildShapes(double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y) { + final double px0 = 8.0 - pole_width, px1 = 8.0 + pole_width, sx0 = 8.0 - side_width, sx1 = 8.0 + side_width; + VoxelShape vp = Block.box(px0, 0, px0, px1, pole_height, px1); + VoxelShape vs1 = Block.box(sx0, side_min_y, 0, sx1, side_max_low_y, sx1); + VoxelShape vs2 = Block.box(sx0, side_min_y, sx0, sx1, side_max_low_y, 16); + VoxelShape vs3 = Block.box(0, side_min_y, sx0, sx1, side_max_low_y, sx1); + VoxelShape vs4 = Block.box(sx0, side_min_y, sx0, 16, side_max_low_y, sx1); + VoxelShape vs5 = Block.box(sx0, side_min_y, 0, sx1, side_max_tall_y, sx1); + VoxelShape vs6 = Block.box(sx0, side_min_y, sx0, sx1, side_max_tall_y, 16); + VoxelShape vs7 = Block.box(0, side_min_y, sx0, sx1, side_max_tall_y, sx1); + VoxelShape vs8 = Block.box(sx0, side_min_y, sx0, 16, side_max_tall_y, sx1); + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Boolean up : UP.getPossibleValues()) { + for (WallSide wh_east : WALL_EAST.getPossibleValues()) { + for (WallSide wh_north : WALL_NORTH.getPossibleValues()) { + for (WallSide wh_west : WALL_WEST.getPossibleValues()) { + for (WallSide wh_south : WALL_SOUTH.getPossibleValues()) { + VoxelShape shape = Shapes.empty(); + shape = combinedShape(shape, wh_east, vs4, vs8); + shape = combinedShape(shape, wh_west, vs3, vs7); + shape = combinedShape(shape, wh_north, vs1, vs5); + shape = combinedShape(shape, wh_south, vs2, vs6); + if (up) shape = Shapes.or(shape, vp); + BlockState bs = defaultBlockState().setValue(UP, up) + .setValue(WALL_EAST, wh_east) + .setValue(WALL_NORTH, wh_north) + .setValue(WALL_WEST, wh_west) + .setValue(WALL_SOUTH, wh_south); + builder.put(bs.setValue(WATERLOGGED, false), shape); + builder.put(bs.setValue(WATERLOGGED, true), shape); + } + } + } + } + } + return builder.build(); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return shape_voxels.getOrDefault(state, Shapes.block()); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return collision_shape_voxels.getOrDefault(state, Shapes.block()); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + } + + protected boolean attachesTo(BlockState facingState, LevelReader world, BlockPos facingPos, Direction side) { + final Block block = facingState.getBlock(); + if ((block instanceof FenceGateBlock) || (block instanceof StandardFenceBlock) || (block instanceof VariantWallBlock)) + return true; + final BlockState oppositeState = world.getBlockState(facingPos.relative(side, 2)); + if (!(oppositeState.getBlock() instanceof StandardFenceBlock)) return false; + return facingState.isRedstoneConductor(world, facingPos) && canSupportCenter(world, facingPos, side); + } + + protected WallSide selectWallHeight(LevelReader world, BlockPos pos, Direction direction) { + return WallSide.LOW; // @todo: implement + } + + public BlockState getStateForPlacement(BlockPlaceContext context) { + LevelReader world = context.getLevel(); + BlockPos pos = context.getClickedPos(); + FluidState fs = context.getLevel().getFluidState(context.getClickedPos()); + boolean n = attachesTo(world.getBlockState(pos.north()), world, pos.north(), Direction.SOUTH); + boolean e = attachesTo(world.getBlockState(pos.east()), world, pos.east(), Direction.WEST); + boolean s = attachesTo(world.getBlockState(pos.south()), world, pos.south(), Direction.NORTH); + boolean w = attachesTo(world.getBlockState(pos.west()), world, pos.west(), Direction.EAST); + boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w); + return defaultBlockState() + .setValue(UP, not_straight) + .setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE) + .setValue(WALL_EAST, e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE) + .setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE) + .setValue(WALL_WEST, w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE) + .setValue(WATERLOGGED, fs.getType() == Fluids.WATER); + } + + @Override + public BlockState updateShape(BlockState state, Direction side, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos) { + if (state.getValue(BlockStateProperties.WATERLOGGED)) + world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); + if (side == Direction.DOWN) return super.updateShape(state, side, facingState, world, pos, facingPos); + boolean n = (side == Direction.NORTH) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_NORTH) != WallSide.NONE); + boolean e = (side == Direction.EAST) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_EAST) != WallSide.NONE); + boolean s = (side == Direction.SOUTH) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_SOUTH) != WallSide.NONE); + boolean w = (side == Direction.WEST) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_WEST) != WallSide.NONE); + boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w); + return state.setValue(UP, not_straight) + .setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE) + .setValue(WALL_EAST, e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE) + .setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE) + .setValue(WALL_WEST, w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE); + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + @SuppressWarnings("deprecation") + public PushReaction getPistonPushReaction(BlockState state) { + return PushReaction.NORMAL; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardStairsBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardStairsBlock.java new file mode 100644 index 0000000..64a9f05 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardStairsBlock.java @@ -0,0 +1,59 @@ +/* + * @file StandardStairsBlock.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Stairs and roof blocks, almost entirely based on vanilla stairs. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.StairBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.PushReaction; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.List; + + +public class StandardStairsBlock extends StairBlock implements StandardBlocks.IStandardBlock { + private final long config; + + public StandardStairsBlock(long config, java.util.function.Supplier state, BlockBehaviour.Properties properties) { + super(state, properties); + this.config = config; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); + } + + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + @SuppressWarnings("deprecation") + public PushReaction getPistonPushReaction(BlockState state) { + return PushReaction.NORMAL; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/TooltipDisplay.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/TooltipDisplay.java new file mode 100644 index 0000000..76eb760 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/TooltipDisplay.java @@ -0,0 +1,130 @@ +/* + * @file Tooltip.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Delayed tooltip for a selected area. Constructed with a + * GUI, invoked in `render()`. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.network.chat.*; +import net.minecraft.util.Mth; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; + + +@OnlyIn(Dist.CLIENT) +public class TooltipDisplay { + private static long default_delay = 800; + private static int default_max_deviation = 1; + + public static void config(long delay, int max_deviation) { + default_delay = clamp(delay, 500, 5000); + default_max_deviation = Mth.clamp(max_deviation, 1, 5); + } + private static long clamp(long p1, long a, long b) { + return p1 < a ? a : Math.min(p1, b); + } + + // --------------------------------------------------------------------------------------------------- + + public static class TipRange { + public final int x0, y0, x1, y1; + public final Supplier text; + + public TipRange(int x, int y, int w, int h, Component text) { + this(x, y, w, h, () -> text); + } + + public TipRange(int x, int y, int w, int h, Supplier text) { + this.text = text; + this.x0 = x; + this.y0 = y; + this.x1 = x0 + w - 1; + this.y1 = y0 + h - 1; + } + + } + + // --------------------------------------------------------------------------------------------------- + + private List ranges = new ArrayList<>(); + private long delay = default_delay; + private int max_deviation = default_max_deviation; + private int x_last, y_last; + private long t; + private static boolean had_render_exception = false; + + public TooltipDisplay() { + t = System.currentTimeMillis(); + } + + public TooltipDisplay init(List ranges, long delay_ms, int max_deviation_xy) { + this.ranges = ranges; + this.delay = delay_ms; + this.max_deviation = max_deviation_xy; + t = System.currentTimeMillis(); + x_last = y_last = 0; + return this; + } + + public TooltipDisplay init(List ranges) { + return init(ranges, default_delay, default_max_deviation); + } + + public TooltipDisplay init(TipRange... ranges) { + return init(Arrays.asList(ranges), default_delay, default_max_deviation); + } + + public TooltipDisplay delay(int ms) { + delay = (ms <= 0) ? default_delay : ms; + return this; + } + + public void resetTimer() { + t = System.currentTimeMillis(); + } + + public boolean render(GuiGraphics mx, final AbstractContainerScreen gui, int x, int y) { + if (had_render_exception) return false; + if ((Math.abs(x - x_last) > max_deviation) || (Math.abs(y - y_last) > max_deviation)) { + x_last = x; + y_last = y; + resetTimer(); + return false; + } else if (Math.abs(System.currentTimeMillis() - t) < delay) { + return false; + } else if (ranges.stream().noneMatch( + (tip) -> { + if ((x < tip.x0) || (x > tip.x1) || (y < tip.y0) || (y > tip.y1)) return false; + String text = tip.text.get().toString(); + if (text.isEmpty()) return false; + try { + mx.renderComponentTooltip(Minecraft.getInstance().font, tip.text.get().toFlatList(Style.EMPTY), x, y); + } catch (Exception ex) { + had_render_exception = true; + Auxiliaries.logError("Tooltip rendering disabled due to exception: '" + ex.getMessage() + "'"); + return false; + } + return true; + }) + ) { + resetTimer(); + return false; + } else { + return true; + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantSlabBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantSlabBlock.java new file mode 100644 index 0000000..4f2eeb7 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantSlabBlock.java @@ -0,0 +1,220 @@ +/* + * @file VariantSlabBlock.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Standard half block horizontal slab characteristics class. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.sounds.SoundSource; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.level.block.state.properties.IntegerProperty; +import net.minecraft.world.level.block.state.properties.SlabType; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +public class VariantSlabBlock extends StandardBlocks.WaterLoggable implements StandardBlocks.IStandardBlock { + public static final EnumProperty TYPE = BlockStateProperties.SLAB_TYPE; + public static final IntegerProperty TEXTURE_VARIANT = IntegerProperty.create("tvariant", 0, 3); + + protected static final VoxelShape[] AABBs = { + Shapes.create(new AABB(0, 8. / 16, 0, 1, 16. / 16, 1)), // top slab + Shapes.create(new AABB(0, 0. / 16, 0, 1, 8. / 16, 1)), // bottom slab + Shapes.create(new AABB(0, 0. / 16, 0, 1, 16. / 16, 1)), // both slabs + Shapes.create(new AABB(0, 0. / 16, 0, 1, 16. / 16, 1)) // << 2bit fill + }; + protected static final int[] num_slabs_contained_in_parts_ = {1, 1, 2, 2}; + private static boolean with_pickup = false; + + public static void on_config(boolean direct_slab_pickup) { + with_pickup = direct_slab_pickup; + } + + protected boolean is_cube(BlockState state) { + return state.getValue(TYPE) == SlabType.DOUBLE; + } + + public VariantSlabBlock(long config, BlockBehaviour.Properties builder) { + super(config, builder); + registerDefaultState(defaultBlockState().setValue(TYPE, SlabType.BOTTOM)); + } + + @Override + public RenderTypeHint getRenderTypeHint() { + return (((config & StandardBlocks.CFG_TRANSLUCENT) != 0) ? (RenderTypeHint.TRANSLUCENT) : (RenderTypeHint.CUTOUT)); + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + if (!Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true)) return; + if (with_pickup && Auxiliaries.Tooltip.helpCondition()) + Auxiliaries.Tooltip.addInformation("engineersdecor.tooltip.slabpickup", "engineersdecor.tooltip.slabpickup", tooltip, flag, true); + } + + @Override + @OnlyIn(Dist.CLIENT) + @SuppressWarnings("deprecation") + public boolean skipRendering(BlockState state, BlockState adjacentBlockState, Direction side) { + return (adjacentBlockState == state) || (super.skipRendering(state, adjacentBlockState, side)); + } + + @Override + public boolean isPossibleToRespawnInThis(BlockState state) { + return false; + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { + return AABBs[state.getValue(TYPE).ordinal() & 0x3]; + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return getShape(state, world, pos, selectionContext); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(TYPE, TEXTURE_VARIANT); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + BlockPos pos = context.getClickedPos(); + if (context.getLevel().getBlockState(pos).getBlock() == this) + return context.getLevel().getBlockState(pos).setValue(TYPE, SlabType.DOUBLE).setValue(WATERLOGGED, false); + final int rnd = Mth.clamp((int) (Mth.getSeed(context.getClickedPos()) & 0x3), 0, 3); + final Direction face = context.getClickedFace(); + final BlockState placement_state = super.getStateForPlacement(context).setValue(TEXTURE_VARIANT, rnd); // fluid state + if (face == Direction.UP) return placement_state.setValue(TYPE, SlabType.BOTTOM); + if (face == Direction.DOWN) return placement_state.setValue(TYPE, SlabType.TOP); + if (!face.getAxis().isHorizontal()) return placement_state; + final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5); + return placement_state.setValue(TYPE, isupper ? SlabType.TOP : SlabType.BOTTOM); + } + + @Override + @SuppressWarnings("deprecation") + public boolean canBeReplaced(BlockState state, BlockPlaceContext context) { + if (context.getItemInHand().getItem() != this.asItem()) return false; + if (!context.replacingClickedOnBlock()) return true; + final Direction face = context.getClickedFace(); + final SlabType type = state.getValue(TYPE); + if ((face == Direction.UP) && (type == SlabType.BOTTOM)) return true; + if ((face == Direction.DOWN) && (type == SlabType.TOP)) return true; + if (!face.getAxis().isHorizontal()) return false; + final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5); + return isupper ? (type == SlabType.BOTTOM) : (type == SlabType.TOP); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState rotate(BlockState state, Rotation rot) { + return state; + } + + @Override + @SuppressWarnings("deprecation") + public BlockState mirror(BlockState state, Mirror mirrorIn) { + return state; + } + + @Override + public boolean hasDynamicDropList() { + return true; + } + + @Override + public List dropList(BlockState state, Level world, BlockEntity te, boolean explosion) { + return new ArrayList<>(Collections.singletonList(new ItemStack(this.asItem(), num_slabs_contained_in_parts_[state.getValue(TYPE).ordinal() & 0x3]))); + } + + @Override + @SuppressWarnings("deprecation") + public void attack(BlockState state, Level world, BlockPos pos, Player player) { + if ((world.isClientSide) || (!with_pickup)) return; + final ItemStack stack = player.getMainHandItem(); + if (stack.isEmpty() || (Block.byItem(stack.getItem()) != this)) return; + if (stack.getCount() >= stack.getMaxStackSize()) return; + Vec3 lv = player.getLookAngle(); + Direction facing = Direction.getNearest((float) lv.x, (float) lv.y, (float) lv.z); + if ((facing != Direction.UP) && (facing != Direction.DOWN)) return; + if (state.getBlock() != this) return; + SlabType type = state.getValue(TYPE); + if (facing == Direction.DOWN) { + if (type == SlabType.DOUBLE) { + world.setBlock(pos, state.setValue(TYPE, SlabType.BOTTOM), 3); + } else { + world.removeBlock(pos, false); + } + } else if (facing == Direction.UP) { + if (type == SlabType.DOUBLE) { + world.setBlock(pos, state.setValue(TYPE, SlabType.TOP), 3); + } else { + world.removeBlock(pos, false); + } + } + if (!player.isCreative()) { + stack.grow(1); + if (player.getInventory() != null) player.getInventory().setChanged(); + } + SoundType st = this.getSoundType(state, world, pos, null); + world.playSound(player, pos, st.getPlaceSound(), SoundSource.BLOCKS, (st.getVolume() + 1f) / 2.5f, 0.9f * st.getPitch()); + } + + @Override + public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { + return (state.getValue(TYPE) != SlabType.DOUBLE) && super.placeLiquid(world, pos, state, fluidState); + } + + @Override + public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { + return (state.getValue(TYPE) != SlabType.DOUBLE) && super.canPlaceLiquid(world, pos, state, fluid); + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantWallBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantWallBlock.java new file mode 100644 index 0000000..92af666 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantWallBlock.java @@ -0,0 +1,200 @@ +/* + * @file VariantWallBlock.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Wall blocks. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.FenceGateBlock; +import net.minecraft.world.level.block.WallBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.*; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.level.material.PushReaction; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Map; + + +public class VariantWallBlock extends WallBlock implements StandardBlocks.IStandardBlock { + public static final BooleanProperty UP = BlockStateProperties.UP; + public static final EnumProperty WALL_EAST = BlockStateProperties.EAST_WALL; + public static final EnumProperty WALL_NORTH = BlockStateProperties.NORTH_WALL; + public static final EnumProperty WALL_SOUTH = BlockStateProperties.SOUTH_WALL; + public static final EnumProperty WALL_WEST = BlockStateProperties.WEST_WALL; + public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; + public static final IntegerProperty TEXTURE_VARIANT = IntegerProperty.create("tvariant", 0, 7); + private final Map shape_voxels; + private final Map collision_shape_voxels; + private final long config; + + public VariantWallBlock(long config, BlockBehaviour.Properties builder) { + super(builder); + shape_voxels = buildWallShapes(4, 16, 4, 0, 16, 16); + collision_shape_voxels = buildWallShapes(6, 16, 5, 0, 24, 24); + this.config = config; + } + + @Override + public long config() { + return config; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); + } + + private static VoxelShape combinedShape(VoxelShape pole, WallSide height, VoxelShape low, VoxelShape high) { + if (height == WallSide.TALL) return Shapes.or(pole, high); + if (height == WallSide.LOW) return Shapes.or(pole, low); + return pole; + } + + protected Map buildWallShapes(double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y) { + final double px0 = 8.0 - pole_width, px1 = 8.0 + pole_width, sx0 = 8.0 - side_width, sx1 = 8.0 + side_width; + VoxelShape vp = Block.box(px0, 0, px0, px1, pole_height, px1); + VoxelShape vs1 = Block.box(sx0, side_min_y, 0, sx1, side_max_low_y, sx1); + VoxelShape vs2 = Block.box(sx0, side_min_y, sx0, sx1, side_max_low_y, 16); + VoxelShape vs3 = Block.box(0, side_min_y, sx0, sx1, side_max_low_y, sx1); + VoxelShape vs4 = Block.box(sx0, side_min_y, sx0, 16, side_max_low_y, sx1); + VoxelShape vs5 = Block.box(sx0, side_min_y, 0, sx1, side_max_tall_y, sx1); + VoxelShape vs6 = Block.box(sx0, side_min_y, sx0, sx1, side_max_tall_y, 16); + VoxelShape vs7 = Block.box(0, side_min_y, sx0, sx1, side_max_tall_y, sx1); + VoxelShape vs8 = Block.box(sx0, side_min_y, sx0, 16, side_max_tall_y, sx1); + Builder builder = ImmutableMap.builder(); + for (Boolean up : UP.getPossibleValues()) { + for (WallSide wh_east : WALL_EAST.getPossibleValues()) { + for (WallSide wh_north : WALL_NORTH.getPossibleValues()) { + for (WallSide wh_west : WALL_WEST.getPossibleValues()) { + for (WallSide wh_south : WALL_SOUTH.getPossibleValues()) { + VoxelShape shape = Shapes.empty(); + shape = combinedShape(shape, wh_east, vs4, vs8); + shape = combinedShape(shape, wh_west, vs3, vs7); + shape = combinedShape(shape, wh_north, vs1, vs5); + shape = combinedShape(shape, wh_south, vs2, vs6); + if (up) shape = Shapes.or(shape, vp); + BlockState bs = defaultBlockState().setValue(UP, up) + .setValue(WALL_EAST, wh_east) + .setValue(WALL_NORTH, wh_north) + .setValue(WALL_WEST, wh_west) + .setValue(WALL_SOUTH, wh_south); + final VoxelShape tvs = shape; + TEXTURE_VARIANT.getPossibleValues().forEach((tv) -> { + builder.put(bs.setValue(TEXTURE_VARIANT, tv).setValue(WATERLOGGED, false), tvs); + builder.put(bs.setValue(TEXTURE_VARIANT, tv).setValue(WATERLOGGED, true), tvs); + }); + } + } + } + } + } + return builder.build(); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return shape_voxels.getOrDefault(state, Shapes.block()); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return collision_shape_voxels.getOrDefault(state, Shapes.block()); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(TEXTURE_VARIANT); + } + + protected boolean attachesTo(BlockState facingState, LevelReader world, BlockPos facingPos, Direction side) { + final Block block = facingState.getBlock(); + if ((block instanceof FenceGateBlock) || (block instanceof WallBlock)) return true; + final BlockState oppositeState = world.getBlockState(facingPos.relative(side, 2)); + if (!(oppositeState.getBlock() instanceof VariantWallBlock)) return false; + return facingState.isRedstoneConductor(world, facingPos) && Block.canSupportCenter(world, facingPos, side); + } + + protected WallSide selectWallHeight(LevelReader world, BlockPos pos, Direction direction) { + return WallSide.LOW; + } + + public BlockState getStateForPlacement(BlockPlaceContext context) { + LevelReader world = context.getLevel(); + BlockPos pos = context.getClickedPos(); + FluidState fs = context.getLevel().getFluidState(context.getClickedPos()); + boolean n = attachesTo(world.getBlockState(pos.north()), world, pos.north(), Direction.SOUTH); + boolean e = attachesTo(world.getBlockState(pos.east()), world, pos.east(), Direction.WEST); + boolean s = attachesTo(world.getBlockState(pos.south()), world, pos.south(), Direction.NORTH); + boolean w = attachesTo(world.getBlockState(pos.west()), world, pos.west(), Direction.EAST); + boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w); + return defaultBlockState().setValue(UP, not_straight) + .setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE) + .setValue(WALL_EAST, e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE) + .setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE) + .setValue(WALL_WEST, w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE) + .setValue(WATERLOGGED, fs.getType() == Fluids.WATER); + } + + @Override + public BlockState updateShape(BlockState state, Direction side, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos) { + if (state.getValue(WATERLOGGED)) world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); + if (side == Direction.DOWN) return super.updateShape(state, side, facingState, world, pos, facingPos); + boolean n = (side == Direction.NORTH) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_NORTH) != WallSide.NONE; + boolean e = (side == Direction.EAST) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_EAST) != WallSide.NONE; + boolean s = (side == Direction.SOUTH) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_SOUTH) != WallSide.NONE; + boolean w = (side == Direction.WEST) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_WEST) != WallSide.NONE; + boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w); + return state.setValue(UP, not_straight) + .setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE) + .setValue(WALL_EAST, e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE) + .setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE) + .setValue(WALL_WEST, w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE) + .setValue(TEXTURE_VARIANT, ((int) Mth.getSeed(pos)) & 0x7); + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + public boolean isPossibleToRespawnInThis(BlockState state) { + return false; + } + + @Override + @SuppressWarnings("deprecation") + public PushReaction getPistonPushReaction(BlockState state) { + return PushReaction.NORMAL; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java b/src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java new file mode 100644 index 0000000..4ae35c5 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java @@ -0,0 +1,20 @@ +package dev.zontreck.libzontreck.events; + +import dev.zontreck.libzontreck.memory.world.BlockRestoreQueue; +import dev.zontreck.libzontreck.memory.world.BlockRestoreQueueRegistry; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.eventbus.api.Event; + +public class BlockRestoreQueueRegistrationEvent extends Event +{ + /** + * Registers the provided queue to be able to be ticked + * @param queue + */ + public void register(BlockRestoreQueue queue) + { + BlockRestoreQueueRegistry.addQueue(queue); + + MinecraftForge.EVENT_BUS.register(queue); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java b/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java index fce1997..d24ffa8 100644 --- a/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java +++ b/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java @@ -52,7 +52,7 @@ public class ForgeEventHandlers { MinecraftForge.EVENT_BUS.post(new ProfileLoadedEvent(prof, player, level)); - DelayedExecutorService.getInstance().schedule(new Task("send-msg", true) { + Thread tx = new Thread(new Task("send-msg", true) { @Override public void run() { // Check player wallet, then send wallet to client @@ -61,7 +61,8 @@ public class ForgeEventHandlers { S2CServerAvailable avail = new S2CServerAvailable(); avail.send(player); } - }, 10); + }); + tx.start(); } @SubscribeEvent diff --git a/src/main/java/dev/zontreck/libzontreck/events/RegisterMigrationsEvent.java b/src/main/java/dev/zontreck/libzontreck/events/RegisterMigrationsEvent.java new file mode 100644 index 0000000..f305c8f --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/events/RegisterMigrationsEvent.java @@ -0,0 +1,22 @@ +package dev.zontreck.libzontreck.events; + +import dev.zontreck.libzontreck.memory.world.DatabaseMigrations; +import net.minecraftforge.eventbus.api.Event; + +import java.util.ArrayList; +import java.util.List; + +public class RegisterMigrationsEvent extends Event +{ + private List migrations = new ArrayList<>(); + + public void register(DatabaseMigrations.Migration migration) + { + migrations.add(migration); + } + + public List getMigrations() + { + return new ArrayList<>(migrations); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/events/RegisterPacketsEvent.java b/src/main/java/dev/zontreck/libzontreck/events/RegisterPacketsEvent.java deleted file mode 100644 index 34a126b..0000000 --- a/src/main/java/dev/zontreck/libzontreck/events/RegisterPacketsEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package dev.zontreck.libzontreck.events; - -import java.util.ArrayList; -import java.util.List; - -import dev.zontreck.libzontreck.networking.packets.IPacket; -import net.minecraftforge.eventbus.api.Event; - -/** - * Used to register your packets with LibZontreck. Packets must extend IPacket and implement PacketSerializable. This is dispatched on both logical sides, and is considered a final event. It is not cancelable - * @see IPacket - */ -public class RegisterPacketsEvent extends Event -{ - public final List packets = new ArrayList<>(); -} diff --git a/src/main/java/dev/zontreck/libzontreck/events/TeleportEvent.java b/src/main/java/dev/zontreck/libzontreck/events/TeleportEvent.java new file mode 100644 index 0000000..2e7d77d --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/events/TeleportEvent.java @@ -0,0 +1,32 @@ +package dev.zontreck.libzontreck.events; + +import dev.zontreck.libzontreck.vectors.WorldPosition; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.eventbus.api.Cancelable; +import net.minecraftforge.eventbus.api.Event; + +/** + * This event should be cancelled if a Teleport Implementation is provided and handles the teleport + *
+ * The event not being cancelled should indicate that the sender should handle teleport themselves. + */ +@Cancelable +public class TeleportEvent extends Event +{ + WorldPosition position; + ServerPlayer player; + + public TeleportEvent(WorldPosition position, ServerPlayer player) + { + this.position=position; + this.player=player; + } + + public ServerPlayer getPlayer() { + return player; + } + + public WorldPosition getPosition() { + return position; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/items/InputItemStackHandler.java b/src/main/java/dev/zontreck/libzontreck/items/InputItemStackHandler.java new file mode 100644 index 0000000..8a38e42 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/items/InputItemStackHandler.java @@ -0,0 +1,47 @@ +package dev.zontreck.libzontreck.items; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.items.ItemStackHandler; + +public class InputItemStackHandler extends ItemStackHandler { + private final ItemStackHandler internalSlot; + + public InputItemStackHandler(ItemStackHandler hidden) { + super(); + internalSlot = hidden; + } + + @Override + public void setSize(int size) { + stacks = NonNullList.withSize(size, ItemStack.EMPTY); + } + + @Override + public void setStackInSlot(int slot, ItemStack stack) { + internalSlot.setStackInSlot(slot, stack); + } + + @Override + public int getSlots() { + return internalSlot.getSlots(); + } + + @Override + public ItemStack getStackInSlot(int slot) { + return internalSlot.getStackInSlot(slot); + } + + @Override + public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) { + setStackInSlot(slot, stack); + + return ItemStack.EMPTY; + } + + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + return ItemStack.EMPTY; + } +} + diff --git a/src/main/java/dev/zontreck/libzontreck/items/OutputItemStackHandler.java b/src/main/java/dev/zontreck/libzontreck/items/OutputItemStackHandler.java new file mode 100644 index 0000000..38edacf --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/items/OutputItemStackHandler.java @@ -0,0 +1,45 @@ +package dev.zontreck.libzontreck.items; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.items.ItemStackHandler; + +public class OutputItemStackHandler extends ItemStackHandler { + private final ItemStackHandler internalSlot; + + public OutputItemStackHandler(ItemStackHandler hidden) { + super(); + internalSlot = hidden; + } + + @Override + public void setSize(int size) { + stacks = NonNullList.withSize(size, ItemStack.EMPTY); + } + + @Override + public void setStackInSlot(int slot, ItemStack stack) { + internalSlot.setStackInSlot(slot, stack); + } + + @Override + public int getSlots() { + return internalSlot.getSlots(); + } + + @Override + public ItemStack getStackInSlot(int slot) { + return internalSlot.getStackInSlot(slot); + } + + @Override + public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) { + return stack; + } + + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + return internalSlot.extractItem(slot, amount, simulate); + } +} + diff --git a/src/main/java/dev/zontreck/libzontreck/memory/PlayerComponent.java b/src/main/java/dev/zontreck/libzontreck/memory/player/PlayerComponent.java similarity index 86% rename from src/main/java/dev/zontreck/libzontreck/memory/PlayerComponent.java rename to src/main/java/dev/zontreck/libzontreck/memory/player/PlayerComponent.java index b169ec1..3c86f7e 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/PlayerComponent.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/player/PlayerComponent.java @@ -1,12 +1,12 @@ -package dev.zontreck.libzontreck.memory; +package dev.zontreck.libzontreck.memory.player; import java.util.UUID; -import dev.zontreck.libzontreck.LibZontreck; import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; import dev.zontreck.libzontreck.vectors.WorldPosition; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.server.ServerLifecycleHooks; public class PlayerComponent { @@ -34,7 +34,7 @@ public class PlayerComponent public static PlayerComponent fromID(UUID ID) { - return new PlayerComponent(LibZontreck.THE_SERVER.getPlayerList().getPlayer(ID)); + return new PlayerComponent(ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayer(ID)); } public CompoundTag serialize() diff --git a/src/main/java/dev/zontreck/libzontreck/memory/PlayerContainer.java b/src/main/java/dev/zontreck/libzontreck/memory/player/PlayerContainer.java similarity index 70% rename from src/main/java/dev/zontreck/libzontreck/memory/PlayerContainer.java rename to src/main/java/dev/zontreck/libzontreck/memory/player/PlayerContainer.java index b898ba5..fcbaa38 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/PlayerContainer.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/player/PlayerContainer.java @@ -1,10 +1,10 @@ -package dev.zontreck.libzontreck.memory; +package dev.zontreck.libzontreck.memory.player; import java.util.UUID; -import dev.zontreck.libzontreck.LibZontreck; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.server.ServerLifecycleHooks; public class PlayerContainer { public UUID ID; @@ -13,7 +13,7 @@ public class PlayerContainer { public PlayerContainer(UUID ID) { - this(LibZontreck.THE_SERVER.getPlayerList().getPlayer(ID)); + this(ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayer(ID)); } public PlayerContainer(ServerPlayer player) { diff --git a/src/main/java/dev/zontreck/libzontreck/memory/VolatilePlayerStorage.java b/src/main/java/dev/zontreck/libzontreck/memory/player/VolatilePlayerStorage.java similarity index 97% rename from src/main/java/dev/zontreck/libzontreck/memory/VolatilePlayerStorage.java rename to src/main/java/dev/zontreck/libzontreck/memory/player/VolatilePlayerStorage.java index d1abae8..630f35e 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/VolatilePlayerStorage.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/player/VolatilePlayerStorage.java @@ -1,4 +1,4 @@ -package dev.zontreck.libzontreck.memory; +package dev.zontreck.libzontreck.memory.player; import java.util.ArrayList; import java.util.Iterator; diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestore.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestore.java new file mode 100644 index 0000000..4e85f16 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestore.java @@ -0,0 +1,19 @@ +package dev.zontreck.libzontreck.memory.world; + +public class BlockRestore extends BlockRestoreQueue +{ + @Override + public String getRestoreQueueName() { + return "BasicBlockSnapshots"; + } + + @Override + public void notifyDirtyQueue(boolean blockAdded) { + return; // We dont care. This is a basic queue + } + + @Override + public boolean sorted() { + return false; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java new file mode 100644 index 0000000..287c95c --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java @@ -0,0 +1,333 @@ +package dev.zontreck.libzontreck.memory.world; + +import dev.zontreck.libzontreck.LibZontreck; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.server.level.ServerLevel; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +import java.io.*; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + + +public abstract class BlockRestoreQueue +{ + private List BLOCK_QUEUE = new ArrayList<>(); + private final BlockRestoreRunner RUNNER; + private ScheduledFuture RUNNING_TASK; + private ScheduledFuture DATABASE_UPLOAD_RUNNER; + + /** + * When in database mode, this flag will be checked by the Restore Runner so a database call is not made unnecessarily. + */ + private boolean hasBlocks = true; + + public BlockRestoreQueue() + { + RUNNER = new BlockRestoreRunner(this); + + MinecraftForge.EVENT_BUS.register(this); + } + + /** + * When true, uses the database to store blocks. The blocks stored in the database will not be loaded into memory at runtime + * @return + */ + public boolean usesDatabase() + { + return false; + } + + /** + * Returns the restore queue name + * @return Name of the restore queue + */ + public abstract String getRestoreQueueName(); + + /** + * @return The number of blocks remaining in this queue + */ + public int getQueuedBlocks() + { + return BLOCK_QUEUE.size(); + } + + /** + * Queues a block to be restored + * @param block + */ + public void enqueueBlock(SavedBlock block) + { + enqueueBlock(block.getBlockPrimitive()); + } + + /** + * Queues a block to be restored + * @param block + */ + public void enqueueBlock(PrimitiveBlock block) + { + /* + if(usesDatabase()) + { + databaseUpdate(block); + notifyDirtyQueue(true); + hasBlocks=true; + return; + }*/ + BLOCK_QUEUE.add(block); + + notifyDirtyQueue(true); + } + + /** + * Called when enqueuing a block, this is a special handler to insert the block to the database. Custom queues should override this to put into a different table, or add additional data + * @param block + */ + public void databaseUpdate(PrimitiveBlock block) + { + + hasBlocks=true; + PreparedStatement pstmt = null; + try { + pstmt = DatabaseWrapper.get().prepareStatement("INSERT INTO `blocks` (queueName, posX, posY, posZ, snapshotID, block) VALUES (?, ?, ?, ?, ?, ?);"); + pstmt.setString(1, getRestoreQueueName()); + pstmt.setInt(2, block.position.getX()); + pstmt.setInt(3, block.position.getY()); + pstmt.setInt(4, block.position.getZ()); + pstmt.setInt(5, 0); + ByteArrayOutputStream blockState = new ByteArrayOutputStream(); + DataOutputStream dos0 = new DataOutputStream(blockState); + NbtIo.write(block.serialize(), dos0); + pstmt.setBytes(6, blockState.toByteArray()); + + + DatabaseWrapper.get().executePreparedStatement(pstmt); + + } catch (Exception e) + { + // Duplicate block insertion, we only cache each block one time by default. If this function is overridden to use a different table, perhaps multiple blocks for the same position could be cached. + } + } + + /** + * Executed when the queue is modified. + * @param blockAdded Whether a block was added or removed from the queue + */ + public abstract void notifyDirtyQueue(boolean blockAdded); + + /** + * Pops a block off the queue, and returns it + * @return A PrimitiveBlock instance of the SavedBlock + */ + public PrimitiveBlock getNextBlock() + { + if (usesDatabase()) { + // Send a query to the database to retrieve the block, and reconstruct here + try { + PreparedStatement sel; + if (sorted()) { + sel = DatabaseWrapper.get().prepareStatement("SELECT * FROM `blocks` WHERE queueName=? ORDER BY posY ASC LIMIT 1;"); + } else + sel = DatabaseWrapper.get().prepareStatement("SELECT * FROM `blocks` WHERE queueName=? LIMIT 1;"); + + sel.setString(1, getRestoreQueueName()); + ResultSet res = DatabaseWrapper.get().executePreparedStatementQuery(sel); + // Now retrieve the block from the database + if (res.next()) { + byte[] data = res.getBytes("block"); + ByteArrayInputStream bais = new ByteArrayInputStream(data); + DataInputStream dis = new DataInputStream(bais); + + PrimitiveBlock block = PrimitiveBlock.deserialize(NbtIo.read(dis)); + + if(block.level.getBlockState(block.position).is(block.blockType)) + { + + try { + res.deleteRow(); + if (!res.rowDeleted()) { + + } + } catch (SQLException e001) { + PreparedStatement pstat = DatabaseWrapper.get().prepareStatement("DELETE FROM `blocks` WHERE queueName=? AND posX=? AND posY=? AND posZ=?;"); + pstat.setString(1, getRestoreQueueName()); + pstat.setInt(2, block.position.getX()); + pstat.setInt(3, block.position.getY()); + pstat.setInt(4, block.position.getZ()); + DatabaseWrapper.get().executePreparedStatement(pstat); + } + } + + return block; + } else return null; + + } catch (SQLException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + PrimitiveBlock blk = BLOCK_QUEUE.get(0); + BLOCK_QUEUE.remove(0); + notifyDirtyQueue(false); + return blk; + } + + /** + * Sets the hasBlocks flag to false to reduce DB Spam + */ + public void setNoBlocks() + { + hasBlocks=false; + } + + /** + * Override to indicate if the list should be sorted by lowest Y value + * + * @return + */ + public abstract boolean sorted(); + + /** + * Whether the queue has blocks or not + * @return + */ + public boolean hasBlocks() + { + if(usesDatabase()) return hasBlocks; + else return getQueuedBlocks() != 0; + } + + /** + * Clears the entire queue, discarding the saved blocks permanently. + */ + public void clear() + { + BLOCK_QUEUE.clear(); + notifyDirtyQueue(false); + } + + /** + * Returns the raw block queue instance + * @return + */ + public List getQueue() { + return BLOCK_QUEUE; + } + + /** + * Sets the block queue, without notifying listeners + * @param queue + */ + public void setQueueNoNotify(List queue) + { + BLOCK_QUEUE = queue; + } + + /** + * Sets the block queue, and notifies any listeners that blocks were potentially added + * @param queue + */ + public void setQueue(List queue) + { + BLOCK_QUEUE = queue; + notifyDirtyQueue(true); + } + + /** + * Gets the restore runner instance initialized for this queue + * @return + */ + public BlockRestoreRunner getRunner() + { + return RUNNER; + } + + /** + * Must be called manually to register a restore queue. This will have a 2 second fixed delay before initial execution + */ + public void schedule(long interval, TimeUnit unit) + { + RUNNING_TASK = LibZontreck.executor.scheduleAtFixedRate(RUNNER, 2000, interval, unit); + + DATABASE_UPLOAD_RUNNER = LibZontreck.executor.scheduleAtFixedRate(new DatabaseUploadRunner(this), 2000, 50, TimeUnit.MILLISECONDS); + + isCancelled=false; + } + + /** + * Cancels the restore job + */ + public void cancel() + { + isCancelled=true; + RUNNING_TASK.cancel(false); + } + + public boolean isCancelled=false; + + /** + * Remove a block from the local queue. This does not impact the database and is used internally + * @param block + */ + public void dequeue(PrimitiveBlock block) + { + BLOCK_QUEUE.remove(block); + } + + /** + * Cancels the repeating upload to database task. This is automatically invoked when cancel has been invoked, and no more tasks are to be uploaded. + */ + public void cancelUploader() + { + DATABASE_UPLOAD_RUNNER.cancel(true); + } + + @SubscribeEvent + public void onServerStopping(ServerStoppingEvent event) + { + cancel(); + } + + /** + * Initialize a restore Queue for a specific level. This will load and import the blocks in the save data into this queue + * @param level The level to load for + * @throws IOException On failure to read a file + */ + public void initialize(ServerLevel level) throws IOException { + if(usesDatabase()) + { + return; + } + var file = SaveDataFactory.builder().withDimension(level).withQueueID(this).withPosition(null).build(); + + if(!file.getSaveDataPath().toFile().exists()) + { + return; + } + CompoundTag tag = NbtIo.read(file.getSaveDataPath().toFile()); + SaveDataFactory.SaveDataManifest manifest = SaveDataFactory.SaveDataManifest.deserialize(tag); + + List files = manifest.get(); + for(SaveDataCoordinates chunk : files) + { + var saved = SaveDataFactory.builder().withDimension(level).withQueueID(this).withPosition(chunk.toBlockPos()).build(); + var saveData = saved.getInstance(); + for(SavedBlock sb : saveData.blocks) + { + enqueueBlock(sb); + } + } + + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java new file mode 100644 index 0000000..57e18e0 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java @@ -0,0 +1,65 @@ +package dev.zontreck.libzontreck.memory.world; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; + +import java.io.IOException; +import java.util.*; + +/** + * DANGER: DO NOT USE THIS CLASS DIRECTLY + */ +public class BlockRestoreQueueRegistry +{ + private static Map QUEUES = new HashMap<>(); + + /** + * Internal use only + * + * @see dev.zontreck.libzontreck.events.BlockRestoreQueueRegistrationEvent + * @param queue The queue to register + */ + public static void addQueue(BlockRestoreQueue queue) { + QUEUES.put(queue.getRestoreQueueName(), queue); + } + + /** + * Retrieves a registered restore queue by its name + * @param restoreQueueName Queue Name + * @return + */ + public static BlockRestoreQueue getQueue(String restoreQueueName) { + return QUEUES.get(restoreQueueName); + } + + /** + * Returns a iterator for a list of all the queues. This cannot remove items from the main queue. + * @return + */ + public static Iterator getReadOnlyQueue() { + List queues = new ArrayList<>(); + queues.addAll(QUEUES.values()); + return queues.iterator(); + } + + /** + * Initialize a block restore queue. + *

+ * Block Restore Queues are level independent, but blocks are not. This loads the blocks saved for this queue in that particular level's hierarchy + * @param level The level to load the queue for + */ + public static void init(ServerLevel level) + { + + Iterator it = BlockRestoreQueueRegistry.getReadOnlyQueue(); + while(it.hasNext()) + { + BlockRestoreQueue queue = it.next(); + try { + queue.initialize(level); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java new file mode 100644 index 0000000..8f1fdad --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java @@ -0,0 +1,56 @@ +package dev.zontreck.libzontreck.memory.world; + +import dev.zontreck.libzontreck.vectors.Vector3d; +import net.minecraft.core.BlockPos; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; + +import java.util.Random; + +public class BlockRestoreRunner implements Runnable +{ + public BlockRestoreRunner(BlockRestoreQueue queue) + { + this.queue = queue; + } + + private BlockRestoreQueue queue; + public final SoundEvent pop = SoundEvents.ITEM_PICKUP; + + @Override + public void run() { + if(queue.getQueuedBlocks() == 0 && !queue.usesDatabase()) return; // We'll be queued back up later + + if(!queue.hasBlocks()) + return; + + PrimitiveBlock prim = queue.getNextBlock(); + if(prim == null){ + queue.setNoBlocks(); + return; // No more blocks. + } + + Level level = prim.level; + + // Everything is restored, play sound + SoundSource ss = SoundSource.NEUTRAL; + BlockPos pos = prim.position; + Random rng = new Random(); + + level.playSound(null, pos, pop, ss, rng.nextFloat(0.75f,1.0f), rng.nextFloat(1)); + + level.setBlock(pos, prim.blockState, Block.UPDATE_CLIENTS, 0); + + BlockEntity entity = level.getBlockEntity(pos); + if(entity != null) + { + entity.load(prim.blockEntity); + } + + + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java new file mode 100644 index 0000000..d535384 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java @@ -0,0 +1,208 @@ +package dev.zontreck.libzontreck.memory.world; + +import com.google.common.collect.Lists; +import dev.zontreck.libzontreck.LibZontreck; +import dev.zontreck.libzontreck.events.RegisterMigrationsEvent; +import net.minecraftforge.common.MinecraftForge; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +public class DatabaseMigrations +{ + public static class Migration + { + String tableID; + int version; + List migrationActions = new ArrayList<>(); + + private Migration(){ + tableID = ""; + version = 0; + } + + /** + * Builder pattern function - Sets the table ID for the migration + * @param tableID + * @return + */ + public Migration withTableID(String tableID) + { + this.tableID = tableID; + return this; + } + + /** + * Builder pattern function - Sets the table version for the migration + * @param version + * @return + */ + public Migration withVersion(int version) + { + this.version = version; + return this; + } + + /** + * Builder pattern function - Adds the action to be executed. The list will operate as FILO. + * @param pstat + * @return + */ + public Migration withMigrationAction(PreparedStatement pstat) + { + migrationActions.add(pstat); + return this; + } + + /** + * Executes the migration as defined by the builder pattern. + */ + public void execute() + { + for(PreparedStatement pstmt : migrationActions) + { + try { + DatabaseWrapper.get().executePreparedStatement(pstmt); + } catch (SQLException e) { + LibZontreck.LOGGER.warn("There was a problem executing a migration. The migration is " + pstmt+"\n\nThis does not necessarily mean a failure. If everything seems to work fine, this migration might not have been necessary.\n\n"); + e.printStackTrace(); + } + } + + try { + + PreparedStatement pstat = DatabaseWrapper.get().prepareStatement("REPLACE INTO `migrations` (tableID, version) VALUES (?,?);"); + pstat.setString(1, tableID); + pstat.setInt(2, version); + + LibZontreck.LOGGER.info("SQL QUERY: " + pstat); + + pstat.execute(); + } catch (SQLException ex) + { + ex.printStackTrace(); + } + + + } + + } + private static List migrations = new ArrayList<>(); + + public static void initMigrations() throws SQLException { + Migration migrationsTable = builder() + .withVersion(1) + .withTableID("migrations"); + + PreparedStatement statement = DatabaseWrapper.get().prepareStatement("CREATE TABLE `migrations` (" + + " `tableID` varchar(255) NOT NULL," + + " `version` int(11) NOT NULL," + + " PRIMARY KEY (`tableID`)," + + " UNIQUE KEY `tableID` (`tableID`)" + + ") ;"); + migrations.add(migrationsTable.withMigrationAction(statement)); + + Migration blocksTable = builder() + .withTableID("blocks") + .withVersion(1); + + PreparedStatement makeBlocksTable = DatabaseWrapper.get().prepareStatement("CREATE TABLE `blocks` (" + + " `time` timestamp NOT NULL DEFAULT current_timestamp()," + + " `queueName` varchar(255) NOT NULL," + + " `posX` int(11) NOT NULL," + + " `posY` int(11) NOT NULL," + + " `posZ` int(11) NOT NULL," + + " `snapshotID` int(11) NOT NULL DEFAULT 0 COMMENT 'Enables multiple blocks existing at the same position'," + + " `block` blob NOT NULL COMMENT 'NBT Data representing a SavedBlock'," + + " PRIMARY KEY (`time`)" + + ") ;"); + + migrations.add(blocksTable.withMigrationAction(makeBlocksTable)); + + Migration blocksUpdate = builder() + .withTableID("blocks") + .withVersion(2); + PreparedStatement removePKey = DatabaseWrapper.get().prepareStatement("ALTER TABLE `blocks` DROP PRIMARY KEY;"); + blocksUpdate.withMigrationAction(removePKey); + PreparedStatement addIDColumn = DatabaseWrapper.get().prepareStatement("ALTER TABLE `blocks` ADD `ID` INT NOT NULL AUTO_INCREMENT FIRST, ADD PRIMARY KEY (`ID`);"); + blocksUpdate.withMigrationAction(addIDColumn); + + migrations.add(blocksUpdate); + + migrations.add(builder() + .withTableID("blocks") + .withVersion(3) + .withMigrationAction(DatabaseWrapper.get().prepareStatement("ALTER TABLE `blocks` ADD UNIQUE (`posX`, `posY`, `posZ`); "))); + + + + RegisterMigrationsEvent rme = new RegisterMigrationsEvent(); + MinecraftForge.EVENT_BUS.post(rme); + + + migrations.addAll(rme.getMigrations()); + + + executeMigrations(); + } + + private static void executeMigrations() + { + + Migration lastTableChecked = null; + for(Migration m : migrations) + { + + if(lastTableChecked == null) lastTableChecked = getCurrentTable(m.tableID); + else { + if(lastTableChecked.tableID != m.tableID) lastTableChecked = getCurrentTable(m.tableID); + } + + if(lastTableChecked == null || m.version > lastTableChecked.version) { + + LibZontreck.LOGGER.info("Executing migration " + m.tableID + ":" + m.version); + m.execute(); + } else { + LibZontreck.LOGGER.info("Skipping migration on table " + m.tableID + "; Current table version is " + lastTableChecked.version); + } + } + } + + /** + * Gets the current table's version using the Migration structure for data fields. Will be null if there is an error on any table except for migrations. + * @return + */ + private static Migration getCurrentTable(String tableID) + { + try{ + PreparedStatement pst = DatabaseWrapper.get().prepareStatement("SELECT * FROM `migrations` WHERE tableID=?;"); + pst.setString(1, tableID); + + var result = pst.executeQuery(); + if(!result.next()) + { + return builder().withTableID(tableID).withVersion(0); + }else { + return builder().withTableID(tableID).withVersion(result.getInt("version")); + } + + }catch (Exception ex) + { + if(tableID == "migrations") + { + return builder().withTableID(tableID) + .withVersion(0); + } + ex.printStackTrace(); + + return null; + } + } + + public static Migration builder() + { + return new Migration(); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseUploadRunner.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseUploadRunner.java new file mode 100644 index 0000000..2430114 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseUploadRunner.java @@ -0,0 +1,26 @@ +package dev.zontreck.libzontreck.memory.world; + +public class DatabaseUploadRunner implements Runnable { + private BlockRestoreQueue QUEUE; + public DatabaseUploadRunner(BlockRestoreQueue queue) + { + QUEUE = queue; + } + + @Override + public void run() { + if(QUEUE.getQueuedBlocks() == 0) + { + if(QUEUE.isCancelled) + { + QUEUE.cancelUploader(); + return; + } + } else { + PrimitiveBlock block = QUEUE.getQueue().get(0); + QUEUE.dequeue(block); + + QUEUE.databaseUpdate(block); + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java new file mode 100644 index 0000000..35c8d59 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java @@ -0,0 +1,163 @@ +package dev.zontreck.libzontreck.memory.world; + + +import dev.zontreck.libzontreck.LibZontreck; +import dev.zontreck.libzontreck.config.ServerConfig; + +import java.sql.*; + +public class DatabaseWrapper { + private Connection connection; + public static boolean hasDB = true; + + private static DatabaseWrapper instance; + + public static DatabaseWrapper get() { + if(!hasDB) { + throw new RuntimeException("Error: Database is not set up"); + } + + if (instance == null) + start(); + + try { + instance.sanityCheck(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + return instance; + } + + /** + * This function will return true if the database drivers are available. + * + * @return + */ + public static boolean databaseIsAvailable() { + return instance.connection!=null; + } + + public DatabaseWrapper() { + connection = null; + } + + public static void start() { + instance = new DatabaseWrapper(); + try { + LibZontreck.LOGGER.info("Connecting to database..."); + LibZontreck.LOGGER.info("jdbc:db ://" + ServerConfig.database.user + "@" + ServerConfig.database.host + "/" + ServerConfig.database.database); + + instance.connect(ServerConfig.database.host, ServerConfig.database.user, ServerConfig.database.password, ServerConfig.database.database); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public static void invalidate() { + instance=null; + hasDB=false; + } + + private void restart() { + + LibZontreck.LOGGER.info("Reconnecting to database..."); + LibZontreck.LOGGER.info("jdbc:db ://" + ServerConfig.database.user + "@" + ServerConfig.database.host + "/" + ServerConfig.database.database); + + try { + instance.connect(ServerConfig.database.host, ServerConfig.database.user, ServerConfig.database.password, ServerConfig.database.database); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public void connect(String url, String username, String password, String database) throws SQLException { + if(database.isBlank()) + { + ServerConfig.init(); + if(ServerConfig.database.database.isBlank()) + { + throw new SQLException("Failed to connect to database"); + } else { + start(); + return; + } + } + try { + // Try MariaDB JDBC driver + Class.forName("org.mariadb.jdbc.Driver"); + connection = DriverManager.getConnection("jdbc:mariadb://" + url + "/" + database, username, password); + } catch (ClassNotFoundException | SQLException e) { + // MariaDB not found or failed to connect, try MySQL + LibZontreck.LOGGER.warn("Failed to connect via MariaDB: " + e.getMessage() + "; Attempting to fall back to mysql"); + try { + Class.forName("com.mysql.cj.jdbc.Driver"); + connection = DriverManager.getConnection("jdbc:mysql://" + url + "/" + database, username, password); + } catch (ClassNotFoundException | SQLException ex) { + // MySQL not found or failed to connect, try SQLite + try { + + Class.forName("com.mysql.jdbc.Driver"); + connection = DriverManager.getConnection("jdbc:mysql://" + url + "/" + database, username, password); + } catch (ClassNotFoundException | SQLException ex1) { + + LibZontreck.LOGGER.warn("Failed to connect via MySQL: " + e.getMessage() + "; " + ex1.getMessage() + "; Attempting to fall back to sqlite"); + + try { + Class.forName("org.sqlite.JDBC"); + connection = DriverManager.getConnection("jdbc:sqlite:" + database + ".db"); + } catch (ClassNotFoundException | SQLException exc) { + LibZontreck.LOGGER.error("Failed to connect via SQLite: " + e.getMessage() + "; If you require the use of the block queues, please check the above warnings for explanation on cause. It could be that you do not have the relevant JDBC installed in your server mods list."); + } + } + } + } + } + + private void sanityCheck() throws SQLException { + if(connection.isClosed() || connection == null) { + restart(); + } + } + + public ResultSet executeQuery(String query) throws SQLException { + if (connection == null) { + throw new SQLException("Connection not established."); + } + Statement statement = connection.createStatement(); + return statement.executeQuery(query); + } + + public int executeUpdate(String statement) throws SQLException { + if (connection == null) { + throw new SQLException("Connection not established."); + } + return connection.createStatement().executeUpdate(statement); + } + + public void disconnect() throws SQLException { + if (connection != null && !connection.isClosed()) { + connection.close(); + } + } + + public int executePreparedStatement(PreparedStatement preparedStatement) throws SQLException { + if (connection == null) { + throw new SQLException("Connection not established."); + } + return preparedStatement.executeUpdate(); + } + + public ResultSet executePreparedStatementQuery(PreparedStatement query) throws SQLException { + if (connection == null) { + throw new SQLException("Connection not established."); + } + return query.executeQuery(); + } + + public PreparedStatement prepareStatement(String query) throws SQLException { + if (connection == null) { + throw new SQLException("Connection not established."); + } + return connection.prepareStatement(query); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java b/src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java new file mode 100644 index 0000000..7cfef67 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java @@ -0,0 +1,80 @@ +package dev.zontreck.libzontreck.memory.world; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +public class PrimitiveBlock +{ + public final SavedBlock savedBlock; + public final Block blockType; + public final BlockState blockState; + public final CompoundTag blockEntity; + public final BlockPos position; + public final ServerLevel level; + + public PrimitiveBlock(SavedBlock savedBlock, Block blockType, BlockState blockState, CompoundTag blockEntity, BlockPos position, ServerLevel level) + { + this.savedBlock = savedBlock; + this.blockType = blockType; + this.blockEntity = blockEntity; + this.position = position; + this.level = level; + this.blockState = blockState; + } + + /** + * Alias method + * @see SavedBlock#serialize() + * @return NBT Tag + */ + public CompoundTag serialize() + { + return savedBlock.serialize(); + } + + /** + * Alias Method + * @see SavedBlock#deserialize(CompoundTag) + * @see SavedBlock#getBlockPrimitive() + * @param tag NBT Tag + * @return A Primitive Block + */ + public static PrimitiveBlock deserialize(CompoundTag tag) + { + return SavedBlock.deserialize(tag).getBlockPrimitive(); + } + + /** + * Compare a block with this primitive block + * @param state The block state to compare + * @param entity The block entity to compare + * @return True if identical + */ + public boolean is(BlockState state, BlockEntity entity) + { + if(state.getBlock() == this.blockType) + { + // Check the block state + if(this.blockState.equals(state)) + { + if(blockEntity == null) return true; // Not all blocks have a block entity. + if(blockEntity.equals(entity.serializeNBT())){ + return true; + }else return false; + } else return false; + }else return false; + } + + /** + * Clones the PrimitiveBlock into a new instance + * @return + */ + public PrimitiveBlock copy() + { + return savedBlock.clone().getBlockPrimitive(); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java new file mode 100644 index 0000000..0b95612 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java @@ -0,0 +1,47 @@ +package dev.zontreck.libzontreck.memory.world; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraftforge.common.Tags; + +public class SaveDataCoordinates +{ + public int X; + public int Z; + private BlockPos source; + + public SaveDataCoordinates(BlockPos pos) + { + int X = pos.getX() >> 4 >> 5; + int Z = pos.getZ() >> 4 >> 5; + + source = pos; + } + + public String getFileName() + { + return "r." + X + "." + Z + ".dat"; + } + + public BlockPos toBlockPos() + { + return source; + } + + public static final String TAG_COORDS = "sdc"; + public CompoundTag toNBT() + { + return NbtUtils.writeBlockPos(source); + } + + public static SaveDataCoordinates deserialize(CompoundTag tag) + { + BlockPos pos = NbtUtils.readBlockPos(tag); + + return new SaveDataCoordinates(pos); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java new file mode 100644 index 0000000..0effe5f --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java @@ -0,0 +1,267 @@ +package dev.zontreck.libzontreck.memory.world; + +import dev.zontreck.libzontreck.LibZontreck; +import dev.zontreck.libzontreck.vectors.WorldPosition; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.*; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.Level; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class SaveDataFactory +{ + static Map datas = new HashMap<>(); + + protected static boolean hasSaveDataInstance(SaveDataFile file) + { + return datas.containsKey(file.getSaveDataPath().toString()); + } + + protected static SaveData getSaveDataInstance(SaveDataFile file) + { + return datas.get(file.getSaveDataPath().toString()); + } + + public static Builder builder() + { + return new Builder(); + } + static class Builder + { + private String modID = "minecraft"; + private String levelName; + private String queueID; + private boolean DBMode; + private BlockPos position; + + public Builder withDimension(Level level) + { + ResourceLocation lv = level.dimension().location(); + this.modID = lv.getNamespace(); + this.levelName = lv.getPath(); + + return this; + } + + public Builder withQueueID(BlockRestoreQueue queue) + { + queueID = queue.getRestoreQueueName(); + + return this; + } + + public Builder withDatabaseMode() + { + DBMode=true; + + return this; + } + + public Builder withPosition(BlockPos pos) + { + position = pos; + + return this; + } + + public SaveDataManifest getManifest() + { + return new SaveDataManifest(); + } + + public SaveDataFile build() + { + return new SaveDataFile(modID, levelName, queueID, DBMode, position); + } + } + + static class SaveDataFile + { + String mod; + String dimension; + String queue; + boolean database; + BlockPos position; + boolean useManifest = false; + + public SaveDataFile(String modID, String levelName, String queueID, boolean DBMode, BlockPos pos) + { + mod = modID; + dimension = levelName; + queue = queueID; + database = DBMode; + position = pos; + + if(pos == null) + { + useManifest=true; + } + } + + /** + * config/LibZontreck/block_snapshots/[mod]/[dimension]/[queueNickName]/r.x.z.dat + * @return + */ + public Path getSaveDataPath() + { + Path path = LibZontreck.BASE_CONFIG.resolve("block_snapshots"); + if(mod != null) path = path.resolve(mod); + if(dimension != null) path = path.resolve(dimension); + if(queue != null) path = path.resolve(queue); + + path.toFile().mkdirs(); + + if(useManifest) return path.resolve("manifest.nbt"); + + SaveDataCoordinates coordinates = new SaveDataCoordinates(position); + path = path.resolve(coordinates.getFileName()); + return path; + } + + /** + * Reads the save data, or initializes a new instance. + *

+ * Additionally, this will check for a pre-existing POJO instance and return if it exists. + * @return + * @throws IOException + */ + public SaveData getInstance() throws IOException { + if(SaveDataFactory.hasSaveDataInstance(this)) return SaveDataFactory.getSaveDataInstance(this); + Path data = getSaveDataPath(); + if(data.toFile().exists()) + { + CompoundTag tag = NbtIo.read(data.toFile()); + + return SaveData.deserialize(tag, this); + } else { + + return new SaveData(this); + } + } + } + + static class SaveDataManifest { + private List SAVE_DATA = new ArrayList<>(); + public static final String TAG_MANIFEST = "manifest"; + + private SaveDataManifest() + { + } + + public void add(SaveDataCoordinates pos){ + SAVE_DATA.add(pos); + } + + public List get() + { + return new ArrayList<>(SAVE_DATA); + } + + public CompoundTag save() + { + CompoundTag tag = new CompoundTag(); + ListTag lst = new ListTag(); + for(SaveDataCoordinates str : SAVE_DATA) + { + lst.add(str.toNBT()); + } + tag.put(TAG_MANIFEST, lst); + return tag; + + } + + public static SaveDataManifest deserialize(CompoundTag tag) + { + SaveDataManifest ret = new SaveDataManifest(); + ListTag lst = tag.getList(TAG_MANIFEST, Tag.TAG_COMPOUND); + for(Tag entry : lst) + { + if(entry instanceof CompoundTag ct) + { + ret.add(SaveDataCoordinates.deserialize(ct)); + } + } + + return ret; + } + } + + static class SaveData { + SaveDataFile myFile; + public static final String TAG_SAVED_BLOCKS = "sb"; + + public List blocks = new ArrayList<>(); + + public SaveData(SaveDataFile file) + { + myFile = file; + } + + /** + * Read a NBT Tag and reconstruct the SaveData POJO + * @param tag + * @param file + * @return + */ + public static SaveData deserialize(CompoundTag tag, SaveDataFile file) { + SaveData data = new SaveData(file); + ListTag lst = tag.getList(TAG_SAVED_BLOCKS, ListTag.TAG_COMPOUND); + for(Tag xTag : lst) + { + if(xTag instanceof CompoundTag ct) + { + SavedBlock sb = SavedBlock.deserialize(ct); + data.blocks.add(sb); + } + } + + return data; + } + + /** + * Write the current save data to NBT + * @return + */ + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + ListTag lst = new ListTag(); + for(SavedBlock block : blocks) + { + lst.add(block.serialize()); + } + + tag.put(TAG_SAVED_BLOCKS, lst); + return tag; + } + + /** + * Imports a full queue to the save data file. + * ! WARNING ! This method will overwrite the SaveDataFile's Queue ID + * * * * + * This will only import for the correct dimension. This method is invoked automatically for each level for each queue when the server is shutting down prior to level unload. + * @param queue Queue to import + * @return The current SaveData instance + */ + public SaveData importQueue(BlockRestoreQueue queue) + { + for(PrimitiveBlock blk : queue.getQueue()) + { + if(WorldPosition.getDim(blk.level) == this.myFile.dimension) + blocks.add(blk.savedBlock); + else + continue; // We only want to add to a save data file, the blocks for the dimension in question. + } + + myFile.queue = queue.getRestoreQueueName(); + return this; + } + + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java new file mode 100644 index 0000000..539d890 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java @@ -0,0 +1,112 @@ +package dev.zontreck.libzontreck.memory.world; + +import dev.zontreck.libzontreck.api.Vector3; +import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; +import dev.zontreck.libzontreck.vectors.WorldPosition; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +public class SavedBlock implements Cloneable +{ + private CompoundTag blockState; + private CompoundTag blockEntity; + private boolean hasBlockEntity; + private WorldPosition position; + + /** + * Take a snapshot of the block, and save as primitive type SavedBlock + * @param position The block's position + * @param level The level the position relates to + * @return A instance of the saved block + */ + public static SavedBlock takeSnapshot(Vector3 position, Level level) + { + SavedBlock savedBlock = new SavedBlock(); + BlockPos pos = position.asBlockPos(); + + BlockState state = level.getBlockState(pos); + savedBlock.blockState = NbtUtils.writeBlockState(state); + BlockEntity entity = level.getBlockEntity(pos); + if(entity == null) + { + savedBlock.hasBlockEntity = false; + }else { + savedBlock.hasBlockEntity = true; + savedBlock.blockEntity = entity.serializeNBT(); + } + + savedBlock.position = new WorldPosition(position.asVector3d(), (ServerLevel) level); + + return savedBlock; + } + + /** + * Saves the stored block as a NBT Tag + * @return CompoundTag + */ + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + tag.put("state", blockState); + if(hasBlockEntity) tag.put("entity", blockEntity); + + tag.put("position", position.serialize()); + + return tag; + } + + /** + * Reads a NBT Tag that represents a stored block, and returns the StoredBlock object + * @param tag Saved NBT + * @return SavedBlock instance + */ + public static SavedBlock deserialize(CompoundTag tag) + { + SavedBlock savedBlock = new SavedBlock(); + savedBlock.blockState = tag.getCompound("state"); + if(tag.contains("entity")) { + savedBlock.blockEntity = tag.getCompound("entity"); + savedBlock.hasBlockEntity=true; + } + + try { + savedBlock.position = new WorldPosition(tag.getCompound("position"), false); + } catch (InvalidDeserialization e) { + throw new RuntimeException(e); + } + + return savedBlock; + } + + public PrimitiveBlock getBlockPrimitive() + { + ServerLevel level = position.getActualDimension(); + + BlockState state = NbtUtils.readBlockState(level.holderLookup(Registries.BLOCK), blockState); + + return new PrimitiveBlock(this, state.getBlock(), state, blockEntity, position.Position.asBlockPos(), level); + } + + @Override + public SavedBlock clone() { + try { + SavedBlock clone = (SavedBlock) super.clone(); + if(blockEntity != null) + clone.blockEntity = blockEntity.copy(); + if(blockState != null) + clone.blockState = blockState.copy(); + if(position != null) + clone.position = position.clone(); + + return clone; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java new file mode 100644 index 0000000..a098a21 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java @@ -0,0 +1,41 @@ +package dev.zontreck.libzontreck.memory.world; + +import dev.zontreck.libzontreck.api.Vector3; +import dev.zontreck.libzontreck.util.PositionUtil; +import dev.zontreck.libzontreck.vectors.Vector3i; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +public class SortedBlockQueue extends BlockRestoreQueue +{ + @Override + public String getRestoreQueueName() { + return "SortedBlockQueue"; + } + @Override + public void notifyDirtyQueue(boolean blockAdded) { + if(blockAdded) { + List queue = getQueue(); + List retQueue = new ArrayList<>(queue.size()); + + // Sort the queue based on block positions + queue.sort(Comparator.comparing(block -> new Vector3i(block.position))); + + // Add blocks in sorted order to the new queue + for (PrimitiveBlock blk : queue) { + retQueue.add(blk.copy()); // Copy block if necessary + } + + setQueueNoNotify(retQueue); + } + } + + @Override + public boolean sorted() { + return true; + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java b/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java index 40a6281..e7bbe1f 100644 --- a/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java +++ b/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java @@ -36,16 +36,9 @@ public class ModMessages { .clientAcceptedVersions(s->true) .serverAcceptedVersions(s->true) .simpleChannel(); - - RegisterPacketsEvent event = new RegisterPacketsEvent(); - MinecraftForge.EVENT_BUS.post(event); INSTANCE=net; - for(IPacket packet : event.packets) - { - packet.register(net); - } net.messageBuilder(S2CPlaySoundPacket.class, PACKET_ID.getAndIncrement(), NetworkDirection.PLAY_TO_CLIENT) .decoder(S2CPlaySoundPacket::new) diff --git a/src/main/java/dev/zontreck/libzontreck/networking/NetworkEvents.java b/src/main/java/dev/zontreck/libzontreck/networking/NetworkEvents.java index 13ef44a..c87b891 100644 --- a/src/main/java/dev/zontreck/libzontreck/networking/NetworkEvents.java +++ b/src/main/java/dev/zontreck/libzontreck/networking/NetworkEvents.java @@ -1,17 +1,5 @@ package dev.zontreck.libzontreck.networking; -import dev.zontreck.libzontreck.events.RegisterPacketsEvent; -import dev.zontreck.libzontreck.networking.packets.S2CPlaySoundPacket; -import dev.zontreck.libzontreck.networking.packets.S2CWalletInitialSyncPacket; -import dev.zontreck.libzontreck.networking.packets.S2CWalletUpdatedPacket; -import net.minecraftforge.eventbus.api.SubscribeEvent; - public class NetworkEvents { - @SubscribeEvent - public void onRegisterPackets(RegisterPacketsEvent ev) - { - ev.packets.add(new S2CWalletUpdatedPacket()); - ev.packets.add(new S2CWalletInitialSyncPacket()); - } } diff --git a/src/main/java/dev/zontreck/libzontreck/profiles/Profile.java b/src/main/java/dev/zontreck/libzontreck/profiles/Profile.java index d6e4692..5df6d63 100644 --- a/src/main/java/dev/zontreck/libzontreck/profiles/Profile.java +++ b/src/main/java/dev/zontreck/libzontreck/profiles/Profile.java @@ -18,7 +18,7 @@ import net.minecraftforge.common.MinecraftForge; /** * A libZontreck user profile - *

+ *

* This is used to contain common player data, as well as be capable of serializing the player's data and sending to/from the client. */ public class Profile { diff --git a/src/main/java/dev/zontreck/libzontreck/util/BlocksUtil.java b/src/main/java/dev/zontreck/libzontreck/util/BlocksUtil.java index 4439147..92edc5e 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/BlocksUtil.java +++ b/src/main/java/dev/zontreck/libzontreck/util/BlocksUtil.java @@ -1,6 +1,6 @@ package dev.zontreck.libzontreck.util; -import dev.zontreck.libzontreck.vectors.Vector3; +import dev.zontreck.libzontreck.vectors.Vector3d; import net.minecraft.server.level.ServerLevel; import java.util.ArrayList; @@ -18,9 +18,9 @@ public class BlocksUtil * @param limit The applicable limit for vein detection * @return List of positions for the vein */ - public static List VeinOf(ServerLevel level, Vector3 start, int limit) + public static List VeinOf(ServerLevel level, Vector3d start, int limit) { - List ret = new ArrayList<>(); + List ret = new ArrayList<>(); diff --git a/src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java b/src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java new file mode 100644 index 0000000..39ee999 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java @@ -0,0 +1,156 @@ +package dev.zontreck.libzontreck.util; + +import dev.zontreck.libzontreck.api.Vector3; +import dev.zontreck.libzontreck.vectors.Vector3d; +import dev.zontreck.libzontreck.vectors.Vector3i; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Provides helper functions for position related things + */ +public class PositionUtil +{ + + public static List makeCube(Vector3 p1, Vector3 p2) + { + List vecs = new ArrayList<>(); + Vector3 work = new Vector3d(); + Vector3d v1 = p1.asVector3d(); + Vector3d v2 = p2.asVector3d(); + + double xx = v1.x; + double yy = v1.y; + double zz = v1.z; + + int yState = 0; + int zState = 0; + int xState = 0; + + for(xx = Math.round(v1.x); (xx != Math.round(v2.x) && xState != 2);) + { + for(zz = Math.round(v1.z); (zz != Math.round(v2.z) && zState != 2);) + { + for(yy = Math.round(v1.y); (yy != Math.round(v2.y) && yState != 2);) + { + work = new Vector3d(xx, yy, zz); + + if(!vecs.contains(work)) vecs.add(work); + + if(yy > v2.y) + { + yy -= 1.0; + if(yy == Math.round(v2.y) && yState == 0) + { + yState++; + }else{ + if(yState == 1) + { + yState ++; + } + } + } else if(yy < v2.y) + { + yy += 1.0; + if(yy == Math.round(v2.y) && yState == 0){ + yState ++; + }else { + if(yState == 1)yState++; + } + } + } + + yState=0; + work = new Vector3d(xx,yy,zz); + + if(!vecs.contains(work)) vecs.add(work); + + if(zz > v2.z) + { + zz -= 1.0; + + if(zz == Math.round(v2.z) && zState == 0)zState++; + else{ + if(zState == 1)zState++; + } + }else if(zz < v2.z) + { + zz += 1.0; + + if(zz == Math.round(v2.z) && zState == 0)zState++; + else { + if(zState==1)zState++; + } + } + } + + zState=0; + work = new Vector3d(xx,yy,zz); + + if(!vecs.contains(work)) vecs.add(work); + + if(xx > v2.x) + { + xx -= 1.0; + + if(xx == Math.round(v2.x) && xState == 0) xState++; + else{ + if(xState == 1)xState++; + } + }else if(xx < v2.x) + { + xx += 1.0; + + if(xx == Math.round(v2.x) && xState==0)xState++; + else{ + if(xState==1)xState++; + } + } + } + + return vecs; + } + + + public static List sortAscending(List vecs) + { + + List copy = new ArrayList<>(vecs); + List sorted = new ArrayList<>(); + + + while(copy.size()>0) + { + Vector3 lowest = findFirstLowestPosition(copy); + copy.remove(lowest); + sorted.add(lowest); + } + + return sorted; + } + + public static Vector3 findFirstLowestPosition(List vecs) + { + + Vector3i lowest = new Vector3i(0, 500, 0); + List copy = new ArrayList<>(vecs); + + Iterator it = copy.iterator(); + while(it.hasNext()) + { + Vector3i entry = it.next().asVector3i(); + if(entry.y < lowest.y) + { + lowest = entry; + it.remove(); + }else it.remove(); + } + + return lowest; + + } + + +} diff --git a/src/main/java/dev/zontreck/libzontreck/util/SNbtIo.java b/src/main/java/dev/zontreck/libzontreck/util/SNbtIo.java index 40fcbb6..120a93e 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/SNbtIo.java +++ b/src/main/java/dev/zontreck/libzontreck/util/SNbtIo.java @@ -3,7 +3,6 @@ package dev.zontreck.libzontreck.util; import com.mojang.brigadier.exceptions.CommandSyntaxException; import dev.zontreck.ariaslib.util.FileIO; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtIo; import net.minecraft.nbt.NbtUtils; import java.io.File; @@ -43,4 +42,4 @@ public class SNbtIo String snbt = NbtUtils.structureToSnbt(tag); FileIO.writeFile(path.toFile().getAbsolutePath(), snbt); } -} +} \ No newline at end of file diff --git a/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java b/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java index abd8d57..8f35d3b 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java +++ b/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java @@ -1,5 +1,6 @@ package dev.zontreck.libzontreck.util; +import java.util.List; import java.util.UUID; import java.util.function.Function; import java.util.function.Supplier; @@ -13,6 +14,7 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.fml.LogicalSide; import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.simple.SimpleChannel; +import net.minecraftforge.server.ServerLifecycleHooks; public class ServerUtilities { @@ -23,7 +25,7 @@ public class ServerUtilities */ public static ServerPlayer getPlayerByID(String id) { - return LibZontreck.THE_SERVER.getPlayerList().getPlayer(UUID.fromString(id)); + return ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayer(UUID.fromString(id)); } /** @@ -79,7 +81,7 @@ public class ServerUtilities public static boolean playerIsOffline(UUID ID) throws InvalidSideException { if(isClient())throw new InvalidSideException("This can only be called on the server"); - if(LibZontreck.THE_SERVER.getPlayerList().getPlayer(ID) == null) return true; + if(ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayer(ID) == null) return true; else return false; } diff --git a/src/main/java/dev/zontreck/libzontreck/util/TagUtils.java b/src/main/java/dev/zontreck/libzontreck/util/TagUtils.java new file mode 100644 index 0000000..d2eca8a --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/util/TagUtils.java @@ -0,0 +1,45 @@ +package dev.zontreck.libzontreck.util; + +import net.minecraft.nbt.CompoundTag; + +public class TagUtils +{ + /** + * Get either the entry, or supply a default value + * @param tag + * @param entry + * @param other + * @return + */ + public static int intOr(CompoundTag tag, String entry, int other) + { + if(tag.contains(entry)) return tag.getInt(entry); + else return other; + } + + /** + * Get either the entry, or supply a default value + * @param tag + * @param entry + * @param other + * @return + */ + public static String strOr(CompoundTag tag, String entry, String other) + { + if(tag.contains(entry)) return tag.getString(entry); + else return other; + } + + /** + * Get either the entry, or supply a default value + * @param tag + * @param entry + * @param other + * @return + */ + public static boolean boolOr(CompoundTag tag, String entry, boolean other) + { + if(tag.contains(entry)) return tag.getBoolean(entry); + else return other; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java b/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java index 904688c..84d9070 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java +++ b/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java @@ -109,10 +109,8 @@ public class HeadCache creds.add( new CreditsEntry(HeadUtilities.cachedLookup("zontreck"), "Aria (zontreck)", "Developer, Designer, Artist", "Aria is the primary developer and project maintainer")); - creds.add( - new CreditsEntry(HeadUtilities.cachedLookup("PossumTheWarrior"), "PossumTheWarrior", "Tester, Adviser, Designer, Artist", "Poss has helped to test the mods from very early on. Poss has also contributed the artwork and mob model for the Possum")); - creds.add( - new CreditsEntry(HeadUtilities.cachedLookup("GemMD"), "GemMD", "Tester, Adviser, Designer", "GemMD has provided advice on marketing and development decisions for various mods")); + creds.add(new CreditsEntry(HeadUtilities.cachedLookup("firesyde424"), "firesyde424", "Tester", "Firesyde has helped to test my mods and given feedback.")); + creds.add(new CreditsEntry(HeadUtilities.cachedLookup("EmberCat42"), "EmberCat42", "Tester", "EmberCat42 has helped to test and reported on a major bug in Night Vision")); CREDITS = creds; diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/ChunkPos.java b/src/main/java/dev/zontreck/libzontreck/vectors/ChunkPos.java index 9cb8fc6..0e60b5a 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/ChunkPos.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/ChunkPos.java @@ -5,10 +5,10 @@ import net.minecraft.server.level.ServerLevel; public class ChunkPos { public Points points; - public Vector2 centerPoints; + public Vector2f centerPoints; public String dim; - public ChunkPos(Vector3 point1, Vector3 point2, ServerLevel lvl) + public ChunkPos(Vector3d point1, Vector3d point2, ServerLevel lvl) { points = new Points(point1, point2, lvl); dim = WorldPosition.getDim(lvl); @@ -17,12 +17,12 @@ public class ChunkPos { public ChunkPos(CompoundTag tag) { points = new Points(tag.getCompound("points")); - centerPoints = new Vector2(tag.getCompound("center")); + centerPoints = Vector2f.deserialize(tag.getCompound("center")); } - public boolean isWithin(Vector3 point) + public boolean isWithin(Vector3d point) { - return point.inside(points.Min, points.Max); + return point.Inside(points.Min, points.Max); } public static ChunkPos getChunkPos(WorldPosition pos) diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/NonAbsVector3.java b/src/main/java/dev/zontreck/libzontreck/vectors/NonAbsVector3.java index d484e1b..bce839f 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/NonAbsVector3.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/NonAbsVector3.java @@ -11,7 +11,7 @@ public class NonAbsVector3 public long y; public long z; - public NonAbsVector3(Vector3 origin) + public NonAbsVector3(Vector3d origin) { x = Math.round(origin.x); y = Math.round(origin.y); diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Points.java b/src/main/java/dev/zontreck/libzontreck/vectors/Points.java index fdb0cf9..93fba5f 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Points.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Points.java @@ -7,8 +7,8 @@ import net.minecraft.server.level.ServerLevel; * Two points within the same dimension */ public class Points { - public Vector3 Min = Vector3.ZERO; - public Vector3 Max = Vector3.ZERO; + public Vector3d Min = Vector3d.ZERO; + public Vector3d Max = Vector3d.ZERO; public String dimension = ""; /** @@ -17,7 +17,7 @@ public class Points { * @param max * @param lvl */ - public Points(Vector3 min, Vector3 max, ServerLevel lvl) + public Points(Vector3d min, Vector3d max, ServerLevel lvl) { dimension = WorldPosition.getDimSafe(lvl); if(min.less(max)) @@ -48,8 +48,8 @@ public class Points { public void deserialize(CompoundTag tag) { - Min = new Vector3(tag.getCompound("min")); - Max = new Vector3(tag.getCompound("max")); + Min = Vector3d.deserialize(tag.getCompound("min")); + Max = Vector3d.deserialize(tag.getCompound("max")); dimension = tag.getString("dim"); } } diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2.java deleted file mode 100644 index 100ae27..0000000 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2.java +++ /dev/null @@ -1,120 +0,0 @@ -package dev.zontreck.libzontreck.vectors; - -import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.world.phys.Vec2; - -public class Vector2 -{ - public static final Vector2 ZERO = new Vector2(0, 0); - - public float x; - public float y; - - public Vec2 asMinecraftVector(){ - return new Vec2(x, y); - } - - public Vector2() - { - - } - - public Vector2(float x, float y) - { - this.x=x; - this.y=y; - } - - public Vector2(Vec2 pos) - { - x=pos.x; - y=pos.y; - } - - public Vector2(String pos) throws InvalidDeserialization - { - // This will be serialized most likely from the ToString method - // Parse - if(pos.startsWith("<")) - { - pos=pos.substring(1, pos.length()-1); // Rip off the ending bracket too - String[] positions = pos.split(", "); - if(positions.length!=2) - { - positions = pos.split(","); - } - - if(positions.length!=2) - { - throw new InvalidDeserialization("Positions must be in the same format provided by ToString() (ex. <1,1> or <1, 1>"); - } - - this.x = Float.parseFloat(positions[0]); - this.y = Float.parseFloat(positions[1]); - // We are done now - } - } - - public Vector2 Clone() - { - Vector2 n = new Vector2(x, y); - return n; - } - - @Override - public String toString() - { - return "<"+String.valueOf(x)+", "+String.valueOf(y) + ">"; - } - - - public CompoundTag serialize() - { - CompoundTag tag = new CompoundTag(); - tag.putFloat("x", x); - tag.putFloat("y", y); - - return tag; - } - - public Vector2(CompoundTag tag) { - this.deserialize(tag); - } - public void deserialize(CompoundTag tag) - { - x=tag.getFloat("x"); - y=tag.getFloat("y"); - } - - public boolean same(Vector2 other) - { - if(x == other.x && y==other.y)return true; - else return false; - } - - public boolean inside(Vector2 point1, Vector2 point2) - { - if(point1.x <= x && point2.x >= x){ - if(point1.y <= y && point2.y >= y) - { - return true; - } - } - - return false; - } - - public boolean greater(Vector2 other) - { - return ((x>other.x) && (y>other.y)); - } - public boolean less(Vector2 other) - { - return ((x>other.x) && (y>other.y)); - } - public boolean equal(Vector2 other) - { - return same(other); - } -} diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2f.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2f.java new file mode 100644 index 0000000..a6c33ed --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2f.java @@ -0,0 +1,200 @@ +package dev.zontreck.libzontreck.vectors; + +import dev.zontreck.libzontreck.api.Vector2; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.phys.Vec2; +import org.jetbrains.annotations.NotNull; + +public class Vector2f implements Vector2 +{ + public static final Vector2f ZERO = new Vector2f(0, 0); + + public float x; + public float y; + + @Override + public Vec2 asMinecraftVector(){ + return new Vec2(x, y); + } + + public Vector2f() + { + + } + + public Vector2f(float x, float y) + { + this.x=x; + this.y=y; + } + + public Vector2f(Vec2 pos) + { + x=pos.x; + y=pos.y; + } + + public static Vector2f parseString(String vector2) + { + Vector2f vec = new Vector2f(); + // This will be serialized most likely from the ToString method + // Parse + if(vector2.startsWith("<")) + { + vector2=vector2.substring(1, vector2.length()-1); // Rip off the ending bracket too + String[] positions = vector2.split(", "); + if(positions.length!=2) + { + positions = vector2.split(","); + } + + if(positions.length!=2) + { + return ZERO; + } + + vec.x = Float.parseFloat(positions[0]); + vec.y = Float.parseFloat(positions[1]); + // We are done now + } + + return vec; + } + + @Override + public Vector2f Clone() + { + Vector2f n = new Vector2f(x, y); + return n; + } + + @Override + public String toString() + { + return "<"+String.valueOf(x)+", "+String.valueOf(y) + ">"; + } + + @Override + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + tag.putFloat("x", x); + tag.putFloat("y", y); + + return tag; + } + + public static Vector2f deserialize(CompoundTag tag) + { + Vector2f vec = new Vector2f(); + vec.x=tag.getFloat("x"); + vec.y=tag.getFloat("y"); + + return vec; + } + + @Override + public boolean Same(Vector2 other) + { + Vector2f ov = other.asVector2f(); + if(x == ov.x && y == ov.y) return true; + return false; + } + + @Override + public boolean Inside(Vector2 point1, Vector2 point2) + { + Vector2f p1 = point1.asVector2f(); + Vector2f p2 = point2.asVector2f(); + + if(p1.x <= x && p2.x >= x){ + if(p1.y <= y && p2.y >= y) + { + return true; + } + } + + return false; + } + + @Override + public Vector2f asVector2f() + { + return this; + } + + @Override + public Vector2i asVector2i() { + return new Vector2i(Math.round(x), Math.round(y)); + } + + @Override + public boolean greater(Vector2 other) + { + Vector2f vec = other.asVector2f(); + return ((x > vec.x) && (y > vec.y)); + } + + @Override + public boolean less(Vector2 other) + { + Vector2f vec = other.asVector2f(); + return ((x > vec.x) && (y > vec.y)); + } + public boolean equal(Vector2 other) + { + return Same(other); + } + + @Override + public Vector2f add(Vector2 other) { + Vector2f vec = other.asVector2f(); + return new Vector2f(x + vec.x, y + vec.y); + } + + @Override + public Vector2f subtract(Vector2 other) { + Vector2f vec = other.asVector2f(); + return new Vector2f(x - vec.x, y - vec.y); + } + + @Override + public double distance(Vector2 other) { + Vector2f vec = subtract(other); + return Math.sqrt((vec.x * vec.x + vec.y * vec.y)); + } + + @Override + public Vector2f moveUp() { + return add(new Vector2f(0,1)); + } + + @Override + public Vector2f moveDown() { + return subtract(new Vector2f(0,1)); + } + + @Override + public Float getX() { + return x; + } + + @Override + public Float getY() { + return y; + } + + @Override + public int compareTo(@NotNull Vector2 other) { + if(other instanceof Vector2f v2f){ + + // Compare x coordinates first + int cmp = Float.compare(this.x, v2f.x); + if (cmp != 0) { + return cmp; + } + // If x coordinates are equal, compare y coordinates + return Float.compare(this.y, v2f.y); + } else return -1; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java index bce3c94..b5e90a7 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java @@ -1,16 +1,18 @@ package dev.zontreck.libzontreck.vectors; -import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; +import dev.zontreck.libzontreck.api.Vector2; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.phys.Vec2; +import org.jetbrains.annotations.NotNull; -public class Vector2i +public class Vector2i implements Vector2 { public static final Vector2i ZERO = new Vector2i(0, 0); public int x; public int y; + @Override public Vec2 asMinecraftVector(){ return new Vec2(x, y); } @@ -32,30 +34,34 @@ public class Vector2i y=(int)Math.floor(pos.y); } - public Vector2i(String pos) throws InvalidDeserialization + public static Vector2i parseString(String vector2) { + Vector2i vec = new Vector2i(); // This will be serialized most likely from the ToString method // Parse - if(pos.startsWith("<")) + if(vector2.startsWith("<")) { - pos=pos.substring(1, pos.length()-1); // Rip off the ending bracket too - String[] positions = pos.split(", "); + vector2=vector2.substring(1, vector2.length()-1); // Rip off the ending bracket too + String[] positions = vector2.split(", "); if(positions.length!=2) { - positions = pos.split(","); + positions = vector2.split(","); } if(positions.length!=2) { - throw new InvalidDeserialization("Positions must be in the same format provided by ToString() (ex. <1,1> or <1, 1>"); + return ZERO; } - this.x = Integer.parseInt(positions[0]); - this.y = Integer.parseInt(positions[1]); + vec.x = Integer.parseInt(positions[0]); + vec.y = Integer.parseInt(positions[1]); // We are done now } + + return vec; } + @Override public Vector2i Clone() { Vector2i n = new Vector2i(x, y); @@ -69,6 +75,7 @@ public class Vector2i } + @Override public CompoundTag serialize() { CompoundTag tag = new CompoundTag(); @@ -78,25 +85,33 @@ public class Vector2i return tag; } - public Vector2i(CompoundTag tag) { - this.deserialize(tag); - } - public void deserialize(CompoundTag tag) + public static Vector2i deserialize(CompoundTag tag) { - x=tag.getInt("x"); - y=tag.getInt("y"); + Vector2i vec = new Vector2i(); + + vec.x=tag.getInt("x"); + vec.y=tag.getInt("y"); + + return vec; } - public boolean same(Vector2i other) + @Override + public boolean Same(Vector2 other) { - if(x == other.x && y==other.y)return true; + Vector2i v2i = other.asVector2i(); + + if(x == v2i.x && y==v2i.y)return true; else return false; } - public boolean inside(Vector2i point1, Vector2i point2) + @Override + public boolean Inside(Vector2 point1, Vector2 point2) { - if(point1.x <= x && point2.x >= x){ - if(point1.y <= y && point2.y >= y) + Vector2i v1i = point1.asVector2i(); + Vector2i v2i = point2.asVector2i(); + + if(v1i.x <= x && v2i.x >= x){ + if(v1i.y <= y && v2i.y >= y) { return true; } @@ -105,16 +120,94 @@ public class Vector2i return false; } - public boolean greater(Vector2i other) - { - return ((x>other.x) && (y>other.y)); + @Override + public Vector2f asVector2f() { + return new Vector2f(x,y); } - public boolean less(Vector2i other) - { - return ((x>other.x) && (y>other.y)); + + @Override + public Vector2i asVector2i() { + return this; } - public boolean equal(Vector2i other) + + @Override + public boolean greater(Vector2 other) { - return same(other); + Vector2i vec = other.asVector2i(); + return ((x > vec.x) && (y > vec.y)); + } + + @Override + public boolean less(Vector2 other) + { + Vector2i vec = other.asVector2i(); + return ((x > vec.x) && (y > vec.y)); + } + + @Override + public boolean equal(Vector2 other) + { + return Same(other); + } + + @Override + public Vector2i add(Vector2 other) { + Vector2i vec = other.asVector2i(); + x += vec.x; + y += vec.y; + + return this; + } + + @Override + public Vector2i subtract(Vector2 other) { + Vector2i vec = other.asVector2i(); + + x -= vec.x; + y -= vec.y; + + return this; + } + + @Override + public double distance(Vector2 other) { + Vector2i vec = subtract(other); + return Math.sqrt((vec.x * vec.x + vec.y * vec.y)); + } + + + @Override + public Vector2i moveUp() { + return add(new Vector2i(0,1)); + } + + @Override + public Vector2i moveDown() { + return subtract(new Vector2i(0,1)); + } + + + @Override + public Integer getX() { + return x; + } + + @Override + public Integer getY() { + return y; + } + + @Override + public int compareTo(@NotNull Vector2 other) { + if(other instanceof Vector2i v2i){ + + // Compare x coordinates first + int cmp = Integer.compare(this.x, v2i.x); + if (cmp != 0) { + return cmp; + } + // If x coordinates are equal, compare y coordinates + return Integer.compare(this.y, v2i.y); + } else return -1; } } diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector3.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector3.java deleted file mode 100644 index c1cf9ee..0000000 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Vector3.java +++ /dev/null @@ -1,285 +0,0 @@ -package dev.zontreck.libzontreck.vectors; - -import java.util.ArrayList; -import java.util.List; - -import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Vec3i; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.world.phys.Vec3; - -public class Vector3 -{ - public static final Vector3 ZERO = new Vector3(0, 0, 0); - - - public double x; - public double y; - public double z; - - public Vec3 asMinecraftVector(){ - return new Vec3(x, y, z); - } - - public Vec3i asMinecraftVec3i() - { - return new Vec3i((int) Math.round(x), (int) Math.round(y), (int) Math.round(z)); - } - - public BlockPos asBlockPos() - { - return new BlockPos(asMinecraftVec3i()); - } - - public Vector3() - { - - } - - public Vector3(double x, double y, double z) - { - this.x=x; - this.y=y; - this.z=z; - } - - public Vector3(Vec3 pos) - { - x=pos.x; - y=pos.y; - z=pos.z; - } - - public Vector3(BlockPos pos) - { - x=pos.getX(); - y=pos.getY(); - z=pos.getZ(); - } - - public Vector3(String pos) throws InvalidDeserialization - { - // This will be serialized most likely from the ToString method - // Parse - if(pos.startsWith("<")) - { - pos=pos.substring(1, pos.length()-1); // Rip off the ending bracket too - String[] positions = pos.split(", "); - if(positions.length!=3) - { - positions = pos.split(","); - } - - if(positions.length!=3) - { - throw new InvalidDeserialization("Positions must be in the same format provided by ToString() (ex. <1,1,1> or <1, 1, 1>"); - } - - this.x = Double.parseDouble(positions[0]); - this.y = Double.parseDouble(positions[1]); - this.z = Double.parseDouble(positions[2]); - // We are done now - } - } - - public List makeCube(Vector3 other) - { - List vecs = new ArrayList<>(); - Vector3 work = new Vector3(); - - double xx = x; - double yy = y; - double zz = z; - - int yState = 0; - int zState = 0; - int xState = 0; - - for(xx = Math.round(x); (xx != Math.round(other.x) && xState != 2);) - { - for(zz = Math.round(z); (zz != Math.round(other.z) && zState != 2);) - { - for(yy = Math.round(y); (yy != Math.round(other.y) && yState != 2);) - { - work = new Vector3(xx, yy, zz); - - if(!vecs.contains(work)) vecs.add(work); - - if(yy > other.y) - { - yy -= 1.0; - if(yy == Math.round(other.y) && yState == 0) - { - yState++; - }else{ - if(yState == 1) - { - yState ++; - } - } - } else if(yy < other.y) - { - yy += 1.0; - if(yy == Math.round(other.y) && yState == 0){ - yState ++; - }else { - if(yState == 1)yState++; - } - } - } - - yState=0; - work = new Vector3(xx,yy,zz); - - if(!vecs.contains(work)) vecs.add(work); - - if(zz > other.z) - { - zz -= 1.0; - - if(zz == Math.round(other.z) && zState == 0)zState++; - else{ - if(zState == 1)zState++; - } - }else if(zz < other.z) - { - zz += 1.0; - - if(zz == Math.round(other.z) && zState == 0)zState++; - else { - if(zState==1)zState++; - } - } - } - - zState=0; - work = new Vector3(xx,yy,zz); - - if(!vecs.contains(work)) vecs.add(work); - - if(xx > other.x) - { - xx -= 1.0; - - if(xx == Math.round(other.x) && xState == 0) xState++; - else{ - if(xState == 1)xState++; - } - }else if(xx < other.x) - { - xx += 1.0; - - if(xx == Math.round(other.x) && xState==0)xState++; - else{ - if(xState==1)xState++; - } - } - } - - return vecs; - } - - public Vector3 subtract(Vector3 other) - { - return new Vector3(x-other.x, y-other.y, z-other.z); - } - public Vector3 add(Vector3 other) - { - return new Vector3(x+other.x, y+other.y, z +other.z); - } - - public double distance(Vector3 other) - { - Vector3 sub = subtract(other); - return Math.sqrt((sub.x * sub.x + sub.y * sub.y + sub.z * sub.z)); - } - - public Vector3 moveUp() - { - Vector3 up = Clone(); - up.y+=1; - return up; - } - public Vector3 moveDown() - { - Vector3 up = Clone(); - up.y-=1; - return up; - } - - - public Vector3 Clone() - { - Vector3 n = new Vector3(x, y, z); - return n; - } - - @Override - public String toString() - { - return "<"+String.valueOf(x)+", "+String.valueOf(y)+", "+String.valueOf(z)+">"; - } - - public NonAbsVector3 rounded() - { - NonAbsVector3 cl = new NonAbsVector3(this); - return cl; - } - - public CompoundTag serialize() - { - CompoundTag tag = new CompoundTag(); - tag.putDouble("x", x); - tag.putDouble("y", y); - tag.putDouble("z", z); - - return tag; - } - - public Vector3(CompoundTag tag) { - this.deserialize(tag); - } - public void deserialize(CompoundTag tag) - { - x=tag.getDouble("x"); - y=tag.getDouble("y"); - z=tag.getDouble("z"); - } - - - public boolean same(Vector3 other) - { - if(x == other.x && y==other.y && z==other.z)return true; - else return false; - } - - - public boolean inside(Vector3 point1, Vector3 point2) - { - if(point1.x <= x && point2.x >= x){ - if(point1.y <= y && point2.y >= y) - { - if(point1.z <= z && point2.z >= z) - { - return true; - } - } - } - - return false; - } - - public boolean greater(Vector3 other) - { - return ((x>other.x) && (y>other.y) && (z>other.z)); - } - public boolean less(Vector3 other) - { - return ((x +{ + public static final Vector3d ZERO = new Vector3d(0, 0, 0); + + + public double x; + public double y; + public double z; + + @Override + public Vec3 asMinecraftVector(){ + return new Vec3(x, y, z); + } + + @Override + public Vec3i asVec3i() + { + return new Vec3i((int) Math.round(x), (int) Math.round(y), (int) Math.round(z)); + } + + @Override + public BlockPos asBlockPos() + { + return new BlockPos(asVec3i()); + } + + public Vector3d() + { + + } + + public Vector3d(double x, double y, double z) + { + this.x=x; + this.y=y; + this.z=z; + } + + public Vector3d(Vec3 pos) + { + x=pos.x; + y=pos.y; + z=pos.z; + } + + public Vector3d(BlockPos pos) + { + x=pos.getX(); + y=pos.getY(); + z=pos.getZ(); + } + + public static Vector3d parseString(String vector3) + { + Vector3d vec = new Vector3d(); + // This will be serialized most likely from the ToString method + // Parse + if(vector3.startsWith("<")) + { + vector3=vector3.substring(1, vector3.length()-1); // Rip off the ending bracket too + String[] positions = vector3.split(", "); + if(positions.length!=3) + { + positions = vector3.split(","); + } + + if(positions.length!=3) + { + return ZERO; + } + + vec.x = Double.parseDouble(positions[0]); + vec.y = Double.parseDouble(positions[1]); + vec.z = Double.parseDouble(positions[2]); + // We are done now + } + + return vec; + } + + @Override + public Vector3d subtract(Vector3 other) + { + Vector3d vec = other.asVector3d(); + return new Vector3d(x-vec.x, y-vec.y, z-vec.z); + } + + @Override + public Vector3d add(Vector3 other) + { + Vector3d vec = other.asVector3d(); + return new Vector3d(x+vec.x, y+vec.y, z +vec.z); + } + + @Override + public double distance(Vector3 other) + { + Vector3d sub = subtract(other); + return Math.sqrt((sub.x * sub.x + sub.y * sub.y + sub.z * sub.z)); + } + + @Override + public Vector3d moveUp() + { + return add(new Vector3d(0,1,0)); + } + public Vector3d moveDown() + { + return subtract(new Vector3d(0,1,0)); + } + + + @Override + public Vector3d Clone() + { + return new Vector3d(x, y, z); + } + + @Override + public String toString() + { + return "<"+String.valueOf(x)+", "+String.valueOf(y)+", "+String.valueOf(z)+">"; + } + + public NonAbsVector3 rounded() + { + NonAbsVector3 cl = new NonAbsVector3(this); + return cl; + } + + @Override + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + tag.putDouble("x", x); + tag.putDouble("y", y); + tag.putDouble("z", z); + + return tag; + } + + public static Vector3d deserialize(CompoundTag tag) + { + Vector3d vec = new Vector3d(); + + vec.x=tag.getDouble("x"); + vec.y=tag.getDouble("y"); + vec.z=tag.getDouble("z"); + + return vec; + } + + @Override + public boolean Same(Vector3 other) + { + Vector3d vec = other.asVector3d(); + if(x == vec.x && y==vec.y && z==vec.z)return true; + else return false; + } + + + @Override + public boolean Inside(Vector3 point1, Vector3 point2) + { + Vector3d v1 = point1.asVector3d(); + Vector3d v2 = point2.asVector3d(); + + if(v1.x <= x && v2.x >= x){ + if(v1.y <= y && v2.y >= y) + { + if(v1.z <= z && v2.z >= z) + { + return true; + } + } + } + + return false; + } + + @Override + public Vector3d asVector3d() { + return this; + } + + @Override + public Vector3i asVector3i() { + return new Vector3i((int) Math.round(x), (int) Math.round(y), (int) Math.round(z)); + } + + @Override + public boolean greater(Vector3 other) + { + Vector3d vec = other.asVector3d(); + return ((x>vec.x) && (y>vec.y) && (z>vec.z)); + } + + @Override + public boolean less(Vector3 other) + { + Vector3d vec = other.asVector3d(); + return ((x doubleVector3) { + if(doubleVector3 instanceof Vector3d v3d) + { + int Ycomp = Double.compare(y, v3d.y); + if(Ycomp!=0)return Ycomp; + int Zcomp = Double.compare(z, v3d.z); + if(Zcomp!=0)return Zcomp; + int Xcomp = Double.compare(x, v3d.x); + if(Xcomp!=0)return Xcomp; + + return 0; + } else return -1; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector3i.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector3i.java new file mode 100644 index 0000000..4cc52b8 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Vector3i.java @@ -0,0 +1,246 @@ +package dev.zontreck.libzontreck.vectors; + +import dev.zontreck.libzontreck.api.Vector3; +import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class Vector3i implements Vector3 +{ + public static final Vector3i ZERO = new Vector3i(0, 0, 0); + + + public int x; + public int y; + public int z; + + @Override + public Vec3 asMinecraftVector(){ + return new Vec3(x, y, z); + } + + @Override + public Vec3i asVec3i() + { + return new Vec3i((int) Math.round(x), (int) Math.round(y), (int) Math.round(z)); + } + + @Override + public BlockPos asBlockPos() + { + return new BlockPos(asVec3i()); + } + + public Vector3i() + { + + } + + public Vector3i(int x, int y, int z) + { + this.x=x; + this.y=y; + this.z=z; + } + + public Vector3i(Vec3i pos) + { + x=pos.getX(); + y=pos.getY(); + z=pos.getZ(); + } + + public Vector3i(BlockPos pos) + { + x=pos.getX(); + y=pos.getY(); + z=pos.getZ(); + } + + public static Vector3i parseString(String vector3) throws InvalidDeserialization + { + Vector3i vec = new Vector3i(); + // This will be serialized most likely from the ToString method + // Parse + if(vector3.startsWith("<")) + { + vector3=vector3.substring(1, vector3.length()-1); // Rip off the ending bracket too + String[] positions = vector3.split(", "); + if(positions.length!=3) + { + positions = vector3.split(","); + } + + if(positions.length!=3) + { + throw new InvalidDeserialization("Positions must be in the same format provided by ToString() (ex. <1,1,1> or <1, 1, 1>"); + } + + vec.x = Integer.parseInt(positions[0]); + vec.y = Integer.parseInt(positions[1]); + vec.z = Integer.parseInt(positions[2]); + // We are done now + } + + return vec; + } + + @Override + public Vector3i subtract(Vector3 other) + { + Vector3i vec = other.asVector3i(); + return new Vector3i(x-vec.x, y-vec.y, z-vec.z); + } + + @Override + public Vector3i add(Vector3 other) + { + Vector3i vec = other.asVector3i(); + return new Vector3i(x+vec.x, y+vec.y, z +vec.z); + } + + @Override + public Vector3i asVector3i() { + return this; + } + + @Override + public Vector3d asVector3d() { + return new Vector3d(x, y, z); + } + + @Override + public double distance(Vector3 other) + { + Vector3i sub = subtract(other); + return Math.sqrt((sub.x * sub.x + sub.y * sub.y + sub.z * sub.z)); + } + + @Override + public Vector3i moveUp() + { + return add(new Vector3i(0,1,0)); + } + public Vector3i moveDown() + { + return subtract(new Vector3i(0,1,0)); + } + + @Override + public Vector3i Clone() + { + Vector3i n = new Vector3i(x, y, z); + return n; + } + + @Override + public String toString() + { + return "<"+String.valueOf(x)+", "+String.valueOf(y)+", "+String.valueOf(z)+">"; + } + + @Override + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + tag.putInt("x", x); + tag.putInt("y", y); + tag.putInt("z", z); + + return tag; + } + + public static Vector3i deserialize(CompoundTag tag) + { + Vector3i vec = new Vector3i(); + + vec.x=tag.getInt("x"); + vec.y=tag.getInt("y"); + vec.z=tag.getInt("z"); + + return vec; + } + + @Override + public boolean Same(Vector3 other) + { + Vector3i vec = other.asVector3i(); + if(x == vec.x && y==vec.y && z==vec.z)return true; + else return false; + } + + @Override + public boolean Inside(Vector3 point1, Vector3 point2) + { + Vector3i v1 = point1.asVector3i(); + Vector3i v2 = point2.asVector3i(); + + if(v1.x <= x && v2.x >= x){ + if(v1.y <= y && v2.y >= y) + { + if(v1.z <= z && v2.z >= z) + { + return true; + } + } + } + + return false; + } + + @Override + public boolean greater(Vector3 other) + { + Vector3i v3 = other.asVector3i(); + return ((x>v3.x) && (y>v3.y) && (z>v3.z)); + } + + @Override + public boolean less(Vector3 other) + { + Vector3i vec = other.asVector3i(); + return ((x integerVector3) { + if(integerVector3 instanceof Vector3i v3i) + { + int Ycomp = Integer.compare(y, v3i.y); + if(Ycomp!=0)return Ycomp; + int Zcomp = Integer.compare(z, v3i.z); + if(Zcomp!=0)return Zcomp; + int Xcomp = Integer.compare(x, v3i.x); + if(Xcomp!=0)return Xcomp; + + return 0; + } else return -1; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java b/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java index 2a2afad..3d62956 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java @@ -4,23 +4,26 @@ import dev.zontreck.libzontreck.LibZontreck; import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; +import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.server.ServerLifecycleHooks; -public class WorldPosition { +public class WorldPosition implements Cloneable +{ - public Vector3 Position; + public Vector3d Position; public String Dimension; public String DimSafe; public WorldPosition(CompoundTag tag, boolean pretty) throws InvalidDeserialization { if (pretty) { - Position = new Vector3(tag.getString("Position")); + Position = Vector3d.parseString(tag.getString("Position")); Dimension = tag.getString("Dimension"); } else { - Position = new Vector3(tag.getCompound("pos")); + Position = Vector3d.deserialize(tag.getCompound("pos")); Dimension = tag.getString("Dimension"); } @@ -28,17 +31,17 @@ public class WorldPosition { } - public WorldPosition(Vector3 pos, String dim) { + public WorldPosition(Vector3d pos, String dim) { Position = pos; Dimension = dim; calcDimSafe(); } public WorldPosition(ServerPlayer player) { - this(new Vector3(player.position()), player.getLevel()); + this(new Vector3d(player.position()), player.serverLevel()); } - public WorldPosition(Vector3 pos, ServerLevel lvl) { + public WorldPosition(Vector3d pos, ServerLevel lvl) { Position = pos; Dimension = lvl.dimension().location().getNamespace() + ":" + lvl.dimension().location().getPath(); calcDimSafe(); @@ -94,7 +97,7 @@ public class WorldPosition { ResourceLocation rl = new ResourceLocation(dims[0], dims[1]); ServerLevel dimL = null; - for (ServerLevel lServerLevel : LibZontreck.THE_SERVER.getAllLevels()) { + for (ServerLevel lServerLevel : ServerLifecycleHooks.getCurrentServer().getAllLevels()) { ResourceLocation XL = lServerLevel.dimension().location(); if (XL.getNamespace().equals(rl.getNamespace())) { @@ -114,14 +117,26 @@ public class WorldPosition { public boolean same(WorldPosition other) { - return Position.same(other.Position) && Dimension == other.Dimension; + return Position.Same(other.Position) && Dimension == other.Dimension; } public ChunkPos getChunkPos() { net.minecraft.world.level.ChunkPos mcChunk = getActualDimension().getChunkAt(Position.asBlockPos()).getPos(); - ChunkPos pos = new ChunkPos(new Vector3(mcChunk.getMinBlockX(), -70, mcChunk.getMinBlockZ()), new Vector3(mcChunk.getMaxBlockX(), 400, mcChunk.getMaxBlockZ()), getActualDimension()); - pos.centerPoints = new Vector2(mcChunk.getMiddleBlockX(), mcChunk.getMiddleBlockZ()); + ChunkPos pos = new ChunkPos(new Vector3d(mcChunk.getMinBlockX(), -70, mcChunk.getMinBlockZ()), new Vector3d(mcChunk.getMaxBlockX(), 400, mcChunk.getMaxBlockZ()), getActualDimension()); + pos.centerPoints = new Vector2f(mcChunk.getMiddleBlockX(), mcChunk.getMiddleBlockZ()); return pos; } + + @Override + public WorldPosition clone() { + try { + WorldPosition clone = (WorldPosition) super.clone(); + if(Position != null) + clone.Position = Position.Clone(); + return clone; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index ac70534..8cf3e73 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -6,32 +6,30 @@ # The name of the mod loader type to load - for regular FML @Mod mods it should be javafml modLoader="javafml" #mandatory # A version range to match for said mod loader - for regular FML @Mod it will be the forge version -loaderVersion="[43,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. +loaderVersion="${loader_version_range}" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. # The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. # Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. -license="GPLv2" +license="${mod_license}" # A URL to refer people to when problems occur with this mod -issueTrackerURL="https://github.com/zontreck/LibZontreckMod/issues/" #optional +#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional # A list of mods - how many allowed here is determined by the individual mod loader [[mods]] #mandatory # The modid of the mod -modId="libzontreck" #mandatory -# The version number of the mod - there's a few well known ${} variables useable here or just hardcode it -# ${file.jarVersion} will substitute the value of the Implementation-Version as read from the mod's JAR file metadata -# see the associated build.gradle script for how to populate this completely automatically during a build -version="${file.jarVersion}" #mandatory +modId="${mod_id}" #mandatory +# The version number of the mod +version="${mod_version}" #mandatory # A display name for the mod -displayName="LibZontreck" #mandatory -# A URL to query for updates for this mod. See the JSON update specification https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/ +displayName="${mod_name}" #mandatory +# A URL to query for updates for this mod. See the JSON update specification https://docs.minecraftforge.net/en/latest/misc/updatechecker/ #updateJSONURL="https://change.me.example.invalid/updates.json" #optional # A URL for the "homepage" for this mod, displayed in the mod UI #displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional # A file name (in the root of the mod JAR) containing a logo for display #logoFile="examplemod.png" #optional # A text field displayed in the mod UI -credits="Zontreck" #optional +#credits="" #optional # A text field displayed in the mod UI -authors="zontreck" #optional +authors="${mod_authors}" #optional # Display Test controls the display for your mod in the server connection screen # MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. # IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. @@ -41,26 +39,26 @@ authors="zontreck" #optional #displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) # The description text for the mod (multi line!) (#mandatory) -description=''' -This is a library mod for common code for all zontreck's mods. -''' +description='''${mod_description}''' # A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. -[[dependencies.libzontreck]] #optional +[[dependencies.${mod_id}]] #optional # the modid of the dependency modId="forge" #mandatory # Does this dependency have to exist - if not, ordering below must be specified mandatory=true #mandatory # The version range of the dependency -versionRange="[43,)" #mandatory -# An ordering relationship for the dependency - BEFORE or AFTER required if the relationship is not mandatory +versionRange="${forge_version_range}" #mandatory +# An ordering relationship for the dependency - BEFORE or AFTER required if the dependency is not mandatory +# BEFORE - This mod is loaded BEFORE the dependency +# AFTER - This mod is loaded AFTER the dependency ordering="NONE" -# Side this dependency is applied on - BOTH, CLIENT or SERVER +# Side this dependency is applied on - BOTH, CLIENT, or SERVER side="BOTH" # Here's another dependency -[[dependencies.libzontreck]] +[[dependencies.${mod_id}]] modId="minecraft" mandatory=true # This version range declares a minimum of the current minecraft version up to but not including the next major version -versionRange="[1.19,1.20)" +versionRange="${minecraft_version_range}" ordering="NONE" -side="BOTH" \ No newline at end of file +side="BOTH" diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index 954af3c..81a429d 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,8 +1,8 @@ { - "pack": { - "description": "LibZontreck resources", - "pack_format": 9, - "forge:resource_pack_format": 8, - "forge:data_pack_format": 9 - } -} \ No newline at end of file + "pack": { + "description": "${mod_id} resources", + "pack_format": 9, + "forge:resource_pack_format": 9, + "forge:data_pack_format": 10 + } +}