From 669fe0c31721e072366c0a0676495ba9a69553bd Mon Sep 17 00:00:00 2001 From: worador Date: Fri, 9 Jan 2026 16:22:32 +0100 Subject: [PATCH 1/3] Complete codebase overhaul and UI/Network optimizations --- .vscode/launch.json | 93 + .vscode/settings.json | 4 + changelog.txt | 9 + gradle.properties | 2 +- gradlew | 0 overhaul_code.txt | 4101 +++++++++++++++++ .../generatorgalore/GeneratorGalore.java | 66 +- .../common/block/Generator.java | 22 +- .../block/entity/GeneratorBlockEntity.java | 160 +- .../common/container/GeneratorScreen.java | 126 +- .../data/LootDataProvider.java | 11 +- .../integrations/FluidFuelRecipeCategory.java | 3 +- .../integrations/JeiPlugin.java | 4 +- .../integrations/SolidFuelRecipeCategory.java | 3 +- .../network/FluidSyncPacket.java | 25 + .../generatorgalore/network/ModPackets.java | 26 + .../registry/GeneratorRegistry.java | 7 +- .../util/FluidContainerUtil.java | 124 +- .../util/GeneratorCreator.java | 23 +- .../generatorgalore/util/GeneratorObject.java | 40 +- 20 files changed, 4636 insertions(+), 213 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json mode change 100644 => 100755 gradlew create mode 100644 overhaul_code.txt create mode 100644 src/main/java/cy/jdkdigital/generatorgalore/network/FluidSyncPacket.java create mode 100644 src/main/java/cy/jdkdigital/generatorgalore/network/ModPackets.java diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..18d1513 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,93 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "request": "launch", + "name": "Client", + "presentation": { + "group": "Mod Development - generatorgalore", + "order": 0 + }, + "projectName": "generatorgalore", + "mainClass": "net.neoforged.devlaunch.Main", + "args": [ + "@/home/worador/Development/Neo-Forge/generatorgalore/build/moddev/clientRunProgramArgs.txt" + ], + "vmArgs": [ + "@/home/worador/Development/Neo-Forge/generatorgalore/build/moddev/clientRunVmArgs.txt", + "-Dfml.modFolders\u003dgeneratorgalore%%/home/worador/Development/Neo-Forge/generatorgalore/bin/main" + ], + "cwd": "${workspaceFolder}/run", + "env": {}, + "console": "internalConsole", + "shortenCommandLine": "none" + }, + { + "type": "java", + "request": "launch", + "name": "Data", + "presentation": { + "group": "Mod Development - generatorgalore", + "order": 1 + }, + "projectName": "generatorgalore", + "mainClass": "net.neoforged.devlaunch.Main", + "args": [ + "@/home/worador/Development/Neo-Forge/generatorgalore/build/moddev/dataRunProgramArgs.txt" + ], + "vmArgs": [ + "@/home/worador/Development/Neo-Forge/generatorgalore/build/moddev/dataRunVmArgs.txt", + "-Dfml.modFolders\u003dgeneratorgalore%%/home/worador/Development/Neo-Forge/generatorgalore/bin/main" + ], + "cwd": "${workspaceFolder}/run", + "env": {}, + "console": "internalConsole", + "shortenCommandLine": "none" + }, + { + "type": "java", + "request": "launch", + "name": "GameTestServer", + "presentation": { + "group": "Mod Development - generatorgalore", + "order": 2 + }, + "projectName": "generatorgalore", + "mainClass": "net.neoforged.devlaunch.Main", + "args": [ + "@/home/worador/Development/Neo-Forge/generatorgalore/build/moddev/gameTestServerRunProgramArgs.txt" + ], + "vmArgs": [ + "@/home/worador/Development/Neo-Forge/generatorgalore/build/moddev/gameTestServerRunVmArgs.txt", + "-Dfml.modFolders\u003dgeneratorgalore%%/home/worador/Development/Neo-Forge/generatorgalore/bin/main" + ], + "cwd": "${workspaceFolder}/run", + "env": {}, + "console": "internalConsole", + "shortenCommandLine": "none" + }, + { + "type": "java", + "request": "launch", + "name": "Server", + "presentation": { + "group": "Mod Development - generatorgalore", + "order": 3 + }, + "projectName": "generatorgalore", + "mainClass": "net.neoforged.devlaunch.Main", + "args": [ + "@/home/worador/Development/Neo-Forge/generatorgalore/build/moddev/serverRunProgramArgs.txt" + ], + "vmArgs": [ + "@/home/worador/Development/Neo-Forge/generatorgalore/build/moddev/serverRunVmArgs.txt", + "-Dfml.modFolders\u003dgeneratorgalore%%/home/worador/Development/Neo-Forge/generatorgalore/bin/main" + ], + "cwd": "${workspaceFolder}/run", + "env": {}, + "console": "internalConsole", + "shortenCommandLine": "none" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..78b61df --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "java.compile.nullAnalysis.mode": "disabled", + "java.configuration.updateBuildConfiguration": "disabled" +} \ No newline at end of file diff --git a/changelog.txt b/changelog.txt index 0375ac1..1b823fe 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,12 @@ +1.21.1-2.0.1 + +- Complete Codebase Overhaul: Refactored and modernized the entire source structure for better maintainability +- Major UI Performance Overhaul: Replaced legacy texture-based rendering with optimized realtime code-based rendering +- Implemented smooth interpolation (lerping) for all UI bars, providing fluid visuals at high refresh rates +- Switched to native Minecraft furnace flame sprites (lit_progress) for improved visual consistency +- Enhanced Magmatic Generator with precise fluid color detection and lava-specific rendering fixes +- Optimized GPU overhead by reducing texture-binding calls, specifically targeting performance gains for integrated graphics (APUs) + 1.21.1-1.6.3 - Fixed fuel validation for fluid generators diff --git a/gradle.properties b/gradle.properties index d51ada9..0d213db 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,7 +18,7 @@ minecraft_version=1.21.1 # as they do not follow standard versioning conventions. minecraft_version_range=[1.21.1, 1.22) # The Neo version must agree with the Minecraft version to get a valid artifact -neo_version=21.1.176 +neo_version=21.1.217 # The Neo version range can use any version of Neo as bounds neo_version_range=[21.1.0,) # The loader version range can only use the major version of FML as bounds diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/overhaul_code.txt b/overhaul_code.txt new file mode 100644 index 0000000..4d2bcdb --- /dev/null +++ b/overhaul_code.txt @@ -0,0 +1,4101 @@ +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/cap/ControlledEnergyStorage.java --- +package cy.jdkdigital.generatorgalore.cap; + +import net.neoforged.neoforge.energy.EnergyStorage; + +public class ControlledEnergyStorage extends EnergyStorage +{ + public ControlledEnergyStorage(int capacity) { + super(capacity); + } + + @Override + public int receiveEnergy(int maxReceive, boolean simulate) { + return receiveEnergy(maxReceive, simulate, false); + } + + public int receiveEnergy(int maxReceive, boolean simulate, boolean internal) { + return internal ? super.receiveEnergy(maxReceive, simulate) : 0; + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/client/event/ClientEvents.java --- +package cy.jdkdigital.generatorgalore.client.event; + +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.common.container.GeneratorScreen; +import cy.jdkdigital.generatorgalore.registry.GeneratorRegistry; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent; +import net.neoforged.neoforge.client.event.RegisterParticleProvidersEvent; + +@EventBusSubscriber(modid = GeneratorGalore.MODID, value = Dist.CLIENT, bus = EventBusSubscriber.Bus.MOD) +public class ClientEvents +{ + @SubscribeEvent + public static void onClientSetup(RegisterMenuScreensEvent event) { + GeneratorRegistry.generators.forEach((resourceLocation, generatorObject) -> { + event.register(generatorObject.getMenuType().get(), GeneratorScreen::new); + }); + } + + @SubscribeEvent + public static void registerParticles(final RegisterParticleProvidersEvent event) { +// event.registerSpecial(ModParticles.RISING_ENCHANT_PARTICLE.get(), RisingEnchantParticle.Provider::new); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/client/particle/RisingEnchantParticle.java --- +package cy.jdkdigital.generatorgalore.client.particle; + +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.particle.*; +import net.minecraft.core.particles.ParticleTypes; + +public class RisingEnchantParticle extends TextureSheetParticle +{ + public RisingEnchantParticle(ClientLevel pLevel, double pX, double pY, double pZ) { + super(pLevel, pX, pY, pZ); + this.gravity = 0.75F; + this.friction = 0.999F; + this.xd *= 0.8F; + this.yd *= 0.8F; + this.zd *= 0.8F; + this.yd = this.random.nextFloat() * 0.4F + 0.05F; + this.quadSize *= this.random.nextFloat() * 1.1F + 0.2F; + this.lifetime = (int)(16.0D / (Math.random() * 0.8D + 0.2D)); + } + + @Override + public ParticleRenderType getRenderType() { + return ParticleRenderType.PARTICLE_SHEET_OPAQUE; + } + + public float getQuadSize(float pScaleFactor) { + float f = ((float)this.age + pScaleFactor) / (float)this.lifetime; + return this.quadSize * (1.0F - f * f); + } + + @Override + public int getLightColor(float pPartialTick) { + int i = super.getLightColor(pPartialTick); + float f = (float)this.age / (float)this.lifetime; + f *= f; + f *= f; + int j = i & 255; + int k = i >> 16 & 255; + k += (int)(f * 15.0F * 16.0F); + if (k > 240) { + k = 240; + } + + return j | k << 16; + } + + @Override + public void tick() { + super.tick(); + if (!this.removed) { + float f = (float)this.age / (float)this.lifetime; + if (this.random.nextFloat() > f) { + this.level.addParticle(ParticleTypes.SMOKE, this.x, this.y, this.z, this.xd, this.yd, this.zd); + } + } + } + + public static class Provider implements ParticleProvider { + private final SpriteSet sprite; + + public Provider(SpriteSet pSprites) { + this.sprite = pSprites; + } + + @Override + public Particle createParticle(RisingEnchantParticleType pType, ClientLevel pLevel, double pX, double pY, double pZ, double pXSpeed, double pYSpeed, double pZSpeed) { + RisingEnchantParticle particle = new RisingEnchantParticle(pLevel, pX, pY, pZ); + particle.pickSprite(this.sprite); + return particle; + } + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/client/particle/RisingEnchantParticleType.java --- +package cy.jdkdigital.generatorgalore.client.particle; + +import com.mojang.serialization.MapCodec; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.core.particles.ParticleType; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; + +public class RisingEnchantParticleType extends ParticleType implements ParticleOptions +{ + public RisingEnchantParticleType() { + super(false); + } + + private final MapCodec CODEC = MapCodec.unit(this::getType); + private final StreamCodec STREAM_CODEC = StreamCodec.unit(this); + + @Override + public RisingEnchantParticleType getType() { + return this; + } + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public StreamCodec streamCodec() { + return STREAM_CODEC; + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/common/block/entity/CapabilityBlockEntity.java --- +package cy.jdkdigital.generatorgalore.common.block.entity; + +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.Nameable; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.capabilities.Capabilities; +import net.neoforged.neoforge.energy.EnergyStorage; +import net.neoforged.neoforge.fluids.capability.templates.FluidTank; +import net.neoforged.neoforge.items.ItemStackHandler; +import org.jetbrains.annotations.NotNull; + +public abstract class CapabilityBlockEntity extends BlockEntity implements MenuProvider, Nameable +{ + public CapabilityBlockEntity(BlockEntityType blockEntityType, BlockPos pos, BlockState state) { + super(blockEntityType, pos, state); + } + + @Override + protected void loadAdditional(CompoundTag pTag, HolderLookup.Provider pRegistries) { + super.loadAdditional(pTag, pRegistries); + this.loadPacketNBT(pTag, pRegistries); + } + + @Override + protected void saveAdditional(CompoundTag pTag, HolderLookup.Provider pRegistries) { + super.saveAdditional(pTag, pRegistries); + this.savePacketNBT(pTag, pRegistries); + } + + @Override + public CompoundTag getUpdateTag(HolderLookup.Provider pRegistries) { + return saveWithId(pRegistries); + } + + @Override + public ClientboundBlockEntityDataPacket getUpdatePacket() { + return ClientboundBlockEntityDataPacket.create(this); + } + + @Override + public void onDataPacket(Connection net, ClientboundBlockEntityDataPacket pkt, HolderLookup.Provider lookupProvider) { + super.onDataPacket(net, pkt, lookupProvider); + this.loadPacketNBT(pkt.getTag(), lookupProvider); + if (level instanceof ClientLevel) { + level.sendBlockUpdated(getBlockPos(), getBlockState(), getBlockState(), 0); + } + } + + @Override + public @NotNull Component getDisplayName() { + return getName(); + } + + public abstract void savePacketNBT(CompoundTag tag, HolderLookup.Provider pRegistries); + + public abstract void loadPacketNBT(CompoundTag tag, HolderLookup.Provider pRegistries); +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/common/block/entity/GeneratorBlockEntity.java --- +package cy.jdkdigital.generatorgalore.common.block.entity; + +import com.mojang.datafixers.util.Pair; +import cy.jdkdigital.generatorgalore.Config; +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.cap.ControlledEnergyStorage; +import cy.jdkdigital.generatorgalore.common.block.Generator; +import cy.jdkdigital.generatorgalore.common.container.GeneratorMenu; +import cy.jdkdigital.generatorgalore.common.container.ManualItemHandler; +import cy.jdkdigital.generatorgalore.util.GeneratorObject; +import cy.jdkdigital.generatorgalore.util.GeneratorUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.server.level.ServerLevel; +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.EnchantedBookItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.material.Fluid; +import net.neoforged.neoforge.capabilities.Capabilities; +import net.neoforged.neoforge.energy.EnergyStorage; +import net.neoforged.neoforge.energy.IEnergyStorage; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.fluids.capability.IFluidHandler; +import net.neoforged.neoforge.fluids.capability.templates.FluidTank; +import cy.jdkdigital.generatorgalore.network.FluidSyncPacket; +import cy.jdkdigital.generatorgalore.network.ModPackets; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.neoforge.network.PacketDistributor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Locale; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +public class GeneratorBlockEntity extends CapabilityBlockEntity +{ + private int tickCounter = 0; + public int litTime; + public int litDuration; + public double remainder = 0; + public int fluidId = 0; + public final GeneratorObject generator; + private final int modifier; + private final int generationRate; + private final int consumptionRate; + public final ControlledEnergyStorage energyHandler; + public final ManualItemHandler inventoryHandler; + public final FluidTank fluidInventory; + private boolean hasLoaded = false; + + public GeneratorBlockEntity(GeneratorObject generator, BlockPos blockPos, BlockState blockState) { + super(generator.getBlockEntityType().get(), blockPos, blockState); + this.generator = generator; + this.modifier = blockState.getBlock() instanceof Generator generatorBlock ? generatorBlock.getModifier() : 1; + this.generationRate = (int) (generator.getOriginalGenerationRate() * this.modifier); + this.consumptionRate = (int) (generator.getOriginalConsumptionRate() * this.modifier); + this.energyHandler = new ControlledEnergyStorage(generator.getBufferCapacity() * this.modifier); + this.inventoryHandler = new ManualItemHandler(2) { + @Override + public boolean isItemValid(int slot, @NotNull ItemStack stack) { + if (slot == GeneratorMenu.SLOT_CHARGE) { + return stack.getCapability(Capabilities.EnergyStorage.ITEM) != null; + } + + return generator.isValidFuelItem(stack); + } + + @Override + protected void onContentsChanged(int slot) { + setChanged(); + } + }; + this.fluidInventory = new FluidTank(10000) { + @Override + public boolean isFluidValid(FluidStack stack) { + return generator.isValidFuelFluid(stack); + } + + @Override + protected void onContentsChanged() { + super.onContentsChanged(); + if (generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID)) { + fluidId = BuiltInRegistries.FLUID.getId(getFluid().getFluid()); + syncFluidToClients(); + setChanged(); + } + } + }; + } + + public static void tick(Level level, BlockPos pos, BlockState state, GeneratorBlockEntity blockEntity) { + int tickRate = Config.SERVER.tickRate.get(); + + if (!blockEntity.hasLoaded) { + blockEntity.hasLoaded = true; + } + + if (++blockEntity.tickCounter % tickRate == 0) { + // Cache generation and consumption rates to avoid redundant calculations + int generationRate = blockEntity.getGenerationRate(); + int consumptionRate = blockEntity.getConsumptionRate(); + int inputPowerAmount = generationRate * tickRate; + AtomicBoolean hasConsumedFuel = new AtomicBoolean(false); + + if (!blockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID)) { + if (blockEntity.isLit()) { + blockEntity.litTime = Math.max(0, blockEntity.litTime - tickRate); + } + // Consume fuels + ItemStack fuelStack = blockEntity.inventoryHandler.getStackInSlot(GeneratorMenu.SLOT_FUEL); + if (!blockEntity.isLit() && !fuelStack.isEmpty() && blockEntity.inventoryHandler.isItemValid(GeneratorMenu.SLOT_FUEL, fuelStack) && blockEntity.energyHandler.getEnergyStored() < blockEntity.energyHandler.getMaxEnergyStored()) { + Pair rate = blockEntity.generator.getGenerationRateForItem(blockEntity.level, fuelStack); + + // Check if energy storage has room for the entire burn or is half full + boolean shouldBurn = + blockEntity.energyHandler.getEnergyStored() < (blockEntity.energyHandler.getMaxEnergyStored() / 2) || + (rate.getFirst() * rate.getSecond()) <= (blockEntity.energyHandler.getMaxEnergyStored() - blockEntity.energyHandler.getEnergyStored()); + + if (shouldBurn) { + blockEntity.generator.setGenerationRate(rate.getFirst().intValue()); + blockEntity.litTime = Config.SERVER.increasedConsumption.get() ? rate.getSecond() / blockEntity.modifier : rate.getSecond(); + + // Do burn + if (blockEntity.litTime == 0) { + blockEntity.litTime = Config.SERVER.increasedConsumption.get() ? consumptionRate / blockEntity.modifier : consumptionRate; + } + blockEntity.litDuration = blockEntity.litTime; + if (blockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.ENCHANTMENT)) { + // strip enchantments + blockEntity.inventoryHandler.setStackInSlot(GeneratorMenu.SLOT_FUEL, new ItemStack(fuelStack.getItem() instanceof EnchantedBookItem ? Items.BOOK : fuelStack.getItem())); + } else if (fuelStack.hasCraftingRemainingItem() && fuelStack.getCount() == 1) { + blockEntity.inventoryHandler.setStackInSlot(GeneratorMenu.SLOT_FUEL, fuelStack.getCraftingRemainingItem()); + } else { + fuelStack.shrink(1); + } + } + } + // Generate power + if (blockEntity.isLit()) { + hasConsumedFuel.set(true); + } + } else if (blockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID) && blockEntity.energyHandler.getEnergyStored() + inputPowerAmount <= blockEntity.energyHandler.getMaxEnergyStored()) { + FluidStack fluidStack = blockEntity.fluidInventory.getFluidInTank(0); + Pair rate = blockEntity.generator.getGenerationRateForFluid(fluidStack); + if (rate != null) { + int fluidConsumeAmount = rate.getSecond() * tickRate * blockEntity.modifier; + if (blockEntity.fluidInventory.getFluidInTank(0).getAmount() >= fluidConsumeAmount) { + blockEntity.fluidInventory.drain(fluidConsumeAmount, IFluidHandler.FluidAction.EXECUTE); + blockEntity.syncFluidToClients(); + blockEntity.generator.setGenerationRate(rate.getFirst()); + blockEntity.generator.setConsumptionRate(rate.getSecond()); + hasConsumedFuel.set(true); + } + } + } + + if (hasConsumedFuel.get()) { + inputPowerAmount = generationRate * tickRate; // recalculate with cached rate + // If the generated FE is not divisible by the tickRate, save the excess for next tick + double tempPowerAmount = inputPowerAmount + blockEntity.remainder; + int addedPower = (int) tempPowerAmount; + blockEntity.remainder = tempPowerAmount - addedPower; + + blockEntity.energyHandler.receiveEnergy(addedPower, false, true); + blockEntity.setOn(true); + } else { + blockEntity.setOn(false); + } + + blockEntity.sendOutPower((int) blockEntity.generator.getTransferRate() * tickRate * blockEntity.modifier); + blockEntity.setChanged(); + } + } + + public int getGenerationRate() { + return this.generationRate; + } + + public int getConsumptionRate() { + return this.consumptionRate; + } + + public boolean isLit() { + return this.litTime > 0; + } + + private void setOn(boolean isOn) { + if (level != null && !level.isClientSide) { + level.setBlockAndUpdate(worldPosition, getBlockState().setValue(BlockStateProperties.LIT, isOn)); + } + } + + private void sendOutPower(int amount) { + if (this.level != null) { + AtomicInteger capacity = new AtomicInteger(energyHandler.getEnergyStored()); + if (capacity.get() > 0) { + AtomicBoolean dirty = new AtomicBoolean(false); + + // Lazy evaluation - only process charge slot if generator has charge slot + if (generator.hasChargeSlot()) { + var chargeItem = inventoryHandler.getStackInSlot(GeneratorMenu.SLOT_CHARGE); + if (!chargeItem.isEmpty()) { + var chargeItemHandler = chargeItem.getCapability(Capabilities.EnergyStorage.ITEM); + if (chargeItemHandler != null) { + int received = chargeItemHandler.receiveEnergy(Math.min(capacity.get(), amount), false); + capacity.addAndGet(-received); + energyHandler.extractEnergy(received, false); + dirty.set(true); + } + } + } + + // Direct neighbor capability query instead of cached list + if (capacity.get() > 0) { + Direction[] directions = Direction.values(); + for (Direction direction : directions) { + var energyCap = level.getCapability(Capabilities.EnergyStorage.BLOCK, worldPosition.relative(direction), direction.getOpposite()); + if (energyCap != null && energyCap.canReceive()) { + int received = energyCap.receiveEnergy(Math.min(capacity.get(), amount), false); + capacity.addAndGet(-received); + energyHandler.extractEnergy(received, false); + dirty.set(true); + + if (capacity.get() <= 0) { + break; + } + } + } + } + + if (dirty.get()) { + this.setChanged(); + } + } + } + } + + @Nullable + @Override + public AbstractContainerMenu createMenu(int id, Inventory inventory, Player player) { + return new GeneratorMenu(id, inventory, this); + } + + public void syncFluidToClients() { + if (level != null && !level.isClientSide && generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID)) { + FluidStack fluidStack = fluidInventory.getFluidInTank(0); + FluidSyncPacket packet = new FluidSyncPacket(worldPosition, fluidStack); + for (ServerPlayer player : level.getServer().getPlayerList().getPlayers()) { + ModPackets.sendToClient(packet, player); + } + } + } + + // Removed refreshConnectedTileEntityCache() method and recipients list + // as we now use direct capability queries per tick + + @Override + public @NotNull Component getName() { + return Component.translatable("block." + GeneratorGalore.MODID + "." + generator.getId().getPath().toLowerCase(Locale.ENGLISH) + "_generator" + (this.modifier > 1 ? "_" + this.modifier + "x" : "")); + } + + @Override + protected void loadAdditional(CompoundTag pTag, HolderLookup.Provider pRegistries) { + super.loadAdditional(pTag, pRegistries); + litTime = pTag.getInt("litTime"); + litDuration = pTag.getInt("litDuration"); + if (pTag.contains("generationRate")) { + generator.setGenerationRate(pTag.getInt("generationRate")); + generator.setConsumptionRate(pTag.getInt("consumptionRate")); + } + } + + @Override + protected void saveAdditional(CompoundTag pTag, HolderLookup.Provider pRegistries) { + super.saveAdditional(pTag, pRegistries); + pTag.putInt("litTime", litTime); + pTag.putInt("litDuration", litDuration); + if (generator.getGenerationRate() != generator.getOriginalGenerationRate()) { + pTag.putInt("generationRate", generator.getGenerationRate()); + } + if (generator.getConsumptionRate() != generator.getOriginalConsumptionRate()) { + pTag.putInt("consumptionRate", generator.getConsumptionRate()); + } + } + + @Override + public void savePacketNBT(CompoundTag tag, HolderLookup.Provider pRegistries) { + tag.put("inv", inventoryHandler.serializeNBT(pRegistries)); + + tag.put("energy", energyHandler.serializeNBT(pRegistries)); + + CompoundTag nbt = new CompoundTag(); + fluidInventory.writeToNBT(pRegistries, nbt); + tag.put("item", nbt); + } + + @Override + public void loadPacketNBT(CompoundTag tag, HolderLookup.Provider pRegistries) { + if (tag.contains("inv")) { + inventoryHandler.deserializeNBT(pRegistries, tag.getCompound("inv")); + } + + if (tag.contains("energy")) { + energyHandler.deserializeNBT(pRegistries, tag.get("energy")); + } + + if (tag.contains("item")) { + fluidInventory.readFromNBT(pRegistries, tag.getCompound("item")); + } + + // set item ID for screens + if (generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID)) { + Fluid fluid = fluidInventory.getFluidInTank(0).getFluid(); + fluidId = BuiltInRegistries.FLUID.getId(fluid); + } + } + + @Override + protected void collectImplicitComponents(DataComponentMap.Builder components) { + super.collectImplicitComponents(components); + // Store energy and fluid data in components + components.set(GeneratorGalore.ENERGY_COMPONENT.get(), energyHandler.getEnergyStored()); + components.set(GeneratorGalore.FLUID_COMPONENT.get(), fluidInventory.getFluid()); + } + + @Override + protected void applyImplicitComponents(BlockEntity.DataComponentInput componentInput) { + super.applyImplicitComponents(componentInput); + // Restore energy and fluid data from components + Integer energy = componentInput.get(GeneratorGalore.ENERGY_COMPONENT.get()); + FluidStack fluid = componentInput.get(GeneratorGalore.FLUID_COMPONENT.get()); + + if (energy != null) { + // Use receiveEnergy with internal flag to set energy directly + energyHandler.receiveEnergy(energy, false, true); + } + + if (fluid != null && !fluid.isEmpty() && generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID)) { + fluidInventory.setFluid(fluid); + } + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/common/block/Generator.java --- +package cy.jdkdigital.generatorgalore.common.block; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.common.block.entity.GeneratorBlockEntity; +import cy.jdkdigital.generatorgalore.util.GeneratorObject; +import cy.jdkdigital.generatorgalore.util.GeneratorUtil; +import net.minecraft.ChatFormatting; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.util.RandomSource; +import net.minecraft.world.Containers; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.ItemInteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.*; +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.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.phys.BlockHitResult; +import net.neoforged.neoforge.capabilities.Capabilities; +import net.neoforged.neoforge.fluids.FluidUtil; +import net.neoforged.neoforge.items.IItemHandler; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nullable; +import java.util.List; + +public class Generator extends BaseEntityBlock +{ + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( + builder -> builder.group( + propertiesCodec(), + GeneratorObject.codec(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "generator_codec")).fieldOf("generator").forGetter(generator -> generator.generator), + Codec.INT.fieldOf("modifier").forGetter(generator -> generator.modifier) + ) + .apply(builder, Generator::new) + ); + + private final GeneratorObject generator; + private final int modifier; + + public Generator(Properties properties, GeneratorObject generator, int modifier) { + super(properties); + this.generator = generator; + this.modifier = modifier; + this.registerDefaultState(this.stateDefinition.any().setValue(HorizontalDirectionalBlock.FACING, Direction.NORTH).setValue(BlockStateProperties.LIT, false)); + } + + @Override + protected MapCodec codec() { + return CODEC; + } + + protected void createBlockStateDefinition(StateDefinition.Builder stateBuilder) { + stateBuilder.add(HorizontalDirectionalBlock.FACING, BlockStateProperties.LIT); + } + + @Nullable + @Override + public BlockEntityTicker getTicker(Level level, @NotNull BlockState state, @NotNull BlockEntityType blockEntityType) { + return level.isClientSide ? null : createTickerHelper(blockEntityType, generator.getBlockEntityType().get(), GeneratorBlockEntity::tick); + } + + @Override + public RenderShape getRenderShape(BlockState blockState) { + return RenderShape.MODEL; + } + + @Override + public BlockState getStateForPlacement(BlockPlaceContext context) { + return this.defaultBlockState().setValue(HorizontalDirectionalBlock.FACING, context.getHorizontalDirection().getOpposite()); + } + + @Override + public @NotNull BlockState mirror(BlockState blockState, Mirror mirror) { + return blockState.rotate(mirror.getRotation(blockState.getValue(HorizontalDirectionalBlock.FACING))); + } + + @Override + public @NotNull BlockState rotate(BlockState blockState, Rotation rotation) { + return blockState.setValue(HorizontalDirectionalBlock.FACING, rotation.rotate(blockState.getValue(HorizontalDirectionalBlock.FACING))); + } + + @Override + public BlockEntity newBlockEntity(@NotNull BlockPos pos, @NotNull BlockState state) { + return new GeneratorBlockEntity(generator, pos, state); + } + + @Override + protected ItemInteractionResult useItemOn(ItemStack pStack, BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHitResult) { + if (generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID) && pStack.getCapability(Capabilities.FluidHandler.ITEM) != null) { + if (FluidUtil.interactWithFluidHandler(pPlayer, pHand, pLevel, pPos, null)) { + pPlayer.swing(pHand); + return ItemInteractionResult.SUCCESS; + } + } + return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION; + } + + @Override + protected InteractionResult useWithoutItem(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, BlockHitResult pHitResult) { + if (pLevel.getBlockEntity(pPos) instanceof GeneratorBlockEntity generatorBlockEntity) { + if (!pLevel.isClientSide) { + pPlayer.openMenu(generatorBlockEntity, packetBuffer -> packetBuffer.writeBlockPos(pPos)); + } + return InteractionResult.SUCCESS_NO_ITEM_USED; + } + return super.useWithoutItem(pState, pLevel, pPos, pPlayer, pHitResult); + } + + + @Override + public void animateTick(BlockState pState, Level level, BlockPos pos, RandomSource random) { + if (pState.getValue(BlockStateProperties.LIT)) { + if (random.nextInt(11) == 0) { + for (int i = 0; i < random.nextInt(1) + 1; ++i) { + switch (generator.getFuelType()) { + case FLUID: + break; + case FOOD: + double d0 = (double) pos.getX() + 0.4D + (double) random.nextFloat() * 0.2D; + double d1 = (double) pos.getY() + 0.7D + (double) random.nextFloat() * 0.3D; + double d2 = (double) pos.getZ() + 0.4D + (double) random.nextFloat() * 0.2D; + level.addParticle(ParticleTypes.SMOKE, d0, d1, d2, 0.0D, 0.0D, 0.0D); + break; + case ENCHANTMENT: + // Enchantment particle effect disabled for now + break; + default: + level.addParticle(ParticleTypes.LAVA, (double) pos.getX() + 0.5D, (double) pos.getY() + 1.0D, (double) pos.getZ() + 0.5D, random.nextFloat() / 2.0F, 5.0E-5D, random.nextFloat() / 2.0F); + } + } + } + + if (random.nextInt(55) == 0) { + for (int i = 0; i < level.random.nextInt(2) + 2; ++i) { + double d0 = (double) pos.getX() + 0.5D; + double d1 = pos.getY(); + double d2 = (double) pos.getZ() + 0.5D; + if (random.nextDouble() < 0.1D) { + level.playLocalSound(d0, d1, d2, SoundEvents.SMOKER_SMOKE, SoundSource.BLOCKS, 1.0F, 1.0F, false); + } + + level.addParticle(ParticleTypes.SMOKE, d0, d1 + 1.1D, d2, 0.0D, 0.0D, 0.0D); + } + } + } + } + + @SuppressWarnings("deprecation") + @Override + public void onRemove(BlockState oldState, Level level, BlockPos pos, BlockState newState, boolean isMoving) { + if (oldState.getBlock() != newState.getBlock() && !level.isClientSide && level.getBlockEntity(pos) instanceof GeneratorBlockEntity generatorBlockEntity) { + // Drop inventory + for (int slot = 0; slot < generatorBlockEntity.inventoryHandler.getSlots(); ++slot) { + Containers.dropItemStack(level, pos.getX(), pos.getY(), pos.getZ(), generatorBlockEntity.inventoryHandler.getStackInSlot(slot)); + } + } + super.onRemove(oldState, level, pos, newState, isMoving); + } + + @Override + public boolean hasAnalogOutputSignal(BlockState pState) { + return true; + } + + @Override + public int getAnalogOutputSignal(BlockState pBlockState, Level pLevel, BlockPos pPos) { + return AbstractContainerMenu.getRedstoneSignalFromBlockEntity(pLevel.getBlockEntity(pPos)); + } + + @Override + public void appendHoverText(ItemStack pStack, Item.TooltipContext pContext, List pTootipComponents, TooltipFlag pTooltipFlag) { + super.appendHoverText(pStack, pContext, pTootipComponents, pTooltipFlag); + + pTootipComponents.add(Component.translatable(GeneratorGalore.MODID + ".screen.generation_rate", generator.getGenerationRate() * this.modifier).withStyle(ChatFormatting.BLUE)); + pTootipComponents.add(Component.translatable(GeneratorGalore.MODID + ".screen.transfer_rate", generator.getTransferRate() * this.modifier).withStyle(ChatFormatting.BLUE)); + pTootipComponents.add(Component.translatable(GeneratorGalore.MODID + ".screen.max_energy", generator.getBufferCapacity() * this.modifier).withStyle(ChatFormatting.BLUE)); + pTootipComponents.add(Component.translatable(GeneratorGalore.MODID + ".screen.fuel_type", generator.getFuelType().getSerializedName()).withStyle(ChatFormatting.BLUE)); + } + + public int getModifier() { + return modifier; + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/common/conditions/GeneratorExistsCondition.java --- +package cy.jdkdigital.generatorgalore.common.conditions; + +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import cy.jdkdigital.generatorgalore.registry.GeneratorRegistry; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.common.conditions.ICondition; + +public record GeneratorExistsCondition(ResourceLocation generatorName) implements ICondition +{ + public static MapCodec CODEC = RecordCodecBuilder.mapCodec( + builder -> builder + .group(ResourceLocation.CODEC.fieldOf("generator").forGetter(GeneratorExistsCondition::generatorName)) + .apply(builder, GeneratorExistsCondition::new)); + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public boolean test(IContext context) { + return GeneratorRegistry.generators.containsKey(generatorName); + } + + @Override + public String toString() { + return "generator_exists(\"" + generatorName + "\")"; + } +}--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/common/container/AbstractContainer.java --- +package cy.jdkdigital.generatorgalore.common.container; + +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +abstract class AbstractContainer extends AbstractContainerMenu +{ + protected AbstractContainer(@Nullable MenuType type, int id) { + super(type, id); + } + + @Nonnull + @Override + public ItemStack quickMoveStack(Player player, int index) { + ItemStack returnStack = ItemStack.EMPTY; + final Slot slot = this.slots.get(index); + if (slot.hasItem()) { + final ItemStack slotStack = slot.getItem(); + returnStack = slotStack.copy(); + + final int containerSlots = this.slots.size() - player.getInventory().items.size(); + + // Move from container to player inventory. + if (index < containerSlots) { + if (!moveItemStackTo(slotStack, containerSlots, this.slots.size(), false)) { + return ItemStack.EMPTY; + } + } else { + int upgradeSlotCount = 0; + // Move from player inv into container +// int upgradeSlotCount = this.getTileEntity() instanceof UpgradeableBlockEntity ? 4 : 0; +// if (upgradeSlotCount > 0 && slotStack.getItem() instanceof UpgradeItem) { +// if (!moveItemStackTo(slotStack, containerSlots - upgradeSlotCount, containerSlots, false)) { +// return ItemStack.EMPTY; +// } +// } else { + if (!moveItemStackTo(slotStack, 0, containerSlots - upgradeSlotCount, false)) { + return ItemStack.EMPTY; + } +// } + } + + if (slotStack.isEmpty()) { + slot.set(ItemStack.EMPTY); + } + else { + slot.setChanged(); + } + + if (slotStack.getCount() == returnStack.getCount()) { + return ItemStack.EMPTY; + } + + slot.onTake(player, slotStack); + } + return returnStack; + } + + protected int addSlotRange(Container handler, int index, int x, int y, int amount, int dx) { + for (int i = 0; i < amount; i++) { + addSlot(new Slot(handler, index, x, y)); + x += dx; + index++; + } + return index; + } + + protected void addSlotBox(Container handler, int startIndex, int x, int y, int horAmount, int dx, int verAmount, int dy) { + for (int j = 0; j < verAmount; j++) { + startIndex = addSlotRange(handler, startIndex, x, y, horAmount, dx); + y += dy; + } + } + + protected void layoutPlayerInventorySlots(Inventory inventory, int startIndex, int leftCol, int topRow) { + // Player inventory + addSlotBox(inventory, startIndex + 9, leftCol, topRow, 9, 18, 3, 18); + + // Hotbar + topRow += 58; + addSlotRange(inventory, startIndex, leftCol, topRow, 9, 18); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/common/container/GeneratorMenu.java --- +package cy.jdkdigital.generatorgalore.common.container; + +import cy.jdkdigital.generatorgalore.cap.ControlledEnergyStorage; +import cy.jdkdigital.generatorgalore.common.block.entity.GeneratorBlockEntity; +import cy.jdkdigital.generatorgalore.util.GeneratorUtil; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.ContainerData; +import net.minecraft.world.inventory.DataSlot; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.fluids.capability.IFluidHandler; + +import java.util.Objects; + +public class GeneratorMenu extends AbstractContainer +{ + public final GeneratorBlockEntity blockEntity; + + public static final int SLOT_FUEL = 0; + public static final int SLOT_CHARGE = 1; + + public GeneratorMenu(final int windowId, final Inventory playerInventory, final FriendlyByteBuf data) { + this(windowId, playerInventory, getTileEntity(playerInventory, data)); + } + + public GeneratorMenu(int id, Inventory inventory, GeneratorBlockEntity blockEntity) { + super(blockEntity.generator.getMenuType().get(), id); + + this.blockEntity = blockEntity; + + addDataSlots(new ContainerData() { + @Override + public int get(int index) { + return switch (index) { + case 0 -> blockEntity.litTime; + case 1 -> blockEntity.litDuration; + default -> 0; + }; + } + + @Override + public void set(int index, int value) { + switch (index) { + case 0 -> blockEntity.litTime = value; + case 1 -> blockEntity.litDuration = value; + } + } + + @Override + public int getCount() { + return 2; + } + }); + + // Energy + addDataSlot(new DataSlot() + { + @Override + public int get() { + return blockEntity.energyHandler.getEnergyStored(); + } + + @Override + public void set(int value) { + if (blockEntity.energyHandler.getEnergyStored() > 0) { + blockEntity.energyHandler.extractEnergy(blockEntity.energyHandler.getEnergyStored(), false); + } + if (value > 0) { + blockEntity.energyHandler.receiveEnergy(value, false, true); + } + } + }); + + if (this.blockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID)) { + // Fluid + addDataSlots(new ContainerData() + { + @Override + public int get(int i) { + return i == 0 ? blockEntity.fluidId : blockEntity.fluidInventory.getFluidInTank(0).getAmount(); + } + + @Override + public void set(int i, int value) { + switch (i) { + case 0: + blockEntity.fluidId = value; + case 1: + FluidStack fluid = blockEntity.fluidInventory.getFluidInTank(0); + if (fluid.isEmpty()) { + blockEntity.fluidInventory.fill(new FluidStack(BuiltInRegistries.FLUID.byId(blockEntity.fluidId), value), IFluidHandler.FluidAction.EXECUTE); + } else { + fluid.setAmount(value); + } + } + } + + @Override + public int getCount() { + return 2; + } + }); + } + + if (blockEntity.inventoryHandler instanceof ManualItemHandler itemHandler) { + addSlot(new ManualSlotItemHandler(itemHandler, SLOT_FUEL, 80, 54)); + if (blockEntity.generator.hasChargeSlot()) { + addSlot(new ManualSlotItemHandler(itemHandler, SLOT_CHARGE, 152, 54)); + } + } + + layoutPlayerInventorySlots(inventory, 0, 8, 84); + } + + private static GeneratorBlockEntity getTileEntity(final Inventory playerInventory, final FriendlyByteBuf data) { + Objects.requireNonNull(playerInventory, "playerInventory cannot be null!"); + Objects.requireNonNull(data, "data cannot be null!"); + final BlockEntity tileAtPos = playerInventory.player.level().getBlockEntity(data.readBlockPos()); + if (tileAtPos instanceof GeneratorBlockEntity) { + return (GeneratorBlockEntity) tileAtPos; + } + throw new IllegalStateException("Tile entity is not correct! " + tileAtPos); + } + + @Override + public boolean stillValid(Player player) { + return this.blockEntity != null && player.distanceToSqr(this.blockEntity.getBlockPos().getX() + 0.5D, this.blockEntity.getBlockPos().getY() + 0.5D, this.blockEntity.getBlockPos().getZ() + 0.5D) <= 64.0D; + } + + public int getLitProgress() { + int i = this.blockEntity.litDuration; + if (i == 0) { + i = 200; + } + return this.blockEntity.litTime * 13 / i; + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/common/container/GeneratorScreen.java --- +package cy.jdkdigital.generatorgalore.common.container; + +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.util.FluidContainerUtil; +import cy.jdkdigital.generatorgalore.util.GeneratorUtil; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.FormattedCharSequence; +import net.minecraft.world.entity.player.Inventory; +import net.neoforged.neoforge.fluids.FluidStack; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.List; + +public class GeneratorScreen extends AbstractContainerScreen +{ + private static final ResourceLocation GUI_SOLID = ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "textures/gui/container/generator_solid.png"); + private static final ResourceLocation GUI_FLUID = ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "textures/gui/container/generator_fluid.png"); + private static final ResourceLocation GUI_SOLID_CHARGING = ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "textures/gui/container/generator_solid_charging.png"); + private static final ResourceLocation GUI_FLUID_CHARGING = ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "textures/gui/container/generator_fluid_charging.png"); + + // Smooth energy bar interpolation variables + private int lastEnergy = 0; + private int clientEnergy = 0; + private boolean firstEnergyRender = true; + + // Smooth fluid level interpolation variables + private int lastFluidAmount = 0; + private int clientFluidAmount = 0; + private boolean firstFluidRender = true; + + public GeneratorScreen(GeneratorMenu screenContainer, Inventory inv, Component titleIn) { + super(screenContainer, inv, titleIn); + } + + @Override + public void render(@Nonnull GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) { + this.renderBackground(guiGraphics, mouseX, mouseY, partialTicks); + super.render(guiGraphics, mouseX, mouseY, partialTicks); + this.renderTooltip(guiGraphics, mouseX, mouseY); + } + + @Override + protected void renderLabels(@Nonnull GuiGraphics guiGraphics, int mouseX, int mouseY) { + guiGraphics.drawString(font, this.title.getString(), 8.0F, 6.0F, 4210752, false); + guiGraphics.drawString(font, this.playerInventoryTitle.getString(), 8.0F, (float) (this.getYSize() - 96 + 2), 4210752, false); + + guiGraphics.drawString(font, Component.translatable(GeneratorGalore.MODID + ".screen.generation_rate", this.menu.blockEntity.getGenerationRate()).getString(), this.menu.blockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID) ? 51.0F : 8.0F, 24.0F, 4210752, false); + + List tooltipList = new ArrayList<>(); + int energyAmount = this.menu.blockEntity.energyHandler.getEnergyStored(); + + // Energy level tooltip - use current energy for tooltip + int currentEnergy = this.menu.blockEntity.energyHandler.getEnergyStored(); + if (isHovering(134, 16, 16, 54, mouseX, mouseY)) { + tooltipList.add(Component.translatable(GeneratorGalore.MODID + ".screen.energy_level", currentEnergy + "/" + this.menu.blockEntity.energyHandler.getMaxEnergyStored() + "FE").getVisualOrderText()); + } + + if (this.menu.blockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID)) { + FluidStack fluidStack = this.menu.blockEntity.fluidInventory.getFluidInTank(0); + + // Fluid level tooltip + if (isHovering(26, 16, 16, 54, mouseX, mouseY)) { + if (fluidStack.getAmount() > 0) { + tooltipList.add(Component.translatable(GeneratorGalore.MODID + ".screen.fluid_level", fluidStack.getHoverName().getString(), fluidStack.getAmount() + "mB").getVisualOrderText()); + } else { + tooltipList.add(Component.translatable(GeneratorGalore.MODID + ".screen.empty").getVisualOrderText()); + } + } + } + if (this.menu.blockEntity.isLit()) { + if (isHovering(81, 38, 13, 13, mouseX, mouseY)) { + tooltipList.add(Component.translatable(GeneratorGalore.MODID + ".screen.fuel_time", this.menu.blockEntity.litTime).getVisualOrderText()); + } + } + guiGraphics.renderTooltip(font, tooltipList, mouseX - getGuiLeft(), mouseY - getGuiTop()); + } + + @Override + protected void renderBg(@NotNull GuiGraphics guiGraphics, float partialTicks, int mouseX, int mouseY) { + var canCharge = this.menu.blockEntity.generator.hasChargeSlot(); + var GUI = this.menu.blockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID) ? + canCharge ? GUI_FLUID_CHARGING : GUI_FLUID : canCharge ? GUI_SOLID_CHARGING : GUI_SOLID; + guiGraphics.blit(GUI, getGuiLeft(), getGuiTop(), 0, 0, this.getXSize(), this.getYSize()); + + // Update energy interpolation + int currentEnergy = this.menu.blockEntity.energyHandler.getEnergyStored(); + int maxEnergy = this.menu.blockEntity.energyHandler.getMaxEnergyStored(); + + // Initialize interpolation variables on first render to prevent overshooting + if (firstEnergyRender) { + lastEnergy = currentEnergy; + clientEnergy = currentEnergy; + firstEnergyRender = false; + } + + // Store last energy value and interpolate for smooth animation + if (lastEnergy != currentEnergy) { + clientEnergy = lastEnergy; + lastEnergy = currentEnergy; + } + + // Calculate interpolated energy level with partial ticks for smooth animation + // Use float calculation for precision and round to nearest integer + float interpolatedEnergy = clientEnergy + (lastEnergy - clientEnergy) * partialTicks; + int energyLevel = Math.round((interpolatedEnergy / maxEnergy) * 54f); + + // Burn progress - use vanilla flame animation + if (this.menu.blockEntity.isLit()) { + int progress = this.menu.getLitProgress(); + // Use correct vanilla furnace flame sprite + guiGraphics.blitSprite(ResourceLocation.withDefaultNamespace("container/furnace/lit_progress"), 14, 14, 0, 0, getGuiLeft() + 81, getGuiTop() + 50 - progress, 14, progress); + } + + // Realtime energy bar rendering - replaces legacy blit calls + if (maxEnergy > 0) { + // Calculate fill height: (current * totalHeight) / max + int fillHeight = (int) (((float) currentEnergy / maxEnergy) * 54f); + + // Main energy bar (red) - grows from bottom up + if (fillHeight > 0) { + guiGraphics.fill( + getGuiLeft() + 134, getGuiTop() + 70 - fillHeight, + getGuiLeft() + 134 + 16, getGuiTop() + 70, + 0xFFFF0000 // Redstone red + ); + } + + // Left accent stripe (1 pixel, lighter red) + if (fillHeight > 0) { + guiGraphics.fill( + getGuiLeft() + 134, getGuiTop() + 70 - fillHeight, + getGuiLeft() + 134 + 1, getGuiTop() + 70, + 0xFFFF5555 // Lighter red accent + ); + } + } + + if (this.menu.blockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID)) { + FluidStack fluidStack = this.menu.blockEntity.fluidInventory.getFluidInTank(0); + int currentFluidAmount = fluidStack.getAmount(); + int fluidCapacity = this.menu.blockEntity.fluidInventory.getTankCapacity(0); + + // Initialize interpolation variables on first render to prevent overshooting + if (firstFluidRender) { + lastFluidAmount = currentFluidAmount; + clientFluidAmount = currentFluidAmount; + firstFluidRender = false; + } + + // Update fluid interpolation for smooth animation + if (lastFluidAmount != currentFluidAmount) { + clientFluidAmount = lastFluidAmount; + lastFluidAmount = currentFluidAmount; + } + + // Calculate interpolated fluid amount with partial ticks for smooth animation + float interpolatedFluidAmount = clientFluidAmount + (lastFluidAmount - clientFluidAmount) * partialTicks; + + if (currentFluidAmount > 0) { + // Calculate fill height: (current * totalHeight) / max + int fluidFillHeight = (int) (((float) currentFluidAmount / fluidCapacity) * 54f); + + // Try to get fluid color dynamically with precise lava detection + int fluidColor = 0xFF3663D9; // Default blue + if (!fluidStack.isEmpty()) { + try { + // Try to get fluid color using the same method as FluidContainerUtil + var attributes = net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions.of(fluidStack.getFluid()); + int tintColor = attributes.getTintColor(); + + // Precise lava color detection + if (fluidStack.getFluid().getFluidType().toString().contains("lava") || tintColor == -1) { + tintColor = 0xFFD45A12; // Strong lava orange + } + + if (tintColor != 0xFFFFFFFF) { // If not white (default), use it + fluidColor = tintColor; + } + // Ensure alpha channel is set + fluidColor = (fluidColor & 0x00FFFFFF) | 0xFF000000; + } catch (Exception e) { + // Fallback to blue if color extraction fails + fluidColor = 0xFF3663D9; + } + } + + // Realtime fluid bar rendering - grows from bottom up + if (fluidFillHeight > 0) { + guiGraphics.fill( + getGuiLeft() + 26, getGuiTop() + 70 - fluidFillHeight, + getGuiLeft() + 26 + 16, getGuiTop() + 70, + fluidColor + ); + } + } + } + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/common/container/ManualItemHandler.java --- +package cy.jdkdigital.generatorgalore.common.container; + +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.items.ItemStackHandler; +import org.jetbrains.annotations.NotNull; + +public class ManualItemHandler extends ItemStackHandler +{ + public ManualItemHandler(int size) { + super(size); + } + + public @NotNull ItemStack extractItem(int slot, int amount, boolean simulate, boolean fromAutomation) { + if (fromAutomation && this.isItemValid(slot, this.getStackInSlot(slot))) { + return ItemStack.EMPTY; + } + return super.extractItem(slot, amount, simulate); + } + + @Override + public @NotNull ItemStack extractItem(int slot, int amount, boolean simulate) { + return extractItem(slot, amount, simulate, true); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/common/container/ManualSlotItemHandler.java --- +package cy.jdkdigital.generatorgalore.common.container; + +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.items.SlotItemHandler; + +import javax.annotation.Nonnull; + +public class ManualSlotItemHandler extends SlotItemHandler +{ + ManualItemHandler handler; + + public ManualSlotItemHandler(ManualItemHandler itemHandler, int index, int xPosition, int yPosition) { + super(itemHandler, index, xPosition, yPosition); + handler = itemHandler; + } + + @Override + public boolean mayPlace(@Nonnull ItemStack stack) { + return handler.isItemValid(this.getSlotIndex(), stack); + } + + @Override + public boolean mayPickup(Player playerIn) { + return !this.handler.extractItem(this.getSlotIndex(), 1, true, false).isEmpty(); + } + + @Override + @Nonnull + public ItemStack remove(int amount) { + return this.handler.extractItem(this.getSlotIndex(), amount, false, false); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/common/datamap/FluidFuelMap.java --- +package cy.jdkdigital.generatorgalore.common.datamap; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.neoforged.neoforge.fluids.crafting.FluidIngredient; + +import java.util.List; + +public record FluidFuelMap(List fuels) { + public static final Codec CODEC = + RecordCodecBuilder.create(in -> in.group( + FluidFuel.CODEC.listOf().fieldOf("fuels").forGetter(FluidFuelMap::fuels) + ).apply(in, FluidFuelMap::new)); + + public record FluidFuel(FluidIngredient fluid, double consumptionRate, double generationRate) { + public static final Codec CODEC = + RecordCodecBuilder.create(in -> in.group( + FluidIngredient.CODEC.fieldOf("fluid").forGetter(FluidFuel::fluid), + Codec.DOUBLE.fieldOf("consumptionRate").forGetter(FluidFuel::consumptionRate), + Codec.DOUBLE.fieldOf("generationRate").forGetter(FluidFuel::generationRate) + ).apply(in, FluidFuel::new)); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/common/datamap/PotionComponentIngredient.java --- +package cy.jdkdigital.generatorgalore.common.datamap; + +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import net.minecraft.core.HolderSet; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.core.component.DataComponentPredicate; +import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.HolderSetCodec; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.ItemLike; +import net.neoforged.neoforge.common.crafting.DataComponentIngredient; +import net.neoforged.neoforge.common.crafting.IngredientType; + +import java.util.Arrays; + +public class PotionComponentIngredient extends DataComponentIngredient +{ + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( + builder -> builder + .group( + HolderSetCodec.create(Registries.ITEM, BuiltInRegistries.ITEM.holderByNameCodec(), false).fieldOf("items").forGetter(PotionComponentIngredient::items), + DataComponentPredicate.CODEC.fieldOf("components").forGetter(PotionComponentIngredient::components) + ) + .apply(builder, PotionComponentIngredient::new)); + + + public PotionComponentIngredient(HolderSet items, DataComponentPredicate components) { + super(items, components, false); + } + + @Override + public IngredientType getType() { + return GeneratorGalore.POTIOM_INGREDIENT_TYPE.get(); + } + + public static Ingredient of(ItemStack stack) { + var builder = DataComponentMap.builder(); + if (stack.has(DataComponents.POTION_CONTENTS)) { + builder.set(DataComponents.POTION_CONTENTS, stack.get(DataComponents.POTION_CONTENTS)); + } + return of(DataComponentPredicate.allOf(builder.build()), stack.getItem()); + } + + public static Ingredient of(DataComponentPredicate predicate, ItemLike... items) { + return of(predicate, HolderSet.direct(Arrays.stream(items).map(ItemLike::asItem).map(Item::builtInRegistryHolder).toList())); + } + + public static Ingredient of(DataComponentPredicate predicate, HolderSet items) { + return new PotionComponentIngredient(items, predicate).toVanilla(); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/common/datamap/SolidFuelMap.java --- +package cy.jdkdigital.generatorgalore.common.datamap; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.world.item.crafting.Ingredient; + +import java.util.List; + +public record SolidFuelMap(List fuels) { + public static final Codec CODEC = + RecordCodecBuilder.create(in -> in.group( + SolidFuel.CODEC.listOf().fieldOf("fuels").forGetter(SolidFuelMap::fuels) + ).apply(in, SolidFuelMap::new)); + + public record SolidFuel(Ingredient item, float consumptionRate, int burnTime, int generationRate) { + public static final Codec CODEC = + RecordCodecBuilder.create(in -> in.group( + Ingredient.CODEC.fieldOf("item").forGetter(SolidFuel::item), + Codec.FLOAT.fieldOf("consumptionRate").forGetter(SolidFuel::consumptionRate), + Codec.INT.fieldOf("burnTime").forGetter(SolidFuel::burnTime), + Codec.INT.fieldOf("generationRate").forGetter(SolidFuel::generationRate) + ).apply(in, SolidFuel::new)); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/common/item/UpgradeItem.java --- +package cy.jdkdigital.generatorgalore.common.item; + +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.util.GeneratorObject; +import cy.jdkdigital.generatorgalore.util.GeneratorUtil; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.block.state.BlockState; + +public class UpgradeItem extends Item +{ + private final String previousTier; + private final GeneratorObject generator; + + public UpgradeItem(Properties properties, String previousTier, GeneratorObject generator) { + super(properties); + this.previousTier = previousTier; + this.generator = generator; + } + + @Override + public InteractionResult useOn(UseOnContext context) { + if (!context.getLevel().isClientSide()) { + BlockState state = context.getLevel().getBlockState(context.getClickedPos()); + ResourceLocation blockId = BuiltInRegistries.BLOCK.getKey(state.getBlock()); + if (blockId.getNamespace().equals(GeneratorGalore.MODID) && blockId.getPath().equals(previousTier + "_generator")) { + GeneratorUtil.replaceGenerator(context.getLevel(), context.getClickedPos(), generator); + if (!context.getPlayer().isCreative()) { + context.getItemInHand().shrink(1); + } + context.getPlayer().swing(context.getHand()); + return InteractionResult.CONSUME; + } + } + return super.useOn(context); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/common/recipe/FluidFuelRecipe.java --- +package cy.jdkdigital.generatorgalore.common.recipe; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.init.ModRecipeTypes; +import net.minecraft.core.HolderLookup; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.*; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.fluids.FluidStack; + +import javax.annotation.Nonnull; +import java.util.List; + +public record FluidFuelRecipe(List fuels, ItemStack generator, float rate, float consumptionRate) implements Recipe +{ + @Override + public boolean matches(RecipeInput pContainer, Level pLevel) { + return false; + } + + @Override + public ItemStack assemble(RecipeInput p_345149_, HolderLookup.Provider p_346030_) { + return null; + } + + @Override + public boolean canCraftInDimensions(int pWidth, int pHeight) { + return true; + } + + @Override + public ItemStack getResultItem(HolderLookup.Provider pRegistries) { + return null; + } + + @Override + public RecipeSerializer getSerializer() { + return ModRecipeTypes.FLUID_FUEL.get(); + } + + @Override + public RecipeType getType() { + return ModRecipeTypes.FLUID_FUEL_TYPE.get(); + } + + public static class Serializer implements RecipeSerializer + { + private static final MapCodec CODEC = RecordCodecBuilder.mapCodec( + builder -> builder.group( + FluidStack.CODEC.listOf().fieldOf("fuels").orElse(List.of()).forGetter(recipe -> recipe.fuels), + ItemStack.CODEC.fieldOf("generator").forGetter(recipe -> recipe.generator), + Codec.FLOAT.fieldOf("rate").forGetter(recipe -> recipe.rate), + Codec.FLOAT.fieldOf("consumptionRate").forGetter(recipe -> recipe.consumptionRate) + ) + .apply(builder, FluidFuelRecipe::new) + ); + + public static final StreamCodec STREAM_CODEC = StreamCodec.of( + FluidFuelRecipe.Serializer::toNetwork, FluidFuelRecipe.Serializer::fromNetwork + ); + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public StreamCodec streamCodec() { + return STREAM_CODEC; + } + + public static FluidFuelRecipe fromNetwork(@Nonnull RegistryFriendlyByteBuf buffer) { + try { + return new FluidFuelRecipe(FluidStack.STREAM_CODEC.apply(ByteBufCodecs.list()).decode(buffer), ItemStack.STREAM_CODEC.decode(buffer), buffer.readFloat(), buffer.readFloat()); + } catch (Exception e) { + GeneratorGalore.LOGGER.error("Error reading item fuels recipe from packet.", e); + throw e; + } + } + + public static void toNetwork(@Nonnull RegistryFriendlyByteBuf buffer, FluidFuelRecipe recipe) { + try { + FluidStack.STREAM_CODEC.apply(ByteBufCodecs.list()).encode(buffer, recipe.fuels()); + ItemStack.STREAM_CODEC.encode(buffer, recipe.generator()); + buffer.writeFloat(recipe.rate()); + buffer.writeFloat(recipe.consumptionRate()); + } catch (Exception e) { + GeneratorGalore.LOGGER.error("Error writing item fuels recipe to packet.", e); + throw e; + } + } + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/common/recipe/SolidFuelRecipe.java --- +package cy.jdkdigital.generatorgalore.common.recipe; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.init.ModRecipeTypes; +import net.minecraft.core.HolderLookup; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.*; +import net.minecraft.world.level.Level; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +public record SolidFuelRecipe(List fuels, ItemStack generator, float rate, float consumptionRate) implements Recipe +{ + @Override + public boolean matches(RecipeInput pContainer, Level pLevel) { + return false; + } + + @Override + public ItemStack assemble(RecipeInput pContainer, HolderLookup.Provider registryAccess) { + return null; + } + + @Override + public boolean canCraftInDimensions(int pWidth, int pHeight) { + return true; + } + + @Override + public ItemStack getResultItem(HolderLookup.Provider registryAccess) { + return null; + } + + @Override + public RecipeSerializer getSerializer() { + return ModRecipeTypes.SOLID_FUEL.get(); + } + + @Override + public RecipeType getType() { + return ModRecipeTypes.SOLID_FUEL_TYPE.get(); + } + + public static class Serializer implements RecipeSerializer + { + private static final MapCodec CODEC = RecordCodecBuilder.mapCodec( + builder -> builder.group( + Ingredient.CODEC.listOf().fieldOf("fuels").orElse(List.of()).forGetter(recipe -> recipe.fuels), + ItemStack.CODEC.fieldOf("generator").forGetter(recipe -> recipe.generator), + Codec.FLOAT.fieldOf("rate").forGetter(recipe -> recipe.rate), + Codec.FLOAT.fieldOf("consumptionRate").forGetter(recipe -> recipe.consumptionRate) + ) + .apply(builder, SolidFuelRecipe::new) + ); + + public static final StreamCodec STREAM_CODEC = StreamCodec.of( + SolidFuelRecipe.Serializer::toNetwork, SolidFuelRecipe.Serializer::fromNetwork + ); + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public StreamCodec streamCodec() { + return STREAM_CODEC; + } + + public static SolidFuelRecipe fromNetwork(@Nonnull RegistryFriendlyByteBuf buffer) { + try { + return new SolidFuelRecipe(Ingredient.CONTENTS_STREAM_CODEC.apply(ByteBufCodecs.list()).decode(buffer), ItemStack.STREAM_CODEC.decode(buffer), buffer.readFloat(), buffer.readInt()); + } catch (Exception e) { + GeneratorGalore.LOGGER.error("Error reading solid fuels recipe from packet. ", e); + throw e; + } + } + + public static void toNetwork(@Nonnull RegistryFriendlyByteBuf buffer, SolidFuelRecipe recipe) { + try { + Ingredient.CONTENTS_STREAM_CODEC.apply(ByteBufCodecs.list()).encode(buffer, recipe.fuels()); + ItemStack.STREAM_CODEC.encode(buffer, recipe.generator()); + buffer.writeFloat(recipe.rate()); + buffer.writeFloat(recipe.consumptionRate()); + } catch (Exception e) { + GeneratorGalore.LOGGER.error("Error writing solid fuels recipe to packet.", e); + throw e; + } + } + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/data/BlockTagProvider.java --- +package cy.jdkdigital.generatorgalore.data; + +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.registry.GeneratorRegistry; +import net.minecraft.core.HolderLookup; +import net.minecraft.data.PackOutput; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.neoforged.neoforge.common.data.BlockTagsProvider; +import net.neoforged.neoforge.common.data.ExistingFileHelper; + +import java.util.concurrent.CompletableFuture; + +public class BlockTagProvider extends BlockTagsProvider +{ + public BlockTagProvider(PackOutput output, CompletableFuture provider, ExistingFileHelper helper) { + super(output, provider, GeneratorGalore.MODID, helper); + } + + @Override + protected void addTags(HolderLookup.Provider provider) { + var pickaxeMineable = tag(BlockTags.MINEABLE_WITH_PICKAXE); + var infiniburn = tag(BlockTags.INFINIBURN_OVERWORLD); + + GeneratorRegistry.generators.forEach((resourceLocation, generatorObject) -> { + pickaxeMineable.addOptional(resourceLocation.withPath(p -> p + "_generator")); + pickaxeMineable.addOptional(resourceLocation.withPath(p -> p + "_generator_8x")); + pickaxeMineable.addOptional(resourceLocation.withPath(p -> p + "_generator_64x")); + }); + + infiniburn.addOptional(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "magmatic_generator")); + infiniburn.addOptional(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "magmatic_generator_8x")); + infiniburn.addOptional(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "magmatic_generator_64x")); + } + + @Override + public String getName() { + return "Generator Galore Block Tags Provider"; + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/data/BlockstateProvider.java --- +package cy.jdkdigital.generatorgalore.data; + +import com.google.gson.JsonElement; +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.registry.GeneratorRegistry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.data.PackOutput; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.neoforged.neoforge.client.model.generators.BlockModelBuilder; +import net.neoforged.neoforge.client.model.generators.BlockStateProvider; +import net.neoforged.neoforge.common.data.ExistingFileHelper; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +public class BlockstateProvider extends BlockStateProvider +{ + protected final PackOutput packOutput; + + protected final Map> models = new HashMap<>(); + + public BlockstateProvider(PackOutput packOutput, ExistingFileHelper exFileHelper) { + super(packOutput, GeneratorGalore.MODID, exFileHelper); + this.packOutput = packOutput; + } + + @Override + protected void registerStatesAndModels() { + GeneratorRegistry.generators.forEach((resourceLocation, generatorObject) -> { + var block = generatorObject.getBlockSupplier().get(); + makeGeneratorBlock(block, "block/generator_base"); + var baseGen = BuiltInRegistries.BLOCK.getKey(block); + + makeGeneratorBlock(BuiltInRegistries.BLOCK.get(baseGen.withPath(p -> p + "_8x")), "block/generator_base_8x"); + makeGeneratorBlock(BuiltInRegistries.BLOCK.get(baseGen.withPath(p -> p + "_64x")), "block/generator_base_64x"); + }); + } + + private void makeGeneratorBlock(Block block, String baseModel) { + var generatorParentModel = generatorTextureMap(block, models().withExistingParent(blockTexture(block).toString(), ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, baseModel))); + var generatorOnParentModel = generatorOnTextureMap(block, models().withExistingParent(blockTexture(block) + "_on", generatorParentModel.getLocation())); + + this.horizontalBlock(block, blockState -> blockState.getValue(BlockStateProperties.LIT) ? generatorOnParentModel : generatorParentModel); + + this.simpleBlockItem(block, generatorParentModel); + } + + private BlockModelBuilder generatorTextureMap(Block pBlock, BlockModelBuilder modelBuilder) { + return modelBuilder + .texture("side", extend(pBlock, "_side")) + .texture("top", extend(pBlock, "_top_off")) + .texture("bottom", extend(pBlock, "_bottom")) + .texture("face", extend(pBlock, "_front")); + } + + private BlockModelBuilder generatorOnTextureMap(Block pBlock, BlockModelBuilder modelBuilder) { + return modelBuilder + .texture("front", ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "block/generator_on_glow")) + .texture("top", extend(pBlock, "_top_on")); + } + + private ResourceLocation extend(Block pBlock, String suffix) { + return blockTexture(pBlock).withPath(p -> p.replace("_8x", "").replace("_64x", "") + suffix); + } + + private ResourceLocation blockKey(Block block) { + return BuiltInRegistries.BLOCK.getKey(block); + } + + private ResourceLocation itemKey(Item item) { + return BuiltInRegistries.ITEM.getKey(item); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/data/DataMapProvider.java --- +package cy.jdkdigital.generatorgalore.data; + +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.common.conditions.GeneratorExistsCondition; +import cy.jdkdigital.generatorgalore.common.datamap.FluidFuelMap; +import cy.jdkdigital.generatorgalore.common.datamap.PotionComponentIngredient; +import cy.jdkdigital.generatorgalore.common.datamap.SolidFuelMap; +import cy.jdkdigital.generatorgalore.registry.GeneratorRegistry; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.data.PackOutput; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.FluidTags; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.alchemy.Potion; +import net.minecraft.world.item.alchemy.PotionContents; +import net.minecraft.world.item.crafting.Ingredient; +import net.neoforged.neoforge.common.crafting.DataComponentIngredient; +import net.neoforged.neoforge.fluids.crafting.FluidIngredient; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class DataMapProvider extends net.neoforged.neoforge.common.data.DataMapProvider +{ + protected DataMapProvider(PackOutput packOutput, CompletableFuture lookupProvider) { + super(packOutput, lookupProvider); + } + + @Override + protected void gather(HolderLookup.Provider provider) { + final var fluidFuels = builder(GeneratorGalore.FLUID_FUEL_MAP); + final var solidFuels = builder(GeneratorGalore.SOLID_FUEL_MAP); + + var lavaGen = ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "magmatic"); + fluidFuels.add(BuiltInRegistries.BLOCK.getKey(GeneratorRegistry.generators.get(lavaGen).getBlockSupplier().get().builtInRegistryHolder().value()), + new FluidFuelMap(List.of( + new FluidFuelMap.FluidFuel(FluidIngredient.tag(FluidTags.create(ResourceLocation.withDefaultNamespace("lava"))), 0.4d, 40d) + )), false, new GeneratorExistsCondition(lavaGen)); + + var enderGen = ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "ender"); + solidFuels.add(BuiltInRegistries.BLOCK.getKey(GeneratorRegistry.generators.get(enderGen).getBlockSupplier().get().builtInRegistryHolder().value()), + new SolidFuelMap(List.of( + new SolidFuelMap.SolidFuel(Ingredient.of(Items.ENDER_PEARL), 1.0f, 1600, 96), + new SolidFuelMap.SolidFuel(Ingredient.of(Items.ENDER_EYE), 1.0f, 3200, 80) + )), false, new GeneratorExistsCondition(enderGen)); + + var halitosisGen = ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "halitosis"); + solidFuels.add(BuiltInRegistries.BLOCK.getKey(GeneratorRegistry.generators.get(halitosisGen).getBlockSupplier().get().builtInRegistryHolder().value()), + new SolidFuelMap(List.of( + new SolidFuelMap.SolidFuel(Ingredient.of(Items.DRAGON_BREATH), 1.0f, 12000, 128) + )), false, new GeneratorExistsCondition(halitosisGen)); + +// List potionFuels = new ArrayList<>(); +// provider.lookup(Registries.POTION).ifPresent( +// potionRegistryLookup -> { +// generatePotionEffectTypes( +// potionFuels, +// potionRegistryLookup, +// Items.POTION +// ); +// generatePotionEffectTypes( +// potionFuels, +// potionRegistryLookup, +// Items.SPLASH_POTION +// ); +// generatePotionEffectTypes( +// potionFuels, +// potionRegistryLookup, +// Items.LINGERING_POTION +// ); +// } +// ); +// var potionGen = ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "potion"); +// solidFuels.add(BuiltInRegistries.BLOCK.getKey(GeneratorRegistry.generators.get(potionGen).getBlockSupplier().get().builtInRegistryHolder().value()), +// new SolidFuelMap(potionFuels), false, new GeneratorExistsCondition(potionGen)); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/data/GeneratorGaloreDataProvider.java --- +package cy.jdkdigital.generatorgalore.data; + +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import net.minecraft.core.HolderLookup; +import net.minecraft.data.DataGenerator; +import net.minecraft.data.PackOutput; +import net.minecraft.data.loot.LootTableProvider; +import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.common.data.ExistingFileHelper; +import net.neoforged.neoforge.data.event.GatherDataEvent; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@EventBusSubscriber(modid = GeneratorGalore.MODID, bus = EventBusSubscriber.Bus.MOD) +public class GeneratorGaloreDataProvider +{ + @SubscribeEvent + public static void gatherData(GatherDataEvent event) { + DataGenerator gen = event.getGenerator(); + PackOutput output = gen.getPackOutput(); + CompletableFuture provider = event.getLookupProvider(); + ExistingFileHelper helper = event.getExistingFileHelper(); + + gen.addProvider(event.includeClient(), new LanguageProvider(output)); + gen.addProvider(event.includeClient(), new BlockstateProvider(output, helper)); + gen.addProvider(event.includeServer(), new LootDataProvider(output, List.of(new LootTableProvider.SubProviderEntry(LootDataProvider.LootProvider::new, LootContextParamSets.BLOCK)), provider)); + gen.addProvider(event.includeServer(), new RecipeProvider(output, provider)); + gen.addProvider(event.includeServer(), new DataMapProvider(output, provider)); + + BlockTagProvider blockTags = new BlockTagProvider(output, provider, helper); + gen.addProvider(event.includeServer(), blockTags); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/data/LanguageProvider.java --- +package cy.jdkdigital.generatorgalore.data; + +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.registry.GeneratorRegistry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.data.PackOutput; + +public class LanguageProvider extends net.neoforged.neoforge.common.data.LanguageProvider +{ + public LanguageProvider(PackOutput output) { + super(output, GeneratorGalore.MODID, "en_us"); + } + + @Override + protected void addTranslations() { + add("itemGroup.generatorgalore", "Generator Galore"); + add("generatorgalore.screen.empty", "Empty"); + add("generatorgalore.screen.energy_level", "Energy: %s"); + add("generatorgalore.screen.fluid_level", "%s: %s"); + add("generatorgalore.screen.fuel_time", "Remaining fuel time: %s"); + add("generatorgalore.screen.generation_rate", "Rate: %sFE/t"); + add("generatorgalore.screen.transfer_rate", "Transfer rate: %sFE/t"); + add("generatorgalore.screen.max_energy", "Max energy: %sFE"); + add("generatorgalore.screen.fuel_type", "Fuel type: %s"); + add("generatorgalore.recipe.solid_fuel", "Solid Fuel"); + add("generatorgalore.recipe.fluid_fuel", "Fluid Fuel"); + + GeneratorRegistry.generators.forEach((resourceLocation, generatorObject) -> { + add("block.generatorgalore." + resourceLocation.getPath() + "_generator", capName(resourceLocation.getPath()) + " Generator"); + add("block.generatorgalore." + resourceLocation.getPath() + "_generator_8x", "8x " + capName(resourceLocation.getPath()) + " Generator"); + add("block.generatorgalore." + resourceLocation.getPath() + "_generator_64x", "64x " + capName(resourceLocation.getPath()) + " Generator"); + if (generatorObject.getUpgradeSupplier() != null) { + var upgradeItem = BuiltInRegistries.ITEM.getKey(generatorObject.getUpgradeSupplier().get()); + add("item.generatorgalore." + upgradeItem.getPath(), capName(upgradeItem.getPath())); + } + }); + } + + @Override + public String getName() { + return "Generator Galore translation provider"; + } + + public static String capName(String name) { + String[] nameParts = name.split("_"); + + for(int i = 0; i < nameParts.length; ++i) { + if (nameParts[i].equals("to")) continue; + String firstChar = nameParts[i].substring(0, 1).toUpperCase(); + nameParts[i] = firstChar + nameParts[i].substring(1); + } + + return String.join(" ", nameParts); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/data/LootDataProvider.java --- +package cy.jdkdigital.generatorgalore.data; + +import com.google.common.collect.Maps; +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.registry.GeneratorRegistry; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.data.CachedOutput; +import net.minecraft.data.DataProvider; +import net.minecraft.data.PackOutput; +import net.minecraft.data.loot.BlockLootSubProvider; +import net.minecraft.data.loot.LootTableProvider; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.flag.FeatureFlags; +import net.minecraft.world.level.block.Block; +import net.minecraft.core.component.DataComponents; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.entries.LootItem; +import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; +import net.minecraft.world.level.storage.loot.functions.CopyComponentsFunction; +import net.minecraft.world.level.storage.loot.predicates.ExplosionCondition; +import net.minecraft.world.level.storage.loot.providers.number.ConstantValue; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +public class LootDataProvider implements DataProvider +{ + private final PackOutput.PathProvider pathProvider; + private final List subProviders; + private final CompletableFuture registries; + + public LootDataProvider(PackOutput output, List providers, CompletableFuture registries) { + this.pathProvider = output.createPathProvider(PackOutput.Target.DATA_PACK, "loot_table"); + this.subProviders = providers; + this.registries = registries; + } + + @Override + public String getName() { + return "Generator Galore Block Loot Table datagen"; + } + + @Override + public CompletableFuture run(CachedOutput pOutput) { + return this.registries.thenComposeAsync(provider -> this.run(pOutput, provider)); + } + + private CompletableFuture run(CachedOutput pOutput, HolderLookup.Provider pProvider) { + final Map map = Maps.newHashMap(); + this.subProviders.forEach((providerEntry) -> { + providerEntry.provider().apply(pProvider).generate((resourceKey, builder) -> { + builder.setRandomSequence(resourceKey.location()); + if (map.put(resourceKey.location(), builder.setParamSet(providerEntry.paramSet()).build()) != null) { + throw new IllegalStateException("Duplicate loot table " + resourceKey.location()); + } + }); + }); + + return CompletableFuture.allOf(map.entrySet().stream().map((entry) -> { + return DataProvider.saveStable(pOutput, pProvider, LootTable.DIRECT_CODEC, entry.getValue(), this.pathProvider.json(entry.getKey())); + }).toArray(CompletableFuture[]::new)); + } + + public static class LootProvider extends BlockLootSubProvider + { + private static final Map> functionTable = new HashMap<>(); + + private final List knownBlocks = new ArrayList<>(); + + public LootProvider(HolderLookup.Provider provider) { + super(Set.of(), FeatureFlags.REGISTRY.allFlags(), provider); + } + + @Override + protected void generate() { + GeneratorRegistry.generators.forEach((resourceLocation, generatorObject) -> { + dropSelf(generatorObject.getBlockSupplier().get()); + var base = BuiltInRegistries.BLOCK.getKey(generatorObject.getBlockSupplier().get()); + dropSelf(BuiltInRegistries.BLOCK.get(base.withPath(p -> p + "_8x"))); + dropSelf(BuiltInRegistries.BLOCK.get(base.withPath(p -> p + "_64x"))); + }); + } + + @Override + protected void add(Block block, LootTable.Builder builder) { + super.add(block, builder); + knownBlocks.add(block); + } + + @Override + protected Iterable getKnownBlocks() { + return knownBlocks; + } + + protected void add(Block block, Function builderFunction) { + this.add(block, builderFunction.apply(block)); + } + + public void dropSelf(@NotNull Block block) { + Function func = functionTable.getOrDefault(block, LootProvider::genOptionalBlockDrop); + this.add(block, func.apply(block)); + } + + protected static LootTable.Builder genOptionalBlockDrop(Block block) { + LootPoolEntryContainer.Builder builder = LootItem.lootTableItem(block) + .apply(CopyComponentsFunction.copyComponents(CopyComponentsFunction.Source.BLOCK_ENTITY) + .include(GeneratorGalore.ENERGY_COMPONENT.get()) + .include(GeneratorGalore.FLUID_COMPONENT.get())) + .when(ExplosionCondition.survivesExplosion()); + + return LootTable.lootTable().withPool( + LootPool.lootPool().setRolls(ConstantValue.exactly(1)) + .add(builder)); + } + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/data/RecipeProvider.java --- +package cy.jdkdigital.generatorgalore.data; + +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.common.conditions.GeneratorExistsCondition; +import cy.jdkdigital.generatorgalore.registry.GeneratorRegistry; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.data.PackOutput; +import net.minecraft.data.recipes.*; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.ItemLike; +import net.neoforged.neoforge.common.Tags; +import net.neoforged.neoforge.common.conditions.IConditionBuilder; + +import java.util.concurrent.CompletableFuture; + +public class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider implements IConditionBuilder +{ + public RecipeProvider(PackOutput gen, CompletableFuture pRegistries) { + super(gen, pRegistries); + } + + @Override + protected void buildRecipes(RecipeOutput pRecipeOutput) { + var copperGenerator = GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "copper")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, copperGenerator.getBlockSupplier().get(), 1) + .unlockedBy(getHasName(Items.FURNACE), has(Items.FURNACE)) + .pattern("III").pattern("IGI").pattern("IRI") + .define('I', Ingredient.of(Tags.Items.INGOTS_COPPER)) + .define('G', Ingredient.of(Items.FURNACE)) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(copperGenerator.getId())), prefixedRecipeId(copperGenerator.getBlockSupplier().get(), "generators/")); + + var ironGenerator = GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "iron")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, ironGenerator.getBlockSupplier().get(), 1) + .unlockedBy(getHasName(copperGenerator.getBlockSupplier().get()), has(copperGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IGI").pattern("IRI") + .define('I', Ingredient.of(Tags.Items.INGOTS_IRON)) + .define('G', Ingredient.of(copperGenerator.getBlockSupplier().get())) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(ironGenerator.getId())), prefixedRecipeId(ironGenerator.getBlockSupplier().get(), "generators/")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, ironGenerator.getUpgradeSupplier().get(), 1) + .unlockedBy(getHasName(copperGenerator.getBlockSupplier().get()), has(copperGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IFI").pattern("IRI") + .define('I', Ingredient.of(Tags.Items.INGOTS_IRON)) + .define('F', Ingredient.of(Items.ITEM_FRAME)) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(ironGenerator.getId())), prefixedRecipeId(ironGenerator.getUpgradeSupplier().get(), "upgrades/")); + + var goldGenerator = GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "gold")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, goldGenerator.getBlockSupplier().get(), 1) + .unlockedBy(getHasName(ironGenerator.getBlockSupplier().get()), has(ironGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IGI").pattern("IRI") + .define('I', Ingredient.of(Tags.Items.INGOTS_GOLD)) + .define('G', Ingredient.of(ironGenerator.getBlockSupplier().get())) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(goldGenerator.getId())), prefixedRecipeId(goldGenerator.getBlockSupplier().get(), "generators/")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, goldGenerator.getUpgradeSupplier().get(), 1) + .unlockedBy(getHasName(ironGenerator.getBlockSupplier().get()), has(ironGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IFI").pattern("IRI") + .define('I', Ingredient.of(Tags.Items.INGOTS_GOLD)) + .define('F', Ingredient.of(Items.ITEM_FRAME)) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(goldGenerator.getId())), prefixedRecipeId(goldGenerator.getUpgradeSupplier().get(), "upgrades/")); + + var culinaryGenerator = GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "culinary")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, culinaryGenerator.getBlockSupplier().get(), 1) + .unlockedBy(getHasName(goldGenerator.getBlockSupplier().get()), has(goldGenerator.getBlockSupplier().get())) + .pattern("ICI").pattern("IGI").pattern("ERE") + .define('I', Ingredient.of(Tags.Items.CROPS)) + .define('C', Ingredient.of(Items.CAKE)) + .define('E', Ingredient.of(Tags.Items.EGGS)) + .define('G', Ingredient.of(goldGenerator.getBlockSupplier().get())) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(culinaryGenerator.getId())), prefixedRecipeId(culinaryGenerator.getBlockSupplier().get(), "generators/")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, culinaryGenerator.getUpgradeSupplier().get(), 1) + .unlockedBy(getHasName(goldGenerator.getBlockSupplier().get()), has(goldGenerator.getBlockSupplier().get())) + .pattern("ICI").pattern("IFI").pattern("ERE") + .define('I', Ingredient.of(Tags.Items.CROPS)) + .define('C', Ingredient.of(Items.CAKE)) + .define('E', Ingredient.of(Tags.Items.EGGS)) + .define('F', Ingredient.of(Items.ITEM_FRAME)) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(culinaryGenerator.getId())), prefixedRecipeId(culinaryGenerator.getUpgradeSupplier().get(), "upgrades/")); + + var potionGenerator = GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "potion")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, potionGenerator.getBlockSupplier().get(), 1) + .unlockedBy(getHasName(culinaryGenerator.getBlockSupplier().get()), has(culinaryGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IGI").pattern("BRB") + .define('I', Ingredient.of(Tags.Items.DYED_PINK)) + .define('B', Ingredient.of(Items.BREWING_STAND)) + .define('G', Ingredient.of(culinaryGenerator.getBlockSupplier().get())) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(potionGenerator.getId())), prefixedRecipeId(potionGenerator.getBlockSupplier().get(), "generators/")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, potionGenerator.getUpgradeSupplier().get(), 1) + .unlockedBy(getHasName(culinaryGenerator.getBlockSupplier().get()), has(culinaryGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IFI").pattern("BRB") + .define('I', Ingredient.of(Tags.Items.DYED_PINK)) + .define('B', Ingredient.of(Items.BREWING_STAND)) + .define('F', Ingredient.of(Items.ITEM_FRAME)) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(potionGenerator.getId())), prefixedRecipeId(potionGenerator.getUpgradeSupplier().get(), "upgrades/")); + + var honeyGenerator = GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "honey")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, honeyGenerator.getBlockSupplier().get(), 1) + .unlockedBy(getHasName(culinaryGenerator.getBlockSupplier().get()), has(culinaryGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IGI").pattern("BRB") + .define('I', Ingredient.of(Items.HONEY_BLOCK)) + .define('B', Ingredient.of(Items.BUCKET)) + .define('G', Ingredient.of(culinaryGenerator.getBlockSupplier().get())) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(honeyGenerator.getId())), prefixedRecipeId(honeyGenerator.getBlockSupplier().get(), "generators/")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, honeyGenerator.getUpgradeSupplier().get(), 1) + .unlockedBy(getHasName(culinaryGenerator.getBlockSupplier().get()), has(culinaryGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IFI").pattern("BRB") + .define('I', Ingredient.of(Items.HONEY_BLOCK)) + .define('B', Ingredient.of(Items.BUCKET)) + .define('F', Ingredient.of(Items.ITEM_FRAME)) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(honeyGenerator.getId())), prefixedRecipeId(honeyGenerator.getUpgradeSupplier().get(), "upgrades/")); + + var diamondGenerator = GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "diamond")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, diamondGenerator.getBlockSupplier().get(), 1) + .unlockedBy(getHasName(goldGenerator.getBlockSupplier().get()), has(goldGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IGI").pattern("IRI") + .define('I', Ingredient.of(Tags.Items.GEMS_DIAMOND)) + .define('G', Ingredient.of(goldGenerator.getBlockSupplier().get())) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(diamondGenerator.getId())), prefixedRecipeId(diamondGenerator.getBlockSupplier().get(), "generators/")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, diamondGenerator.getUpgradeSupplier().get(), 1) + .unlockedBy(getHasName(goldGenerator.getBlockSupplier().get()), has(goldGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IFI").pattern("IRI") + .define('I', Ingredient.of(Tags.Items.GEMS_DIAMOND)) + .define('F', Ingredient.of(Items.ITEM_FRAME)) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(diamondGenerator.getId())), prefixedRecipeId(diamondGenerator.getUpgradeSupplier().get(), "upgrades/")); + + var emeraldGenerator = GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "emerald")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, emeraldGenerator.getBlockSupplier().get(), 1) + .unlockedBy(getHasName(diamondGenerator.getBlockSupplier().get()), has(diamondGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IGI").pattern("IRI") + .define('I', Ingredient.of(Tags.Items.GEMS_EMERALD)) + .define('G', Ingredient.of(diamondGenerator.getBlockSupplier().get())) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(emeraldGenerator.getId())), prefixedRecipeId(emeraldGenerator.getBlockSupplier().get(), "generators/")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, emeraldGenerator.getUpgradeSupplier().get(), 1) + .unlockedBy(getHasName(diamondGenerator.getBlockSupplier().get()), has(diamondGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IFI").pattern("IRI") + .define('I', Ingredient.of(Tags.Items.GEMS_EMERALD)) + .define('F', Ingredient.of(Items.ITEM_FRAME)) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(emeraldGenerator.getId())), prefixedRecipeId(emeraldGenerator.getUpgradeSupplier().get(), "upgrades/")); + + var netheriteGenerator = GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "netherite")); + SmithingTransformRecipeBuilder.smithing( + Ingredient.of(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE), + Ingredient.of(diamondGenerator.getBlockSupplier().get()), + Ingredient.of(Items.NETHERITE_INGOT), + RecipeCategory.MISC, + netheriteGenerator.getBlockSupplier().get().asItem() + ) + .unlocks(getHasName(Items.NETHERITE_INGOT), has(Items.NETHERITE_INGOT)) + .unlocks(getHasName(diamondGenerator.getBlockSupplier().get()), has(diamondGenerator.getBlockSupplier().get())) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(netheriteGenerator.getId())), prefixedRecipeId(netheriteGenerator.getBlockSupplier().get(), "generators/")); + SmithingTransformRecipeBuilder.smithing( + Ingredient.of(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE), + Ingredient.of(diamondGenerator.getUpgradeSupplier().get()), + Ingredient.of(Items.NETHERITE_INGOT), + RecipeCategory.MISC, + netheriteGenerator.getUpgradeSupplier().get() + ) + .unlocks(getHasName(Items.NETHERITE_INGOT), has(Items.NETHERITE_INGOT)) + .unlocks(getHasName(diamondGenerator.getBlockSupplier().get()), has(diamondGenerator.getBlockSupplier().get())) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(netheriteGenerator.getId())), prefixedRecipeId(netheriteGenerator.getUpgradeSupplier().get(), "upgrades/")); + + var netherstarGenerator = GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "netherstar")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, netherstarGenerator.getBlockSupplier().get(), 1) + .unlockedBy(getHasName(netheriteGenerator.getBlockSupplier().get()), has(netheriteGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IGI").pattern("IRI") + .define('I', Ingredient.of(Items.WITHER_SKELETON_SKULL)) + .define('G', Ingredient.of(netheriteGenerator.getBlockSupplier().get())) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(netherstarGenerator.getId())), prefixedRecipeId(netherstarGenerator.getBlockSupplier().get(), "generators/")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, netherstarGenerator.getUpgradeSupplier().get(), 1) + .unlockedBy(getHasName(netheriteGenerator.getBlockSupplier().get()), has(netheriteGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IFI").pattern("IRI") + .define('I', Ingredient.of(Items.WITHER_SKELETON_SKULL)) + .define('F', Ingredient.of(Items.ITEM_FRAME)) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(netherstarGenerator.getId())), prefixedRecipeId(netherstarGenerator.getUpgradeSupplier().get(), "upgrades/")); + + var obsidianGenerator = GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "obsidian")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, obsidianGenerator.getBlockSupplier().get(), 1) + .unlockedBy(getHasName(diamondGenerator.getBlockSupplier().get()), has(diamondGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IGI").pattern("IRI") + .define('I', Ingredient.of(Tags.Items.OBSIDIANS)) + .define('G', Ingredient.of(diamondGenerator.getBlockSupplier().get())) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(obsidianGenerator.getId())), prefixedRecipeId(obsidianGenerator.getBlockSupplier().get(), "generators/")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, obsidianGenerator.getUpgradeSupplier().get(), 1) + .unlockedBy(getHasName(diamondGenerator.getBlockSupplier().get()), has(diamondGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IFI").pattern("IRI") + .define('I', Ingredient.of(Tags.Items.OBSIDIANS)) + .define('F', Ingredient.of(Items.ITEM_FRAME)) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(obsidianGenerator.getId())), prefixedRecipeId(obsidianGenerator.getUpgradeSupplier().get(), "upgrades/")); + + var magmaticGenerator = GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "magmatic")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, magmaticGenerator.getBlockSupplier().get(), 1) + .unlockedBy(getHasName(obsidianGenerator.getBlockSupplier().get()), has(obsidianGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IGI").pattern("IRI") + .define('I', Ingredient.of(Tags.Items.INGOTS_GOLD)) + .define('G', Ingredient.of(obsidianGenerator.getBlockSupplier().get())) + .define('R', Ingredient.of(Tags.Items.BUCKETS_LAVA)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(magmaticGenerator.getId())), prefixedRecipeId(magmaticGenerator.getBlockSupplier().get(), "generators/")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, magmaticGenerator.getUpgradeSupplier().get(), 1) + .unlockedBy(getHasName(obsidianGenerator.getBlockSupplier().get()), has(obsidianGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IFI").pattern("IRI") + .define('I', Ingredient.of(Tags.Items.INGOTS_GOLD)) + .define('F', Ingredient.of(Items.ITEM_FRAME)) + .define('R', Ingredient.of(Tags.Items.BUCKETS_LAVA)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(magmaticGenerator.getId())), prefixedRecipeId(magmaticGenerator.getUpgradeSupplier().get(), "upgrades/")); + + var enchantmentGenerator = GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "enchantment")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, enchantmentGenerator.getBlockSupplier().get(), 1) + .unlockedBy(getHasName(obsidianGenerator.getBlockSupplier().get()), has(obsidianGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IGI").pattern("IRI") + .define('I', Ingredient.of(Items.ENCHANTED_BOOK)) + .define('G', Ingredient.of(obsidianGenerator.getBlockSupplier().get())) + .define('R', Ingredient.of(Items.ENCHANTING_TABLE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(enchantmentGenerator.getId())), prefixedRecipeId(enchantmentGenerator.getBlockSupplier().get(), "generators/")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, enchantmentGenerator.getUpgradeSupplier().get(), 1) + .unlockedBy(getHasName(obsidianGenerator.getBlockSupplier().get()), has(obsidianGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IFI").pattern("IRI") + .define('I', Ingredient.of(Items.ENCHANTED_BOOK)) + .define('F', Ingredient.of(Items.ITEM_FRAME)) + .define('R', Ingredient.of(Items.ENCHANTING_TABLE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(enchantmentGenerator.getId())), prefixedRecipeId(enchantmentGenerator.getUpgradeSupplier().get(), "upgrades/")); + + var enderGenerator = GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "ender")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, enderGenerator.getBlockSupplier().get(), 1) + .unlockedBy(getHasName(obsidianGenerator.getBlockSupplier().get()), has(obsidianGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IGI").pattern("IRI") + .define('I', Ingredient.of(Tags.Items.ENDER_PEARLS)) + .define('G', Ingredient.of(obsidianGenerator.getBlockSupplier().get())) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(enderGenerator.getId())), prefixedRecipeId(enderGenerator.getBlockSupplier().get(), "generators/")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, enderGenerator.getUpgradeSupplier().get(), 1) + .unlockedBy(getHasName(obsidianGenerator.getBlockSupplier().get()), has(obsidianGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IFI").pattern("IRI") + .define('I', Ingredient.of(Tags.Items.ENDER_PEARLS)) + .define('F', Ingredient.of(Items.ITEM_FRAME)) + .define('R', Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(enderGenerator.getId())), prefixedRecipeId(enderGenerator.getUpgradeSupplier().get(), "upgrades/")); + + var halitosisGenerator = GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "halitosis")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, halitosisGenerator.getBlockSupplier().get(), 1) + .unlockedBy(getHasName(enderGenerator.getBlockSupplier().get()), has(enderGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IGI").pattern("IRI") + .define('I', Ingredient.of(Tags.Items.GEMS_AMETHYST)) + .define('G', Ingredient.of(enderGenerator.getBlockSupplier().get())) + .define('R', Ingredient.of(Items.END_ROD)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(halitosisGenerator.getId())), prefixedRecipeId(halitosisGenerator.getBlockSupplier().get(), "generators/")); + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, halitosisGenerator.getUpgradeSupplier().get(), 1) + .unlockedBy(getHasName(enderGenerator.getBlockSupplier().get()), has(enderGenerator.getBlockSupplier().get())) + .pattern("III").pattern("IFI").pattern("IRI") + .define('I', Ingredient.of(Tags.Items.GEMS_AMETHYST)) + .define('F', Ingredient.of(Items.ITEM_FRAME)) + .define('R', Ingredient.of(Items.END_ROD)) + .save(pRecipeOutput.withConditions(new GeneratorExistsCondition(halitosisGenerator.getId())), prefixedRecipeId(halitosisGenerator.getUpgradeSupplier().get(), "upgrades/")); + + GeneratorRegistry.generators.forEach((resourceLocation, generatorObject) -> { + var base = BuiltInRegistries.BLOCK.getKey(generatorObject.getBlockSupplier().get()); + var gen8x = BuiltInRegistries.BLOCK.get(base.withPath(p -> p + "_8x")); + var gen64x = BuiltInRegistries.BLOCK.get(base.withPath(p -> p + "_64x")); + + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, gen8x, 1) + .unlockedBy(getHasName(generatorObject.getBlockSupplier().get()), has(generatorObject.getBlockSupplier().get())) + .pattern("III").pattern("IFI").pattern("III") + .define('I', Ingredient.of(generatorObject.getBlockSupplier().get())) + .define('F', Ingredient.of(Items.ECHO_SHARD)) + .save(generatorObject.has8x() ? pRecipeOutput.withConditions(new GeneratorExistsCondition(generatorObject.getId())) : pRecipeOutput.withConditions(modLoaded("removethisconditiontoenable")), prefixedRecipeId(gen8x, "8x/")); + + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, gen64x, 1) + .unlockedBy(getHasName(gen8x), has(gen8x)) + .pattern("III").pattern("IFI").pattern("III") + .define('I', Ingredient.of(gen8x)) + .define('F', Ingredient.of(Items.CONDUIT)) + .save(generatorObject.has64x() ? pRecipeOutput.withConditions(new GeneratorExistsCondition(generatorObject.getId())) : pRecipeOutput.withConditions(modLoaded("removethisconditiontoenable")), prefixedRecipeId(gen64x, "64x/")); + }); + } + + private static ResourceLocation prefixedRecipeId(ItemLike item, String prefix) { + return BuiltInRegistries.ITEM.getKey(item.asItem()).withPath(path -> prefix + path); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/init/ModBlockEntityTypes.java --- +package cy.jdkdigital.generatorgalore.init; + +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.common.block.entity.GeneratorBlockEntity; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; + +import java.util.function.Supplier; + +public class ModBlockEntityTypes +{ +// public static Supplier> GENERATOR; +// public static void registerGeneratorBlockEntities() { +// GENERATOR = register("generator", () -> createBlockEntityType((pos, state) -> new GeneratorBlockEntity(pos, state), generator.getBlockSupplier().get()))); +// } + + public static > Supplier register(String id, Supplier supplier) { + return GeneratorGalore.BLOCK_ENTITIES.register(id, supplier); + } + + public static BlockEntityType createBlockEntityType(BlockEntityType.BlockEntitySupplier factory, Block... blocks) { + return BlockEntityType.Builder.of(factory, blocks).build(null); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/init/ModContainerTypes.java --- +package cy.jdkdigital.generatorgalore.init; + +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MenuType; +import net.neoforged.neoforge.common.extensions.IMenuTypeExtension; + +import java.util.function.Supplier; + +public class ModContainerTypes +{ + public static Supplier> register(String id, IMenuTypeExtension item) { + return GeneratorGalore.CONTAINER_TYPES.register(id, () -> IMenuTypeExtension.create(item::create)); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/init/ModParticles.java --- +package cy.jdkdigital.generatorgalore.init; + +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.client.particle.RisingEnchantParticleType; +import net.minecraft.core.particles.ParticleType; +import net.neoforged.neoforge.registries.DeferredHolder; + +public class ModParticles +{ + public static final DeferredHolder, ParticleType> RISING_ENCHANT_PARTICLE = GeneratorGalore.PARTICLE_TYPES.register("rising_enchant_particle", RisingEnchantParticleType::new); +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/init/ModRecipeTypes.java --- +package cy.jdkdigital.generatorgalore.init; + +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.common.recipe.FluidFuelRecipe; +import cy.jdkdigital.generatorgalore.common.recipe.SolidFuelRecipe; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeInput; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.item.crafting.RecipeType; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.registries.DeferredHolder; + +public final class ModRecipeTypes +{ + public static final DeferredHolder, RecipeSerializer> SOLID_FUEL = GeneratorGalore.RECIPE_SERIALIZERS.register("solid_fuel", SolidFuelRecipe.Serializer::new); + public static final DeferredHolder, RecipeSerializer> FLUID_FUEL = GeneratorGalore.RECIPE_SERIALIZERS.register("fluid_fuel", FluidFuelRecipe.Serializer::new); + + public static DeferredHolder, RecipeType> SOLID_FUEL_TYPE = registerRecipeType("solid_fuel"); + public static DeferredHolder, RecipeType> FLUID_FUEL_TYPE = registerRecipeType("fluid_fuel"); + + static > DeferredHolder, RecipeType> registerRecipeType(final String name) { + return GeneratorGalore.RECIPE_TYPES.register(name, () -> new RecipeType() { + public String toString() { + return GeneratorGalore.MODID + ":" + name; + } + }); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/init/ModTags.java --- +package cy.jdkdigital.generatorgalore.init; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.FluidTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.material.Fluid; + +import java.util.HashMap; +import java.util.Map; + +public class ModTags +{ + public static Map> itemTagCache = new HashMap<>(); + public static Map> fluidTagCache = new HashMap<>(); + + public static TagKey getItemTag(ResourceLocation resourceLocation) { + if (!itemTagCache.containsKey(resourceLocation)) { + itemTagCache.put(resourceLocation, ItemTags.create(resourceLocation)); + } + return itemTagCache.get(resourceLocation); + } + + public static TagKey getFluidTag(ResourceLocation resourceLocation) { + if (!fluidTagCache.containsKey(resourceLocation)) { + fluidTagCache.put(resourceLocation, FluidTags.create(resourceLocation)); + } + return fluidTagCache.get(resourceLocation); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/integrations/FluidFuelRecipeCategory.java --- +package cy.jdkdigital.generatorgalore.integrations; + +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.common.recipe.FluidFuelRecipe; +import cy.jdkdigital.generatorgalore.registry.GeneratorRegistry; +import mezz.jei.api.constants.VanillaTypes; +import mezz.jei.api.gui.builder.IRecipeLayoutBuilder; +import mezz.jei.api.gui.drawable.IDrawable; +import mezz.jei.api.gui.ingredient.IRecipeSlotsView; +import mezz.jei.api.helpers.IGuiHelper; +import mezz.jei.api.neoforge.NeoForgeTypes; +import mezz.jei.api.recipe.IFocusGroup; +import mezz.jei.api.recipe.RecipeIngredientRole; +import mezz.jei.api.recipe.RecipeType; +import mezz.jei.api.recipe.category.IRecipeCategory; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +public class FluidFuelRecipeCategory implements IRecipeCategory +{ + private final IDrawable background; + private final IDrawable icon; + + public FluidFuelRecipeCategory(IGuiHelper guiHelper) { + ResourceLocation location = ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "textures/gui/jei/fluid_fuel_recipe.png"); + this.background = guiHelper.createDrawable(location, 0, 0, 126, 70); + this.icon = guiHelper.createDrawableIngredient(VanillaTypes.ITEM_STACK, new ItemStack(GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "magmatic")).getBlockSupplier().get())); + } + + @Override + public @NotNull RecipeType getRecipeType() { + return JeiPlugin.FLUID_FUEL_RECIPE_TYPE; + } + + @Override + public @NotNull Component getTitle() { + return Component.translatable(GeneratorGalore.MODID + ".recipe.fluid_fuel"); + } + + @Override + public @NotNull IDrawable getBackground() { + return background; + } + + @Override + public @NotNull IDrawable getIcon() { + return icon; + } + + @Override + public void setRecipe(IRecipeLayoutBuilder builder, FluidFuelRecipe recipe, @NotNull IFocusGroup iFocusGroup) { + builder.addSlot(RecipeIngredientRole.INPUT, 0, 41) + .addItemStack(recipe.generator()) + .setSlotName("generator"); + builder.addSlot(RecipeIngredientRole.INPUT, 18, 3) + .addIngredients(NeoForgeTypes.FLUID_STACK, recipe.fuels()) + .setFluidRenderer(1000, false, 16, 54) + .setSlotName("fuels"); + } + + @Override + public void draw(@NotNull FluidFuelRecipe recipe, @NotNull IRecipeSlotsView recipeSlotsView, @NotNull GuiGraphics poseStack, double mouseX, double mouseY) { + Minecraft minecraft = Minecraft.getInstance(); + poseStack.drawString(minecraft.font, "Rate: " + recipe.rate() + "FE/t", 37F, 14F, 4210752, false); + poseStack.drawString(minecraft.font, "Burn rate: " + recipe.consumptionRate() + "mB/t", 37F, 32F, 4210752, false); + poseStack.drawString(minecraft.font, "Total: " + (int) (recipe.rate() / recipe.consumptionRate() * 1000) + "FE/B", 37F, 50F, 4210752, false); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/integrations/JeiPlugin.java --- +package cy.jdkdigital.generatorgalore.integrations; + +import cy.jdkdigital.generatorgalore.Config; +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.common.datamap.SolidFuelMap; +import cy.jdkdigital.generatorgalore.common.recipe.FluidFuelRecipe; +import cy.jdkdigital.generatorgalore.common.recipe.SolidFuelRecipe; +import cy.jdkdigital.generatorgalore.init.ModTags; +import cy.jdkdigital.generatorgalore.registry.GeneratorRegistry; +import cy.jdkdigital.generatorgalore.util.GeneratorObject; +import cy.jdkdigital.generatorgalore.util.GeneratorUtil; +import mezz.jei.api.IModPlugin; +import mezz.jei.api.helpers.IGuiHelper; +import mezz.jei.api.helpers.IJeiHelpers; +import mezz.jei.api.recipe.RecipeType; +import mezz.jei.api.recipe.vanilla.IJeiFuelingRecipe; +import mezz.jei.api.registration.IGuiHandlerRegistration; +import mezz.jei.api.registration.IRecipeCatalystRegistration; +import mezz.jei.api.registration.IRecipeCategoryRegistration; +import mezz.jei.api.registration.IRecipeRegistration; +import mezz.jei.common.util.RegistryUtil; +import mezz.jei.library.plugins.vanilla.cooking.fuel.FuelRecipeMaker; +import net.minecraft.client.Minecraft; +import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.food.FoodProperties; +import net.minecraft.world.item.EnchantedBookItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.alchemy.PotionContents; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.enchantment.EnchantmentInstance; +import net.neoforged.neoforge.fluids.FluidStack; + +import javax.annotation.Nonnull; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; + +@mezz.jei.api.JeiPlugin +public class JeiPlugin implements IModPlugin +{ + private static final ResourceLocation pluginId = ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, GeneratorGalore.MODID); + + public static RecipeType SOLID_FUEL_RECIPE_TYPE = RecipeType.create(GeneratorGalore.MODID, "solid_fuels", SolidFuelRecipe.class); + public static RecipeType FLUID_FUEL_RECIPE_TYPE = RecipeType.create(GeneratorGalore.MODID, "fluid_fuels", FluidFuelRecipe.class); + + public JeiPlugin() { + } + + @Nonnull + @Override + public ResourceLocation getPluginUid() { + return pluginId; + } + + @Override + public void registerRecipeCatalysts(IRecipeCatalystRegistration registration) { + GeneratorRegistry.generators.forEach((resourceLocation, generator) -> { + if (generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID)) { + registration.addRecipeCatalyst(new ItemStack(generator.getBlockSupplier().get()), FLUID_FUEL_RECIPE_TYPE); + } else if (generator.getFuelType().equals(GeneratorUtil.FuelType.SOLID) && generator.getFuelTag().equals(GeneratorUtil.EMPTY_TAG) && generator.getFuelList() == null) { + registration.addRecipeCatalyst(new ItemStack(generator.getBlockSupplier().get()), SOLID_FUEL_RECIPE_TYPE); + } else { + registration.addRecipeCatalyst(new ItemStack(generator.getBlockSupplier().get()), SOLID_FUEL_RECIPE_TYPE); + } + }); + } + + @Override + public void registerCategories(IRecipeCategoryRegistration registration) { + IJeiHelpers jeiHelpers = registration.getJeiHelpers(); + IGuiHelper guiHelper = jeiHelpers.getGuiHelper(); + + registration.addRecipeCategories(new FluidFuelRecipeCategory(guiHelper)); + registration.addRecipeCategories(new SolidFuelRecipeCategory(guiHelper)); + } + + static List vanillaFuelRecipes; + static List foodList; + static List enchantmentList; + static List potionList; + @Override + public void registerRecipes(IRecipeRegistration registration) { + vanillaFuelRecipes = FuelRecipeMaker.getFuelRecipes(registration.getIngredientManager()); + foodList = registration.getIngredientManager().getAllItemStacks().stream().filter((stack) -> { + FoodProperties foodProperties = stack.getItem().getFoodProperties(stack, null); + return foodProperties != null; + }).toList(); + enchantmentList = RegistryUtil.getRegistry(Registries.ENCHANTMENT).holders().map(enchantment -> { + List books = new ArrayList<>(); + IntStream.range(0, enchantment.value().getMaxLevel()).forEach( + i -> books.add(EnchantedBookItem.createForEnchantment(new EnchantmentInstance(enchantment, i + 1))) + ); + return books; + }).flatMap(Collection::stream).toList(); + List basePotions = List.of(Items.POTION, Items.SPLASH_POTION, Items.LINGERING_POTION); + potionList = GeneratorUtil.getPotionFuels(Minecraft.getInstance().level.registryAccess()); + + GeneratorRegistry.generators.forEach((resourceLocation, generator) -> { + addGeneratorFuelRecipes(registration, generator, generator.getBlockSupplier().get().asItem().getDefaultInstance(), 1); + if (generator.has8x()) { + addGeneratorFuelRecipes(registration, generator, generator.getBlockSupplier().get().asItem().getDefaultInstance(), 8); + } + if (generator.has64x()) { + addGeneratorFuelRecipes(registration, generator, generator.getBlockSupplier().get().asItem().getDefaultInstance(), 64); + } + }); + } + + private void addGeneratorFuelRecipes(IRecipeRegistration registration, GeneratorObject generator, ItemStack genIngredient, int modifier) { + var solidFuelData = generator.getBlockSupplier().get().builtInRegistryHolder().getData(GeneratorGalore.SOLID_FUEL_MAP); + var fluidFuelData = generator.getBlockSupplier().get().builtInRegistryHolder().getData(GeneratorGalore.FLUID_FUEL_MAP); + + float consumptionModifier = Config.SERVER.increasedConsumption.get() ? modifier : 1; + + if (generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID) && fluidFuelData == null) { + var fuelRecipes = new ArrayList(); + if (!generator.getFuelTag().equals(GeneratorUtil.EMPTY_TAG)) { + var fluids = BuiltInRegistries.FLUID.getTag(ModTags.getFluidTag(generator.getFuelTag())); + if (fluids.isPresent()) { + List fluidStacks = fluids.get().stream().map(fluid -> new FluidStack(fluid, 10000)).toList(); + fuelRecipes.add(new FluidFuelRecipe(fluidStacks, genIngredient, (float) generator.getGenerationRate() * modifier, (float) generator.getConsumptionRate() * consumptionModifier)); + } + } + registration.addRecipes(FLUID_FUEL_RECIPE_TYPE, fuelRecipes); + } else if (fluidFuelData != null) { + // Datamap fluid fuel generator + var fuelRecipes = new ArrayList(); + fluidFuelData.fuels().forEach(fuel -> { + fuelRecipes.add(new FluidFuelRecipe(List.of(fuel.fluid().getStacks()), genIngredient, (float) fuel.generationRate() * modifier, (float) fuel.consumptionRate() * consumptionModifier)); + }); + registration.addRecipes(FLUID_FUEL_RECIPE_TYPE, fuelRecipes); + } else if (solidFuelData != null) { + // Datamap solid fuel generator + var fuelRecipes = new ArrayList(); + solidFuelData.fuels().forEach(fuel -> { + fuelRecipes.add(new SolidFuelRecipe(List.of(fuel.item()), genIngredient, (float) fuel.generationRate() * modifier, fuel.burnTime() / consumptionModifier)); + }); + registration.addRecipes(SOLID_FUEL_RECIPE_TYPE, fuelRecipes); + } else if (generator.getFuelType().equals(GeneratorUtil.FuelType.SOLID) && generator.getFuelTag().equals(GeneratorUtil.EMPTY_TAG) && generator.getFuelList() == null) { + // Standard generator + var fuelRecipes = new ArrayList(); + vanillaFuelRecipes.forEach(fuelingRecipe -> { + fuelRecipes.add(new SolidFuelRecipe(List.of(Ingredient.of(fuelingRecipe.getInputs().get(0))), genIngredient, (float) generator.getGenerationRate() * modifier, (int) (fuelingRecipe.getBurnTime() * generator.getConsumptionRate() / consumptionModifier))); + }); + registration.addRecipes(SOLID_FUEL_RECIPE_TYPE, fuelRecipes); + } else { + var fuelRecipes = new ArrayList(); + if (generator.getFuelType().equals(GeneratorUtil.FuelType.SOLID)) { + if (generator.getFuelList() != null) { + // Manual fuels item list + generator.getFuelList().forEach((itemId, fuel) -> { + fuelRecipes.add(new SolidFuelRecipe(List.of(Ingredient.of(BuiltInRegistries.ITEM.get(itemId))), genIngredient, fuel.rate() * modifier, fuel.burnTime() / consumptionModifier)); + }); + } else if (!generator.getFuelTag().equals(GeneratorUtil.EMPTY_TAG)) { + // Item tag + fuelRecipes.add(new SolidFuelRecipe(List.of(Ingredient.of(ModTags.getItemTag(generator.getFuelTag()))), genIngredient, (float) generator.getGenerationRate() * modifier, (int) generator.getConsumptionRate() * consumptionModifier)); + } + } else if (generator.getFuelType().equals(GeneratorUtil.FuelType.FOOD)) { + foodList.forEach((stack) -> { + var rate = GeneratorUtil.calculateFoodGenerationRate(generator, stack); + fuelRecipes.add(new SolidFuelRecipe(List.of(Ingredient.of(stack)), genIngredient, rate.getFirst() * modifier, rate.getSecond() / consumptionModifier)); + }); + } else if (generator.getFuelType().equals(GeneratorUtil.FuelType.ENCHANTMENT)) { + enchantmentList.forEach((stack) -> { + var rate = GeneratorUtil.calculateEnchantmentGenerationRate(generator, stack); + fuelRecipes.add(new SolidFuelRecipe(List.of(Ingredient.of(stack)), genIngredient, rate.getFirst() * modifier, rate.getSecond() / consumptionModifier)); + }); + } else if (generator.getFuelType().equals(GeneratorUtil.FuelType.POTION)) { + potionList.forEach((fuel) -> { + fuelRecipes.add(new SolidFuelRecipe(List.of(fuel.item()), genIngredient, fuel.generationRate() * modifier, fuel.consumptionRate() / consumptionModifier)); + }); + } + + registration.addRecipes(SOLID_FUEL_RECIPE_TYPE, fuelRecipes); + } + } + + @Override + public void registerGuiHandlers(IGuiHandlerRegistration registration) { +// registration.addRecipeClickArea(GeneratorScreen.class, 35, 35, 24, 16, SOLID_FUEL_RECIPE_TYPE); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/integrations/SolidFuelRecipeCategory.java --- +package cy.jdkdigital.generatorgalore.integrations; + +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.common.recipe.SolidFuelRecipe; +import cy.jdkdigital.generatorgalore.registry.GeneratorRegistry; +import cy.jdkdigital.generatorgalore.util.GeneratorObject; +import cy.jdkdigital.generatorgalore.util.GeneratorUtil; +import mezz.jei.api.constants.VanillaTypes; +import mezz.jei.api.gui.builder.IRecipeLayoutBuilder; +import mezz.jei.api.gui.drawable.IDrawable; +import mezz.jei.api.gui.ingredient.IRecipeSlotsView; +import mezz.jei.api.helpers.IGuiHelper; +import mezz.jei.api.recipe.IFocusGroup; +import mezz.jei.api.recipe.RecipeIngredientRole; +import mezz.jei.api.recipe.RecipeType; +import mezz.jei.api.recipe.category.IRecipeCategory; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; + +public class SolidFuelRecipeCategory implements IRecipeCategory +{ + private final IDrawable background; + private final IDrawable icon; + + public SolidFuelRecipeCategory(IGuiHelper guiHelper) { + ResourceLocation location = ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "textures/gui/jei/solid_fuel_recipe.png"); + this.background = guiHelper.createDrawable(location, 0, 0, 126, 70); + this.icon = guiHelper.createDrawableIngredient(VanillaTypes.ITEM_STACK, new ItemStack(GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "iron")).getBlockSupplier().get())); + } + + @Override + public @NotNull RecipeType getRecipeType() { + return JeiPlugin.SOLID_FUEL_RECIPE_TYPE; + } + + @Override + public @NotNull Component getTitle() { + return Component.translatable(GeneratorGalore.MODID + ".recipe.solid_fuel"); + } + + @Override + public @NotNull IDrawable getBackground() { + return background; + } + + @Override + public @NotNull IDrawable getIcon() { + return icon; + } + + @Override + public void setRecipe(IRecipeLayoutBuilder builder, SolidFuelRecipe recipe, @NotNull IFocusGroup iFocusGroup) { + builder.addSlot(RecipeIngredientRole.INPUT, 0, 41) + .addItemStack(recipe.generator()) + .setSlotName("generator"); + builder.addSlot(RecipeIngredientRole.INPUT, 18, 41) + .addItemStacks(recipe.fuels().stream().flatMap(ingredient -> Arrays.stream(ingredient.getItems())).toList()) + .setSlotName("fuels"); + } + + @Override + public void draw(@NotNull SolidFuelRecipe recipe, @NotNull IRecipeSlotsView recipeSlotsView, @NotNull GuiGraphics poseStack, double mouseX, double mouseY) { + Minecraft minecraft = Minecraft.getInstance(); + poseStack.drawString(minecraft.font, "Rate: " + recipe.rate() + "FE/t", 37F, 14F, 4210752, false); + poseStack.drawString(minecraft.font, "Burntime: " + recipe.consumptionRate(), 37F, 32F, 4210752, false); + poseStack.drawString(minecraft.font, "Total: " + (int) (recipe.rate() * recipe.consumptionRate()) + "FE", 37F, 50F, 4210752, false); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/network/ModPackets.java --- +package cy.jdkdigital.generatorgalore.network; + +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.neoforge.network.PacketDistributor; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; +import net.neoforged.neoforge.network.registration.PayloadRegistrar; +import cy.jdkdigital.generatorgalore.GeneratorGalore; + +public class ModPackets { + public static void registerPackets(RegisterPayloadHandlersEvent event) { + final PayloadRegistrar registrar = event.registrar(GeneratorGalore.MODID).versioned("1.0"); + registrar.playToClient(FluidSyncPacket.TYPE, FluidSyncPacket.STREAM_CODEC, ModPackets::handle); + } + + public static void handle(FluidSyncPacket packet, net.neoforged.neoforge.network.handling.IPayloadContext context) { + context.enqueueWork(() -> { + if (context.player().level().getBlockEntity(packet.pos()) instanceof cy.jdkdigital.generatorgalore.common.block.entity.GeneratorBlockEntity blockEntity) { + blockEntity.fluidInventory.setFluid(packet.fluidStack()); + } + }); + } + + public static void sendToClient(FluidSyncPacket packet, ServerPlayer player) { + PacketDistributor.sendToPlayer(player, packet); + } +}--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/network/FluidSyncPacket.java --- +package cy.jdkdigital.generatorgalore.network; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.neoforged.neoforge.fluids.FluidStack; +import cy.jdkdigital.generatorgalore.GeneratorGalore; + +public record FluidSyncPacket(BlockPos pos, FluidStack fluidStack) implements CustomPacketPayload { + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "fluid_sync")); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + BlockPos.STREAM_CODEC, + FluidSyncPacket::pos, + FluidStack.STREAM_CODEC, + FluidSyncPacket::fluidStack, + FluidSyncPacket::new + ); + + @Override + public Type type() { + return TYPE; + } +}--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/registry/GeneratorRegistry.java --- +package cy.jdkdigital.generatorgalore.registry; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.util.GeneratorCreator; +import cy.jdkdigital.generatorgalore.util.GeneratorObject; +import cy.jdkdigital.generatorgalore.util.GeneratorUtil; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.neoforged.fml.ModList; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.filefilter.FileFilterUtils; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.*; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +public class GeneratorRegistry +{ + public static final Map generators = new LinkedHashMap<>(); + + public static void discoverGenerators() { + try { + discoverGeneratorFiles(); + } catch (IOException e) { + GeneratorGalore.LOGGER.error("Failed to discover generators", e); + } + } + + private static void discoverGeneratorFiles() throws IOException { + File lockFile = new File(GeneratorUtil.LOCK_FILE.toString(), "defaults.lock"); + if (!lockFile.exists()) { + FileUtils.write(lockFile, "This lock file means the standard generator have already been added and you can now do your own custom stuff to them.", StandardCharsets.UTF_8); + setupDefaultFiles("/data/" + GeneratorGalore.MODID + "/generator", Paths.get(GeneratorUtil.GENERATORS.toString()), true); + } else { + setupDefaultFiles("/data/" + GeneratorGalore.MODID + "/generator", Paths.get(GeneratorUtil.GENERATORS.toString()), false); + } + + var files = GeneratorUtil.GENERATORS.toFile().listFiles((FileFilter) FileFilterUtils.suffixFileFilter(".json")); + if (files == null) + return; + + for (var file : files) { + JsonObject json; + InputStreamReader reader = null; + ResourceLocation id = null; + GeneratorObject generator = null; + + try { + var parser = new JsonParser(); + reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8); + json = parser.parse(reader).getAsJsonObject(); + var name = file.getName().replace(".json", ""); + id = ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, name); + + if (json.has("requiredMod") && !ModList.get().isLoaded(json.get("requiredMod").getAsString())) { + continue; + } + + generator = GeneratorCreator.create(id, json); + + reader.close(); + } catch (Exception e) { + GeneratorGalore.LOGGER.error("An error occurred while creating generator with id {}", id, e); + } finally { + IOUtils.closeQuietly(reader); + } + + if (generator != null) { + GeneratorGalore.LOGGER.debug("adding generator " + generator.getId()); + generators.put(generator.getId(), generator); + } else { + GeneratorGalore.LOGGER.error("failed to load generator " + id); + } + } + } + + public static void setupDefaultFiles(String dataPath, Path targetPath, boolean override) { + List roots = List.of(ModList.get().getModFileById(GeneratorGalore.MODID).getFile().getFilePath()); + if (override) { + GeneratorGalore.LOGGER.debug("[Generator Galore] Pulling defaults from: " + roots); + } + + if (roots.isEmpty()) { + throw new RuntimeException("Failed to load defaults."); + } + + for (Path modRoot : roots) { + setupDefaultFiles(dataPath, targetPath, modRoot, override); + } + } + + public static void setupDefaultFiles(String dataPath, Path targetPath, Path modPath, boolean override) { + GeneratorGalore.LOGGER.debug("Loading generator files from " + dataPath + " to " + targetPath); + if (Files.isRegularFile(modPath)) { + try(FileSystem fileSystem = FileSystems.newFileSystem(modPath)) { + Path path = fileSystem.getPath(dataPath); + if (Files.exists(path)) { + copyFiles(path, targetPath, override); + } + } catch (IOException e) { + GeneratorGalore.LOGGER.error("Could not load source {}!!", modPath); + GeneratorGalore.LOGGER.error(e.getLocalizedMessage()); + } + } else if (Files.isDirectory(modPath)) { + copyFiles(Paths.get(modPath.toString(), dataPath), targetPath, override); + } + } + + private static void copyFiles(Path source, Path targetPath, boolean override) { + try (Stream sourceStream = Files.walk(source)) { + sourceStream.filter(f -> f.getFileName().toString().endsWith(".json")) + .forEach(path -> { + try { + if (override) { + Files.copy(path, Paths.get(targetPath.toString(), path.getFileName().toString()), StandardCopyOption.REPLACE_EXISTING); + } else { + Files.copy(path, Paths.get(targetPath.toString(), path.getFileName().toString())); + } + } catch (IOException e) { + if (override) { + GeneratorGalore.LOGGER.error("Could not copy file: {}, Target: {}", path, targetPath); + } + } + }); + } catch (IOException e) { + GeneratorGalore.LOGGER.error("Could not stream source files: {}", source); + GeneratorGalore.LOGGER.error(e.getLocalizedMessage()); + } + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/util/collection/MultiMap.java --- +package cy.jdkdigital.generatorgalore.util.collection; + +import com.google.common.collect.ImmutableMultimap; + +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; + +public class MultiMap> { + protected final Map map; + private final Function collectionMappingFunction; + + public MultiMap(Supplier collectionSupplier) { + this(new HashMap<>(), collectionSupplier); + } + + public MultiMap(Map map, Supplier collectionSupplier) { + this.map = map; + this.collectionMappingFunction = (k -> collectionSupplier.get()); + } + + public Collection get(K key) { + T collection = map.get(key); + if (collection != null) { + return Collections.unmodifiableCollection(collection); + } + return Collections.emptyList(); + } + + public boolean put(K key, V value) { + T collection = map.computeIfAbsent(key, collectionMappingFunction); + return collection.add(value); + } + + public boolean remove(K key, V value) { + T collection = map.get(key); + return collection != null && collection.remove(value); + } + + public boolean containsKey(K key) { + return map.containsKey(key); + } + + public boolean contains(K key, V value) { + T collection = map.get(key); + return collection != null && collection.contains(value); + } + + public Set> entrySet() { + return map.entrySet(); + } + + public Set keySet() { + return map.keySet(); + } + + public Collection allValues() { + return this.map.values().stream() + .flatMap(Collection::stream) + .toList(); + } + + public void clear() { + map.clear(); + } + + public ImmutableMultimap toImmutable() { + ImmutableMultimap.Builder builder = ImmutableMultimap.builder(); + map.forEach(builder::putAll); + return builder.build(); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/util/collection/SetMultiMap.java --- +package cy.jdkdigital.generatorgalore.util.collection; + +import com.google.common.collect.ImmutableSetMultimap; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +public class SetMultiMap extends MultiMap> +{ + public SetMultiMap() { + this(HashSet::new); + } + + public SetMultiMap(Supplier> collectionSupplier) { + super(collectionSupplier); + } + + public SetMultiMap(Map> map, Supplier> collectionSupplier) { + super(map, collectionSupplier); + } + + @Override + public Set get(K key) { + Set collection = map.get(key); + if (collection != null) { + return Collections.unmodifiableSet(collection); + } + return Collections.emptySet(); + } + + @Override + public ImmutableSetMultimap toImmutable() { + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + map.forEach(builder::putAll); + return builder.build(); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/util/FluidContainerUtil.java --- +package cy.jdkdigital.generatorgalore.util; + +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.world.inventory.InventoryMenu; +import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions; +import net.neoforged.neoforge.fluids.FluidStack; + +public class FluidContainerUtil +{ + private static float getRed(int color) { + return (float) (color >> 16 & 255) / 255.0F; + } + + private static float getGreen(int color) { + return (float) (color >> 8 & 255) / 255.0F; + } + + private static float getBlue(int color) { + return (float) (color & 255) / 255.0F; + } + + private static float getAlpha(int color) { + return (float) (color >> 24 & 255) / 255.0F; + } + + public static void setColors(int color) { + RenderSystem.setShaderColor(getRed(color), getGreen(color), getBlue(color), getAlpha(color)); + } + + public static void renderFluidTank(GuiGraphics matrices, AbstractContainerScreen screen, FluidStack stack, int capacity, int x, int y, int width, int height, int depth) { + renderFluidTank(matrices, screen, stack, stack.getAmount(), capacity, x, y, width, height, depth); + } + + public static void renderFluidTank(GuiGraphics matrices, AbstractContainerScreen screen, FluidStack stack, int amount, int capacity, int x, int y, int width, int height, int depth) { + if(!stack.isEmpty() && capacity > 0) { + // Hard clamp to ensure amount never exceeds capacity + amount = Math.min(amount, capacity); + + // Use float calculation for precision, then round to nearest integer + // This ensures that (10000 / 10000) * 54 = exactly 54 + int fluidHeight = Math.max(1, Math.round((amount / (float)capacity) * height)); + int maxY = y + height; + + // Draw pragmatic fallback: solid colored rectangle first + int fluidColor = getFluidColorForFallback(stack); + int absoluteX = screen.getGuiLeft() + x; + int absoluteY = screen.getGuiTop() + y; + + // Draw solid colored fallback rectangle with high Z-Level (150) + // The formula y + height - fluidHeight ensures the fluid starts at the correct position + // and ends exactly at y + height when full + matrices.fill(absoluteX, absoluteY + height - fluidHeight, absoluteX + width, absoluteY + height, depth, fluidColor); + + // Then try to render the actual fluid texture using modern NeoForge 1.21.1 approach + renderTiledFluid(matrices, screen, stack, x, maxY - fluidHeight, width, fluidHeight, depth); + } + } + + private static int getFluidColorForFallback(FluidStack stack) { + // Default to lava orange color (0xFFFF4500) + int defaultColor = 0xFFFF4500; + + try { + // Try to get the actual fluid color from attributes + var attributes = IClientFluidTypeExtensions.of(stack.getFluid()); + int tintColor = attributes.getTintColor(); + if (tintColor != 0xFFFFFFFF) { // If not white (default), use it + return tintColor; + } + } catch (Exception e) { + // If anything fails, use default lava orange + return defaultColor; + } + + return defaultColor; + } + + public static void renderTiledFluid(GuiGraphics guiGraphics, AbstractContainerScreen screen, FluidStack stack, int x, int y, int width, int height, int depth) { + if (!stack.isEmpty()) { + try { + // Get fluid attributes and texture location using modern NeoForge 1.21.1 approach + var attributes = IClientFluidTypeExtensions.of(stack.getFluid()); + + // Get the sprite using the official Minecraft texture atlas system + TextureAtlasSprite sprite = Minecraft.getInstance().getTextureAtlas(InventoryMenu.BLOCK_ATLAS).apply(attributes.getStillTexture()); + + // Set the fluid color tint + setColors(attributes.getTintColor()); + + // Calculate absolute screen coordinates + int absoluteX = screen.getGuiLeft() + x; + int absoluteY = screen.getGuiTop() + y; + + // Use the official GuiGraphics.blit method for sprite rendering + // This handles tiling and UV coordinates automatically + guiGraphics.blit(absoluteX, absoluteY, depth, width, height, sprite); + + // Reset shader color + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + } catch (Exception e) { + // If texture rendering fails, the fallback rectangle will still be visible + } + } + } +}--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/util/GeneratorCreator.java --- +package cy.jdkdigital.generatorgalore.util; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import com.mojang.serialization.JsonOps; +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.common.block.Generator; +import cy.jdkdigital.generatorgalore.common.block.entity.GeneratorBlockEntity; +import cy.jdkdigital.generatorgalore.common.container.GeneratorMenu; +import cy.jdkdigital.generatorgalore.common.item.UpgradeItem; +import cy.jdkdigital.generatorgalore.init.ModBlockEntityTypes; +import cy.jdkdigital.generatorgalore.init.ModContainerTypes; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.level.block.SoundType; +import net.neoforged.fml.loading.FMLEnvironment; +import net.neoforged.neoforge.registries.DeferredHolder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +public class GeneratorCreator +{ + public static GeneratorObject create(ResourceLocation id, JsonObject json) throws JsonSyntaxException { + var generatorOptional = GeneratorObject.codec(id).parse(JsonOps.INSTANCE, json).result(); + + if (generatorOptional.isPresent()) { + var generator = generatorOptional.get(); + var name = String.format("%s_%s", generator.getId().getPath(), "generator"); + + Supplier generatorBlock = GeneratorGalore.BLOCKS.register(name, () -> new Generator( + BlockBehaviour.Properties.of() + .mapColor(MapColor.METAL) + .strength(3.5F, 6.0F) + .sound(SoundType.METAL) + .requiresCorrectToolForDrops(), generator, 1)); + generator.setBlockSupplier(generatorBlock); + List> generatorBlocks = new ArrayList<>(); + generatorBlocks.add(generatorBlock); + if (generator.has8x() || !FMLEnvironment.production) { + Supplier gen8x = GeneratorGalore.BLOCKS.register(name + "_8x", () -> new Generator( + BlockBehaviour.Properties.of() + .mapColor(MapColor.METAL) + .strength(3.5F, 6.0F) + .sound(SoundType.METAL) + .requiresCorrectToolForDrops(), generator, 8)); + generatorBlocks.add(gen8x); + GeneratorGalore.ITEMS.register(name + "_8x", () -> new BlockItem(gen8x.get(), new Item.Properties())); + } + if (generator.has64x() || !FMLEnvironment.production) { + Supplier gen64x = GeneratorGalore.BLOCKS.register(name + "_64x", () -> new Generator( + BlockBehaviour.Properties.of() + .mapColor(MapColor.METAL) + .strength(3.5F, 6.0F) + .sound(SoundType.METAL) + .requiresCorrectToolForDrops(), generator, 64)); + generatorBlocks.add(gen64x); + GeneratorGalore.ITEMS.register(name + "_64x", () -> new BlockItem(gen64x.get(), new Item.Properties())); + } + generator.setBlockEntityType(ModBlockEntityTypes.register(name, () -> ModBlockEntityTypes.createBlockEntityType((pos, state) -> new GeneratorBlockEntity(generator, pos, state), generatorBlocks.stream().map(Supplier::get).toList().toArray(new Block[0])))); + + generator.setMenuType(ModContainerTypes.register(name, GeneratorMenu::new)); + + GeneratorGalore.ITEMS.register(name, () -> new BlockItem(generator.getBlockSupplier().get(), new Item.Properties())); + + if (json.has("previousTier")) { + String previousTier = json.get("previousTier").getAsString(); + generator.setUpgradeSupplier(GeneratorGalore.ITEMS.register(previousTier + "_to_" + generator.getId().getPath() + "_upgrade", () -> new UpgradeItem(new Item.Properties(), previousTier, generator))); + } + + // Custom fuels list + if (json.has("fuelList")) { + generator.setFuelList(parseFuelList(generator, json.get("fuelList"))); + } + + return generator; + } else { + GeneratorGalore.LOGGER.info("failed to read generator configuration for " + id); + } + return null; + } + + private static Map parseFuelList(GeneratorObject generator, JsonElement fuelList) { + Map fuels = new HashMap<>(); + for (JsonElement jsonElement : fuelList.getAsJsonArray()) { + var el = jsonElement.getAsJsonObject(); + var id = ResourceLocation.parse(el.get("item").getAsString()); + fuels.put(id, new Fuel( + el.has("rate") ? el.get("rate").getAsFloat() : (float) generator.getGenerationRate(), + el.get("burnTime").getAsInt() + )); + } + return fuels; + } + + public record Fuel(float rate, int burnTime) {} +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/util/GeneratorObject.java --- +package cy.jdkdigital.generatorgalore.util; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.common.block.entity.GeneratorBlockEntity; +import cy.jdkdigital.generatorgalore.common.container.GeneratorMenu; +import cy.jdkdigital.generatorgalore.init.ModTags; +import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.PotionItem; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.neoforged.neoforge.fluids.FluidStack; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.function.Supplier; + +public class GeneratorObject +{ + private final ResourceLocation id; + private Supplier blockSupplier; + private Supplier> blockEntityType; + private Supplier upgradeSupplier; + private Supplier> menuType; + private final GeneratorUtil.FuelType fuelType; + private final int generationRate; + private int modifiedGenerationRate = 0; + private final int transferRate; + private int modifiedConsumptionRate; + private final int consumptionRate; + private final int bufferCapacity; + private final boolean hasChargeSlot; + private final ResourceLocation fuelTag; + private final boolean has8x; + private final boolean has64x; + private Map fuelList; + + public GeneratorObject(ResourceLocation id, GeneratorUtil.FuelType fuelType, int generationRate, int transferRate, int consumptionRate, int bufferCapacity, boolean hasChargeSlot, ResourceLocation fuelTag, boolean has8x, boolean has64x) { + this.id = id; + this.fuelType = fuelType; + this.generationRate = generationRate; + this.transferRate = transferRate; + this.consumptionRate = consumptionRate; + this.bufferCapacity = bufferCapacity; + this.hasChargeSlot = hasChargeSlot; + this.fuelTag = fuelTag; + this.has8x = has8x; + this.has64x = has64x; + } + + public ResourceLocation getId() { + return id; + } + + public Supplier getBlockSupplier() { + return blockSupplier; + } + + public void setBlockSupplier(Supplier blockSupplier) { + this.blockSupplier = blockSupplier; + } + + public Supplier> getBlockEntityType() { + return blockEntityType; + } + + public void setBlockEntityType(Supplier> blockEntityType) { + this.blockEntityType = blockEntityType; + } + + public Supplier getUpgradeSupplier() { + return upgradeSupplier; + } + + public void setUpgradeSupplier(Supplier upgradeSupplier) { + this.upgradeSupplier = upgradeSupplier; + } + + public Supplier> getMenuType() { + return menuType; + } + + public void setMenuType(Supplier> menuType) { + this.menuType = menuType; + } + + public GeneratorUtil.FuelType getFuelType() { + return this.fuelType; + } + + public int getGenerationRate() { + return modifiedGenerationRate > 0 ? modifiedGenerationRate : generationRate; + } + + public int getConsumptionRate() { + return modifiedConsumptionRate > 0 ? modifiedConsumptionRate : consumptionRate; + } + + public void setGenerationRate(int generationRate) { + this.modifiedGenerationRate = generationRate; + } + + public void setConsumptionRate(int consumptionRate) { + this.modifiedConsumptionRate = consumptionRate; + } + + public int getOriginalGenerationRate() { + return generationRate; + } + + public int getOriginalConsumptionRate() { + return consumptionRate; + } + + public int getTransferRate() { + return transferRate; + } + + public int getBufferCapacity() { + return bufferCapacity; + } + + public boolean hasChargeSlot() { + return hasChargeSlot; + } + + public ResourceLocation getFuelTag() { + return this.fuelTag; + } + + public static Codec codec(ResourceLocation id) { + return RecordCodecBuilder.create(instance -> instance.group( + ResourceLocation.CODEC.fieldOf("id").orElse(id).forGetter(GeneratorObject::getId), + GeneratorUtil.FuelType.CODEC.fieldOf("fuelType").orElse(GeneratorUtil.FuelType.SOLID).forGetter(GeneratorObject::getFuelType), + Codec.INT.fieldOf("generationRate").forGetter(GeneratorObject::getOriginalGenerationRate), + Codec.INT.fieldOf("transferRate").forGetter(GeneratorObject::getTransferRate), + Codec.INT.fieldOf("consumptionRate").forGetter(GeneratorObject::getConsumptionRate), + Codec.INT.fieldOf("bufferCapacity").forGetter(GeneratorObject::getBufferCapacity), + Codec.BOOL.fieldOf("hasChargeSlot").orElse(true).forGetter(GeneratorObject::hasChargeSlot), + ResourceLocation.CODEC.fieldOf("fuelTag").orElse(GeneratorUtil.EMPTY_TAG).forGetter(GeneratorObject::getFuelTag), + Codec.BOOL.fieldOf("has8x").orElse(true).forGetter(GeneratorObject::has8x), + Codec.BOOL.fieldOf("has64x").orElse(true).forGetter(GeneratorObject::has64x) + ).apply(instance, GeneratorObject::new)); + } + + public void setFuelList(Map fuelList) { + this.fuelList = fuelList; + } + + public Map getFuelList() { + return fuelList; + } + + public boolean has8x() { + return has8x; + } + + public boolean has64x() { + return has64x; + } + + public boolean isValidFuelItem(@NotNull ItemStack stack) { + var fuelData = getBlockSupplier().get().builtInRegistryHolder().getData(GeneratorGalore.SOLID_FUEL_MAP); + if (fuelData != null) { + return !fuelData.fuels().stream().filter(solidFuel -> solidFuel.item().test(stack)).toList().isEmpty(); + } + + if (!getFuelTag().equals(GeneratorUtil.EMPTY_TAG)) { + return stack.is(ModTags.getItemTag(getFuelTag())); + } + if (getFuelType().equals(GeneratorUtil.FuelType.FOOD)) { + return stack.get(DataComponents.FOOD) != null; + } + if (getFuelType().equals(GeneratorUtil.FuelType.ENCHANTMENT)) { + return !EnchantmentHelper.getEnchantmentsForCrafting(stack).isEmpty(); + } + if (getFuelType().equals(GeneratorUtil.FuelType.POTION)) { + return stack.getItem() instanceof PotionItem; + } + if (getFuelList() != null) { + return getFuelList().containsKey(BuiltInRegistries.ITEM.getKey(stack.getItem())); + } + + // Modern approach: Check for burn time using Data Components or fallback to legacy method + return stack.getBurnTime(RecipeType.SMELTING) > 0; + } + + public boolean isValidFuelFluid(FluidStack stack) { + var fuelData = getBlockSupplier().get().builtInRegistryHolder().getData(GeneratorGalore.FLUID_FUEL_MAP); + if (fuelData != null) { + return !fuelData.fuels().stream().filter(solidFuel -> solidFuel.fluid().test(stack)).toList().isEmpty(); + } + + if (!getFuelTag().equals(GeneratorUtil.EMPTY_TAG)) { + return stack.getFluid().is(ModTags.getFluidTag(getFuelTag())); + } + + return false; + } + + public Pair getGenerationRateForItem(Level level, ItemStack fuelStack) { + var fuelData = getBlockSupplier().get().builtInRegistryHolder().getData(GeneratorGalore.SOLID_FUEL_MAP); + if (fuelData != null) { + var validFuels = fuelData.fuels().stream().filter(solidFuel -> solidFuel.item().test(fuelStack)).toList(); + return validFuels.isEmpty() ? + new Pair<>((float) getGenerationRate(), (int) (fuelStack.getBurnTime(RecipeType.SMELTING) * getConsumptionRate())) : + new Pair<>((float) validFuels.getFirst().generationRate(), (int) (validFuels.getFirst().burnTime() * validFuels.getFirst().consumptionRate())); + } + + Pair rate; + if (getFuelType().equals(GeneratorUtil.FuelType.ENCHANTMENT)) { + rate = GeneratorUtil.calculateEnchantmentGenerationRate(this, fuelStack); + } else if (getFuelType().equals(GeneratorUtil.FuelType.POTION)) { + rate = GeneratorUtil.calculatePotionGenerationRate(level, this, fuelStack); + } else if (getFuelType().equals(GeneratorUtil.FuelType.FOOD)) { + rate = GeneratorUtil.calculateFoodGenerationRate(this, fuelStack); + } else if (getFuelList() != null) { + var fuel = getFuelList().get(BuiltInRegistries.ITEM.getKey(fuelStack.getItem())); + rate = new Pair<>(fuel.rate() > 0 ? fuel.rate() : (float)getOriginalGenerationRate(), fuel.burnTime()); + } else { + rate = new Pair<>((float) getGenerationRate(), (int) (fuelStack.getBurnTime(RecipeType.SMELTING) * getConsumptionRate())); + } + return rate; + } + + public Pair getGenerationRateForFluid(FluidStack fluidStack) { + if (isValidFuelFluid(fluidStack)) { + var fuelData = getBlockSupplier().get().builtInRegistryHolder().getData(GeneratorGalore.FLUID_FUEL_MAP); + if (fuelData != null) { + var validFuels = fuelData.fuels().stream().filter(solidFuel -> solidFuel.fluid().test(fluidStack)).toList(); + return validFuels.isEmpty() ? + Pair.of(getGenerationRate(), getConsumptionRate()) : + Pair.of((int)validFuels.getFirst().generationRate(), (int)validFuels.getFirst().consumptionRate()); + } + return Pair.of(getGenerationRate(), getConsumptionRate()); + } + return null; + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/util/GeneratorUtil.java --- +package cy.jdkdigital.generatorgalore.util; + +import com.mojang.datafixers.util.Pair; +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.common.block.entity.GeneratorBlockEntity; +import cy.jdkdigital.generatorgalore.common.container.GeneratorMenu; +import cy.jdkdigital.generatorgalore.common.datamap.PotionComponentIngredient; +import cy.jdkdigital.generatorgalore.common.datamap.SolidFuelMap; +import net.minecraft.client.Minecraft; +import net.minecraft.core.BlockPos; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.StringRepresentable; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.food.FoodProperties; +import net.minecraft.world.item.EnchantedBookItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.alchemy.Potion; +import net.minecraft.world.item.alchemy.PotionContents; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.HorizontalDirectionalBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.neoforged.fml.loading.FMLPaths; +import net.neoforged.neoforge.items.ItemStackHandler; + +import java.io.IOException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + +public class GeneratorUtil +{ + public enum FuelType implements StringRepresentable + { + SOLID("SOLID"), + FLUID("FLUID"), + FOOD("FOOD"), + ENCHANTMENT("ENCHANTMENT"), + POTION("POTION"); + + private final String key; + + public static EnumCodec CODEC = StringRepresentable.fromEnum(GeneratorUtil.FuelType::values); + + FuelType(String key) { + this.key = key; + } + + @Override + public String getSerializedName() { + return this.key; + } + } + public static ResourceLocation EMPTY_TAG = ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "empty"); + public static String FUEL_SOLID = "SOLID"; + public static String FUEL_FLUID = "FLUID"; + public static String FUEL_FOOD = "FOOD"; + public static String FUEL_ENCHANTMENT = "ENCHANTMENT"; + public static final Path LOCK_FILE = createCustomPath(""); + public static final Path GENERATORS = createCustomPath("generator"); + + private static Path createCustomPath(String pathName) { + Path customPath = Paths.get(FMLPaths.CONFIGDIR.get().toAbsolutePath().toString(), GeneratorGalore.MODID, pathName); + createDirectory(customPath, pathName); + return customPath; + } + + private static void createDirectory(Path path, String dirName) { + try { + Files.createDirectories(path); + } catch (FileAlreadyExistsException e) { //ignored + } catch (IOException e) { + GeneratorGalore.LOGGER.error("failed to create \""+dirName+"\" directory"); + } + } + + public static void replaceGenerator(Level level, BlockPos pos, GeneratorObject generator) { + BlockState existingGenerator = level.getBlockState(pos); + BlockState newGenerator = generator.getBlockSupplier().get() + .defaultBlockState() + .setValue(HorizontalDirectionalBlock.FACING, existingGenerator.getValue(HorizontalDirectionalBlock.FACING)) + .setValue(BlockStateProperties.LIT, existingGenerator.getValue(BlockStateProperties.LIT)); + + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof GeneratorBlockEntity generatorBlockEntity) { + CompoundTag tag = generatorBlockEntity.saveWithoutMetadata(level.registryAccess()); + + if (generatorBlockEntity.inventoryHandler instanceof ItemStackHandler itemHandler) { + itemHandler.setStackInSlot(GeneratorMenu.SLOT_FUEL, ItemStack.EMPTY); + itemHandler.setStackInSlot(GeneratorMenu.SLOT_CHARGE, ItemStack.EMPTY); + } + + level.setBlockAndUpdate(pos, newGenerator); + level.getBlockEntity(pos).loadCustomOnly(tag, level.registryAccess()); + } + } + + public static Pair calculateFoodGenerationRate(GeneratorObject generator, ItemStack stack) { + FoodProperties foodProperties = stack.getItem().getFoodProperties(stack, null); + if (foodProperties != null) { + int value = foodProperties.nutrition(); + float saturation = foodProperties.saturation(); + double totalRF = value * saturation * 8000; + + return Pair.of((float) (value * generator.getOriginalGenerationRate()), (int) (totalRF / generator.getGenerationRate())); + } + return Pair.of((float) generator.getGenerationRate(), (int) generator.getConsumptionRate()); + } + + public static Pair calculateEnchantmentGenerationRate(GeneratorObject generator, ItemStack stack) { + if (stack.isEnchanted() || stack.getItem() instanceof EnchantedBookItem) { + double totalRF = 0; + var enchantments = EnchantmentHelper.getEnchantmentsForCrafting(stack); + for(var entry : enchantments.entrySet()) { + var enchantment = entry.getKey().value(); + float level = (float) entry.getValue(); + float max =(float) enchantment.getMaxLevel(); + float min = (float) enchantment.getMinCost(entry.getValue()); + float weight = enchantment.getWeight(); + + totalRF = totalRF + Math.abs(Math.sqrt(Math.min(level + 1d, max) / max) * Math.pow(max, 2) * (level + 1) * (min/Math.sqrt(weight))) * 400; + } + + return Pair.of((float) generator.getGenerationRate(), (int) (totalRF / generator.getGenerationRate())); + } + return Pair.of((float) generator.getGenerationRate(), (int) generator.getConsumptionRate()); + } + + public static Pair calculatePotionGenerationRate(Level level, GeneratorObject generator, ItemStack stack) { + List fuels = GeneratorUtil.getPotionFuels(level.registryAccess()); + for (SolidFuelMap.SolidFuel fuel : fuels) { + if (fuel.item().test(stack)) { + return Pair.of((float) fuel.generationRate(), (int) fuel.consumptionRate()); + } + } + return Pair.of(0f, 1); + } + + public static List getPotionFuels(HolderLookup.Provider provider) { + List potionFuels = new ArrayList<>(); + provider.lookup(Registries.POTION).ifPresent( + potionRegistryLookup -> { + generatePotionEffectTypes( + potionFuels, + potionRegistryLookup, + Items.POTION + ); + generatePotionEffectTypes( + potionFuels, + potionRegistryLookup, + Items.SPLASH_POTION + ); + generatePotionEffectTypes( + potionFuels, + potionRegistryLookup, + Items.LINGERING_POTION + ); + } + ); + return potionFuels; + } + + private static void generatePotionEffectTypes(List potionFuels, HolderLookup potions, Item item) { + potions.listElements() + .map(potion -> { + var stack = PotionContents.createItemStack(item, potion); + int burnTime = 0; + for (MobEffectInstance mobEffectInstance : stack.get(DataComponents.POTION_CONTENTS).getAllEffects()) { + burnTime += 3 * (1 + mobEffectInstance.getAmplifier()) * (mobEffectInstance.getDuration() * 3) + (potion.getKey().location().getPath().contains("strong_") ? 6000 : 0); + } + return new SolidFuelMap.SolidFuel(PotionComponentIngredient.of(stack), 1.0f, burnTime, 8); + }) + .forEach(potionFuels::add); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/util/PotionUtil.java --- +package cy.jdkdigital.generatorgalore.util; + +import cy.jdkdigital.generatorgalore.GeneratorGalore; +import cy.jdkdigital.generatorgalore.util.collection.SetMultiMap; +import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.alchemy.Potion; +import net.minecraft.world.item.alchemy.PotionBrewing; +import net.minecraft.world.item.alchemy.PotionContents; +import net.minecraft.world.item.alchemy.Potions; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.common.brewing.BrewingRecipe; +import net.neoforged.neoforge.common.brewing.IBrewingRecipe; + +import java.util.*; +import java.util.stream.Collectors; + +public class PotionUtil +{ + public static final Map brewingStepCache = new HashMap<>(); + private static final SetMultiMap potionMap = new SetMultiMap<>(); + + public static SetMultiMap getPotionMap(Level level) { + if (potionMap.allValues().isEmpty() && level instanceof ServerLevel serverLevel) { + + List brewingRecipes = serverLevel.potionBrewing().getRecipes(); + brewingRecipes.stream() + .filter(Objects::nonNull) + .map(IBrewingRecipe.class::cast) + .findFirst() + .ifPresent(vanillaBrewingRecipe -> addVanillaBrewingRecipes(potionMap, vanillaBrewingRecipe)); + addModdedBrewingRecipes(brewingRecipes, potionMap); + } + return potionMap; + } + + private static void addVanillaBrewingRecipes(SetMultiMap potionMap, IBrewingRecipe vanillaBrewingRecipe) { + List potionIngredients = BuiltInRegistries.ITEM.stream().map(ItemStack::new).filter(PotionBrewing.EMPTY::isIngredient).toList(); + + List basePotions = List.of(Items.POTION, Items.SPLASH_POTION, Items.LINGERING_POTION); + + List knownPotions = BuiltInRegistries.POTION.holders().map(potion -> { + List potions = new ArrayList<>(); + if (potion.value().getEffects().size() > 0) { + for (Item input : basePotions) { + ItemStack result = new ItemStack(input); + result.set(DataComponents.POTION_CONTENTS, new PotionContents(potion)); + potions.add(result); + } + } + return potions; + }).flatMap(Collection::stream).collect(Collectors.toCollection(ArrayList::new)); + + boolean foundNewPotions; + do { + List newPotions = getNewPotions(knownPotions, potionIngredients, potionMap, vanillaBrewingRecipe); + foundNewPotions = !newPotions.isEmpty(); + knownPotions.addAll(newPotions); + } while (foundNewPotions); + } + + private static List getNewPotions(Collection knownPotions, List potionReagents, SetMultiMap potionMap, IBrewingRecipe vanillaBrewingRecipe) { + List newPotions = new ArrayList<>(); + for (ItemStack potionInput : knownPotions) { + for (ItemStack potionReagent : potionReagents) { + ItemStack potionOutput = vanillaBrewingRecipe.getOutput(potionInput.copy(), potionReagent); + if (potionOutput.isEmpty()) { + continue; + } + + if (potionInput.getItem() == potionOutput.getItem()) { + var potionOutputType = potionOutput.get(DataComponents.POTION_CONTENTS); + if (potionOutputType.potion().get().equals(Potions.WATER)) { + continue; + } + + var potionInputType = potionInput.get(DataComponents.POTION_CONTENTS); + if (potionInputType.is(potionOutputType.potion().get())) { + continue; + } + } + + // Add to potion map + potionMap.put(getUniquePotionName(potionOutput), getUniquePotionName(potionInput)); + } + } + return newPotions; + } + + private static void addModdedBrewingRecipes(Collection brewingRecipes, SetMultiMap potionMap) { + for (IBrewingRecipe iBrewingRecipe : brewingRecipes) { + if (iBrewingRecipe instanceof BrewingRecipe brewingRecipe) { + ItemStack[] ingredients = brewingRecipe.getIngredient().getItems(); + if (ingredients.length > 0) { + Ingredient inputIngredient = brewingRecipe.getInput(); + ItemStack output = brewingRecipe.getOutput(); + ItemStack[] inputs = inputIngredient.getItems(); + // Add to potion map + for (ItemStack input: inputs) { + potionMap.put(getUniquePotionName(output), getUniquePotionName(input)); + } + } + } + } + } + + public static String getUniquePotionName(ItemStack stack) { + StringBuilder potionUid = new StringBuilder(BuiltInRegistries.ITEM.getKey(stack.getItem()).toString()); + if (stack.has(DataComponents.POTION_CONTENTS)) { + var potionData = stack.get(DataComponents.POTION_CONTENTS); + if (potionData != null) { + potionData.getAllEffects().forEach(mobEffectInstance -> potionUid.append(mobEffectInstance.getDescriptionId())); + } + } + return potionUid.toString(); + } +} +--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/GeneratorGalore.java --- +package cy.jdkdigital.generatorgalore; + +import com.mojang.logging.LogUtils; +import com.mojang.serialization.MapCodec; +import cy.jdkdigital.generatorgalore.common.conditions.GeneratorExistsCondition; +import cy.jdkdigital.generatorgalore.common.datamap.FluidFuelMap; +import cy.jdkdigital.generatorgalore.common.datamap.PotionComponentIngredient; +import cy.jdkdigital.generatorgalore.common.datamap.SolidFuelMap; +import cy.jdkdigital.generatorgalore.registry.GeneratorRegistry; +import cy.jdkdigital.generatorgalore.util.GeneratorUtil; +import cy.jdkdigital.generatorgalore.network.ModPackets; +import net.minecraft.core.particles.ParticleType; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.config.ModConfig; +import net.neoforged.neoforge.capabilities.Capabilities; +import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent; +import net.neoforged.neoforge.common.conditions.ICondition; +import net.neoforged.neoforge.common.crafting.IngredientType; +import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; +import net.neoforged.neoforge.registries.DeferredHolder; +import net.neoforged.neoforge.registries.DeferredRegister; +import net.neoforged.neoforge.registries.NeoForgeRegistries; +import net.neoforged.neoforge.registries.datamaps.DataMapType; +import net.neoforged.neoforge.registries.datamaps.RegisterDataMapTypesEvent; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.core.component.DataComponents; +import com.mojang.serialization.Codec; +import net.neoforged.neoforge.registries.DeferredRegister; +import net.neoforged.neoforge.fluids.FluidStack; +import java.util.function.Supplier; +import org.slf4j.Logger; + +@Mod(GeneratorGalore.MODID) +public class GeneratorGalore { + public static final String MODID = "generatorgalore"; + public static final Logger LOGGER = LogUtils.getLogger(); + + public static final DeferredRegister BLOCKS = DeferredRegister.create(Registries.BLOCK, MODID); + public static final DeferredRegister ITEMS = DeferredRegister.create(Registries.ITEM, MODID); + public static final DeferredRegister> CONTAINER_TYPES = DeferredRegister.create(Registries.MENU, MODID); + public static final DeferredRegister> BLOCK_ENTITIES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, MODID); + public static final DeferredRegister> RECIPE_SERIALIZERS = DeferredRegister.create(Registries.RECIPE_SERIALIZER, MODID); + public static final DeferredRegister> RECIPE_TYPES = DeferredRegister.create(Registries.RECIPE_TYPE, MODID); + public static final DeferredRegister> PARTICLE_TYPES = DeferredRegister.create(Registries.PARTICLE_TYPE, MODID); + public static final DeferredRegister CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MODID); + public static final DeferredRegister> CONDITION_CODECS = DeferredRegister.create(NeoForgeRegistries.Keys.CONDITION_CODECS, MODID); + public static final DeferredRegister> INGREDIENT_TYPES = DeferredRegister.create(NeoForgeRegistries.Keys.INGREDIENT_TYPES, MODID); + + public static final DeferredHolder TAB = CREATIVE_MODE_TABS.register(MODID, () -> CreativeModeTab.builder() + .withTabsBefore(CreativeModeTabs.SPAWN_EGGS) + .icon(() -> new ItemStack(GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(MODID, "iron")).getBlockSupplier().get())) + .title(Component.literal("Generator Galore")) + .build()); + + public static final DeferredHolder, MapCodec> GENERATOR_EXISTS_CONDITION = CONDITION_CODECS.register("generator_exists", () -> GeneratorExistsCondition.CODEC); + public static final DataMapType FLUID_FUEL_MAP = DataMapType.builder(ResourceLocation.fromNamespaceAndPath(MODID, "fluid_fuel_map"), Registries.BLOCK, FluidFuelMap.CODEC).synced(FluidFuelMap.CODEC, false).build(); + public static final DataMapType SOLID_FUEL_MAP = DataMapType.builder(ResourceLocation.fromNamespaceAndPath(MODID, "solid_fuel_map"), Registries.BLOCK, SolidFuelMap.CODEC).synced(SolidFuelMap.CODEC, false).build(); + public static final DeferredHolder, IngredientType> POTIOM_INGREDIENT_TYPE = INGREDIENT_TYPES.register("component", () -> new IngredientType<>(PotionComponentIngredient.CODEC)); + + public static final DeferredRegister> DATA_COMPONENTS = DeferredRegister.create(Registries.DATA_COMPONENT_TYPE, MODID); + public static final Supplier> ENERGY_COMPONENT = DATA_COMPONENTS.register("energy_storage", () -> DataComponentType.builder().persistent(Codec.INT).networkSynchronized(ByteBufCodecs.VAR_INT).build()); + public static final Supplier> FLUID_COMPONENT = DATA_COMPONENTS.register("fluid_storage", () -> DataComponentType.builder().persistent(FluidStack.CODEC).networkSynchronized(FluidStack.STREAM_CODEC).build()); + + public GeneratorGalore(IEventBus modEventBus, ModContainer modContainer) { + GeneratorRegistry.discoverGenerators(); + + BLOCKS.register(modEventBus); + BLOCK_ENTITIES.register(modEventBus); + CONTAINER_TYPES.register(modEventBus); + ITEMS.register(modEventBus); + RECIPE_SERIALIZERS.register(modEventBus); + RECIPE_TYPES.register(modEventBus); + PARTICLE_TYPES.register(modEventBus); + CREATIVE_MODE_TABS.register(modEventBus); + CONDITION_CODECS.register(modEventBus); + INGREDIENT_TYPES.register(modEventBus); + DATA_COMPONENTS.register(modEventBus); + + modContainer.registerConfig(ModConfig.Type.SERVER, Config.SERVER_CONFIG); + modContainer.registerConfig(ModConfig.Type.CLIENT, Config.CLIENT_CONFIG); + } + + @EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD, modid = MODID) + public static class EventHandler { + + @SubscribeEvent + public static void registerPayloadHandlers(RegisterPayloadHandlersEvent event) { + ModPackets.registerPackets(event); + } + + @SubscribeEvent + public static void buildContents(BuildCreativeModeTabContentsEvent event) { + if (event.getTabKey() == TAB.getKey()) { + GeneratorRegistry.generators.values().forEach(generatorObject -> { + event.accept(generatorObject.getBlockSupplier().get()); + }); + } + } + + @SubscribeEvent + private static void registerDataMap(final RegisterDataMapTypesEvent event) { + event.register(FLUID_FUEL_MAP); + event.register(SOLID_FUEL_MAP); + } + + @SubscribeEvent + public static void registerCapabilities(RegisterCapabilitiesEvent event) { + GeneratorRegistry.generators.values().forEach(generatorObject -> { + event.registerBlockEntity( + Capabilities.ItemHandler.BLOCK, + generatorObject.getBlockEntityType().get(), + (myBlockEntity, side) -> myBlockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID) ? null : myBlockEntity.inventoryHandler + ); + event.registerBlockEntity( + Capabilities.EnergyStorage.BLOCK, + generatorObject.getBlockEntityType().get(), + (myBlockEntity, side) -> myBlockEntity.energyHandler + ); + event.registerBlockEntity( + Capabilities.FluidHandler.BLOCK, + generatorObject.getBlockEntityType().get(), + (myBlockEntity, side) -> myBlockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID) ? myBlockEntity.fluidInventory : null + ); + }); + } + } +}--- FILE: ./src/main/java/cy/jdkdigital/generatorgalore/Config.java --- +package cy.jdkdigital.generatorgalore; + +import net.neoforged.neoforge.common.ModConfigSpec; + +public class Config +{ + private static final ModConfigSpec.Builder CLIENT_BUILDER = new ModConfigSpec.Builder(); + private static final ModConfigSpec.Builder SERVER_BUILDER = new ModConfigSpec.Builder(); + public static final ModConfigSpec CLIENT_CONFIG; + public static final Client CLIENT = new Client(CLIENT_BUILDER); + public static final ModConfigSpec SERVER_CONFIG; + public static final General SERVER = new General(SERVER_BUILDER); + + static { + CLIENT_CONFIG = CLIENT_BUILDER.build(); + SERVER_CONFIG = SERVER_BUILDER.build(); + } + + public static class Client + { + public Client(ModConfigSpec.Builder builder) { + builder.push("Client"); + + builder.pop(); + } + } + + public static class General + { + public final ModConfigSpec.IntValue tickRate; + public final ModConfigSpec.BooleanValue increasedConsumption; + + public General(ModConfigSpec.Builder builder) { + builder.push("General"); + + tickRate = builder + .comment("Generator tickrateaka aka how often should the generator tick. Default is once every 5 ticks. Increase if you're having performance issues") + .defineInRange("tickRate", 5, 1, 64); + + increasedConsumption = builder + .comment("Make 8x and 64x increase the consumption rate and not just the production rate. This will be default on in 1.22") + .define("increasedConsumption", false); + + builder.pop(); + } + } +} \ No newline at end of file diff --git a/src/main/java/cy/jdkdigital/generatorgalore/GeneratorGalore.java b/src/main/java/cy/jdkdigital/generatorgalore/GeneratorGalore.java index 7009a85..9457bd6 100644 --- a/src/main/java/cy/jdkdigital/generatorgalore/GeneratorGalore.java +++ b/src/main/java/cy/jdkdigital/generatorgalore/GeneratorGalore.java @@ -8,6 +8,7 @@ import cy.jdkdigital.generatorgalore.common.datamap.SolidFuelMap; import cy.jdkdigital.generatorgalore.registry.GeneratorRegistry; import cy.jdkdigital.generatorgalore.util.GeneratorUtil; +import cy.jdkdigital.generatorgalore.network.ModPackets; import net.minecraft.core.particles.ParticleType; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; @@ -33,43 +34,52 @@ import net.neoforged.neoforge.common.conditions.ICondition; import net.neoforged.neoforge.common.crafting.IngredientType; import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; import net.neoforged.neoforge.registries.DeferredHolder; import net.neoforged.neoforge.registries.DeferredRegister; import net.neoforged.neoforge.registries.NeoForgeRegistries; import net.neoforged.neoforge.registries.datamaps.DataMapType; import net.neoforged.neoforge.registries.datamaps.RegisterDataMapTypesEvent; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.core.component.DataComponents; +import com.mojang.serialization.Codec; +import net.neoforged.neoforge.registries.DeferredRegister; +import net.neoforged.neoforge.fluids.FluidStack; +import java.util.function.Supplier; import org.slf4j.Logger; @Mod(GeneratorGalore.MODID) -public class GeneratorGalore -{ +public class GeneratorGalore { public static final String MODID = "generatorgalore"; public static final Logger LOGGER = LogUtils.getLogger(); - public static final DeferredRegister BLOCKS = DeferredRegister.create(Registries.BLOCK, GeneratorGalore.MODID); - public static final DeferredRegister ITEMS = DeferredRegister.create(Registries.ITEM, GeneratorGalore.MODID); - public static final DeferredRegister> CONTAINER_TYPES = DeferredRegister.create(Registries.MENU, GeneratorGalore.MODID); - public static final DeferredRegister> BLOCK_ENTITIES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, GeneratorGalore.MODID); - public static final DeferredRegister> RECIPE_SERIALIZERS = DeferredRegister.create(Registries.RECIPE_SERIALIZER, GeneratorGalore.MODID); - public static final DeferredRegister> RECIPE_TYPES = DeferredRegister.create(Registries.RECIPE_TYPE, GeneratorGalore.MODID); - public static final DeferredRegister> PARTICLE_TYPES = DeferredRegister.create(Registries.PARTICLE_TYPE, GeneratorGalore.MODID); - public static final DeferredRegister CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, GeneratorGalore.MODID); + public static final DeferredRegister BLOCKS = DeferredRegister.create(Registries.BLOCK, MODID); + public static final DeferredRegister ITEMS = DeferredRegister.create(Registries.ITEM, MODID); + public static final DeferredRegister> CONTAINER_TYPES = DeferredRegister.create(Registries.MENU, MODID); + public static final DeferredRegister> BLOCK_ENTITIES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, MODID); + public static final DeferredRegister> RECIPE_SERIALIZERS = DeferredRegister.create(Registries.RECIPE_SERIALIZER, MODID); + public static final DeferredRegister> RECIPE_TYPES = DeferredRegister.create(Registries.RECIPE_TYPE, MODID); + public static final DeferredRegister> PARTICLE_TYPES = DeferredRegister.create(Registries.PARTICLE_TYPE, MODID); + public static final DeferredRegister CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MODID); public static final DeferredRegister> CONDITION_CODECS = DeferredRegister.create(NeoForgeRegistries.Keys.CONDITION_CODECS, MODID); public static final DeferredRegister> INGREDIENT_TYPES = DeferredRegister.create(NeoForgeRegistries.Keys.INGREDIENT_TYPES, MODID); - public static DeferredHolder TAB = CREATIVE_MODE_TABS.register(MODID, () -> { - return CreativeModeTab.builder() - .withTabsBefore(CreativeModeTabs.SPAWN_EGGS) - .icon(() -> new ItemStack(BuiltInRegistries.ITEM.get(ResourceLocation.fromNamespaceAndPath(MODID, "iron_generator")))) - .title(Component.literal("Generator Galore")) - .build(); - }); + public static final DeferredHolder TAB = CREATIVE_MODE_TABS.register(MODID, () -> CreativeModeTab.builder() + .withTabsBefore(CreativeModeTabs.SPAWN_EGGS) + .icon(() -> new ItemStack(GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(MODID, "iron")).getBlockSupplier().get())) + .title(Component.literal("Generator Galore")) + .build()); + public static final DeferredHolder, MapCodec> GENERATOR_EXISTS_CONDITION = CONDITION_CODECS.register("generator_exists", () -> GeneratorExistsCondition.CODEC); public static final DataMapType FLUID_FUEL_MAP = DataMapType.builder(ResourceLocation.fromNamespaceAndPath(MODID, "fluid_fuel_map"), Registries.BLOCK, FluidFuelMap.CODEC).synced(FluidFuelMap.CODEC, false).build(); public static final DataMapType SOLID_FUEL_MAP = DataMapType.builder(ResourceLocation.fromNamespaceAndPath(MODID, "solid_fuel_map"), Registries.BLOCK, SolidFuelMap.CODEC).synced(SolidFuelMap.CODEC, false).build(); - public static final DeferredHolder, IngredientType> POTIOM_INGREDIENT_TYPE = INGREDIENT_TYPES.register("component", () -> new IngredientType<>(PotionComponentIngredient.CODEC)); + public static final DeferredRegister> DATA_COMPONENTS = DeferredRegister.create(Registries.DATA_COMPONENT_TYPE, MODID); + public static final Supplier> ENERGY_COMPONENT = DATA_COMPONENTS.register("energy_storage", () -> DataComponentType.builder().persistent(Codec.INT).networkSynchronized(ByteBufCodecs.VAR_INT).build()); + public static final Supplier> FLUID_COMPONENT = DATA_COMPONENTS.register("fluid_storage", () -> DataComponentType.builder().persistent(FluidStack.CODEC).networkSynchronized(FluidStack.STREAM_CODEC).build()); + public GeneratorGalore(IEventBus modEventBus, ModContainer modContainer) { GeneratorRegistry.discoverGenerators(); @@ -83,20 +93,26 @@ public GeneratorGalore(IEventBus modEventBus, ModContainer modContainer) { CREATIVE_MODE_TABS.register(modEventBus); CONDITION_CODECS.register(modEventBus); INGREDIENT_TYPES.register(modEventBus); + DATA_COMPONENTS.register(modEventBus); modContainer.registerConfig(ModConfig.Type.SERVER, Config.SERVER_CONFIG); modContainer.registerConfig(ModConfig.Type.CLIENT, Config.CLIENT_CONFIG); } @EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD, modid = MODID) - public static class EventHandler - { + public static class EventHandler { + + @SubscribeEvent + public static void registerPayloadHandlers(RegisterPayloadHandlersEvent event) { + ModPackets.registerPackets(event); + } + @SubscribeEvent public static void buildContents(BuildCreativeModeTabContentsEvent event) { - if (event.getTab().equals(TAB.get())) { - for (DeferredHolder item: GeneratorGalore.ITEMS.getEntries()) { - event.accept(item.get()); - } + if (event.getTabKey() == TAB.getKey()) { + GeneratorRegistry.generators.values().forEach(generatorObject -> { + event.accept(generatorObject.getBlockSupplier().get()); + }); } } @@ -127,4 +143,4 @@ public static void registerCapabilities(RegisterCapabilitiesEvent event) { }); } } -} +} \ No newline at end of file diff --git a/src/main/java/cy/jdkdigital/generatorgalore/common/block/Generator.java b/src/main/java/cy/jdkdigital/generatorgalore/common/block/Generator.java index c610b1b..ed1bc78 100644 --- a/src/main/java/cy/jdkdigital/generatorgalore/common/block/Generator.java +++ b/src/main/java/cy/jdkdigital/generatorgalore/common/block/Generator.java @@ -55,7 +55,7 @@ public class Generator extends BaseEntityBlock .apply(builder, Generator::new) ); - GeneratorObject generator; + private final GeneratorObject generator; private final int modifier; public Generator(Properties properties, GeneratorObject generator, int modifier) { @@ -120,7 +120,6 @@ protected ItemInteractionResult useItemOn(ItemStack pStack, BlockState pState, L protected InteractionResult useWithoutItem(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, BlockHitResult pHitResult) { if (pLevel.getBlockEntity(pPos) instanceof GeneratorBlockEntity generatorBlockEntity) { if (!pLevel.isClientSide) { - generatorBlockEntity.refreshConnectedTileEntityCache(); pPlayer.openMenu(generatorBlockEntity, packetBuffer -> packetBuffer.writeBlockPos(pPos)); } return InteractionResult.SUCCESS_NO_ITEM_USED; @@ -128,23 +127,6 @@ protected InteractionResult useWithoutItem(BlockState pState, Level pLevel, Bloc return super.useWithoutItem(pState, pLevel, pPos, pPlayer, pHitResult); } - @Override - public void onPlace(BlockState state, Level level, BlockPos pos, BlockState newState, boolean something) { - BlockEntity generatorTile = level.getBlockEntity(pos); - if (generatorTile instanceof GeneratorBlockEntity generatorBlockEntity) { - generatorBlockEntity.refreshConnectedTileEntityCache(); - } - super.onPlace(state, level, pos, newState, something); - } - - @Override - public BlockState updateShape(BlockState state, Direction direction, BlockState newState, LevelAccessor level, BlockPos pos, BlockPos facingPos) { - BlockEntity generatorTile = level.getBlockEntity(pos); - if (generatorTile instanceof GeneratorBlockEntity generatorBlockEntity) { - generatorBlockEntity.refreshConnectedTileEntityCache(); - } - return super.updateShape(state, direction, newState, level, pos, facingPos); - } @Override public void animateTick(BlockState pState, Level level, BlockPos pos, RandomSource random) { @@ -161,7 +143,7 @@ public void animateTick(BlockState pState, Level level, BlockPos pos, RandomSour level.addParticle(ParticleTypes.SMOKE, d0, d1, d2, 0.0D, 0.0D, 0.0D); break; case ENCHANTMENT: -// level.addParticle(ModParticles.RISING_ENCHANT_PARTICLE.get(), (double) pos.getX() + 0.5D, (double) pos.getY() + 1.0D, (double) pos.getZ() + 0.5D, random.nextFloat() / 2.0F, 5.0E-5D, random.nextFloat() / 2.0F); + // Enchantment particle effect disabled for now break; default: level.addParticle(ParticleTypes.LAVA, (double) pos.getX() + 0.5D, (double) pos.getY() + 1.0D, (double) pos.getZ() + 0.5D, random.nextFloat() / 2.0F, 5.0E-5D, random.nextFloat() / 2.0F); diff --git a/src/main/java/cy/jdkdigital/generatorgalore/common/block/entity/GeneratorBlockEntity.java b/src/main/java/cy/jdkdigital/generatorgalore/common/block/entity/GeneratorBlockEntity.java index 8ef0df1..6a11131 100644 --- a/src/main/java/cy/jdkdigital/generatorgalore/common/block/entity/GeneratorBlockEntity.java +++ b/src/main/java/cy/jdkdigital/generatorgalore/common/block/entity/GeneratorBlockEntity.java @@ -12,9 +12,11 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.HolderLookup; +import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; @@ -27,15 +29,18 @@ import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.material.Fluid; import net.neoforged.neoforge.capabilities.Capabilities; +import net.neoforged.neoforge.energy.EnergyStorage; import net.neoforged.neoforge.energy.IEnergyStorage; import net.neoforged.neoforge.fluids.FluidStack; import net.neoforged.neoforge.fluids.capability.IFluidHandler; import net.neoforged.neoforge.fluids.capability.templates.FluidTank; +import cy.jdkdigital.generatorgalore.network.FluidSyncPacket; +import cy.jdkdigital.generatorgalore.network.ModPackets; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.neoforge.network.PacketDistributor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.List; import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -49,17 +54,19 @@ public class GeneratorBlockEntity extends CapabilityBlockEntity public int fluidId = 0; public final GeneratorObject generator; private final int modifier; + private final int generationRate; + private final int consumptionRate; public final ControlledEnergyStorage energyHandler; public final ManualItemHandler inventoryHandler; public final FluidTank fluidInventory; - private List recipients = new ArrayList<>(); private boolean hasLoaded = false; public GeneratorBlockEntity(GeneratorObject generator, BlockPos blockPos, BlockState blockState) { super(generator.getBlockEntityType().get(), blockPos, blockState); this.generator = generator; - this.modifier = blockState.getBlock() instanceof Generator generatorBlock ? generatorBlock.getModifier() : 1; + this.generationRate = (int) (generator.getOriginalGenerationRate() * this.modifier); + this.consumptionRate = (int) (generator.getOriginalConsumptionRate() * this.modifier); this.energyHandler = new ControlledEnergyStorage(generator.getBufferCapacity() * this.modifier); this.inventoryHandler = new ManualItemHandler(2) { @Override @@ -76,32 +83,36 @@ protected void onContentsChanged(int slot) { setChanged(); } }; - this.fluidInventory = new FluidTank(10000) { - @Override - public boolean isFluidValid(FluidStack stack) { - return generator.isValidFuelFluid(stack); - } + this.fluidInventory = new FluidTank(10000) { + @Override + public boolean isFluidValid(FluidStack stack) { + return generator.isValidFuelFluid(stack); + } - @Override - protected void onContentsChanged() { - super.onContentsChanged(); - if (generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID)) { - fluidId = BuiltInRegistries.FLUID.getId(getFluid().getFluid()); - setChanged(); + @Override + protected void onContentsChanged() { + super.onContentsChanged(); + if (generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID)) { + fluidId = BuiltInRegistries.FLUID.getId(getFluid().getFluid()); + syncFluidToClients(); + setChanged(); + } } - } - }; + }; } public static void tick(Level level, BlockPos pos, BlockState state, GeneratorBlockEntity blockEntity) { int tickRate = Config.SERVER.tickRate.get(); - if (!blockEntity.hasLoaded || blockEntity.tickCounter%111 == 0) { - blockEntity.refreshConnectedTileEntityCache(); + if (!blockEntity.hasLoaded) { blockEntity.hasLoaded = true; } + if (++blockEntity.tickCounter % tickRate == 0) { - double inputPowerAmount = blockEntity.getGenerationRate() * tickRate; + // Cache generation and consumption rates to avoid redundant calculations + int generationRate = blockEntity.getGenerationRate(); + int consumptionRate = blockEntity.getConsumptionRate(); + int inputPowerAmount = generationRate * tickRate; AtomicBoolean hasConsumedFuel = new AtomicBoolean(false); if (!blockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID)) { @@ -119,18 +130,18 @@ public static void tick(Level level, BlockPos pos, BlockState state, GeneratorBl (rate.getFirst() * rate.getSecond()) <= (blockEntity.energyHandler.getMaxEnergyStored() - blockEntity.energyHandler.getEnergyStored()); if (shouldBurn) { - blockEntity.generator.setGenerationRate(rate.getFirst()); + blockEntity.generator.setGenerationRate(rate.getFirst().intValue()); blockEntity.litTime = Config.SERVER.increasedConsumption.get() ? rate.getSecond() / blockEntity.modifier : rate.getSecond(); // Do burn if (blockEntity.litTime == 0) { - blockEntity.litTime = (int) (Config.SERVER.increasedConsumption.get() ? blockEntity.generator.getConsumptionRate() / blockEntity.modifier : blockEntity.generator.getConsumptionRate()); + blockEntity.litTime = Config.SERVER.increasedConsumption.get() ? consumptionRate / blockEntity.modifier : consumptionRate; } blockEntity.litDuration = blockEntity.litTime; if (blockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.ENCHANTMENT)) { // strip enchantments blockEntity.inventoryHandler.setStackInSlot(GeneratorMenu.SLOT_FUEL, new ItemStack(fuelStack.getItem() instanceof EnchantedBookItem ? Items.BOOK : fuelStack.getItem())); - } else if (!fuelStack.getCraftingRemainingItem().isEmpty() && fuelStack.getCount() == 1) { + } else if (fuelStack.hasCraftingRemainingItem() && fuelStack.getCount() == 1) { blockEntity.inventoryHandler.setStackInSlot(GeneratorMenu.SLOT_FUEL, fuelStack.getCraftingRemainingItem()); } else { fuelStack.shrink(1); @@ -142,12 +153,13 @@ public static void tick(Level level, BlockPos pos, BlockState state, GeneratorBl hasConsumedFuel.set(true); } } else if (blockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID) && blockEntity.energyHandler.getEnergyStored() + inputPowerAmount <= blockEntity.energyHandler.getMaxEnergyStored()) { - var fluidStack = blockEntity.fluidInventory.getFluidInTank(0); - Pair rate = blockEntity.generator.getGenerationRateForFluid(fluidStack); + FluidStack fluidStack = blockEntity.fluidInventory.getFluidInTank(0); + Pair rate = blockEntity.generator.getGenerationRateForFluid(fluidStack); if (rate != null) { - double fluidConsumeAmount = rate.getSecond() * tickRate * blockEntity.modifier; + int fluidConsumeAmount = rate.getSecond() * tickRate * blockEntity.modifier; if (blockEntity.fluidInventory.getFluidInTank(0).getAmount() >= fluidConsumeAmount) { - blockEntity.fluidInventory.drain((int) fluidConsumeAmount, IFluidHandler.FluidAction.EXECUTE); + blockEntity.fluidInventory.drain(fluidConsumeAmount, IFluidHandler.FluidAction.EXECUTE); + blockEntity.syncFluidToClients(); blockEntity.generator.setGenerationRate(rate.getFirst()); blockEntity.generator.setConsumptionRate(rate.getSecond()); hasConsumedFuel.set(true); @@ -156,11 +168,11 @@ public static void tick(Level level, BlockPos pos, BlockState state, GeneratorBl } if (hasConsumedFuel.get()) { - inputPowerAmount = blockEntity.getGenerationRate() * tickRate; // recalculate + inputPowerAmount = generationRate * tickRate; // recalculate with cached rate // If the generated FE is not divisible by the tickRate, save the excess for next tick - inputPowerAmount = (inputPowerAmount + blockEntity.remainder); - int addedPower = (int) inputPowerAmount; - blockEntity.remainder = inputPowerAmount - addedPower; + double tempPowerAmount = inputPowerAmount + blockEntity.remainder; + int addedPower = (int) tempPowerAmount; + blockEntity.remainder = tempPowerAmount - addedPower; blockEntity.energyHandler.receiveEnergy(addedPower, false, true); blockEntity.setOn(true); @@ -173,12 +185,12 @@ public static void tick(Level level, BlockPos pos, BlockState state, GeneratorBl } } - public double getGenerationRate() { - return generator.getGenerationRate() * this.modifier; + public int getGenerationRate() { + return this.generationRate; } - public double getConsumptionRate() { - return generator.getConsumptionRate() * this.modifier; + public int getConsumptionRate() { + return this.consumptionRate; } public boolean isLit() { @@ -197,6 +209,7 @@ private void sendOutPower(int amount) { if (capacity.get() > 0) { AtomicBoolean dirty = new AtomicBoolean(false); + // Lazy evaluation - only process charge slot if generator has charge slot if (generator.hasChargeSlot()) { var chargeItem = inventoryHandler.getStackInSlot(GeneratorMenu.SLOT_CHARGE); if (!chargeItem.isEmpty()) { @@ -210,19 +223,24 @@ private void sendOutPower(int amount) { } } - for (IEnergyStorage handler : recipients) { - boolean doContinue = capacity.get() > 0; - if (handler.canReceive() && doContinue) { - int received = handler.receiveEnergy(Math.min(capacity.get(), amount), false); - capacity.addAndGet(-received); - energyHandler.extractEnergy(received, false); - dirty.set(true); - } + // Direct neighbor capability query instead of cached list + if (capacity.get() > 0) { + Direction[] directions = Direction.values(); + for (Direction direction : directions) { + var energyCap = level.getCapability(Capabilities.EnergyStorage.BLOCK, worldPosition.relative(direction), direction.getOpposite()); + if (energyCap != null && energyCap.canReceive()) { + int received = energyCap.receiveEnergy(Math.min(capacity.get(), amount), false); + capacity.addAndGet(-received); + energyHandler.extractEnergy(received, false); + dirty.set(true); - if (!doContinue) { - break; + if (capacity.get() <= 0) { + break; + } + } } } + if (dirty.get()) { this.setChanged(); } @@ -236,20 +254,19 @@ public AbstractContainerMenu createMenu(int id, Inventory inventory, Player play return new GeneratorMenu(id, inventory, this); } - public void refreshConnectedTileEntityCache() { - if (level instanceof ServerLevel) { - List recipients = new ArrayList<>(); - Direction[] directions = Direction.values(); - for (Direction direction : directions) { - IEnergyStorage energyCap = level.getCapability(Capabilities.EnergyStorage.BLOCK, worldPosition.relative(direction), direction.getOpposite()); - if (energyCap != null) { - recipients.add(energyCap); - } + public void syncFluidToClients() { + if (level != null && !level.isClientSide && generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID)) { + FluidStack fluidStack = fluidInventory.getFluidInTank(0); + FluidSyncPacket packet = new FluidSyncPacket(worldPosition, fluidStack); + for (ServerPlayer player : level.getServer().getPlayerList().getPlayers()) { + ModPackets.sendToClient(packet, player); } - this.recipients = recipients; } } + // Removed refreshConnectedTileEntityCache() method and recipients list + // as we now use direct capability queries per tick + @Override public @NotNull Component getName() { return Component.translatable("block." + GeneratorGalore.MODID + "." + generator.getId().getPath().toLowerCase(Locale.ENGLISH) + "_generator" + (this.modifier > 1 ? "_" + this.modifier + "x" : "")); @@ -261,8 +278,8 @@ protected void loadAdditional(CompoundTag pTag, HolderLookup.Provider pRegistrie litTime = pTag.getInt("litTime"); litDuration = pTag.getInt("litDuration"); if (pTag.contains("generationRate")) { - generator.setGenerationRate(pTag.getDouble("generationRate")); - generator.setConsumptionRate(pTag.getDouble("consumptionRate")); + generator.setGenerationRate(pTag.getInt("generationRate")); + generator.setConsumptionRate(pTag.getInt("consumptionRate")); } } @@ -272,10 +289,10 @@ protected void saveAdditional(CompoundTag pTag, HolderLookup.Provider pRegistrie pTag.putInt("litTime", litTime); pTag.putInt("litDuration", litDuration); if (generator.getGenerationRate() != generator.getOriginalGenerationRate()) { - pTag.putDouble("generationRate", generator.getGenerationRate()); + pTag.putInt("generationRate", generator.getGenerationRate()); } if (generator.getConsumptionRate() != generator.getOriginalConsumptionRate()) { - pTag.putDouble("consumptionRate", generator.getConsumptionRate()); + pTag.putInt("consumptionRate", generator.getConsumptionRate()); } } @@ -310,4 +327,29 @@ public void loadPacketNBT(CompoundTag tag, HolderLookup.Provider pRegistries) { fluidId = BuiltInRegistries.FLUID.getId(fluid); } } + + @Override + protected void collectImplicitComponents(DataComponentMap.Builder components) { + super.collectImplicitComponents(components); + // Store energy and fluid data in components + components.set(GeneratorGalore.ENERGY_COMPONENT.get(), energyHandler.getEnergyStored()); + components.set(GeneratorGalore.FLUID_COMPONENT.get(), fluidInventory.getFluid()); + } + + @Override + protected void applyImplicitComponents(BlockEntity.DataComponentInput componentInput) { + super.applyImplicitComponents(componentInput); + // Restore energy and fluid data from components + Integer energy = componentInput.get(GeneratorGalore.ENERGY_COMPONENT.get()); + FluidStack fluid = componentInput.get(GeneratorGalore.FLUID_COMPONENT.get()); + + if (energy != null) { + // Use receiveEnergy with internal flag to set energy directly + energyHandler.receiveEnergy(energy, false, true); + } + + if (fluid != null && !fluid.isEmpty() && generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID)) { + fluidInventory.setFluid(fluid); + } + } } diff --git a/src/main/java/cy/jdkdigital/generatorgalore/common/container/GeneratorScreen.java b/src/main/java/cy/jdkdigital/generatorgalore/common/container/GeneratorScreen.java index 6e11f76..927ec65 100644 --- a/src/main/java/cy/jdkdigital/generatorgalore/common/container/GeneratorScreen.java +++ b/src/main/java/cy/jdkdigital/generatorgalore/common/container/GeneratorScreen.java @@ -23,6 +23,16 @@ public class GeneratorScreen extends AbstractContainerScreen private static final ResourceLocation GUI_SOLID_CHARGING = ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "textures/gui/container/generator_solid_charging.png"); private static final ResourceLocation GUI_FLUID_CHARGING = ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "textures/gui/container/generator_fluid_charging.png"); + // Smooth energy bar interpolation variables + private int lastEnergy = 0; + private int clientEnergy = 0; + private boolean firstEnergyRender = true; + + // Smooth fluid level interpolation variables + private int lastFluidAmount = 0; + private int clientFluidAmount = 0; + private boolean firstFluidRender = true; + public GeneratorScreen(GeneratorMenu screenContainer, Inventory inv, Component titleIn) { super(screenContainer, inv, titleIn); } @@ -44,9 +54,10 @@ protected void renderLabels(@Nonnull GuiGraphics guiGraphics, int mouseX, int mo List tooltipList = new ArrayList<>(); int energyAmount = this.menu.blockEntity.energyHandler.getEnergyStored(); - // Energy level tooltip + // Energy level tooltip - use current energy for tooltip + int currentEnergy = this.menu.blockEntity.energyHandler.getEnergyStored(); if (isHovering(134, 16, 16, 54, mouseX, mouseY)) { - tooltipList.add(Component.translatable(GeneratorGalore.MODID + ".screen.energy_level", energyAmount + "/" + this.menu.blockEntity.energyHandler.getMaxEnergyStored() + "FE").getVisualOrderText()); + tooltipList.add(Component.translatable(GeneratorGalore.MODID + ".screen.energy_level", currentEnergy + "/" + this.menu.blockEntity.energyHandler.getMaxEnergyStored() + "FE").getVisualOrderText()); } if (this.menu.blockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID)) { @@ -76,21 +87,116 @@ protected void renderBg(@NotNull GuiGraphics guiGraphics, float partialTicks, in canCharge ? GUI_FLUID_CHARGING : GUI_FLUID : canCharge ? GUI_SOLID_CHARGING : GUI_SOLID; guiGraphics.blit(GUI, getGuiLeft(), getGuiTop(), 0, 0, this.getXSize(), this.getYSize()); - // Burn progress + // Update energy interpolation + int currentEnergy = this.menu.blockEntity.energyHandler.getEnergyStored(); + int maxEnergy = this.menu.blockEntity.energyHandler.getMaxEnergyStored(); + + // Initialize interpolation variables on first render to prevent overshooting + if (firstEnergyRender) { + lastEnergy = currentEnergy; + clientEnergy = currentEnergy; + firstEnergyRender = false; + } + + // Store last energy value and interpolate for smooth animation + if (lastEnergy != currentEnergy) { + clientEnergy = lastEnergy; + lastEnergy = currentEnergy; + } + + // Calculate interpolated energy level with partial ticks for smooth animation + // Use float calculation for precision and round to nearest integer + float interpolatedEnergy = clientEnergy + (lastEnergy - clientEnergy) * partialTicks; + int energyLevel = Math.round((interpolatedEnergy / maxEnergy) * 54f); + + // Burn progress - use vanilla flame animation if (this.menu.blockEntity.isLit()) { int progress = this.menu.getLitProgress(); - guiGraphics.blit(GUI, getGuiLeft() + 81, getGuiTop() + 50 - progress, 176, 12 - progress, 14, progress); + // Use correct vanilla furnace flame sprite + guiGraphics.blitSprite(ResourceLocation.withDefaultNamespace("container/furnace/lit_progress"), 14, 14, 0, 0, getGuiLeft() + 81, getGuiTop() + 50 - progress, 14, progress); } - // Draw energy level - int energyLevel = (int) ((float) this.menu.blockEntity.energyHandler.getEnergyStored() * 54f / (float) this.menu.blockEntity.energyHandler.getMaxEnergyStored()); - guiGraphics.blit(GUI, getGuiLeft() + 134, getGuiTop() + 70 - energyLevel, 176, 70 - energyLevel, 16, energyLevel + 1); + // Realtime energy bar rendering - replaces legacy blit calls + if (maxEnergy > 0) { + // Calculate fill height: (current * totalHeight) / max + int fillHeight = (int) (((float) currentEnergy / maxEnergy) * 54f); + + // Main energy bar (red) - grows from bottom up + if (fillHeight > 0) { + guiGraphics.fill( + getGuiLeft() + 134, getGuiTop() + 70 - fillHeight, + getGuiLeft() + 134 + 16, getGuiTop() + 70, + 0xFFFF0000 // Redstone red + ); + } + + // Left accent stripe (1 pixel, lighter red) + if (fillHeight > 0) { + guiGraphics.fill( + getGuiLeft() + 134, getGuiTop() + 70 - fillHeight, + getGuiLeft() + 134 + 1, getGuiTop() + 70, + 0xFFFF5555 // Lighter red accent + ); + } + } if (this.menu.blockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID)) { - // Draw item tank FluidStack fluidStack = this.menu.blockEntity.fluidInventory.getFluidInTank(0); - if (fluidStack.getAmount() > 0) { - FluidContainerUtil.renderFluidTank(guiGraphics, this, fluidStack, this.menu.blockEntity.fluidInventory.getTankCapacity(0), 26, 16, 16, 54, 100); + int currentFluidAmount = fluidStack.getAmount(); + int fluidCapacity = this.menu.blockEntity.fluidInventory.getTankCapacity(0); + + // Initialize interpolation variables on first render to prevent overshooting + if (firstFluidRender) { + lastFluidAmount = currentFluidAmount; + clientFluidAmount = currentFluidAmount; + firstFluidRender = false; + } + + // Update fluid interpolation for smooth animation + if (lastFluidAmount != currentFluidAmount) { + clientFluidAmount = lastFluidAmount; + lastFluidAmount = currentFluidAmount; + } + + // Calculate interpolated fluid amount with partial ticks for smooth animation + float interpolatedFluidAmount = clientFluidAmount + (lastFluidAmount - clientFluidAmount) * partialTicks; + + if (currentFluidAmount > 0) { + // Calculate fill height: (current * totalHeight) / max + int fluidFillHeight = (int) (((float) currentFluidAmount / fluidCapacity) * 54f); + + // Try to get fluid color dynamically with precise lava detection + int fluidColor = 0xFF3663D9; // Default blue + if (!fluidStack.isEmpty()) { + try { + // Try to get fluid color using the same method as FluidContainerUtil + var attributes = net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions.of(fluidStack.getFluid()); + int tintColor = attributes.getTintColor(); + + // Precise lava color detection + if (fluidStack.getFluid().getFluidType().toString().contains("lava") || tintColor == -1) { + tintColor = 0xFFD45A12; // Strong lava orange + } + + if (tintColor != 0xFFFFFFFF) { // If not white (default), use it + fluidColor = tintColor; + } + // Ensure alpha channel is set + fluidColor = (fluidColor & 0x00FFFFFF) | 0xFF000000; + } catch (Exception e) { + // Fallback to blue if color extraction fails + fluidColor = 0xFF3663D9; + } + } + + // Realtime fluid bar rendering - grows from bottom up + if (fluidFillHeight > 0) { + guiGraphics.fill( + getGuiLeft() + 26, getGuiTop() + 70 - fluidFillHeight, + getGuiLeft() + 26 + 16, getGuiTop() + 70, + fluidColor + ); + } } } } diff --git a/src/main/java/cy/jdkdigital/generatorgalore/data/LootDataProvider.java b/src/main/java/cy/jdkdigital/generatorgalore/data/LootDataProvider.java index 53fcd63..56e2546 100644 --- a/src/main/java/cy/jdkdigital/generatorgalore/data/LootDataProvider.java +++ b/src/main/java/cy/jdkdigital/generatorgalore/data/LootDataProvider.java @@ -1,6 +1,7 @@ package cy.jdkdigital.generatorgalore.data; import com.google.common.collect.Maps; +import cy.jdkdigital.generatorgalore.GeneratorGalore; import cy.jdkdigital.generatorgalore.registry.GeneratorRegistry; import net.minecraft.core.HolderLookup; import net.minecraft.core.registries.BuiltInRegistries; @@ -12,10 +13,12 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.world.flag.FeatureFlags; import net.minecraft.world.level.block.Block; +import net.minecraft.core.component.DataComponents; import net.minecraft.world.level.storage.loot.LootPool; import net.minecraft.world.level.storage.loot.LootTable; import net.minecraft.world.level.storage.loot.entries.LootItem; import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; +import net.minecraft.world.level.storage.loot.functions.CopyComponentsFunction; import net.minecraft.world.level.storage.loot.predicates.ExplosionCondition; import net.minecraft.world.level.storage.loot.providers.number.ConstantValue; import org.jetbrains.annotations.NotNull; @@ -43,7 +46,7 @@ public String getName() { @Override public CompletableFuture run(CachedOutput pOutput) { - return this.registries.thenCompose(provider -> this.run(pOutput, provider)); + return this.registries.thenComposeAsync(provider -> this.run(pOutput, provider)); } private CompletableFuture run(CachedOutput pOutput, HolderLookup.Provider pProvider) { @@ -103,7 +106,11 @@ public void dropSelf(@NotNull Block block) { } protected static LootTable.Builder genOptionalBlockDrop(Block block) { - LootPoolEntryContainer.Builder builder = LootItem.lootTableItem(block).when(ExplosionCondition.survivesExplosion()); + LootPoolEntryContainer.Builder builder = LootItem.lootTableItem(block) + .apply(CopyComponentsFunction.copyComponents(CopyComponentsFunction.Source.BLOCK_ENTITY) + .include(GeneratorGalore.ENERGY_COMPONENT.get()) + .include(GeneratorGalore.FLUID_COMPONENT.get())) + .when(ExplosionCondition.survivesExplosion()); return LootTable.lootTable().withPool( LootPool.lootPool().setRolls(ConstantValue.exactly(1)) diff --git a/src/main/java/cy/jdkdigital/generatorgalore/integrations/FluidFuelRecipeCategory.java b/src/main/java/cy/jdkdigital/generatorgalore/integrations/FluidFuelRecipeCategory.java index 186a447..4cba6c8 100644 --- a/src/main/java/cy/jdkdigital/generatorgalore/integrations/FluidFuelRecipeCategory.java +++ b/src/main/java/cy/jdkdigital/generatorgalore/integrations/FluidFuelRecipeCategory.java @@ -2,6 +2,7 @@ import cy.jdkdigital.generatorgalore.GeneratorGalore; import cy.jdkdigital.generatorgalore.common.recipe.FluidFuelRecipe; +import cy.jdkdigital.generatorgalore.registry.GeneratorRegistry; import mezz.jei.api.constants.VanillaTypes; import mezz.jei.api.gui.builder.IRecipeLayoutBuilder; import mezz.jei.api.gui.drawable.IDrawable; @@ -28,7 +29,7 @@ public class FluidFuelRecipeCategory implements IRecipeCategory public FluidFuelRecipeCategory(IGuiHelper guiHelper) { ResourceLocation location = ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "textures/gui/jei/fluid_fuel_recipe.png"); this.background = guiHelper.createDrawable(location, 0, 0, 126, 70); - this.icon = guiHelper.createDrawableIngredient(VanillaTypes.ITEM_STACK, new ItemStack(BuiltInRegistries.ITEM.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "magmatic_generator")))); + this.icon = guiHelper.createDrawableIngredient(VanillaTypes.ITEM_STACK, new ItemStack(GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "magmatic")).getBlockSupplier().get())); } @Override diff --git a/src/main/java/cy/jdkdigital/generatorgalore/integrations/JeiPlugin.java b/src/main/java/cy/jdkdigital/generatorgalore/integrations/JeiPlugin.java index e79ba9a..60fc8c8 100644 --- a/src/main/java/cy/jdkdigital/generatorgalore/integrations/JeiPlugin.java +++ b/src/main/java/cy/jdkdigital/generatorgalore/integrations/JeiPlugin.java @@ -103,10 +103,10 @@ public void registerRecipes(IRecipeRegistration registration) { GeneratorRegistry.generators.forEach((resourceLocation, generator) -> { addGeneratorFuelRecipes(registration, generator, generator.getBlockSupplier().get().asItem().getDefaultInstance(), 1); if (generator.has8x()) { - addGeneratorFuelRecipes(registration, generator, BuiltInRegistries.ITEM.get(BuiltInRegistries.BLOCK.getKey(generator.getBlockSupplier().get()).withPath(p -> p + "_8x")).getDefaultInstance(), 8); + addGeneratorFuelRecipes(registration, generator, generator.getBlockSupplier().get().asItem().getDefaultInstance(), 8); } if (generator.has64x()) { - addGeneratorFuelRecipes(registration, generator, BuiltInRegistries.ITEM.get(BuiltInRegistries.BLOCK.getKey(generator.getBlockSupplier().get()).withPath(p -> p + "_64x")).getDefaultInstance(), 64); + addGeneratorFuelRecipes(registration, generator, generator.getBlockSupplier().get().asItem().getDefaultInstance(), 64); } }); } diff --git a/src/main/java/cy/jdkdigital/generatorgalore/integrations/SolidFuelRecipeCategory.java b/src/main/java/cy/jdkdigital/generatorgalore/integrations/SolidFuelRecipeCategory.java index 48b3a25..8ac6689 100644 --- a/src/main/java/cy/jdkdigital/generatorgalore/integrations/SolidFuelRecipeCategory.java +++ b/src/main/java/cy/jdkdigital/generatorgalore/integrations/SolidFuelRecipeCategory.java @@ -2,6 +2,7 @@ import cy.jdkdigital.generatorgalore.GeneratorGalore; import cy.jdkdigital.generatorgalore.common.recipe.SolidFuelRecipe; +import cy.jdkdigital.generatorgalore.registry.GeneratorRegistry; import cy.jdkdigital.generatorgalore.util.GeneratorObject; import cy.jdkdigital.generatorgalore.util.GeneratorUtil; import mezz.jei.api.constants.VanillaTypes; @@ -31,7 +32,7 @@ public class SolidFuelRecipeCategory implements IRecipeCategory public SolidFuelRecipeCategory(IGuiHelper guiHelper) { ResourceLocation location = ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "textures/gui/jei/solid_fuel_recipe.png"); this.background = guiHelper.createDrawable(location, 0, 0, 126, 70); - this.icon = guiHelper.createDrawableIngredient(VanillaTypes.ITEM_STACK, new ItemStack(BuiltInRegistries.ITEM.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "iron_generator")))); + this.icon = guiHelper.createDrawableIngredient(VanillaTypes.ITEM_STACK, new ItemStack(GeneratorRegistry.generators.get(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "iron")).getBlockSupplier().get())); } @Override diff --git a/src/main/java/cy/jdkdigital/generatorgalore/network/FluidSyncPacket.java b/src/main/java/cy/jdkdigital/generatorgalore/network/FluidSyncPacket.java new file mode 100644 index 0000000..beebdda --- /dev/null +++ b/src/main/java/cy/jdkdigital/generatorgalore/network/FluidSyncPacket.java @@ -0,0 +1,25 @@ +package cy.jdkdigital.generatorgalore.network; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.neoforged.neoforge.fluids.FluidStack; +import cy.jdkdigital.generatorgalore.GeneratorGalore; + +public record FluidSyncPacket(BlockPos pos, FluidStack fluidStack) implements CustomPacketPayload { + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(GeneratorGalore.MODID, "fluid_sync")); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + BlockPos.STREAM_CODEC, + FluidSyncPacket::pos, + FluidStack.STREAM_CODEC, + FluidSyncPacket::fluidStack, + FluidSyncPacket::new + ); + + @Override + public Type type() { + return TYPE; + } +} \ No newline at end of file diff --git a/src/main/java/cy/jdkdigital/generatorgalore/network/ModPackets.java b/src/main/java/cy/jdkdigital/generatorgalore/network/ModPackets.java new file mode 100644 index 0000000..8b08529 --- /dev/null +++ b/src/main/java/cy/jdkdigital/generatorgalore/network/ModPackets.java @@ -0,0 +1,26 @@ +package cy.jdkdigital.generatorgalore.network; + +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.neoforge.network.PacketDistributor; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; +import net.neoforged.neoforge.network.registration.PayloadRegistrar; +import cy.jdkdigital.generatorgalore.GeneratorGalore; + +public class ModPackets { + public static void registerPackets(RegisterPayloadHandlersEvent event) { + final PayloadRegistrar registrar = event.registrar(GeneratorGalore.MODID).versioned("1.0"); + registrar.playToClient(FluidSyncPacket.TYPE, FluidSyncPacket.STREAM_CODEC, ModPackets::handle); + } + + public static void handle(FluidSyncPacket packet, net.neoforged.neoforge.network.handling.IPayloadContext context) { + context.enqueueWork(() -> { + if (context.player().level().getBlockEntity(packet.pos()) instanceof cy.jdkdigital.generatorgalore.common.block.entity.GeneratorBlockEntity blockEntity) { + blockEntity.fluidInventory.setFluid(packet.fluidStack()); + } + }); + } + + public static void sendToClient(FluidSyncPacket packet, ServerPlayer player) { + PacketDistributor.sendToPlayer(player, packet); + } +} \ No newline at end of file diff --git a/src/main/java/cy/jdkdigital/generatorgalore/registry/GeneratorRegistry.java b/src/main/java/cy/jdkdigital/generatorgalore/registry/GeneratorRegistry.java index efb7520..8804147 100644 --- a/src/main/java/cy/jdkdigital/generatorgalore/registry/GeneratorRegistry.java +++ b/src/main/java/cy/jdkdigital/generatorgalore/registry/GeneratorRegistry.java @@ -3,7 +3,6 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import cy.jdkdigital.generatorgalore.GeneratorGalore; -import cy.jdkdigital.generatorgalore.init.ModBlockEntityTypes; import cy.jdkdigital.generatorgalore.util.GeneratorCreator; import cy.jdkdigital.generatorgalore.util.GeneratorObject; import cy.jdkdigital.generatorgalore.util.GeneratorUtil; @@ -25,13 +24,13 @@ public class GeneratorRegistry { - public static Map generators = new LinkedHashMap<>(); + public static final Map generators = new LinkedHashMap<>(); public static void discoverGenerators() { try { discoverGeneratorFiles(); } catch (IOException e) { - e.printStackTrace(); + GeneratorGalore.LOGGER.error("Failed to discover generators", e); } } @@ -81,8 +80,6 @@ private static void discoverGeneratorFiles() throws IOException { GeneratorGalore.LOGGER.error("failed to load generator " + id); } } - -// ModBlockEntityTypes.registerGeneratorBlockEntities(); } public static void setupDefaultFiles(String dataPath, Path targetPath, boolean override) { diff --git a/src/main/java/cy/jdkdigital/generatorgalore/util/FluidContainerUtil.java b/src/main/java/cy/jdkdigital/generatorgalore/util/FluidContainerUtil.java index 5df84f6..6f50b2a 100644 --- a/src/main/java/cy/jdkdigital/generatorgalore/util/FluidContainerUtil.java +++ b/src/main/java/cy/jdkdigital/generatorgalore/util/FluidContainerUtil.java @@ -1,16 +1,13 @@ package cy.jdkdigital.generatorgalore.util; import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.*; +import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.client.renderer.GameRenderer; import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.inventory.InventoryMenu; import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions; import net.neoforged.neoforge.fluids.FluidStack; -import org.joml.Matrix4f; public class FluidContainerUtil { @@ -34,82 +31,79 @@ public static void setColors(int color) { RenderSystem.setShaderColor(getRed(color), getGreen(color), getBlue(color), getAlpha(color)); } - public static void bindTexture(ResourceLocation texture) { - RenderSystem.setShader(GameRenderer::getPositionTexShader); - RenderSystem.setShaderTexture(0, texture); - } - public static void renderFluidTank(GuiGraphics matrices, AbstractContainerScreen screen, FluidStack stack, int capacity, int x, int y, int width, int height, int depth) { renderFluidTank(matrices, screen, stack, stack.getAmount(), capacity, x, y, width, height, depth); } public static void renderFluidTank(GuiGraphics matrices, AbstractContainerScreen screen, FluidStack stack, int amount, int capacity, int x, int y, int width, int height, int depth) { if(!stack.isEmpty() && capacity > 0) { + // Hard clamp to ensure amount never exceeds capacity + amount = Math.min(amount, capacity); + + // Use float calculation for precision, then round to nearest integer + // This ensures that (10000 / 10000) * 54 = exactly 54 + int fluidHeight = Math.max(1, Math.round((amount / (float)capacity) * height)); int maxY = y + height; - int fluidHeight = Math.min(height * amount / capacity, height); + + // Draw pragmatic fallback: solid colored rectangle first + int fluidColor = getFluidColorForFallback(stack); + int absoluteX = screen.getGuiLeft() + x; + int absoluteY = screen.getGuiTop() + y; + + // Draw solid colored fallback rectangle with high Z-Level (150) + // The formula y + height - fluidHeight ensures the fluid starts at the correct position + // and ends exactly at y + height when full + matrices.fill(absoluteX, absoluteY + height - fluidHeight, absoluteX + width, absoluteY + height, depth, fluidColor); + + // Then try to render the actual fluid texture using modern NeoForge 1.21.1 approach renderTiledFluid(matrices, screen, stack, x, maxY - fluidHeight, width, fluidHeight, depth); } } - public static void renderTiledFluid(GuiGraphics matrices, AbstractContainerScreen screen, FluidStack stack, int x, int y, int width, int height, int depth) { - if (!stack.isEmpty()) { + private static int getFluidColorForFallback(FluidStack stack) { + // Default to lava orange color (0xFFFF4500) + int defaultColor = 0xFFFF4500; + + try { + // Try to get the actual fluid color from attributes var attributes = IClientFluidTypeExtensions.of(stack.getFluid()); - TextureAtlasSprite fluidSprite = screen.getMinecraft().getTextureAtlas(InventoryMenu.BLOCK_ATLAS).apply(attributes.getStillTexture()); - setColors(attributes.getTintColor()); - renderTiledTextureAtlas(matrices, screen, fluidSprite, x, y, width, height, depth, false); - RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + int tintColor = attributes.getTintColor(); + if (tintColor != 0xFFFFFFFF) { // If not white (default), use it + return tintColor; + } + } catch (Exception e) { + // If anything fails, use default lava orange + return defaultColor; } + + return defaultColor; } - public static void renderTiledTextureAtlas(GuiGraphics matrices, AbstractContainerScreen screen, TextureAtlasSprite sprite, int x, int y, int width, int height, int depth, boolean upsideDown) { - // start drawing sprites - bindTexture(sprite.atlasLocation()); - - int spriteHeight = sprite.contents().height(); - int spriteWidth = sprite.contents().width(); - // tile vertically - int startX = x + screen.getGuiLeft(); - int startY = y + screen.getGuiTop(); - - Matrix4f matrix = matrices.pose().last().pose(); - - final int xTileCount = width / spriteWidth; - final int xRemainder = width - (xTileCount * spriteWidth); - final long yTileCount = height / spriteHeight; - final long yRemainder = height - (yTileCount * spriteHeight); - - for (int xTile = 0; xTile <= xTileCount; xTile++) { - for (int yTile = 0; yTile <= yTileCount; yTile++) { - int widthLeft = (xTile == xTileCount) ? xRemainder : spriteWidth; - long heightLeft = (yTile == yTileCount) ? yRemainder : spriteHeight; - int x2 = startX + (xTile * spriteWidth); - int y2 = startY + height - ((yTile + 1) * spriteHeight); - if (widthLeft > 0 && heightLeft > 0) { - long maskTop = spriteHeight - heightLeft; - int maskRight = spriteWidth - widthLeft; - - drawTextureWithMasking(matrix, x2, y2, sprite, maskTop, maskRight, 100); - } + public static void renderTiledFluid(GuiGraphics guiGraphics, AbstractContainerScreen screen, FluidStack stack, int x, int y, int width, int height, int depth) { + if (!stack.isEmpty()) { + try { + // Get fluid attributes and texture location using modern NeoForge 1.21.1 approach + var attributes = IClientFluidTypeExtensions.of(stack.getFluid()); + + // Get the sprite using the official Minecraft texture atlas system + TextureAtlasSprite sprite = Minecraft.getInstance().getTextureAtlas(InventoryMenu.BLOCK_ATLAS).apply(attributes.getStillTexture()); + + // Set the fluid color tint + setColors(attributes.getTintColor()); + + // Calculate absolute screen coordinates + int absoluteX = screen.getGuiLeft() + x; + int absoluteY = screen.getGuiTop() + y; + + // Use the official GuiGraphics.blit method for sprite rendering + // This handles tiling and UV coordinates automatically + guiGraphics.blit(absoluteX, absoluteY, depth, width, height, sprite); + + // Reset shader color + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + } catch (Exception e) { + // If texture rendering fails, the fallback rectangle will still be visible } } } - - private static void drawTextureWithMasking(Matrix4f matrix, float xCoord, float yCoord, TextureAtlasSprite textureSprite, long maskTop, long maskRight, float zLevel) { - float uMin = textureSprite.getU0(); - float uMax = textureSprite.getU1(); - float vMin = textureSprite.getV0(); - float vMax = textureSprite.getV1(); - uMax = uMax - (maskRight / 16F * (uMax - uMin)); - vMax = vMax - (maskTop / 16F * (vMax - vMin)); - - RenderSystem.setShader(GameRenderer::getPositionTexShader); - - Tesselator tesselator = Tesselator.getInstance(); - BufferBuilder bufferBuilder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX); - bufferBuilder.addVertex(matrix, xCoord, yCoord + 16, zLevel).setUv(uMin, vMax); - bufferBuilder.addVertex(matrix, xCoord + 16 - maskRight, yCoord + 16, zLevel).setUv(uMax, vMax); - bufferBuilder.addVertex(matrix, xCoord + 16 - maskRight, yCoord + maskTop, zLevel).setUv(uMax, vMin); - bufferBuilder.addVertex(matrix, xCoord, yCoord + maskTop, zLevel).setUv(uMin, vMin); - BufferUploader.drawWithShader(bufferBuilder.buildOrThrow()); - } -} +} \ No newline at end of file diff --git a/src/main/java/cy/jdkdigital/generatorgalore/util/GeneratorCreator.java b/src/main/java/cy/jdkdigital/generatorgalore/util/GeneratorCreator.java index f785ded..1e8128f 100644 --- a/src/main/java/cy/jdkdigital/generatorgalore/util/GeneratorCreator.java +++ b/src/main/java/cy/jdkdigital/generatorgalore/util/GeneratorCreator.java @@ -17,6 +17,8 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.level.block.SoundType; import net.neoforged.fml.loading.FMLEnvironment; import net.neoforged.neoforge.registries.DeferredHolder; @@ -35,17 +37,32 @@ public static GeneratorObject create(ResourceLocation id, JsonObject json) throw var generator = generatorOptional.get(); var name = String.format("%s_%s", generator.getId().getPath(), "generator"); - Supplier generatorBlock = GeneratorGalore.BLOCKS.register(name, () -> new Generator(BlockBehaviour.Properties.ofFullCopy(Blocks.FURNACE), generator, 1)); + Supplier generatorBlock = GeneratorGalore.BLOCKS.register(name, () -> new Generator( + BlockBehaviour.Properties.of() + .mapColor(MapColor.METAL) + .strength(3.5F, 6.0F) + .sound(SoundType.METAL) + .requiresCorrectToolForDrops(), generator, 1)); generator.setBlockSupplier(generatorBlock); List> generatorBlocks = new ArrayList<>(); generatorBlocks.add(generatorBlock); if (generator.has8x() || !FMLEnvironment.production) { - Supplier gen8x = GeneratorGalore.BLOCKS.register(name + "_8x", () -> new Generator(BlockBehaviour.Properties.ofFullCopy(Blocks.FURNACE), generator, 8)); + Supplier gen8x = GeneratorGalore.BLOCKS.register(name + "_8x", () -> new Generator( + BlockBehaviour.Properties.of() + .mapColor(MapColor.METAL) + .strength(3.5F, 6.0F) + .sound(SoundType.METAL) + .requiresCorrectToolForDrops(), generator, 8)); generatorBlocks.add(gen8x); GeneratorGalore.ITEMS.register(name + "_8x", () -> new BlockItem(gen8x.get(), new Item.Properties())); } if (generator.has64x() || !FMLEnvironment.production) { - Supplier gen64x = GeneratorGalore.BLOCKS.register(name + "_64x", () -> new Generator(BlockBehaviour.Properties.ofFullCopy(Blocks.FURNACE), generator, 64)); + Supplier gen64x = GeneratorGalore.BLOCKS.register(name + "_64x", () -> new Generator( + BlockBehaviour.Properties.of() + .mapColor(MapColor.METAL) + .strength(3.5F, 6.0F) + .sound(SoundType.METAL) + .requiresCorrectToolForDrops(), generator, 64)); generatorBlocks.add(gen64x); GeneratorGalore.ITEMS.register(name + "_64x", () -> new BlockItem(gen64x.get(), new Item.Properties())); } diff --git a/src/main/java/cy/jdkdigital/generatorgalore/util/GeneratorObject.java b/src/main/java/cy/jdkdigital/generatorgalore/util/GeneratorObject.java index 019b42c..6c7ca7d 100644 --- a/src/main/java/cy/jdkdigital/generatorgalore/util/GeneratorObject.java +++ b/src/main/java/cy/jdkdigital/generatorgalore/util/GeneratorObject.java @@ -7,6 +7,7 @@ import cy.jdkdigital.generatorgalore.common.block.entity.GeneratorBlockEntity; import cy.jdkdigital.generatorgalore.common.container.GeneratorMenu; import cy.jdkdigital.generatorgalore.init.ModTags; +import net.minecraft.core.component.DataComponents; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.inventory.MenuType; @@ -32,11 +33,11 @@ public class GeneratorObject private Supplier upgradeSupplier; private Supplier> menuType; private final GeneratorUtil.FuelType fuelType; - private final double generationRate; - private double modifiedGenerationRate = 0; - private final double transferRate; - private double modifiedConsumptionRate; - private final double consumptionRate; + private final int generationRate; + private int modifiedGenerationRate = 0; + private final int transferRate; + private int modifiedConsumptionRate; + private final int consumptionRate; private final int bufferCapacity; private final boolean hasChargeSlot; private final ResourceLocation fuelTag; @@ -44,7 +45,7 @@ public class GeneratorObject private final boolean has64x; private Map fuelList; - public GeneratorObject(ResourceLocation id, GeneratorUtil.FuelType fuelType, double generationRate, double transferRate, double consumptionRate, int bufferCapacity, boolean hasChargeSlot, ResourceLocation fuelTag, boolean has8x, boolean has64x) { + public GeneratorObject(ResourceLocation id, GeneratorUtil.FuelType fuelType, int generationRate, int transferRate, int consumptionRate, int bufferCapacity, boolean hasChargeSlot, ResourceLocation fuelTag, boolean has8x, boolean has64x) { this.id = id; this.fuelType = fuelType; this.generationRate = generationRate; @@ -97,31 +98,31 @@ public GeneratorUtil.FuelType getFuelType() { return this.fuelType; } - public double getGenerationRate() { + public int getGenerationRate() { return modifiedGenerationRate > 0 ? modifiedGenerationRate : generationRate; } - public double getConsumptionRate() { + public int getConsumptionRate() { return modifiedConsumptionRate > 0 ? modifiedConsumptionRate : consumptionRate; } - public void setGenerationRate(double generationRate) { + public void setGenerationRate(int generationRate) { this.modifiedGenerationRate = generationRate; } - public void setConsumptionRate(double consumptionRate) { + public void setConsumptionRate(int consumptionRate) { this.modifiedConsumptionRate = consumptionRate; } - public double getOriginalGenerationRate() { + public int getOriginalGenerationRate() { return generationRate; } - public double getOriginalConsumptionRate() { + public int getOriginalConsumptionRate() { return consumptionRate; } - public double getTransferRate() { + public int getTransferRate() { return transferRate; } @@ -141,9 +142,9 @@ public static Codec codec(ResourceLocation id) { return RecordCodecBuilder.create(instance -> instance.group( ResourceLocation.CODEC.fieldOf("id").orElse(id).forGetter(GeneratorObject::getId), GeneratorUtil.FuelType.CODEC.fieldOf("fuelType").orElse(GeneratorUtil.FuelType.SOLID).forGetter(GeneratorObject::getFuelType), - Codec.DOUBLE.fieldOf("generationRate").forGetter(GeneratorObject::getOriginalGenerationRate), - Codec.DOUBLE.fieldOf("transferRate").forGetter(GeneratorObject::getTransferRate), - Codec.DOUBLE.fieldOf("consumptionRate").forGetter(GeneratorObject::getConsumptionRate), + Codec.INT.fieldOf("generationRate").forGetter(GeneratorObject::getOriginalGenerationRate), + Codec.INT.fieldOf("transferRate").forGetter(GeneratorObject::getTransferRate), + Codec.INT.fieldOf("consumptionRate").forGetter(GeneratorObject::getConsumptionRate), Codec.INT.fieldOf("bufferCapacity").forGetter(GeneratorObject::getBufferCapacity), Codec.BOOL.fieldOf("hasChargeSlot").orElse(true).forGetter(GeneratorObject::hasChargeSlot), ResourceLocation.CODEC.fieldOf("fuelTag").orElse(GeneratorUtil.EMPTY_TAG).forGetter(GeneratorObject::getFuelTag), @@ -178,7 +179,7 @@ public boolean isValidFuelItem(@NotNull ItemStack stack) { return stack.is(ModTags.getItemTag(getFuelTag())); } if (getFuelType().equals(GeneratorUtil.FuelType.FOOD)) { - return stack.getItem().getFoodProperties(stack, null) != null; + return stack.get(DataComponents.FOOD) != null; } if (getFuelType().equals(GeneratorUtil.FuelType.ENCHANTMENT)) { return !EnchantmentHelper.getEnchantmentsForCrafting(stack).isEmpty(); @@ -190,6 +191,7 @@ public boolean isValidFuelItem(@NotNull ItemStack stack) { return getFuelList().containsKey(BuiltInRegistries.ITEM.getKey(stack.getItem())); } + // Modern approach: Check for burn time using Data Components or fallback to legacy method return stack.getBurnTime(RecipeType.SMELTING) > 0; } @@ -231,14 +233,14 @@ public Pair getGenerationRateForItem(Level level, ItemStack fuel return rate; } - public Pair getGenerationRateForFluid(FluidStack fluidStack) { + public Pair getGenerationRateForFluid(FluidStack fluidStack) { if (isValidFuelFluid(fluidStack)) { var fuelData = getBlockSupplier().get().builtInRegistryHolder().getData(GeneratorGalore.FLUID_FUEL_MAP); if (fuelData != null) { var validFuels = fuelData.fuels().stream().filter(solidFuel -> solidFuel.fluid().test(fluidStack)).toList(); return validFuels.isEmpty() ? Pair.of(getGenerationRate(), getConsumptionRate()) : - Pair.of(validFuels.getFirst().generationRate(), validFuels.getFirst().consumptionRate()); + Pair.of((int)validFuels.getFirst().generationRate(), (int)validFuels.getFirst().consumptionRate()); } return Pair.of(getGenerationRate(), getConsumptionRate()); } From baeddd56f0ce7052d0baa38af86b7e426f4f47b0 Mon Sep 17 00:00:00 2001 From: worador Date: Fri, 9 Jan 2026 16:24:43 +0100 Subject: [PATCH 2/3] Cleanup: Remove local IDE settings (.vscode) --- .vscode/launch.json | 93 ------------------------------------------- .vscode/settings.json | 4 -- 2 files changed, 97 deletions(-) delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/settings.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 18d1513..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "type": "java", - "request": "launch", - "name": "Client", - "presentation": { - "group": "Mod Development - generatorgalore", - "order": 0 - }, - "projectName": "generatorgalore", - "mainClass": "net.neoforged.devlaunch.Main", - "args": [ - "@/home/worador/Development/Neo-Forge/generatorgalore/build/moddev/clientRunProgramArgs.txt" - ], - "vmArgs": [ - "@/home/worador/Development/Neo-Forge/generatorgalore/build/moddev/clientRunVmArgs.txt", - "-Dfml.modFolders\u003dgeneratorgalore%%/home/worador/Development/Neo-Forge/generatorgalore/bin/main" - ], - "cwd": "${workspaceFolder}/run", - "env": {}, - "console": "internalConsole", - "shortenCommandLine": "none" - }, - { - "type": "java", - "request": "launch", - "name": "Data", - "presentation": { - "group": "Mod Development - generatorgalore", - "order": 1 - }, - "projectName": "generatorgalore", - "mainClass": "net.neoforged.devlaunch.Main", - "args": [ - "@/home/worador/Development/Neo-Forge/generatorgalore/build/moddev/dataRunProgramArgs.txt" - ], - "vmArgs": [ - "@/home/worador/Development/Neo-Forge/generatorgalore/build/moddev/dataRunVmArgs.txt", - "-Dfml.modFolders\u003dgeneratorgalore%%/home/worador/Development/Neo-Forge/generatorgalore/bin/main" - ], - "cwd": "${workspaceFolder}/run", - "env": {}, - "console": "internalConsole", - "shortenCommandLine": "none" - }, - { - "type": "java", - "request": "launch", - "name": "GameTestServer", - "presentation": { - "group": "Mod Development - generatorgalore", - "order": 2 - }, - "projectName": "generatorgalore", - "mainClass": "net.neoforged.devlaunch.Main", - "args": [ - "@/home/worador/Development/Neo-Forge/generatorgalore/build/moddev/gameTestServerRunProgramArgs.txt" - ], - "vmArgs": [ - "@/home/worador/Development/Neo-Forge/generatorgalore/build/moddev/gameTestServerRunVmArgs.txt", - "-Dfml.modFolders\u003dgeneratorgalore%%/home/worador/Development/Neo-Forge/generatorgalore/bin/main" - ], - "cwd": "${workspaceFolder}/run", - "env": {}, - "console": "internalConsole", - "shortenCommandLine": "none" - }, - { - "type": "java", - "request": "launch", - "name": "Server", - "presentation": { - "group": "Mod Development - generatorgalore", - "order": 3 - }, - "projectName": "generatorgalore", - "mainClass": "net.neoforged.devlaunch.Main", - "args": [ - "@/home/worador/Development/Neo-Forge/generatorgalore/build/moddev/serverRunProgramArgs.txt" - ], - "vmArgs": [ - "@/home/worador/Development/Neo-Forge/generatorgalore/build/moddev/serverRunVmArgs.txt", - "-Dfml.modFolders\u003dgeneratorgalore%%/home/worador/Development/Neo-Forge/generatorgalore/bin/main" - ], - "cwd": "${workspaceFolder}/run", - "env": {}, - "console": "internalConsole", - "shortenCommandLine": "none" - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 78b61df..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "java.compile.nullAnalysis.mode": "disabled", - "java.configuration.updateBuildConfiguration": "disabled" -} \ No newline at end of file From 8224468b3847b9e04f0a5dc668df0e12e017e18a Mon Sep 17 00:00:00 2001 From: worador Date: Fri, 9 Jan 2026 21:03:22 +0100 Subject: [PATCH 3/3] fix: resolve compilation errors and filter 8x/64x variants from creative tab --- .../generatorgalore/GeneratorGalore.java | 101 +++++++++--------- 1 file changed, 51 insertions(+), 50 deletions(-) diff --git a/src/main/java/cy/jdkdigital/generatorgalore/GeneratorGalore.java b/src/main/java/cy/jdkdigital/generatorgalore/GeneratorGalore.java index 9457bd6..d89273d 100644 --- a/src/main/java/cy/jdkdigital/generatorgalore/GeneratorGalore.java +++ b/src/main/java/cy/jdkdigital/generatorgalore/GeneratorGalore.java @@ -1,6 +1,7 @@ package cy.jdkdigital.generatorgalore; import com.mojang.logging.LogUtils; +import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import cy.jdkdigital.generatorgalore.common.conditions.GeneratorExistsCondition; import cy.jdkdigital.generatorgalore.common.datamap.FluidFuelMap; @@ -9,10 +10,11 @@ import cy.jdkdigital.generatorgalore.registry.GeneratorRegistry; import cy.jdkdigital.generatorgalore.util.GeneratorUtil; import cy.jdkdigital.generatorgalore.network.ModPackets; +import net.minecraft.core.component.DataComponentType; import net.minecraft.core.particles.ParticleType; -import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.Component; +import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.inventory.MenuType; import net.minecraft.world.item.CreativeModeTab; @@ -24,9 +26,7 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntityType; import net.neoforged.bus.api.IEventBus; -import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.ModContainer; -import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.fml.common.Mod; import net.neoforged.fml.config.ModConfig; import net.neoforged.neoforge.capabilities.Capabilities; @@ -34,21 +34,17 @@ import net.neoforged.neoforge.common.conditions.ICondition; import net.neoforged.neoforge.common.crafting.IngredientType; import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent; +import net.neoforged.neoforge.fluids.FluidStack; import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; import net.neoforged.neoforge.registries.DeferredHolder; import net.neoforged.neoforge.registries.DeferredRegister; import net.neoforged.neoforge.registries.NeoForgeRegistries; import net.neoforged.neoforge.registries.datamaps.DataMapType; import net.neoforged.neoforge.registries.datamaps.RegisterDataMapTypesEvent; -import net.minecraft.core.component.DataComponentType; -import net.minecraft.network.codec.ByteBufCodecs; -import net.minecraft.core.component.DataComponents; -import com.mojang.serialization.Codec; -import net.neoforged.neoforge.registries.DeferredRegister; -import net.neoforged.neoforge.fluids.FluidStack; -import java.util.function.Supplier; import org.slf4j.Logger; +import java.util.function.Supplier; + @Mod(GeneratorGalore.MODID) public class GeneratorGalore { public static final String MODID = "generatorgalore"; @@ -64,6 +60,7 @@ public class GeneratorGalore { public static final DeferredRegister CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MODID); public static final DeferredRegister> CONDITION_CODECS = DeferredRegister.create(NeoForgeRegistries.Keys.CONDITION_CODECS, MODID); public static final DeferredRegister> INGREDIENT_TYPES = DeferredRegister.create(NeoForgeRegistries.Keys.INGREDIENT_TYPES, MODID); + public static final DeferredRegister> DATA_COMPONENTS = DeferredRegister.create(Registries.DATA_COMPONENT_TYPE, MODID); public static final DeferredHolder TAB = CREATIVE_MODE_TABS.register(MODID, () -> CreativeModeTab.builder() .withTabsBefore(CreativeModeTabs.SPAWN_EGGS) @@ -76,7 +73,6 @@ public class GeneratorGalore { public static final DataMapType SOLID_FUEL_MAP = DataMapType.builder(ResourceLocation.fromNamespaceAndPath(MODID, "solid_fuel_map"), Registries.BLOCK, SolidFuelMap.CODEC).synced(SolidFuelMap.CODEC, false).build(); public static final DeferredHolder, IngredientType> POTIOM_INGREDIENT_TYPE = INGREDIENT_TYPES.register("component", () -> new IngredientType<>(PotionComponentIngredient.CODEC)); - public static final DeferredRegister> DATA_COMPONENTS = DeferredRegister.create(Registries.DATA_COMPONENT_TYPE, MODID); public static final Supplier> ENERGY_COMPONENT = DATA_COMPONENTS.register("energy_storage", () -> DataComponentType.builder().persistent(Codec.INT).networkSynchronized(ByteBufCodecs.VAR_INT).build()); public static final Supplier> FLUID_COMPONENT = DATA_COMPONENTS.register("fluid_storage", () -> DataComponentType.builder().persistent(FluidStack.CODEC).networkSynchronized(FluidStack.STREAM_CODEC).build()); @@ -95,52 +91,57 @@ public GeneratorGalore(IEventBus modEventBus, ModContainer modContainer) { INGREDIENT_TYPES.register(modEventBus); DATA_COMPONENTS.register(modEventBus); + modEventBus.addListener(this::registerPayloadHandlers); + modEventBus.addListener(this::addCreative); + modEventBus.addListener(this::registerDataMap); + modEventBus.addListener(this::registerCapabilities); + modContainer.registerConfig(ModConfig.Type.SERVER, Config.SERVER_CONFIG); modContainer.registerConfig(ModConfig.Type.CLIENT, Config.CLIENT_CONFIG); } - @EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD, modid = MODID) - public static class EventHandler { - - @SubscribeEvent - public static void registerPayloadHandlers(RegisterPayloadHandlersEvent event) { - ModPackets.registerPackets(event); - } + private void registerPayloadHandlers(RegisterPayloadHandlersEvent event) { + ModPackets.registerPackets(event); + } - @SubscribeEvent - public static void buildContents(BuildCreativeModeTabContentsEvent event) { - if (event.getTabKey() == TAB.getKey()) { - GeneratorRegistry.generators.values().forEach(generatorObject -> { - event.accept(generatorObject.getBlockSupplier().get()); - }); - } + private void addCreative(BuildCreativeModeTabContentsEvent event) { + if (event.getTabKey().equals(TAB.getKey())) { + // Wir nutzen NUR ITEMS, da dort ALLES (Generatoren + Upgrades) landet. + // Das verhindert Doppeleinträge und wir können zentral filtern. + ITEMS.getEntries().forEach(itemHolder -> { + String path = itemHolder.getId().getPath(); + + // Wir schmeißen nur die 8x und 64x GENERATOREN raus. + // Upgrades (wie "upgrade_8x") bleiben drin, falls sie nicht exakt so enden. + if (!path.endsWith("_8x") && !path.endsWith("_64x")) { + event.accept(itemHolder.get()); + } + }); } + } - @SubscribeEvent - private static void registerDataMap(final RegisterDataMapTypesEvent event) { - event.register(FLUID_FUEL_MAP); - event.register(SOLID_FUEL_MAP); - } + private void registerDataMap(final RegisterDataMapTypesEvent event) { + event.register(FLUID_FUEL_MAP); + event.register(SOLID_FUEL_MAP); + } - @SubscribeEvent - public static void registerCapabilities(RegisterCapabilitiesEvent event) { - GeneratorRegistry.generators.values().forEach(generatorObject -> { - event.registerBlockEntity( - Capabilities.ItemHandler.BLOCK, - generatorObject.getBlockEntityType().get(), - (myBlockEntity, side) -> myBlockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID) ? null : myBlockEntity.inventoryHandler - ); - event.registerBlockEntity( - Capabilities.EnergyStorage.BLOCK, - generatorObject.getBlockEntityType().get(), - (myBlockEntity, side) -> myBlockEntity.energyHandler - ); - event.registerBlockEntity( - Capabilities.FluidHandler.BLOCK, - generatorObject.getBlockEntityType().get(), - (myBlockEntity, side) -> myBlockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID) ? myBlockEntity.fluidInventory : null - ); - }); - } + private void registerCapabilities(RegisterCapabilitiesEvent event) { + GeneratorRegistry.generators.values().forEach(generatorObject -> { + event.registerBlockEntity( + Capabilities.ItemHandler.BLOCK, + generatorObject.getBlockEntityType().get(), + (myBlockEntity, side) -> myBlockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID) ? null : myBlockEntity.inventoryHandler + ); + event.registerBlockEntity( + Capabilities.EnergyStorage.BLOCK, + generatorObject.getBlockEntityType().get(), + (myBlockEntity, side) -> myBlockEntity.energyHandler + ); + event.registerBlockEntity( + Capabilities.FluidHandler.BLOCK, + generatorObject.getBlockEntityType().get(), + (myBlockEntity, side) -> myBlockEntity.generator.getFuelType().equals(GeneratorUtil.FuelType.FLUID) ? myBlockEntity.fluidInventory : null + ); + }); } } \ No newline at end of file