diff --git a/.gitattributes b/.gitattributes index 5d2d27e..aab7415 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,3 +10,5 @@ # are changed when only line endings change. src/generated/**/.cache/cache text eol=lf src/generated/**/*.json text eol=lf + +src/generated/** linguist-generated=true diff --git a/build.gradle b/build.gradle index 813e441..de2c767 100644 --- a/build.gradle +++ b/build.gradle @@ -109,9 +109,11 @@ dependencies { implementation "net.neoforged:neoforge:${neo_version}" runtimeOnly "curse.maven:emi-580555:5704405" -// runtimeOnly fg.deobf("curse.maven:jei-238222:4712868") + implementation "curse.maven:jei-238222:5802637" runtimeOnly "curse.maven:jade-324717:5493270" compileOnly "curse.maven:ftb-teams-forge-404468:5448371" + runtimeOnly "curse.maven:powah-rearchitected-633483:5735677" + runtimeOnly "curse.maven:cloth-config-348521:5729127" runtimeOnly "curse.maven:mystical-agriculture-246640:5654250" runtimeOnly "curse.maven:mystical-agradditions-256247:5648567" diff --git a/gradle.properties b/gradle.properties index 539dc28..0279e5c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -32,7 +32,7 @@ mod_name=Pylons # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=All Rights Reserved # The mod version. See https://semver.org/ -mod_version=5.0.2 +mod_version=5.1.0 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/generated/resources/assets/pylons/lang/en_us.json b/src/generated/resources/assets/pylons/lang/en_us.json index 61e7b48..b3e5bd5 100644 --- a/src/generated/resources/assets/pylons/lang/en_us.json +++ b/src/generated/resources/assets/pylons/lang/en_us.json @@ -6,6 +6,9 @@ "chat.pylons.expelled": "You have been expelled from %s's chunk!", "gui.pylons.blockedMobs": "Add mobs to prevent spawns:", "gui.pylons.effects": "Active potion effects:", + "gui.pylons.energyMissing": "Not enough power.", + "gui.pylons.fluxBar": "Redstone Flux:", + "gui.pylons.fluxData": "%d/%d RF stored", "gui.pylons.insideWorldSpawn": "Too close to world spawn.", "gui.pylons.inventoryFull": "Inventory is full.", "gui.pylons.inventoryMissing": "Place inventory above pylon.", @@ -23,6 +26,10 @@ "item.pylons.player_filter": "Player Filter", "item.pylons.potion_filter": "Potion Filter", "itemGroup.pylons": "Pylons", + "pylons.int.jei.category.expulsion_pylon": "This block allows you to expel other players from the selected chunk range (1x1 to 5x5 chunks).\nYou can whitelist players with a Player Filter.\n\nCan be toggled automatically with redstone.\n", + "pylons.int.jei.category.harvester_pylon": "Harvests crops in a radius (from 3x3 to 9x9 blocks) around the pylon and outputs to an inventory above.\nPlace the pylon inside the water block of the farm, or level with the crops.\n\nBy default (configurable) this will require a hoe in the pylon, and use 1 durability per harvest,\nunless the hoe is unbreakable. Unbreaking and other durability enchants are supported.\n\nOptionally can be configured to require power instead of durability.\n\nCan be toggled automatically with redstone.\n", + "pylons.int.jei.category.infusion_pylon": "Allows you to apply effects to yourself from any distance with activated Potion Filters.\nBy default (configurable) this will load the chunk it's placed in, while the owner is online.\n\nActivate potion filters by applying a potion effect to yourself,\nand then right clicking with the filter in hand to extract it.\n\nBy default (configurable) the minimum duration that can be extracted is 60 seconds,\nand the filter requires 1 hour of duration to activate.\n\nDuplicate potion filters can be combined by placing one in each hand and right-clicking.\n\nCan be toggled automatically with redstone.\n", + "pylons.int.jei.category.interdiction_pylon": "Prevents natural and forced spawns of specified mobs within the selected chunk range (1x1 to 5x5 chunks).\nAdd Mob Filters to specify which mobs to block.\n\nUsing the lifeless filter will instead block all mobs, but only natural spawns, with a much larger range.\n\nCan be toggled automatically with redstone.\n", "tooltip.pylons.activated": "Activated", "tooltip.pylons.effect_denied": "Effect is disabled in the config.", "tooltip.pylons.expulsion": "Used in the Expulsion Pylon.", diff --git a/src/main/java/net/permutated/pylons/ConfigManager.java b/src/main/java/net/permutated/pylons/ConfigManager.java index 6939e8c..2f6a74f 100644 --- a/src/main/java/net/permutated/pylons/ConfigManager.java +++ b/src/main/java/net/permutated/pylons/ConfigManager.java @@ -48,6 +48,9 @@ public static class ServerConfig { public final ModConfigSpec.IntValue harvesterWorkDelay; public final ModConfigSpec.BooleanValue harvesterRequiresTool; public final ModConfigSpec.BooleanValue harvesterCanBeAutomated; + public final ModConfigSpec.BooleanValue harvesterRequiresPower; + public final ModConfigSpec.IntValue harvesterPowerCost; + public final ModConfigSpec.IntValue harvesterPowerBuffer; ServerConfig(ModConfigSpec.Builder builder) { @@ -137,6 +140,19 @@ public static class ServerConfig { "By default, unbreakable tools are required for full automation.") .define("harvesterCanBeAutomated", false); + harvesterRequiresPower = builder + .comment("Whether the harvester requires power to work.", + "If enabled, it will disable the tool requirement and instead have an RF cost per block.") + .define("harvesterRequiresPower", false); + + harvesterPowerCost = builder + .comment("The RF cost per block harvested, if power usage is enabled.") + .defineInRange("harvesterPowerCost", 5, 1, 10_000); + + harvesterPowerBuffer = builder + .comment("Buffer size should be greater than power cost per block * 80.") + .defineInRange("harvesterPowerBuffer", 1_000, 100, 1_000_000); + builder.pop(); } } diff --git a/src/main/java/net/permutated/pylons/compat/jei/JEIPlugin.java b/src/main/java/net/permutated/pylons/compat/jei/JEIPlugin.java new file mode 100644 index 0000000..01d6733 --- /dev/null +++ b/src/main/java/net/permutated/pylons/compat/jei/JEIPlugin.java @@ -0,0 +1,30 @@ +package net.permutated.pylons.compat.jei; + +import mezz.jei.api.IModPlugin; +import mezz.jei.api.JeiPlugin; +import mezz.jei.api.constants.VanillaTypes; +import mezz.jei.api.registration.IRecipeRegistration; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.permutated.pylons.ModRegistry; +import net.permutated.pylons.util.Constants; +import net.permutated.pylons.util.ResourceUtil; + +import static net.permutated.pylons.util.TranslationKey.translateJei; + +@JeiPlugin +public class JEIPlugin implements IModPlugin { + + @Override + public ResourceLocation getPluginUid() { + return ResourceUtil.prefix("jei_plugin"); + } + + @Override + public void registerRecipes(IRecipeRegistration registration) { + registration.addIngredientInfo(new ItemStack(ModRegistry.EXPULSION_PYLON.get()), VanillaTypes.ITEM_STACK, translateJei(Constants.EXPULSION_PYLON)); + registration.addIngredientInfo(new ItemStack(ModRegistry.HARVESTER_PYLON.get()), VanillaTypes.ITEM_STACK, translateJei(Constants.HARVESTER_PYLON)); + registration.addIngredientInfo(new ItemStack(ModRegistry.INFUSION_PYLON.get()), VanillaTypes.ITEM_STACK, translateJei(Constants.INFUSION_PYLON)); + registration.addIngredientInfo(new ItemStack(ModRegistry.INTERDICTION_PYLON.get()), VanillaTypes.ITEM_STACK, translateJei(Constants.INTERDICTION_PYLON)); + } +} diff --git a/src/main/java/net/permutated/pylons/data/client/Languages.java b/src/main/java/net/permutated/pylons/data/client/Languages.java index 2fef783..293f7a2 100644 --- a/src/main/java/net/permutated/pylons/data/client/Languages.java +++ b/src/main/java/net/permutated/pylons/data/client/Languages.java @@ -4,9 +4,11 @@ import net.neoforged.neoforge.common.data.LanguageProvider; import net.permutated.pylons.ModRegistry; import net.permutated.pylons.Pylons; +import net.permutated.pylons.util.Constants; import static net.permutated.pylons.util.TranslationKey.chat; import static net.permutated.pylons.util.TranslationKey.gui; +import static net.permutated.pylons.util.TranslationKey.jei; import static net.permutated.pylons.util.TranslationKey.tab; import static net.permutated.pylons.util.TranslationKey.tooltip; @@ -41,6 +43,7 @@ protected void addTranslations() { add(gui("insideWorldSpawn"), "Too close to world spawn."); add(gui("toolMissing"), "Hoe required for operation."); add(gui("inventoryMissing"), "Place inventory above pylon."); + add(gui("energyMissing"), "Not enough power."); add(gui("inventoryFull"), "Inventory is full."); add(gui("working"), "Pylon is working."); add(gui("whitelist"), "Add players to whitelist:"); @@ -49,6 +52,8 @@ protected void addTranslations() { add(gui("workArea"), "Work area (in chunks)"); add(gui("workAreaBlocks"), "Work area (in blocks)"); add(gui("toggleWork"), "Working status"); + add(gui("fluxBar"), "Redstone Flux:"); + add(gui("fluxData"), "%d/%d RF stored"); add(tab(), "Pylons"); add(chat("expelled"), "You have been expelled from %s's chunk!"); @@ -95,6 +100,49 @@ protected void addTranslations() { add(tooltip("expulsion"), "Used in the Expulsion Pylon."); add(tooltip("infusion"), "Used in the Infusion Pylon."); add(tooltip("interdiction"), "Used in the Interdiction Pylon."); + + add(jei(Constants.HARVESTER_PYLON), """ +Harvests crops in a radius (from 3x3 to 9x9 blocks) around the pylon and outputs to an inventory above. +Place the pylon inside the water block of the farm, or level with the crops. + +By default (configurable) this will require a hoe in the pylon, and use 1 durability per harvest, +unless the hoe is unbreakable. Unbreaking and other durability enchants are supported. + +Optionally can be configured to require power instead of durability. + +Can be toggled automatically with redstone. + """); + + add(jei(Constants.INFUSION_PYLON), """ +Allows you to apply effects to yourself from any distance with activated Potion Filters. +By default (configurable) this will load the chunk it's placed in, while the owner is online. + +Activate potion filters by applying a potion effect to yourself, +and then right clicking with the filter in hand to extract it. + +By default (configurable) the minimum duration that can be extracted is 60 seconds, +and the filter requires 1 hour of duration to activate. + +Duplicate potion filters can be combined by placing one in each hand and right-clicking. + +Can be toggled automatically with redstone. + """); + + add(jei(Constants.EXPULSION_PYLON), """ +This block allows you to expel other players from the selected chunk range (1x1 to 5x5 chunks). +You can whitelist players with a Player Filter. + +Can be toggled automatically with redstone. + """); + + add(jei(Constants.INTERDICTION_PYLON), """ +Prevents natural and forced spawns of specified mobs within the selected chunk range (1x1 to 5x5 chunks). +Add Mob Filters to specify which mobs to block. + +Using the lifeless filter will instead block all mobs, but only natural spawns, with a much larger range. + +Can be toggled automatically with redstone. + """); } } } diff --git a/src/main/java/net/permutated/pylons/machines/base/AbstractPylonBlock.java b/src/main/java/net/permutated/pylons/machines/base/AbstractPylonBlock.java index 0fd7c8a..7f33772 100644 --- a/src/main/java/net/permutated/pylons/machines/base/AbstractPylonBlock.java +++ b/src/main/java/net/permutated/pylons/machines/base/AbstractPylonBlock.java @@ -22,6 +22,9 @@ 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.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.material.MapColor; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.shapes.CollisionContext; @@ -37,6 +40,7 @@ import javax.annotation.Nullable; public abstract class AbstractPylonBlock extends Block implements EntityBlock { + public static final BooleanProperty ENABLED = BlockStateProperties.ENABLED; private static final VoxelShape SHAPE = Shapes.or( Block.box(0.0D, 0.0D, 0.0D, 16.0D, 3.0D, 16.0D), Block.box(1.0D, 3.0D, 1.0D, 15.0D, 16.0D, 15.0D) @@ -44,6 +48,13 @@ public abstract class AbstractPylonBlock extends Block implements EntityBlock { protected AbstractPylonBlock() { super(Properties.of().mapColor(MapColor.METAL).strength(INDESTRUCTIBLE, 1200F)); + this.registerDefaultState(this.defaultBlockState() + .setValue(ENABLED, Boolean.TRUE)); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + builder.add(ENABLED); } @Override @@ -120,6 +131,29 @@ public void onRemove(BlockState state, Level level, BlockPos pos, BlockState new } } + @Override + @SuppressWarnings("java:S1874") // deprecated method from super class + public void onPlace(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) { + if (!newState.is(state.getBlock())) { + this.checkPoweredState(level, pos, state); + } + } + + @Override + @SuppressWarnings("java:S1874") // deprecated method from super class + public void neighborChanged(BlockState state, Level level, BlockPos pos, Block block, BlockPos neighborPos, boolean isMoving) { + this.checkPoweredState(level, pos, state); + } + + //TODO avoid chunk loading? + private void checkPoweredState(Level level, BlockPos pos, BlockState state) { + boolean flag = !level.hasNeighborSignal(pos); + if (!Boolean.valueOf(flag).equals((state.getValue(ENABLED)))) { + level.setBlock(pos, state.setValue(ENABLED, flag), Block.UPDATE_ALL); + } + + } + @Override protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult blockRayTraceResult) { if (!world.isClientSide) { diff --git a/src/main/java/net/permutated/pylons/machines/base/AbstractPylonContainer.java b/src/main/java/net/permutated/pylons/machines/base/AbstractPylonContainer.java index c87b304..7aded89 100644 --- a/src/main/java/net/permutated/pylons/machines/base/AbstractPylonContainer.java +++ b/src/main/java/net/permutated/pylons/machines/base/AbstractPylonContainer.java @@ -34,9 +34,15 @@ public abstract class AbstractPylonContainer extends AbstractContainerMenu { protected final DataHolder dataHolder; protected final String ownerName; protected final BlockPos blockPos; + protected final boolean usesEnergy; protected AbstractPylonContainer(@Nullable MenuType containerType, int windowId, Inventory playerInventory, FriendlyByteBuf packetBuffer) { + this(containerType, windowId, playerInventory, packetBuffer, false); + } + + protected AbstractPylonContainer(@Nullable MenuType containerType, int windowId, Inventory playerInventory, FriendlyByteBuf packetBuffer, boolean usesEnergy) { super(containerType, windowId); + this.usesEnergy = usesEnergy; blockPos = packetBuffer.readBlockPos(); Level level = playerInventory.player.getCommandSenderWorld(); @@ -134,6 +140,7 @@ public ItemStack quickMoveStack(Player playerIn, int index) { } public void registerHandlerSlots(IItemHandler inventory) { + if (usesEnergy) return; for (int slot = 0; slot < AbstractPylonTile.SLOTS; slot++) { addSlot(new SlotItemHandler(inventory, slot, 8 + slot * 18, 54)); } @@ -154,6 +161,28 @@ public void registerPlayerSlots(IItemHandler wrappedInventory) { public void registerDataSlots() { addDataSlot(dataHolder::getRange, dataHolder::setRange); addDataSlot(dataHolder::getEnabled, dataHolder::setEnabled); + + // split max energy + addDataSlot(() -> dataHolder.getMaxEnergy() & 0xffff, value -> { + int energyStored = dataHolder.getMaxEnergy() & 0xffff0000; + dataHolder.setMaxEnergy(energyStored + (value & 0xffff)); + }); + + addDataSlot(() -> (dataHolder.getMaxEnergy() >> 16) & 0xffff, value -> { + int energyStored = dataHolder.getMaxEnergy() & 0x0000ffff; + dataHolder.setMaxEnergy(energyStored | (value << 16)); + }); + + // split current energy + addDataSlot(() -> dataHolder.getEnergy() & 0xffff, value -> { + int energyStored = dataHolder.getEnergy() & 0xffff0000; + dataHolder.setEnergy(energyStored + (value & 0xffff)); + }); + + addDataSlot(() -> (dataHolder.getEnergy() >> 16) & 0xffff, value -> { + int energyStored = dataHolder.getEnergy() & 0x0000ffff; + dataHolder.setEnergy(energyStored | (value << 16)); + }); } private void addDataSlot(IntSupplier getter, IntConsumer setter) { diff --git a/src/main/java/net/permutated/pylons/machines/base/AbstractPylonScreen.java b/src/main/java/net/permutated/pylons/machines/base/AbstractPylonScreen.java index c883449..f5090ad 100644 --- a/src/main/java/net/permutated/pylons/machines/base/AbstractPylonScreen.java +++ b/src/main/java/net/permutated/pylons/machines/base/AbstractPylonScreen.java @@ -9,18 +9,28 @@ import net.minecraft.network.chat.MutableComponent; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.player.Inventory; +import net.permutated.pylons.ConfigManager; import net.permutated.pylons.util.Constants; import net.permutated.pylons.util.ResourceUtil; +import net.permutated.pylons.util.TextureHolder; import net.permutated.pylons.util.TranslationKey; +import java.util.List; + public abstract class AbstractPylonScreen extends AbstractContainerScreen { protected final ResourceLocation gui; + protected final boolean usesEnergy; protected Button workButton; protected Button rangeButton; - protected AbstractPylonScreen(T container, Inventory inv, Component name) { + protected AbstractPylonScreen(T container, Inventory inventory, Component title) { + this(container, inventory, title, false); + } + + protected AbstractPylonScreen(T container, Inventory inv, Component name, boolean usesEnergy) { super(container, inv, name); - this.gui = ResourceUtil.gui("pylon"); + this.usesEnergy = ConfigManager.SERVER.harvesterRequiresPower.getAsBoolean(); + this.gui = usesEnergy ? ResourceUtil.gui("pylon_energy") : ResourceUtil.gui("pylon"); this.imageWidth = 176; this.imageHeight = 172; this.inventoryLabelY = this.imageHeight - 94; @@ -81,6 +91,19 @@ protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, in int relX = (this.width - this.imageWidth) / 2; int relY = (this.height - this.imageHeight) / 2; graphics.blit(gui, relX, relY, 0, 0, this.imageWidth, this.imageHeight); + + if (usesEnergy) { + float energyFraction = this.menu.dataHolder.getEnergyFraction(); + var energyHolder = new TextureHolder(8, 54, 0, 172, 160, 16); + graphics.blit(gui, + relX + energyHolder.progressOffsetX(), + relY + energyHolder.progressOffsetY(), + energyHolder.textureOffsetX(), + energyHolder.textureOffsetY(), + energyHolder.getWidthFraction(energyFraction), + energyHolder.textureHeight() + ); + } } @Override @@ -97,6 +120,17 @@ protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) { drawText(graphics, component, 30); } + @Override + protected void renderTooltip(GuiGraphics guiGraphics, int x, int y) { + super.renderTooltip(guiGraphics, x, y); + if (usesEnergy && this.isHovering(8, 54, 160, 16, x, y)) { + guiGraphics.renderComponentTooltip(this.font, List.of( + translate("fluxBar"), + translate("fluxData", this.menu.dataHolder.getEnergy(), this.menu.dataHolder.getMaxEnergy()) + ), x, y); + } + } + protected void drawText(GuiGraphics graphics, Component component, int yPos) { graphics.drawString(this.font, component, 8, yPos, 4210752, false); } diff --git a/src/main/java/net/permutated/pylons/machines/base/AbstractPylonTile.java b/src/main/java/net/permutated/pylons/machines/base/AbstractPylonTile.java index ac9a426..db5b248 100644 --- a/src/main/java/net/permutated/pylons/machines/base/AbstractPylonTile.java +++ b/src/main/java/net/permutated/pylons/machines/base/AbstractPylonTile.java @@ -10,6 +10,7 @@ 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.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; @@ -34,11 +35,15 @@ public abstract class AbstractPylonTile extends BlockEntity { protected AbstractPylonTile(BlockEntityType tileEntityType, BlockPos pos, BlockState state) { super(tileEntityType, pos, state); + int buffer = ConfigManager.SERVER.harvesterPowerBuffer.getAsInt(); + energyStorage = new PylonEnergyStorage(this::setChanged, buffer, buffer); } public static final int SLOTS = 9; public static final UUID NONE = new UUID(0,0); + protected final PylonEnergyStorage energyStorage; + protected final ItemStackHandler itemStackHandler = new PylonItemStackHandler(SLOTS) { @Override public boolean isItemValid(int slot, @Nonnull ItemStack stack) { @@ -59,6 +64,7 @@ protected boolean canAccessInventory() { */ public static void registerCapabilities(RegisterCapabilitiesEvent event, BlockEntityType blockEntityType) { event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, blockEntityType, (pylon, side) -> pylon.canAccessInventory() ? pylon.itemStackHandler : null); + event.registerBlockEntity(Capabilities.EnergyStorage.BLOCK, blockEntityType, (pylon, side) -> ConfigManager.SERVER.harvesterRequiresPower.getAsBoolean() ? pylon.energyStorage : null); } public void dropItems() { @@ -150,7 +156,7 @@ public void updateContainer(FriendlyByteBuf packetBuffer) { String username = getOwnerName(); packetBuffer.writeBlockPos(worldPosition); - packetBuffer.writeInt(shouldWork ? 1 : 0); + packetBuffer.writeInt(shouldWork() ? 1 : 0); packetBuffer.writeInt(getSelectedRange()); packetBuffer.writeInt(username.length()); packetBuffer.writeUtf(username); @@ -160,8 +166,8 @@ public void updateContainer(FriendlyByteBuf packetBuffer) { @Override protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) { tag.put(Constants.NBT.INV, itemStackHandler.serializeNBT(registries)); + tag.put(Constants.NBT.ENERGY, energyStorage.serializeNBT(registries)); tag.put(Constants.NBT.RANGE, range.serializeNBT()); - tag.putBoolean(Constants.NBT.ENABLED, shouldWork); tag.putUUID(Constants.NBT.OWNER, owner); } @@ -169,8 +175,8 @@ protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) @Override public void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) { itemStackHandler.deserializeNBT(registries, tag.getCompound(Constants.NBT.INV)); + energyStorage.deserializeNBT(registries, tag.get(Constants.NBT.ENERGY)); range.deserializeNBT(tag.getCompound(Constants.NBT.RANGE)); - shouldWork = tag.getBoolean(Constants.NBT.ENABLED); owner = tag.getUUID(Constants.NBT.OWNER); super.loadAdditional(tag, registries); } @@ -209,18 +215,14 @@ protected void onContentsChanged(int slot) { } } - protected boolean shouldWork = true; - public boolean shouldWork() { - return shouldWork; + return getBlockState().getValue(AbstractPylonBlock.ENABLED); } public void handleWorkPacket() { if (this.level != null) { - shouldWork = !shouldWork; - this.setChanged(); - this.level.sendBlockUpdated(getBlockPos(), getBlockState(), getBlockState(), 2); - + boolean shouldWork = !shouldWork(); + level.setBlock(getBlockPos(), getBlockState().setValue(AbstractPylonBlock.ENABLED, shouldWork), Block.UPDATE_ALL); if (!shouldWork) removeChunkloads(); } } diff --git a/src/main/java/net/permutated/pylons/machines/base/DataHolder.java b/src/main/java/net/permutated/pylons/machines/base/DataHolder.java index 962688b..b937351 100644 --- a/src/main/java/net/permutated/pylons/machines/base/DataHolder.java +++ b/src/main/java/net/permutated/pylons/machines/base/DataHolder.java @@ -1,9 +1,23 @@ package net.permutated.pylons.machines.base; +import net.minecraft.util.Mth; + public interface DataHolder { int getEnabled(); int getRange(); + int getEnergy(); + int getMaxEnergy(); default void setEnabled(int enabled) {} default void setRange(int range) {} + default void setEnergy(int energy) {} + default void setMaxEnergy(int maxEnergy) {} + + default float getEnergyFraction() { + if (getEnergy() == 0) { + return 0f; + } else { + return ((float) Mth.clamp(getEnergy(), 0, getMaxEnergy())) / getMaxEnergy(); + } + } } diff --git a/src/main/java/net/permutated/pylons/machines/base/DataHolderClient.java b/src/main/java/net/permutated/pylons/machines/base/DataHolderClient.java index a37f549..dbc37e9 100644 --- a/src/main/java/net/permutated/pylons/machines/base/DataHolderClient.java +++ b/src/main/java/net/permutated/pylons/machines/base/DataHolderClient.java @@ -3,6 +3,8 @@ public class DataHolderClient implements DataHolder { private int enabled; private int range; + private int energy; + private int maxEnergy; @Override public int getEnabled() { @@ -14,6 +16,16 @@ public int getRange() { return range; } + @Override + public int getEnergy() { + return energy; + } + + @Override + public int getMaxEnergy() { + return maxEnergy; + } + @Override public void setEnabled(int enabled) { this.enabled = enabled; @@ -23,4 +35,14 @@ public void setEnabled(int enabled) { public void setRange(int range) { this.range = range; } + + @Override + public void setEnergy(int energy) { + this.energy = energy; + } + + @Override + public void setMaxEnergy(int maxEnergy) { + this.maxEnergy = maxEnergy; + } } diff --git a/src/main/java/net/permutated/pylons/machines/base/DataHolderServer.java b/src/main/java/net/permutated/pylons/machines/base/DataHolderServer.java index c75ce90..893ee47 100644 --- a/src/main/java/net/permutated/pylons/machines/base/DataHolderServer.java +++ b/src/main/java/net/permutated/pylons/machines/base/DataHolderServer.java @@ -15,4 +15,14 @@ public int getEnabled() { public int getRange() { return blockEntity.getSelectedRange(); } + + @Override + public int getEnergy() { + return blockEntity.energyStorage.getEnergyStored(); + } + + @Override + public int getMaxEnergy() { + return blockEntity.energyStorage.getMaxEnergyStored(); + } } diff --git a/src/main/java/net/permutated/pylons/machines/base/PylonEnergyStorage.java b/src/main/java/net/permutated/pylons/machines/base/PylonEnergyStorage.java new file mode 100644 index 0000000..9aa9a63 --- /dev/null +++ b/src/main/java/net/permutated/pylons/machines/base/PylonEnergyStorage.java @@ -0,0 +1,58 @@ +package net.permutated.pylons.machines.base; + +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.Tag; +import net.neoforged.neoforge.energy.EnergyStorage; + +import javax.annotation.Nullable; + +public class PylonEnergyStorage extends EnergyStorage { + + private final Runnable listener; + + public PylonEnergyStorage(Runnable listener, int capacity, int maxRecieve) { + super(capacity, maxRecieve, 0); + this.listener = listener; + } + + public void onEnergyChanged() { + listener.run(); + } + + @Override + public int receiveEnergy(int maxReceive, boolean simulate) { + int rc = super.receiveEnergy(maxReceive, simulate); + if (rc > 0 && !simulate) { + onEnergyChanged(); + } + return rc; + } + + @Override + public int extractEnergy(int maxExtract, boolean simulate) { + int rc = super.extractEnergy(maxExtract, simulate); + if (rc > 0 && !simulate) { + onEnergyChanged(); + } + return rc; + } + + public boolean consumeEnergy(int request, boolean simulate) { + int consumed = Math.max(0, request); + if (this.energy > consumed) { + if (!simulate) { + this.energy -= consumed; + onEnergyChanged(); + } + return true; + } + return false; + } + + @Override + public void deserializeNBT(HolderLookup.Provider provider, @Nullable Tag nbt) { + if (nbt != null) { + super.deserializeNBT(provider, nbt); + } + } +} diff --git a/src/main/java/net/permutated/pylons/machines/harvester/HarvesterPylonBlock.java b/src/main/java/net/permutated/pylons/machines/harvester/HarvesterPylonBlock.java index 9e0d6d4..f0a7a2d 100644 --- a/src/main/java/net/permutated/pylons/machines/harvester/HarvesterPylonBlock.java +++ b/src/main/java/net/permutated/pylons/machines/harvester/HarvesterPylonBlock.java @@ -36,6 +36,7 @@ public HarvesterPylonBlock() { @Override protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); builder.add(WATERLOGGED); } diff --git a/src/main/java/net/permutated/pylons/machines/harvester/HarvesterPylonContainer.java b/src/main/java/net/permutated/pylons/machines/harvester/HarvesterPylonContainer.java index 25a5a57..37112cd 100644 --- a/src/main/java/net/permutated/pylons/machines/harvester/HarvesterPylonContainer.java +++ b/src/main/java/net/permutated/pylons/machines/harvester/HarvesterPylonContainer.java @@ -11,7 +11,7 @@ public class HarvesterPylonContainer extends AbstractPylonContainer { private final HarvesterPylonTile.Status workStatus; public HarvesterPylonContainer(int windowId, Inventory playerInventory, FriendlyByteBuf packetBuffer) { - super(ModRegistry.HARVESTER_PYLON_CONTAINER.get(), windowId, playerInventory, packetBuffer); + super(ModRegistry.HARVESTER_PYLON_CONTAINER.get(), windowId, playerInventory, packetBuffer, HarvesterPylonTile.requiresPower()); workStatus = packetBuffer.readEnum(HarvesterPylonTile.Status.class); } diff --git a/src/main/java/net/permutated/pylons/machines/harvester/HarvesterPylonScreen.java b/src/main/java/net/permutated/pylons/machines/harvester/HarvesterPylonScreen.java index e7ab62c..092ebfc 100644 --- a/src/main/java/net/permutated/pylons/machines/harvester/HarvesterPylonScreen.java +++ b/src/main/java/net/permutated/pylons/machines/harvester/HarvesterPylonScreen.java @@ -10,7 +10,7 @@ @SuppressWarnings("java:S110") // inheritance required public class HarvesterPylonScreen extends AbstractPylonScreen { public HarvesterPylonScreen(HarvesterPylonContainer container, Inventory inv, Component name) { - super(container, inv, name); + super(container, inv, name, HarvesterPylonTile.requiresPower()); } @Override @@ -28,6 +28,9 @@ protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) { case MISSING_TOOL: drawText(graphics, translate("toolMissing").withStyle(ChatFormatting.RED), 42); break; + case MISSING_ENERGY: + drawText(graphics, translate("energyMissing").withStyle(ChatFormatting.RED), 42); + break; case INVENTORY_FULL: drawText(graphics, translate("inventoryFull").withStyle(ChatFormatting.RED), 42); break; diff --git a/src/main/java/net/permutated/pylons/machines/harvester/HarvesterPylonTile.java b/src/main/java/net/permutated/pylons/machines/harvester/HarvesterPylonTile.java index 6e03162..2797ae1 100644 --- a/src/main/java/net/permutated/pylons/machines/harvester/HarvesterPylonTile.java +++ b/src/main/java/net/permutated/pylons/machines/harvester/HarvesterPylonTile.java @@ -42,7 +42,15 @@ protected boolean canAccessInventory() { } private boolean requiresTool() { - return Boolean.TRUE.equals(ConfigManager.SERVER.harvesterRequiresTool.get()); + return !requiresPower() && ConfigManager.SERVER.harvesterRequiresTool.getAsBoolean(); + } + + public static boolean requiresPower() { + return ConfigManager.SERVER.harvesterRequiresPower.getAsBoolean(); + } + + private int getPowerCost() { + return ConfigManager.SERVER.harvesterPowerCost.getAsInt(); } private int getWorkDelay() { @@ -58,6 +66,7 @@ public enum Status { MISSING_INVENTORY, INVENTORY_FULL, UPDATE_ERROR, + MISSING_ENERGY, } @@ -135,7 +144,14 @@ public void tick() { continue; } - if (requiresTool()) { + if (requiresPower()) { + if (!energyStorage.consumeEnergy(getPowerCost(), true)) { + workStatus = Status.MISSING_ENERGY; + return; + } else { + energyStorage.consumeEnergy(getPowerCost(), false); + } + } else if (requiresTool()) { if (hoeSlot == -1) { workStatus = Status.MISSING_TOOL; return; @@ -183,13 +199,21 @@ public void tick() { int age = blockState.getValue(recipe.getAgeProperty()); if (age > recipe.getMinAge() && age == recipe.getMaxAge()) { - if (requiresTool()) { + if (requiresPower()) { + if (!energyStorage.consumeEnergy(getPowerCost(), true)) { + workStatus = Status.MISSING_ENERGY; + return; + } else { + energyStorage.consumeEnergy(getPowerCost(), false); + } + } else if (requiresTool()) { if (hoeSlot == -1) { workStatus = Status.MISSING_TOOL; return; } else { ItemStack replace = itemStackHandler.getStackInSlot(hoeSlot).copy(); - replace.hurtAndBreak(1, serverLevel, null, item -> {}); + replace.hurtAndBreak(1, serverLevel, null, item -> { + }); itemStackHandler.setStackInSlot(hoeSlot, replace); } } diff --git a/src/main/java/net/permutated/pylons/util/TextureHolder.java b/src/main/java/net/permutated/pylons/util/TextureHolder.java new file mode 100644 index 0000000..ab69ab8 --- /dev/null +++ b/src/main/java/net/permutated/pylons/util/TextureHolder.java @@ -0,0 +1,14 @@ +package net.permutated.pylons.util; + +public record TextureHolder(int progressOffsetX, int progressOffsetY, + int textureOffsetX, int textureOffsetY, + int textureWidth, int textureHeight) { + + public int getWidthFraction(float fraction) { + return (int) (textureWidth * fraction); + } + + public int progressWidthOffset(float fraction) { + return progressOffsetX + (textureWidth - (int) (textureWidth * fraction)); + } +} diff --git a/src/main/java/net/permutated/pylons/util/TranslationKey.java b/src/main/java/net/permutated/pylons/util/TranslationKey.java index 010526c..d0ba1a9 100644 --- a/src/main/java/net/permutated/pylons/util/TranslationKey.java +++ b/src/main/java/net/permutated/pylons/util/TranslationKey.java @@ -1,5 +1,7 @@ package net.permutated.pylons.util; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; import net.permutated.pylons.Pylons; public class TranslationKey { @@ -36,4 +38,8 @@ public static String jei(String key) { public static String chat(String key) { return String.format(FORMAT, "chat", key); } + + public static MutableComponent translateJei(String key) { + return Component.translatable(TranslationKey.jei(key)); + } } diff --git a/src/main/resources/assets/pylons/textures/gui/pylon_energy.png b/src/main/resources/assets/pylons/textures/gui/pylon_energy.png new file mode 100644 index 0000000..dbc3692 Binary files /dev/null and b/src/main/resources/assets/pylons/textures/gui/pylon_energy.png differ