diff --git a/src/main/java/com/simibubi/create/AllBlocks.java b/src/main/java/com/simibubi/create/AllBlocks.java index 4335fb2c74..2a7d4aeb81 100644 --- a/src/main/java/com/simibubi/create/AllBlocks.java +++ b/src/main/java/com/simibubi/create/AllBlocks.java @@ -209,6 +209,7 @@ import com.simibubi.create.content.processing.basin.BasinBlock; import com.simibubi.create.content.processing.basin.BasinGenerator; import com.simibubi.create.content.processing.basin.BasinMovementBehaviour; +import com.simibubi.create.content.processing.basin.MountedBasinInteractionBehaviour; import com.simibubi.create.content.processing.burner.BlazeBurnerBlock; import com.simibubi.create.content.processing.burner.BlazeBurnerBlockItem; import com.simibubi.create.content.processing.burner.BlazeBurnerMovementBehaviour; @@ -740,6 +741,9 @@ public class AllBlocks { .blockstate(new BasinGenerator()::generate) .addLayer(() -> RenderType::cutoutMipped) .onRegister(movementBehaviour(new BasinMovementBehaviour())) + .onRegister(interactionBehaviour(new MountedBasinInteractionBehaviour())) + .transform(mountedItemStorage(AllMountedStorageTypes.BASIN_ITEM)) + .transform(mountedFluidStorage(AllMountedStorageTypes.BASIN_FLUID)) .item() .transform(customItemModel("_", "block")) .register(); diff --git a/src/main/java/com/simibubi/create/AllMountedStorageTypes.java b/src/main/java/com/simibubi/create/AllMountedStorageTypes.java index fc2af0017d..bfd9022a1d 100644 --- a/src/main/java/com/simibubi/create/AllMountedStorageTypes.java +++ b/src/main/java/com/simibubi/create/AllMountedStorageTypes.java @@ -13,6 +13,8 @@ import com.simibubi.create.content.logistics.crate.CreativeCrateMountedStorageType; import com.simibubi.create.content.logistics.depot.storage.DepotMountedStorageType; import com.simibubi.create.content.logistics.vault.ItemVaultMountedStorageType; +import com.simibubi.create.content.processing.basin.BasinMountedFluidStorageType; +import com.simibubi.create.content.processing.basin.BasinMountedItemStorageType; import com.simibubi.create.foundation.data.CreateRegistrate; import com.simibubi.create.impl.contraption.storage.FallbackMountedStorageType; import com.tterrag.registrate.util.entry.RegistryEntry; @@ -26,6 +28,8 @@ public class AllMountedStorageTypes { public static final RegistryEntry, FallbackMountedStorageType> FALLBACK = simpleItem("fallback", FallbackMountedStorageType::new); // registrations for these are handled by the blocks, not the types + public static final RegistryEntry, BasinMountedItemStorageType> BASIN_ITEM = simpleItem("basin", BasinMountedItemStorageType::new); + public static final RegistryEntry, BasinMountedFluidStorageType> BASIN_FLUID = simpleFluid("basin", BasinMountedFluidStorageType::new); public static final RegistryEntry, CreativeCrateMountedStorageType> CREATIVE_CRATE = simpleItem("creative_crate", CreativeCrateMountedStorageType::new); public static final RegistryEntry, ItemVaultMountedStorageType> VAULT = simpleItem("vault", ItemVaultMountedStorageType::new); public static final RegistryEntry, ToolboxMountedStorageType> TOOLBOX = simpleItem("toolbox", ToolboxMountedStorageType::new); diff --git a/src/main/java/com/simibubi/create/content/processing/basin/BasinBlockEntity.java b/src/main/java/com/simibubi/create/content/processing/basin/BasinBlockEntity.java index 3caf9596f5..12adab6e7f 100644 --- a/src/main/java/com/simibubi/create/content/processing/basin/BasinBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/processing/basin/BasinBlockEntity.java @@ -6,6 +6,9 @@ import java.util.List; import java.util.Optional; +import com.simibubi.create.content.processing.basin.BasinMountedFluidStorage.MountedBasinTankHalf; +import com.simibubi.create.foundation.utility.InventoryUtil; + import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -792,6 +795,21 @@ public boolean addToGoggleTooltip(List tooltip, boolean isPlayerSneak return cachedHeatLevel; } + public void applyInventories(BasinInventory inputInventory, SmartInventory outputInventory) { + InventoryUtil.copyInventoryToFrom(this.inputInventory, inputInventory); + InventoryUtil.copyInventoryToFrom(this.outputInventory, outputInventory); + } + + public void applyTanks(MountedBasinTankHalf inputTank, MountedBasinTankHalf outputTank) { + TankSegment[] inputSegments = this.inputTank.getTanks(); + inputSegments[0].getTank().setFluid(inputTank.firstStack()); + inputSegments[1].getTank().setFluid(inputTank.secondStack()); + + TankSegment[] outputSegments = this.outputTank.getTanks(); + outputSegments[0].getTank().setFluid(outputTank.firstStack()); + outputSegments[1].getTank().setFluid(outputTank.secondStack()); + } + static class BasinValueBox extends ValueBoxTransform.Sided { @Override diff --git a/src/main/java/com/simibubi/create/content/processing/basin/BasinInventory.java b/src/main/java/com/simibubi/create/content/processing/basin/BasinInventory.java index 3f58d9d52e..96dc524db6 100644 --- a/src/main/java/com/simibubi/create/content/processing/basin/BasinInventory.java +++ b/src/main/java/com/simibubi/create/content/processing/basin/BasinInventory.java @@ -3,24 +3,31 @@ import com.simibubi.create.foundation.item.SmartInventory; import net.minecraft.world.item.ItemStack; -import net.neoforged.neoforge.items.ItemHandlerHelper; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class BasinInventory extends SmartInventory { - private BasinBlockEntity blockEntity; - + private final @Nullable BasinBlockEntity blockEntity; + public boolean packagerMode; - public BasinInventory(int slots, BasinBlockEntity be) { + public BasinInventory(int slots, @Nullable BasinBlockEntity be) { super(slots, be, 64, true); this.blockEntity = be; } + public BasinInventory(int slots) { + this(slots, null); + } + @Override + @NotNull public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) { if (packagerMode) // Unique stack insertion only matters for belt setups return inv.insertItem(slot, stack, simulate); - + int firstFreeSlot = -1; for (int i = 0; i < getSlots(); i++) { @@ -42,9 +49,10 @@ public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) { } @Override + @NotNull public ItemStack extractItem(int slot, int amount, boolean simulate) { ItemStack extractItem = super.extractItem(slot, amount, simulate); - if (!simulate && !extractItem.isEmpty()) + if (!simulate && blockEntity != null && !extractItem.isEmpty()) blockEntity.notifyChangeOfContents(); return extractItem; } diff --git a/src/main/java/com/simibubi/create/content/processing/basin/BasinMountedFluidStorage.java b/src/main/java/com/simibubi/create/content/processing/basin/BasinMountedFluidStorage.java new file mode 100644 index 0000000000..a9189f112c --- /dev/null +++ b/src/main/java/com/simibubi/create/content/processing/basin/BasinMountedFluidStorage.java @@ -0,0 +1,269 @@ +package com.simibubi.create.content.processing.basin; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import com.simibubi.create.AllMountedStorageTypes; +import com.simibubi.create.api.contraption.storage.SyncedMountedStorage; +import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorage; + +import com.simibubi.create.content.contraptions.Contraption; +import com.simibubi.create.foundation.fluid.CombinedTankWrapper; +import com.simibubi.create.foundation.fluid.SmartFluidTank; + +import net.createmod.catnip.animation.LerpedFloat; +import net.createmod.catnip.animation.LerpedFloat.Chaser; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +import net.neoforged.neoforge.fluids.FluidStack; + +import net.neoforged.neoforge.fluids.capability.IFluidHandler; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; + +public class BasinMountedFluidStorage extends MountedFluidStorage implements SyncedMountedStorage { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + MountedBasinTankHalf.CODEC.fieldOf("inputTank").forGetter(BasinMountedFluidStorage::inputTank), + MountedBasinTankHalf.CODEC.fieldOf("outputTank").forGetter(BasinMountedFluidStorage::outputTank) + ).apply(i, BasinMountedFluidStorage::new)); + + protected BasinMountedFluidStorage(MountedBasinTankHalf input, MountedBasinTankHalf output) { + super(AllMountedStorageTypes.BASIN_FLUID.get()); + + this.inputTankHalf = input; + + this.outputTankHalf = output; + + this.bothTanks = new CombinedTankWrapper(input, output); + } + + final MountedBasinTankHalf inputTankHalf; + final MountedBasinTankHalf outputTankHalf; + final CombinedTankWrapper bothTanks; + + private boolean dirty; + + public MountedBasinTankHalf inputTank() { + return this.inputTankHalf; + } + + public MountedBasinTankHalf outputTank() { + return this.outputTankHalf; + } + + @Override + public boolean isDirty() { return this.dirty; } + + @Override + public void markClean() { this.dirty = false; } + + public void markDirty() { this.dirty = true; } + + @Override + public void afterSync(Contraption contraption, BlockPos localPos) {} + + public void tickChasers() { + this.inputTankHalf.tickChasers(); + this.outputTankHalf.tickChasers(); + } + + public float getTotalFluidUnits(float partialTicks) { + float fluidUnits = this.inputTankHalf.getTotalUnits(partialTicks) + + this.outputTankHalf.getTotalUnits(partialTicks); + int totalRendered = this.inputTankHalf.getRenderedFluids() + + this.outputTankHalf.getRenderedFluids(); + + if (totalRendered == 0) return 0; + if (fluidUnits < 1) return 0; + return fluidUnits; + } + + @Override + public void unmount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) { + if (be instanceof BasinBlockEntity basin) { + basin.applyTanks(this.inputTankHalf, this.outputTankHalf); + } + } + + @Override + public int getTanks() { + return this.bothTanks.getTanks(); + } + + @Override + @NotNull + public FluidStack getFluidInTank(int tank) { + return this.bothTanks.getFluidInTank(tank); + } + + @Override + public int getTankCapacity(int tank) { + return 1000; + } + + @Override + public boolean isFluidValid(int tank, @NotNull FluidStack fluidStack) { + return this.bothTanks.isFluidValid(tank, fluidStack); + } + + @Override + public int fill(@NotNull FluidStack fluidStack, @NotNull FluidAction fluidAction) { + return this.inputTankHalf.fill(fluidStack, fluidAction); + } + + @Override + @NotNull + public FluidStack drain(@NotNull FluidStack fluidStack, @NotNull FluidAction fluidAction) { + return this.bothTanks.drain(fluidStack, fluidAction); + } + + @Override + @NotNull + public FluidStack drain(int tank, @NotNull FluidAction fluidAction) { + return this.bothTanks.drain(tank, fluidAction); + } + + public static class MountedBasinTankHalf extends CombinedTankWrapper { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + Codec.BOOL.fieldOf("insertionAllowed").forGetter(MountedBasinTankHalf::insertionAllowed), + FluidStack.OPTIONAL_CODEC.fieldOf("firstTank").forGetter(MountedBasinTankHalf::firstStack), + FluidStack.OPTIONAL_CODEC.fieldOf("secondTank").forGetter(MountedBasinTankHalf::secondStack), + Codec.FLOAT.fieldOf("previousFirstValue").forGetter(MountedBasinTankHalf::previousFirstValue), + Codec.FLOAT.fieldOf("previousSecondValue").forGetter(MountedBasinTankHalf::previousSecondValue) + ).apply(i, MountedBasinTankHalf::fromStacks)); + + private final boolean insertionAllowed; + private final LerpedFloat firstLevel; + private final LerpedFloat secondLevel; + + private float previousFirstValue; + private float previousSecondValue; + private float currentFirstValue; + private float currentSecondValue; + private Consumer updateCallback; + + public MountedBasinTankHalf(boolean insertionAllowed, IFluidHandler firstTank, IFluidHandler secondTank) { + super(firstTank, secondTank); + + previousFirstValue = 0; + previousSecondValue = 0; + + firstLevel = LerpedFloat.linear() + .startWithValue(0) + .chase(0, .25, Chaser.EXP); + + secondLevel = LerpedFloat.linear() + .startWithValue(0) + .chase(0, .25, Chaser.EXP); + + this.insertionAllowed = insertionAllowed; + enforceVariety(); + } + + public static MountedBasinTankHalf fromStacks(boolean insertionAllowed, FluidStack first, FluidStack second, float previousFirstValue, float previousSecondValue) { + SmartFluidTank firstTank = new SmartFluidTank(getTankCapacity(), s -> {}); + SmartFluidTank secondTank = new SmartFluidTank(getTankCapacity(), s -> {}); + + MountedBasinTankHalf tankHalf = new MountedBasinTankHalf(insertionAllowed, firstTank, secondTank); + tankHalf.fillTank(0, first.copy()); + tankHalf.fillTank(1, second.copy()); + + float firstFill = first.getAmount() / (float) getTankCapacity(); + float secondFill = second.getAmount() / (float) getTankCapacity(); + +// LogUtils.getLogger().info("first: {} -> {}", previousFirstValue, firstFill); +// LogUtils.getLogger().info("second: {} -> {}", previousSecondValue, secondFill); + + tankHalf.currentFirstValue = firstFill; + tankHalf.currentSecondValue = secondFill; + + tankHalf.firstLevel.setValue(previousFirstValue); + tankHalf.secondLevel.setValue(previousSecondValue); + tankHalf.firstLevel.chase(firstFill, .25, Chaser.EXP); + tankHalf.secondLevel.chase(secondFill, .25, Chaser.EXP); + + firstTank.setUpdateCallback(tankHalf::onFluidStackChanged); + secondTank.setUpdateCallback(tankHalf::onFluidStackChanged); + + return tankHalf; + } + + public void onFluidStackChanged(FluidStack stack) { + this.previousFirstValue = this.currentFirstValue; + this.previousSecondValue = this.currentSecondValue; + this.currentFirstValue = firstStack().getAmount() / (float) getTankCapacity(); + this.currentSecondValue = secondStack().getAmount() / (float) getTankCapacity(); + + if (this.updateCallback != null) this.updateCallback.accept(stack); + } + + public void tickChasers() { + firstLevel.tickChaser(); + secondLevel.tickChaser(); + } + + public int getRenderedFluids() { + int total = 0; + for (int i = 0; i < this.getTanks(); i++) { + if (!this.getFluidInTank(i).isEmpty()) total++; + } + + return total; + } + + public float previousFirstValue() { + return this.previousFirstValue; + } + + public float previousSecondValue() { + return this.previousSecondValue; + } + + public FluidStack firstStack() { + return this.getFluidInTank(0); + } + + public FluidStack secondStack() { + return this.getFluidInTank(1); + } + + public float getTotalUnits(float partialTicks, int tank) { + if (tank == 0) return firstLevel.getValue(partialTicks) * 1000; + else return secondLevel.getValue(partialTicks) * 1000; + } + + public float getTotalUnits(float partialTicks) { + return firstLevel.getValue(partialTicks) * 1000 + + secondLevel.getValue(partialTicks) * 1000; + } + + public void setUpdateCallback(Consumer updateCallback) { + this.updateCallback = updateCallback; + } + + public static int getTankCapacity() { + return 1000; + } + + public boolean insertionAllowed() { + return insertionAllowed; + } + + private void fillTank(int tank, FluidStack stack) { + this.getHandlerFromIndex(tank).fill(stack, FluidAction.EXECUTE); + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + if (!insertionAllowed) + return 0; + return super.fill(resource, action); + } + } +} diff --git a/src/main/java/com/simibubi/create/content/processing/basin/BasinMountedFluidStorageType.java b/src/main/java/com/simibubi/create/content/processing/basin/BasinMountedFluidStorageType.java new file mode 100644 index 0000000000..4416032579 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/processing/basin/BasinMountedFluidStorageType.java @@ -0,0 +1,58 @@ +package com.simibubi.create.content.processing.basin; + +import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorageType; + +import com.simibubi.create.content.processing.basin.BasinMountedFluidStorage.MountedBasinTankHalf; + +import com.simibubi.create.foundation.blockEntity.behaviour.fluid.SmartFluidTankBehaviour.TankSegment; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +import net.neoforged.neoforge.fluids.FluidStack; + +import org.jetbrains.annotations.Nullable; + +public class BasinMountedFluidStorageType extends MountedFluidStorageType { + public BasinMountedFluidStorageType() { super(BasinMountedFluidStorage.CODEC); } + + @Override + public @Nullable BasinMountedFluidStorage mount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) { + if(be instanceof BasinBlockEntity basin) { + TankSegment[] inputTanks = basin.inputTank.getTanks(); + FluidStack firstInputStack = inputTanks[0].getTank().getFluid(); + FluidStack secondInputStack = inputTanks[1].getTank().getFluid(); + MountedBasinTankHalf inputTankHalf = MountedBasinTankHalf.fromStacks( + true, + firstInputStack, + secondInputStack, + firstInputStack.getAmount(), + secondInputStack.getAmount() + ); + + TankSegment[] outputTanks = basin.outputTank.getTanks(); + FluidStack firstOutputStack = outputTanks[0].getTank().getFluid(); + FluidStack secondOutputStack = outputTanks[1].getTank().getFluid(); + MountedBasinTankHalf outputTankHalf = MountedBasinTankHalf.fromStacks( + false, + firstOutputStack, + secondOutputStack, + firstOutputStack.getAmount(), + secondOutputStack.getAmount() + ); + + BasinMountedFluidStorage storage = new BasinMountedFluidStorage(inputTankHalf, outputTankHalf); + + inputTankHalf.setUpdateCallback(s -> storage.markDirty()); + outputTankHalf.setUpdateCallback(s -> storage.markDirty()); + + // i'm just... assuming these input and output tanks have two segments........ since that's how it's hardcoded + + return storage; + } + + return null; + } +} diff --git a/src/main/java/com/simibubi/create/content/processing/basin/BasinMountedItemStorage.java b/src/main/java/com/simibubi/create/content/processing/basin/BasinMountedItemStorage.java new file mode 100644 index 0000000000..43bf525f3e --- /dev/null +++ b/src/main/java/com/simibubi/create/content/processing/basin/BasinMountedItemStorage.java @@ -0,0 +1,158 @@ +package com.simibubi.create.content.processing.basin; + +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import com.simibubi.create.AllMountedStorageTypes; +import com.simibubi.create.api.contraption.storage.SyncedMountedStorage; +import com.simibubi.create.api.contraption.storage.item.MountedItemStorage; +import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType; + +import com.simibubi.create.content.contraptions.Contraption; +import com.simibubi.create.foundation.codec.CreateCodecs; + +import com.simibubi.create.foundation.utility.InventoryUtil; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo; + +import net.neoforged.neoforge.items.IItemHandlerModifiable; + +import net.neoforged.neoforge.items.wrapper.CombinedInvWrapper; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class BasinMountedItemStorage extends MountedItemStorage implements SyncedMountedStorage { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( + i -> i.group( + CreateCodecs.BASIN_INVENTORY.fieldOf("inputInventory").forGetter(BasinMountedItemStorage::getInputInventory), + CreateCodecs.BASIN_INVENTORY.fieldOf("outputInventory").forGetter(BasinMountedItemStorage::getOutputInventory) + ).apply(i, BasinMountedItemStorage::fromCodec) + ); + + protected BasinInventory inputInventory; + protected BasinInventory outputInventory; + protected IItemHandlerModifiable itemCapability; + + private int timesChanged; + private boolean dirty; + + protected BasinMountedItemStorage(MountedItemStorageType type) { + super(type); + + inputInventory = new BasinInventory(9, null); + + outputInventory = new BasinInventory(9, null); + outputInventory.forbidInsertion().withMaxStackSize(64); + + itemCapability = new CombinedInvWrapper(inputInventory, outputInventory); + + timesChanged = 0; + } + + protected BasinMountedItemStorage() { this(AllMountedStorageTypes.BASIN_ITEM.get()); } + + @Override + public boolean handleInteraction(ServerPlayer player, Contraption contraption, StructureBlockInfo info) { + // interaction is handled in the Interaction Behavior, takes items out from basin + return false; + } + + protected static BasinMountedItemStorage fromCodec(BasinInventory input, BasinInventory output) { + BasinMountedItemStorage basinMountedItemStorage = new BasinMountedItemStorage(); + + InventoryUtil.copyInventoryToFrom(basinMountedItemStorage.inputInventory, input); + InventoryUtil.copyInventoryToFrom(basinMountedItemStorage.outputInventory, output); + + basinMountedItemStorage.listenForChanges(); + + return basinMountedItemStorage; + } + + public static BasinMountedItemStorage fromBasin(BasinBlockEntity basin) { + BasinMountedItemStorage basinMountedItemStorage = new BasinMountedItemStorage(); + + InventoryUtil.copyInventoryToFrom(basinMountedItemStorage.inputInventory, basin.inputInventory); + InventoryUtil.copyInventoryToFrom(basinMountedItemStorage.outputInventory, basin.outputInventory); + + basinMountedItemStorage.listenForChanges(); + + return basinMountedItemStorage; + } + + protected void listenForChanges() { + this.inputInventory.whenContentsChanged(this::increaseTimesChanged); + this.outputInventory.whenContentsChanged(this::increaseTimesChanged); + } + + public BasinInventory getInputInventory() { return this.inputInventory; } + public BasinInventory getOutputInventory() { return this.outputInventory; } + public IItemHandlerModifiable getCapability() { return this.itemCapability; } + + public int getTimesChanged() { return timesChanged; } + + @Override + public boolean isDirty() { return this.dirty; } + + @Override + public void markClean() { this.dirty = false; } + + public void increaseTimesChanged(int $) { + this.timesChanged++; + this.dirty = true; + } + + @Override + public void afterSync(Contraption contraption, BlockPos localPos) {} + + @Override + public void unmount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) { + if(be instanceof BasinBlockEntity basin) { + basin.applyInventories(inputInventory, outputInventory); + } + } + + @Override + public void setStackInSlot(int slot, @NotNull ItemStack itemStack) { + itemCapability.setStackInSlot(slot, itemStack); + } + + @Override + public int getSlots() { + return itemCapability.getSlots(); + } + + @Override + @NotNull + public ItemStack getStackInSlot(int slot) { + return itemCapability.getStackInSlot(slot); + } + + @Override + @NotNull + public ItemStack insertItem(int slot, @NotNull ItemStack itemStack, boolean simulate) { + return itemCapability.insertItem(slot, itemStack, simulate); + } + + @Override + @NotNull + public ItemStack extractItem(int slot, int amount, boolean simulate) { + return itemCapability.extractItem(slot, amount, simulate); + } + + @Override + public int getSlotLimit(int slot) { + return itemCapability.getSlotLimit(slot); + } + + @Override + public boolean isItemValid(int slot, @NotNull ItemStack itemStack) { + return itemCapability.isItemValid(slot, itemStack); + } +} diff --git a/src/main/java/com/simibubi/create/content/processing/basin/BasinMountedItemStorageType.java b/src/main/java/com/simibubi/create/content/processing/basin/BasinMountedItemStorageType.java new file mode 100644 index 0000000000..ce967c1663 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/processing/basin/BasinMountedItemStorageType.java @@ -0,0 +1,21 @@ +package com.simibubi.create.content.processing.basin; + +import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +import org.jetbrains.annotations.Nullable; + +public class BasinMountedItemStorageType extends MountedItemStorageType { + public BasinMountedItemStorageType() { + super(BasinMountedItemStorage.CODEC); + } + + @Override + public @Nullable BasinMountedItemStorage mount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) { + return be instanceof BasinBlockEntity basin ? BasinMountedItemStorage.fromBasin(basin) : null; + } +} diff --git a/src/main/java/com/simibubi/create/content/processing/basin/BasinMovementBehaviour.java b/src/main/java/com/simibubi/create/content/processing/basin/BasinMovementBehaviour.java index a9d11d4db1..c83b11a9be 100644 --- a/src/main/java/com/simibubi/create/content/processing/basin/BasinMovementBehaviour.java +++ b/src/main/java/com/simibubi/create/content/processing/basin/BasinMovementBehaviour.java @@ -1,59 +1,132 @@ package com.simibubi.create.content.processing.basin; -import java.util.HashMap; -import java.util.Map; - import com.simibubi.create.api.behaviour.movement.MovementBehaviour; +import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorage; +import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorageWrapper; +import com.simibubi.create.api.contraption.storage.item.MountedItemStorage; +import com.simibubi.create.api.contraption.storage.item.MountedItemStorageWrapper; import com.simibubi.create.content.contraptions.behaviour.MovementContext; +import com.simibubi.create.content.contraptions.render.ContraptionMatrices; +import com.simibubi.create.content.logistics.box.PackageEntity; +import com.simibubi.create.foundation.item.ItemHelper; +import com.simibubi.create.foundation.virtualWorld.VirtualRenderWorld; + +import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.core.Direction; +import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; -import net.neoforged.neoforge.items.ItemStackHandler; +import net.neoforged.neoforge.items.ItemHandlerHelper; -public class BasinMovementBehaviour implements MovementBehaviour { - public Map getOrReadInventory(MovementContext context) { - Map map = new HashMap<>(); - map.put("InputItems", new ItemStackHandler(9)); - map.put("OutputItems", new ItemStackHandler(8)); - map.forEach((s, h) -> h.deserializeNBT(context.world.registryAccess(), context.blockEntityData.getCompound(s))); - return map; - } +import org.jetbrains.annotations.Nullable; + +import java.util.List; +public class BasinMovementBehaviour implements MovementBehaviour { @Override public void tick(MovementContext context) { - MovementBehaviour.super.tick(context); - if (context.temporaryData == null || (boolean) context.temporaryData) { - Vec3 facingVec = context.rotation.apply(Vec3.atLowerCornerOf(Direction.UP.getNormal())); - facingVec.normalize(); - if (Direction.getNearest(facingVec.x, facingVec.y, facingVec.z) == Direction.DOWN) - dump(context, facingVec); + BasinMountedItemStorage storage = getMountedItemStorage(context); + if (storage == null) return; + + Vec3 facingVec = context.rotation.apply(Vec3.atLowerCornerOf(Direction.UP.getNormal())).normalize(); + Direction nearest = Direction.getNearest(facingVec.x, facingVec.y, facingVec.z); + + if (nearest == Direction.DOWN) { + int newTimesChanged = storage.getTimesChanged(); + if (getLastTimesChanged(context) != newTimesChanged) { + dump(context, storage, facingVec); + } + } else if(nearest == Direction.UP) { + pickup(context, storage); } } - private void dump(MovementContext context, Vec3 facingVec) { - getOrReadInventory(context).forEach((key, itemStackHandler) -> { - for (int i = 0; i < itemStackHandler.getSlots(); i++) { - if (itemStackHandler.getStackInSlot(i) - .isEmpty()) - continue; - ItemEntity itemEntity = new ItemEntity(context.world, context.position.x, context.position.y, - context.position.z, itemStackHandler.getStackInSlot(i)); - itemEntity.setDeltaMovement(facingVec.scale(.05)); - context.world.addFreshEntity(itemEntity); - itemStackHandler.setStackInSlot(i, ItemStack.EMPTY); + private void pickup(MovementContext context, BasinMountedItemStorage storage) { + Level world = context.world; + + Vec3 halfBlock = new Vec3(0.4, 0.4, 0.4); + Vec3 posTop = context.position.add(halfBlock); + Vec3 posBottom = context.position.subtract(halfBlock); + + List items = world.getEntities((Entity) null, new AABB(posTop, posBottom), + e -> e instanceof ItemEntity || e instanceof PackageEntity); + + for (Entity entity : items) { + if (!entity.isAlive()) + continue; + ItemStack toInsert = ItemHelper.fromItemEntity(entity); + ItemStack remainder = + ItemHandlerHelper.insertItemStacked(storage, toInsert, false); + if (remainder.getCount() == toInsert.getCount()) + continue; + if (remainder.isEmpty()) { + entity.discard(); + continue; } - context.blockEntityData.put(key, itemStackHandler.serializeNBT(context.world.registryAccess())); - }); - // FIXME: Why are we setting client-side data here? - if (context.contraption.entity.level().isClientSide) { - BlockEntity blockEntity = context.contraption.getBlockEntityClientSide(context.localPos); - if (blockEntity instanceof BasinBlockEntity) - ((BasinBlockEntity) blockEntity).readOnlyItems(context.blockEntityData, context.world.registryAccess()); + + if (entity instanceof ItemEntity item) + item.setItem(remainder); + } + } + + private void dump(MovementContext context, BasinMountedItemStorage storage, Vec3 facingVec) { + for (int i = 0; i < storage.getSlots(); i++) { + if (storage.getStackInSlot(i).isEmpty()) + continue; + + ItemEntity itemEntity = new ItemEntity(context.world, context.position.x, context.position.y, context.position.z, storage.getStackInSlot(i)); + itemEntity.setDeltaMovement(facingVec.scale(.05)); + context.world.addFreshEntity(itemEntity); + + storage.setStackInSlot(i, ItemStack.EMPTY); + } + + context.temporaryData = storage.getTimesChanged(); + } + + private int getLastTimesChanged(MovementContext context) { + if(context.temporaryData != null) return (int)context.temporaryData; + return 0; + } + + private @Nullable BasinMountedItemStorage getMountedItemStorage(MovementContext context) { + MountedItemStorageWrapper wrapper = context.contraption.getStorage().getMountedItems(); + MountedItemStorage storageHere = wrapper.storages.get(context.localPos); + + if(storageHere instanceof BasinMountedItemStorage basin) return basin; + return null; + } + + private @Nullable BasinMountedFluidStorage getMountedFluidStorage(MovementContext context) { + MountedFluidStorageWrapper wrapper = context.contraption.getStorage().getFluids(); + MountedFluidStorage storageHere = wrapper.storages.get(context.localPos); + + if(storageHere instanceof BasinMountedFluidStorage basin) return basin; + return null; + } + + @Override + public boolean disableBlockEntityRendering() { + return true; + } + + @Override + public void renderInContraption(MovementContext context, VirtualRenderWorld renderWorld, ContraptionMatrices matrices, MultiBufferSource buffer) { + BlockEntity blockEntity = context.contraption.getBlockEntityClientSide(context.localPos); + if (blockEntity instanceof BasinBlockEntity) { + BasinMountedItemStorage mountedItemStorage = getMountedItemStorage(context); + if(mountedItemStorage == null) return; + + BasinMountedFluidStorage mountedFluidStorage = getMountedFluidStorage(context); + + BasinRenderer.renderInContraption(context, renderWorld, matrices, buffer, + mountedItemStorage, mountedFluidStorage); } - context.temporaryData = false; // did already dump, so can't anymore } } diff --git a/src/main/java/com/simibubi/create/content/processing/basin/BasinRenderer.java b/src/main/java/com/simibubi/create/content/processing/basin/BasinRenderer.java index d92729f491..829515276f 100644 --- a/src/main/java/com/simibubi/create/content/processing/basin/BasinRenderer.java +++ b/src/main/java/com/simibubi/create/content/processing/basin/BasinRenderer.java @@ -1,10 +1,16 @@ package com.simibubi.create.content.processing.basin; import com.mojang.blaze3d.vertex.PoseStack; +import com.simibubi.create.content.contraptions.behaviour.MovementContext; +import com.simibubi.create.content.contraptions.render.ContraptionMatrices; +import com.simibubi.create.content.processing.basin.BasinMountedFluidStorage.MountedBasinTankHalf; import com.simibubi.create.foundation.blockEntity.behaviour.fluid.SmartFluidTankBehaviour; import com.simibubi.create.foundation.blockEntity.behaviour.fluid.SmartFluidTankBehaviour.TankSegment; import com.simibubi.create.foundation.blockEntity.renderer.SmartBlockEntityRenderer; +import com.simibubi.create.foundation.render.BlockEntityRenderHelper; +import com.simibubi.create.foundation.virtualWorld.VirtualRenderWorld; + import dev.engine_room.flywheel.lib.transform.TransformStack; import net.createmod.catnip.animation.AnimationTickHolder; import net.createmod.catnip.data.IntAttached; @@ -14,6 +20,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; +import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Direction.Axis; @@ -21,6 +28,7 @@ import net.minecraft.util.RandomSource; import net.minecraft.world.item.ItemDisplayContext; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; @@ -28,6 +36,8 @@ import net.neoforged.neoforge.items.IItemHandlerModifiable; import net.neoforged.neoforge.items.ItemStackHandler; +import org.jetbrains.annotations.Nullable; + public class BasinRenderer extends SmartBlockEntityRenderer { public BasinRenderer(BlockEntityRendererProvider.Context context) { @@ -52,22 +62,18 @@ protected void renderSafe(BasinBlockEntity basin, float partialTicks, PoseStack RandomSource r = RandomSource.create(pos.hashCode()); Vec3 baseVector = new Vec3(.125, level, 0); - IItemHandlerModifiable inv = basin.itemCapability; - if (inv == null) - inv = new ItemStackHandler(); + IItemHandlerModifiable handler = basin.itemCapability; + if (handler == null) + handler = new ItemStackHandler(); - int itemCount = 0; - for (int slot = 0; slot < inv.getSlots(); slot++) - if (!inv.getStackInSlot(slot) - .isEmpty()) - itemCount++; + int itemCount = getItemCount(handler); if (itemCount == 1) baseVector = new Vec3(0, level, 0); float anglePartition = 360f / itemCount; - for (int slot = 0; slot < inv.getSlots(); slot++) { - ItemStack stack = inv.getStackInSlot(slot); + for (int slot = 0; slot < handler.getSlots(); slot++) { + ItemStack stack = handler.getStackInSlot(slot); if (stack.isEmpty()) continue; @@ -136,7 +142,7 @@ protected void renderSafe(BasinBlockEntity basin, float partialTicks, PoseStack } } - protected void renderItem(PoseStack ms, MultiBufferSource buffer, int light, int overlay, ItemStack stack) { + protected static void renderItem(PoseStack ms, MultiBufferSource buffer, int light, int overlay, ItemStack stack) { Minecraft mc = Minecraft.getInstance(); mc.getItemRenderer() .renderStatic(stack, ItemDisplayContext.GROUND, light, overlay, ms, buffer, mc.level, 0); @@ -185,6 +191,127 @@ protected float renderFluids(BasinBlockEntity basin, float partialTicks, PoseSta return yMax; } + public static void renderInContraption(MovementContext context, VirtualRenderWorld renderWorld, + ContraptionMatrices matrices, MultiBufferSource buffer, + BasinMountedItemStorage itemStorage, @Nullable BasinMountedFluidStorage fluidStorage) { + PoseStack ms = matrices.getModelViewProjection(); + int light = BlockEntityRenderHelper.getLight(context.world, renderWorld, context.localPos, matrices.getLight()); + + ms.pushPose(); + + BlockEntity blockEntity = context.contraption.getBlockEntityClientSide(context.localPos); + if(!(blockEntity instanceof BasinBlockEntity)) return; + + + Vec3 basinCenterPos = Vec3.atLowerCornerOf(context.localPos); + ms.translate(basinCenterPos.x, basinCenterPos.y, basinCenterPos.z); + + float fluidLevel = 0; + if(fluidStorage != null) fluidLevel = renderFluidsInContraption(context, renderWorld, ms, light, buffer, fluidStorage); + + float level = Mth.clamp(fluidLevel - .3f, .125f, .6f); + Vec3 baseVector = new Vec3(.125, level, 0); + ms.translate(.5, .2f, .5); + + RandomSource random = RandomSource.create(context.localPos.hashCode()); + + IItemHandlerModifiable handler = itemStorage.itemCapability; + + int itemCount = getItemCount(handler); + + if (itemCount == 1) + baseVector = new Vec3(0, level, 0); + + float anglePartition = 360f / itemCount; + for (int slot = 0; slot < handler.getSlots(); slot++) { + ItemStack stack = handler.getStackInSlot(slot); + if (stack.isEmpty()) + continue; + + ms.pushPose(); + + if (fluidLevel > 0) { + ms.translate(0, + (Mth.sin( + AnimationTickHolder.getRenderTime(context.world) / 12f + anglePartition * itemCount) + 1.5f) + * 1 / 32f, + 0); + } + + Vec3 itemPosition = VecHelper.rotate(baseVector, anglePartition * itemCount, Axis.Y); + ms.translate(itemPosition.x, itemPosition.y, itemPosition.z); + TransformStack.of(ms) + .rotateYDegrees(anglePartition * itemCount + 35) + .rotateXDegrees(65); + + for (int i = 0; i <= stack.getCount() / 8; i++) { + ms.pushPose(); + + Vec3 vec = VecHelper.offsetRandomly(Vec3.ZERO, random, 1 / 16f); + + ms.translate(vec.x, vec.y, vec.z); + renderItem(ms, buffer, light, OverlayTexture.NO_OVERLAY, stack); + ms.popPose(); + } + ms.popPose(); + + itemCount--; + } + + ms.popPose(); + } + + public static float renderFluidsInContraption(MovementContext context, VirtualRenderWorld renderWorld, PoseStack ms, int light, + MultiBufferSource buffer, BasinMountedFluidStorage mountedFluidStorage) { + float partialTicks = AnimationTickHolder.getPartialTicks(); + + mountedFluidStorage.tickChasers(); + float totalUnits = mountedFluidStorage.getTotalFluidUnits(partialTicks); + if (totalUnits < 1) + return 0; + + float fluidLevel = Mth.clamp(totalUnits / 2000, 0, 1); + + fluidLevel = 1 - ((1 - fluidLevel) * (1 - fluidLevel)); + + float xMin = 2 / 16f; + float xMax = 2 / 16f; + final float yMin = 2 / 16f; + final float yMax = yMin + 12 / 16f * fluidLevel; + final float zMin = 2 / 16f; + final float zMax = 14 / 16f; + + MountedBasinTankHalf[] tankHalves = {mountedFluidStorage.inputTank(), mountedFluidStorage.outputTank()}; + for (MountedBasinTankHalf mountedBasinTankHalf : tankHalves) { + for(int tank : new int[]{0, 1}) { + FluidStack renderedFluid = mountedBasinTankHalf.getFluidInTank(tank); + if (renderedFluid.isEmpty()) + continue; + + float units = mountedBasinTankHalf.getTotalUnits(partialTicks, tank); + if (units < 1) + continue; + + float partial = Mth.clamp(units / totalUnits, 0, 1); + xMax += partial * 12 / 16f; + NeoForgeCatnipServices.FLUID_RENDERER.renderFluidBox(renderedFluid, xMin, yMin, zMin, xMax, yMax, zMax, + buffer, ms, light, false, false); + + xMin = xMax; + } + } + + return yMax; + } + + private static int getItemCount(IItemHandlerModifiable handler) { + int itemCount = 0; + for (int slot = 0; slot < handler.getSlots(); slot++) + if (!handler.getStackInSlot(slot).isEmpty()) + itemCount++; + return itemCount; + } + @Override public int getViewDistance() { return 16; diff --git a/src/main/java/com/simibubi/create/content/processing/basin/MountedBasinInteractionBehaviour.java b/src/main/java/com/simibubi/create/content/processing/basin/MountedBasinInteractionBehaviour.java new file mode 100644 index 0000000000..58bf7bbfd9 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/processing/basin/MountedBasinInteractionBehaviour.java @@ -0,0 +1,48 @@ +package com.simibubi.create.content.processing.basin; + +import com.simibubi.create.api.behaviour.interaction.MovingInteractionBehaviour; +import com.simibubi.create.api.contraption.storage.item.MountedItemStorage; +import com.simibubi.create.content.contraptions.AbstractContraptionEntity; + +import com.simibubi.create.content.contraptions.MountedStorageManager; + +import net.minecraft.core.BlockPos; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; + +public class MountedBasinInteractionBehaviour extends MovingInteractionBehaviour { + @Override + public boolean handlePlayerInteraction(Player player, InteractionHand activeHand, BlockPos localPos, AbstractContraptionEntity contraptionEntity) { + Level level = player.level(); + if (level.isClientSide) + return true; + + MountedStorageManager manager = contraptionEntity.getContraption().getStorage(); + MountedItemStorage storage = manager.getAllItemStorages().get(localPos); + if (!(storage instanceof BasinMountedItemStorage basin)) + return false; + + boolean success = false; + for (int slot = 0; slot < basin.getSlots(); slot++) { + ItemStack stackInSlot = basin.getStackInSlot(slot); + if (stackInSlot.isEmpty()) + continue; + player.getInventory() + .placeItemBackInInventory(stackInSlot); + basin.setStackInSlot(slot, ItemStack.EMPTY); + success = true; + } + if (success) { + BlockPos soundPos = BlockPos.containing(contraptionEntity.toGlobalVector(Vec3.atCenterOf(localPos), 0)); + level.playSound(null, soundPos, SoundEvents.ITEM_PICKUP, SoundSource.PLAYERS, .2f, + 1f + level.getRandom().nextFloat()); + } + + return success; + } +} diff --git a/src/main/java/com/simibubi/create/foundation/blockEntity/behaviour/fluid/SmartFluidTankBehaviour.java b/src/main/java/com/simibubi/create/foundation/blockEntity/behaviour/fluid/SmartFluidTankBehaviour.java index 6e27a26e2b..da75b0ef03 100644 --- a/src/main/java/com/simibubi/create/foundation/blockEntity/behaviour/fluid/SmartFluidTankBehaviour.java +++ b/src/main/java/com/simibubi/create/foundation/blockEntity/behaviour/fluid/SmartFluidTankBehaviour.java @@ -257,6 +257,8 @@ public LerpedFloat getFluidLevel() { return fluidLevel; } + public SmartFluidTank getTank() { return tank; } + public float getTotalUnits(float partialTicks) { return fluidLevel.getValue(partialTicks) * tank.getCapacity(); } diff --git a/src/main/java/com/simibubi/create/foundation/codec/CreateCodecs.java b/src/main/java/com/simibubi/create/foundation/codec/CreateCodecs.java index 032aa5ff65..2db2168b2c 100644 --- a/src/main/java/com/simibubi/create/foundation/codec/CreateCodecs.java +++ b/src/main/java/com/simibubi/create/foundation/codec/CreateCodecs.java @@ -2,6 +2,8 @@ import java.util.function.Function; +import com.simibubi.create.content.processing.basin.BasinInventory; + import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval; import com.mojang.serialization.Codec; @@ -34,6 +36,10 @@ public class CreateCodecs { slots -> slots.toHandler(ItemStackHandler::new), ItemSlots::fromHandler )); + public static final Codec BASIN_INVENTORY = Codec.lazyInitialized(() -> ItemSlots.CODEC.xmap( + slots -> slots.toHandler(BasinInventory::new), ItemSlots::fromHandler + )); + public static Codec boundedIntStr(int min) { return INT_STR.validate(i -> i >= min ? DataResult.success(i) : DataResult.error(() -> "Value under minimum of " + min)); } diff --git a/src/main/java/com/simibubi/create/foundation/fluid/SmartFluidTank.java b/src/main/java/com/simibubi/create/foundation/fluid/SmartFluidTank.java index 7d81114ec4..0caf0d0c67 100644 --- a/src/main/java/com/simibubi/create/foundation/fluid/SmartFluidTank.java +++ b/src/main/java/com/simibubi/create/foundation/fluid/SmartFluidTank.java @@ -25,4 +25,7 @@ public void setFluid(FluidStack stack) { updateCallback.accept(stack); } + public void setUpdateCallback(Consumer updateCallback) { + this.updateCallback = updateCallback; + } } diff --git a/src/main/java/com/simibubi/create/foundation/item/SmartInventory.java b/src/main/java/com/simibubi/create/foundation/item/SmartInventory.java index af22e332e5..8ded264837 100644 --- a/src/main/java/com/simibubi/create/foundation/item/SmartInventory.java +++ b/src/main/java/com/simibubi/create/foundation/item/SmartInventory.java @@ -16,6 +16,8 @@ import net.neoforged.neoforge.items.IItemHandlerModifiable; import net.neoforged.neoforge.items.ItemStackHandler; +import org.jetbrains.annotations.Nullable; + public class SmartInventory extends ItemHandlerContainer implements IItemHandlerModifiable, INBTSerializable { @@ -25,19 +27,19 @@ public class SmartInventory extends ItemHandlerContainer protected SyncedStackHandler wrapped; protected int stackSize; - public SmartInventory(int slots, SyncedBlockEntity be) { + public SmartInventory(int slots, @Nullable SyncedBlockEntity be) { this(slots, be, 64, false); } - public SmartInventory(int slots, SyncedBlockEntity be, BiPredicate isValid) { + public SmartInventory(int slots, @Nullable SyncedBlockEntity be, BiPredicate isValid) { this(slots, be, 64, false, isValid); } - public SmartInventory(int slots, SyncedBlockEntity be, int stackSize, boolean stackNonStackables) { + public SmartInventory(int slots, @Nullable SyncedBlockEntity be, int stackSize, boolean stackNonStackables) { this(new SyncedStackHandler(slots, be, stackNonStackables, stackSize), stackSize, stackNonStackables); } - public SmartInventory(int slots, SyncedBlockEntity be, int stackSize, boolean stackNonStackables, BiPredicate isValid) { + public SmartInventory(int slots, @Nullable SyncedBlockEntity be, int stackSize, boolean stackNonStackables, BiPredicate isValid) { this(new SyncedStackHandler(slots, be, stackNonStackables, stackSize, isValid), stackSize, stackNonStackables); } @@ -145,18 +147,18 @@ private SyncedStackHandler getInv() { protected static class SyncedStackHandler extends ItemStackHandler { - private SyncedBlockEntity blockEntity; + private @Nullable SyncedBlockEntity blockEntity; private boolean stackNonStackables; private int stackSize; private BiPredicate isValid = super::isItemValid; private Consumer updateCallback; - public SyncedStackHandler(int slots, SyncedBlockEntity be, boolean stackNonStackables, int stackSize, BiPredicate isValid) { + public SyncedStackHandler(int slots, @Nullable SyncedBlockEntity be, boolean stackNonStackables, int stackSize, BiPredicate isValid) { this(slots, be, stackNonStackables, stackSize); this.isValid = isValid; } - public SyncedStackHandler(int slots, SyncedBlockEntity be, boolean stackNonStackables, int stackSize) { + public SyncedStackHandler(int slots, @Nullable SyncedBlockEntity be, boolean stackNonStackables, int stackSize) { super(slots); this.blockEntity = be; this.stackNonStackables = stackNonStackables; @@ -168,7 +170,8 @@ protected void onContentsChanged(int slot) { super.onContentsChanged(slot); if (updateCallback != null) updateCallback.accept(slot); - blockEntity.notifyUpdate(); + if (blockEntity != null) + blockEntity.notifyUpdate(); } @Override diff --git a/src/main/java/com/simibubi/create/foundation/render/BlockEntityRenderHelper.java b/src/main/java/com/simibubi/create/foundation/render/BlockEntityRenderHelper.java index a57901851c..19332778c3 100644 --- a/src/main/java/com/simibubi/create/foundation/render/BlockEntityRenderHelper.java +++ b/src/main/java/com/simibubi/create/foundation/render/BlockEntityRenderHelper.java @@ -56,18 +56,9 @@ public static void renderBlockEntities(List blockEntities, BitSet s .translate(pos); try { - int realLevelLight = LevelRenderer.getLightColor(realLevel, getLightPos(lightTransform, pos)); - - int light; - if (renderLevel != null) { - renderLevel.setExternalLight(realLevelLight); - light = LevelRenderer.getLightColor(renderLevel, pos); - } else { - light = realLevelLight; - } + int light = getLight(realLevel, renderLevel, pos, lightTransform); renderer.render(blockEntity, pt, ms, buffer, light, OverlayTexture.NO_OVERLAY); - } catch (Exception e) { // Prevent this BE from causing more issues in the future. erroredBEsOut.set(i); @@ -85,7 +76,18 @@ public static void renderBlockEntities(List blockEntities, BitSet s } } - private static BlockPos getLightPos(@Nullable Matrix4f lightTransform, BlockPos contraptionPos) { + public static int getLight(Level realLevel, VirtualRenderWorld renderLevel, BlockPos pos, @Nullable Matrix4f lightTransform) { + int realLevelLight = LevelRenderer.getLightColor(realLevel, getLightPos(lightTransform, pos)); + + if (renderLevel != null) { + renderLevel.setExternalLight(realLevelLight); + return LevelRenderer.getLightColor(renderLevel, pos); + } + + return realLevelLight; + } + + public static BlockPos getLightPos(@Nullable Matrix4f lightTransform, BlockPos contraptionPos) { if (lightTransform != null) { Vector4f lightVec = new Vector4f(contraptionPos.getX() + .5f, contraptionPos.getY() + .5f, contraptionPos.getZ() + .5f, 1); lightVec.mul(lightTransform); diff --git a/src/main/java/com/simibubi/create/foundation/utility/InventoryUtil.java b/src/main/java/com/simibubi/create/foundation/utility/InventoryUtil.java new file mode 100644 index 0000000000..96f1e69aed --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/utility/InventoryUtil.java @@ -0,0 +1,12 @@ +package com.simibubi.create.foundation.utility; + +import net.neoforged.neoforge.items.IItemHandler; +import net.neoforged.neoforge.items.IItemHandlerModifiable; + +public class InventoryUtil { + public static void copyInventoryToFrom(IItemHandlerModifiable copy, IItemHandler original) { + for (int i = 0; i < original.getSlots(); i++) { + copy.setStackInSlot(i, original.getStackInSlot(i).copy()); + } + } +}