pipes (not done for fabric) and tank (maybe for fabric?)

This commit is contained in:
2026-02-03 14:47:35 -05:00
parent 9deea24e7b
commit a30721d9b8
39 changed files with 2201 additions and 100 deletions
@@ -7,6 +7,7 @@ import net.cmr.jurassicrevived.config.JRConfigManager;
import net.cmr.jurassicrevived.entity.ModEntities;
import net.cmr.jurassicrevived.item.ModCreativeTabs;
import net.cmr.jurassicrevived.item.ModItems;
import net.cmr.jurassicrevived.networking.ModPackets;
import net.cmr.jurassicrevived.platform.Services;
import net.cmr.jurassicrevived.recipe.ModRecipes;
import net.cmr.jurassicrevived.screen.ModMenuTypes;
@@ -68,5 +69,6 @@ public class CommonClass
ModEvents.init();
ModSounds.register();
ModPackets.register();
}
}
@@ -5,6 +5,7 @@ import net.cmr.jurassicrevived.block.entity.custom.PipeBlockEntity;
import net.cmr.jurassicrevived.block.entity.energy.ModEnergyUtil;
import net.cmr.jurassicrevived.config.JRConfigManager;
import net.cmr.jurassicrevived.item.ModItems;
import net.cmr.jurassicrevived.platform.Services;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
@@ -198,6 +199,15 @@ public class PipeBlock extends Block implements EntityBlock, SimpleWaterloggedBl
};
}
if (level instanceof Level lvl) {
boolean platformHasHandler = switch (this.transport) {
case ITEMS -> Services.TRANSFER.getItemHandler(lvl, neighborPos, dir.getOpposite()).isPresent();
case FLUIDS -> Services.TRANSFER.getFluidHandler(lvl, neighborPos, dir.getOpposite()).isPresent();
case ENERGY -> Services.TRANSFER.getEnergyHandler(lvl, neighborPos, dir.getOpposite()).isPresent();
};
hasCommonConnection = hasCommonConnection || platformHasHandler;
}
if (hasCommonConnection) {
if (existing == ConnectionType.CONNECTOR_PULL) {
return ConnectionType.CONNECTOR_PULL;
@@ -352,14 +362,14 @@ public class PipeBlock extends Block implements EntityBlock, SimpleWaterloggedBl
}
public int getMaxItemsPerTick() {
return Math.max(0, JRConfigManager.get().itemsPerSecond / 20);
return Math.max(1, JRConfigManager.get().itemsPerSecond / 20);
}
public int getMaxFluidPerTick() {
return Math.max(0, JRConfigManager.get().milliBucketsPerSecond / 20);
return Math.max(1, JRConfigManager.get().milliBucketsPerSecond / 20);
}
public int getMaxEnergyPerTick() {
return Math.max(0, JRConfigManager.get().fePerSecond / 20);
return Math.max(1, JRConfigManager.get().fePerSecond / 20);
}
}
@@ -1,11 +1,16 @@
package net.cmr.jurassicrevived.block.entity.custom;
import dev.architectury.fluid.FluidStack;
import net.cmr.jurassicrevived.block.custom.PipeBlock;
import net.cmr.jurassicrevived.block.custom.PipeBlock.ConnectionType;
import net.cmr.jurassicrevived.block.custom.PipeBlock.Transport;
import net.cmr.jurassicrevived.block.entity.ModBlockEntities;
import net.cmr.jurassicrevived.block.entity.energy.ModEnergyStorage;
import net.cmr.jurassicrevived.block.entity.energy.ModEnergyUtil;
import net.cmr.jurassicrevived.platform.Services;
import net.cmr.jurassicrevived.platform.transfer.PlatformEnergyHandler;
import net.cmr.jurassicrevived.platform.transfer.PlatformFluidHandler;
import net.cmr.jurassicrevived.platform.transfer.PlatformItemHandler;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.Container;
@@ -16,7 +21,8 @@ import net.minecraft.world.level.block.state.BlockState;
import java.util.*;
public class PipeBlockEntity extends BlockEntity {
public class PipeBlockEntity extends BlockEntity
{
private final Transport transport;
@@ -39,9 +45,8 @@ public class PipeBlockEntity extends BlockEntity {
PipeBlock block = (PipeBlock) state.getBlock();
int itemCap = block.getMaxItemsPerTick();
// Fallback caps if Config is not ready
int fluidCap = 1000;
int energyCap = 1000;
int fluidCap = block.getMaxFluidPerTick();
int energyCap = block.getMaxEnergyPerTick();
switch (be.transport) {
case ITEMS -> transferItems(level, pos, state, itemCap);
@@ -52,9 +57,12 @@ public class PipeBlockEntity extends BlockEntity {
// ===== Network discovery =====
private record PipeEndpoint(BlockPos pipePos, Direction side) {}
private record PipeEndpoint(BlockPos pipePos, Direction side)
{
}
private static class Network {
private static class Network
{
final List<PipeEndpoint> sources = new ArrayList<>();
final List<PipeEndpoint> sinks = new ArrayList<>();
}
@@ -99,73 +107,188 @@ public class PipeBlockEntity extends BlockEntity {
private static void transferItems(Level level, BlockPos pos, BlockState state, int perTickLimit) {
Network net = discoverNetwork(level, pos, Transport.ITEMS);
List<Container> outputs = new ArrayList<>();
for (PipeEndpoint ep : net.sinks) {
BlockEntity be = level.getBlockEntity(ep.pipePos.relative(ep.side));
if (be instanceof Container c) outputs.add(c);
}
if (outputs.isEmpty()) return;
if (net.sources.isEmpty() || net.sinks.isEmpty()) return;
Map<BlockPos, List<PipeEndpoint>> sinksByPipe = indexSinksByPipe(net.sinks);
int remaining = perTickLimit;
for (PipeEndpoint ep : net.sources) {
for (PipeEndpoint srcEp : net.sources) {
if (remaining <= 0) break;
BlockEntity be = level.getBlockEntity(ep.pipePos.relative(ep.side));
if (!(be instanceof Container src)) continue;
for (int i = 0; i < src.getContainerSize() && remaining > 0; i++) {
ItemStack stack = src.getItem(i);
if (stack.isEmpty()) continue;
BlockPos srcPos = srcEp.pipePos.relative(srcEp.side);
Direction srcSide = srcEp.side.getOpposite();
ItemStack toMove = stack.copy();
toMove.setCount(Math.min(stack.getCount(), remaining));
PlatformItemHandler src = Services.TRANSFER
.getItemHandler(level, srcPos, srcSide)
.orElse(null);
if (src == null) continue;
for (Container out : outputs) {
// Logic to insert into vanilla container (simplified)
// You might want a helper for this
}
}
PipeEndpoint sinkEp = findNearestSink(level, srcEp.pipePos, sinksByPipe, Transport.ITEMS);
if (sinkEp == null) continue;
BlockPos dstPos = sinkEp.pipePos.relative(sinkEp.side);
Direction dstSide = sinkEp.side.getOpposite();
PlatformItemHandler dst = Services.TRANSFER
.getItemHandler(level, dstPos, dstSide)
.orElse(null);
if (dst == null) continue;
remaining = moveFromSourceToSingleTarget(src, dst, remaining);
}
}
// ===== Energy Transfer (Using Custom Energy System) =====
private static PipeEndpoint findNearestSink(
Level level,
BlockPos startPipe,
Map<BlockPos, List<PipeEndpoint>> sinksByPipe,
Transport transport
) {
ArrayDeque<BlockPos> q = new ArrayDeque<>();
HashSet<BlockPos> seen = new HashSet<>();
q.add(startPipe);
seen.add(startPipe);
while (!q.isEmpty()) {
BlockPos p = q.removeFirst();
List<PipeEndpoint> sinksHere = sinksByPipe.get(p);
if (sinksHere != null && !sinksHere.isEmpty()) {
return sinksHere.get(0);
}
BlockState st = level.getBlockState(p);
if (!(st.getBlock() instanceof PipeBlock pb) || pb.getTransport() != transport) continue;
for (Direction d : Direction.values()) {
if (st.getValue(PipeBlock.getProp(d)) == ConnectionType.PIPE) {
BlockPos np = p.relative(d);
if (seen.add(np)) q.add(np);
}
}
}
return null;
}
private static void transferEnergy(Level level, BlockPos pos, BlockState state, int perTickLimit) {
Network net = discoverNetwork(level, pos, Transport.ENERGY);
List<ModEnergyStorage> outputs = new ArrayList<>();
for (PipeEndpoint ep : net.sinks) {
BlockEntity be = level.getBlockEntity(ep.pipePos.relative(ep.side));
if (be instanceof ModEnergyUtil.EnergyProvider provider) {
ModEnergyStorage storage = provider.getEnergyStorage(ep.side.getOpposite());
if (storage != null && storage.canReceive()) outputs.add(storage);
}
}
if (outputs.isEmpty()) return;
if (net.sources.isEmpty() || net.sinks.isEmpty()) return;
Map<BlockPos, List<PipeEndpoint>> sinksByPipe = indexSinksByPipe(net.sinks);
int remaining = perTickLimit;
for (PipeEndpoint ep : net.sources) {
for (PipeEndpoint srcEp : net.sources) {
if (remaining <= 0) break;
BlockEntity be = level.getBlockEntity(ep.pipePos.relative(ep.side));
if (be instanceof ModEnergyUtil.EnergyProvider provider) {
ModEnergyStorage src = provider.getEnergyStorage(ep.side.getOpposite());
if (src == null || !src.canExtract()) continue;
int canExtract = src.extractEnergy(remaining, true);
if (canExtract <= 0) continue;
BlockPos srcPos = srcEp.pipePos.relative(srcEp.side);
Direction srcSide = srcEp.side.getOpposite();
for (ModEnergyStorage out : outputs) {
int accepted = out.receiveEnergy(canExtract, true);
if (accepted > 0) {
int actuallyExtracted = src.extractEnergy(accepted, false);
out.receiveEnergy(actuallyExtracted, false);
remaining -= actuallyExtracted;
break;
}
}
}
PlatformEnergyHandler src = Services.TRANSFER
.getEnergyHandler(level, srcPos, srcSide)
.orElse(null);
if (src == null) continue;
PipeEndpoint sinkEp = findNearestSink(level, srcEp.pipePos, sinksByPipe, Transport.ENERGY);
if (sinkEp == null) continue;
BlockPos dstPos = sinkEp.pipePos.relative(sinkEp.side);
Direction dstSide = sinkEp.side.getOpposite();
PlatformEnergyHandler dst = Services.TRANSFER
.getEnergyHandler(level, dstPos, dstSide)
.orElse(null);
if (dst == null) continue;
int available = src.extract(remaining, true);
if (available <= 0) continue;
int accepted = dst.insert(available, true);
if (accepted <= 0) continue;
int extracted = src.extract(accepted, false);
int inserted = dst.insert(extracted, false);
remaining -= inserted;
}
}
private static void transferFluids(Level level, BlockPos pos, BlockState state, int perTickLimit) {
// Implementation would use Architectury FluidStack similarly to energy
Network net = discoverNetwork(level, pos, Transport.FLUIDS);
if (net.sources.isEmpty() || net.sinks.isEmpty()) return;
Map<BlockPos, List<PipeEndpoint>> sinksByPipe = indexSinksByPipe(net.sinks);
long remaining = perTickLimit;
for (PipeEndpoint srcEp : net.sources) {
if (remaining <= 0) break;
BlockPos srcPos = srcEp.pipePos.relative(srcEp.side);
Direction srcSide = srcEp.side.getOpposite();
PlatformFluidHandler src = Services.TRANSFER
.getFluidHandler(level, srcPos, srcSide)
.orElse(null);
if (src == null) continue;
PipeEndpoint sinkEp = findNearestSink(level, srcEp.pipePos, sinksByPipe, Transport.FLUIDS);
if (sinkEp == null) continue;
BlockPos dstPos = sinkEp.pipePos.relative(sinkEp.side);
Direction dstSide = sinkEp.side.getOpposite();
PlatformFluidHandler dst = Services.TRANSFER
.getFluidHandler(level, dstPos, dstSide)
.orElse(null);
if (dst == null) continue;
for (FluidStack candidate : src.getExtractableFluids()) {
if (candidate.isEmpty()) continue;
long available = src.extract(candidate, remaining, true);
if (available <= 0) continue;
long accepted = dst.insert(candidate, available, true);
if (accepted <= 0) continue;
FluidStack toMove = candidate.copy();
toMove.setAmount(accepted);
long extracted = src.extract(toMove, accepted, false);
long inserted = dst.insert(toMove, extracted, false);
remaining -= inserted;
if (remaining <= 0) break;
}
}
}
private static Map<BlockPos, List<PipeEndpoint>> indexSinksByPipe(List<PipeEndpoint> sinks) {
Map<BlockPos, List<PipeEndpoint>> map = new HashMap<>();
for (PipeEndpoint ep : sinks) {
map.computeIfAbsent(ep.pipePos, k -> new ArrayList<>()).add(ep);
}
return map;
}
private static int moveFromSourceToSingleTarget(PlatformItemHandler src, PlatformItemHandler dst, int limit) {
int remaining = limit;
for (ItemStack candidate : src.getExtractableStacks()) {
if (candidate.isEmpty()) continue;
int available = src.extract(candidate, remaining, true);
if (available <= 0) continue;
int accepted = dst.insert(candidate, available, true);
if (accepted <= 0) continue;
int extracted = src.extract(candidate, accepted, false);
if (extracted <= 0) continue;
int inserted = dst.insert(candidate, extracted, false);
remaining -= inserted;
if (remaining <= 0) break;
}
return remaining;
}
}
@@ -3,19 +3,29 @@ package net.cmr.jurassicrevived.block.entity.custom;
import dev.architectury.fluid.FluidStack;
import dev.architectury.registry.menu.ExtendedMenuProvider;
import net.cmr.jurassicrevived.block.entity.ModBlockEntities;
import net.cmr.jurassicrevived.platform.transfer.InternalFluidHandler;
import net.cmr.jurassicrevived.platform.transfer.InternalFluidProvider;
import net.cmr.jurassicrevived.screen.custom.TankMenu;
import net.cmr.jurassicrevived.config.JRConfigManager;
import net.cmr.jurassicrevived.platform.Services;
import net.cmr.jurassicrevived.platform.services.IItemFluidHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.Containers;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.core.Direction;
import net.minecraft.world.inventory.AbstractContainerMenu;
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;
@@ -25,7 +35,7 @@ import org.jetbrains.annotations.Nullable;
/*import net.minecraft.core.HolderLookup;
*///?}
public class TankBlockEntity extends BlockEntity implements ExtendedMenuProvider {
public class TankBlockEntity extends BlockEntity implements ExtendedMenuProvider, InternalFluidProvider {
public final SimpleContainer itemHandler = new SimpleContainer(2) {
@Override
public void setChanged() {
@@ -46,6 +56,66 @@ public class TankBlockEntity extends BlockEntity implements ExtendedMenuProvider
private FluidStack fluidStack = FluidStack.empty();
private static final long CAPACITY = 64000;
public TankFluidHandler getTank(@Nullable Direction side) {
return tank;
}
private final TankFluidHandler tank = new TankFluidHandler();
@Override
public InternalFluidHandler getFluidHandler(@Nullable Direction side) {
return tank;
}
public class TankFluidHandler implements InternalFluidHandler {
@Override
public FluidStack getFluid() {
return fluidStack;
}
@Override
public long getCapacity() {
return CAPACITY;
}
public long fill(FluidStack stack, boolean simulate) {
if (stack.isEmpty()) return 0;
if (!fluidStack.isEmpty() && fluidStack.getFluid() != stack.getFluid()) return 0;
long space = CAPACITY - fluidStack.getAmount();
if (space <= 0) return 0;
long toFill = Math.min(space, stack.getAmount());
if (!simulate) {
fluidStack = FluidStack.create(stack.getFluid(), fluidStack.getAmount() + toFill);
setChanged();
if (level != null && !level.isClientSide()) {
level.sendBlockUpdated(getBlockPos(), getBlockState(), getBlockState(), 3);
}
}
return toFill;
}
public FluidStack drain(long amount, boolean simulate) {
if (fluidStack.isEmpty() || amount <= 0) return FluidStack.empty();
long drained = Math.min(amount, fluidStack.getAmount());
FluidStack out = FluidStack.create(fluidStack.getFluid(), drained);
if (!simulate) {
long remaining = fluidStack.getAmount() - drained;
fluidStack = remaining > 0
? FluidStack.create(fluidStack.getFluid(), remaining)
: FluidStack.empty();
setChanged();
if (level != null && !level.isClientSide()) {
level.sendBlockUpdated(getBlockPos(), getBlockState(), getBlockState(), 3);
}
}
return out;
}
}
public TankBlockEntity(BlockPos pos, BlockState blockState) {
super(ModBlockEntities.TANK_BE.get(), pos, blockState);
}
@@ -83,6 +153,112 @@ public class TankBlockEntity extends BlockEntity implements ExtendedMenuProvider
}
public void tick(Level level, BlockPos blockPos, BlockState blockState) {
if (level.isClientSide) return;
ItemStack input = itemHandler.getItem(0);
if (input.isEmpty()) return;
ItemStack output = itemHandler.getItem(1);
long rate = Math.max(1, JRConfigManager.get().milliBucketsPerSecond / 20);
IItemFluidHelper helper = Services.ITEM_FLUID;
ItemStack inputOne = input.copy();
inputOne.setCount(1);
var containedOpt = helper.getContainedFluid(inputOne);
if (containedOpt.isPresent() && !containedOpt.get().isEmpty()) {
FluidStack contained = containedOpt.get();
long canFill = tank.fill(contained, true);
long toDrain = Math.min(rate, canFill);
if (toDrain <= 0) return;
IItemFluidHelper.TransferResult drained = helper.drain(inputOne, toDrain, false);
// If partial drain failed (amount 0), and we requested less than full capacity, try requesting full capacity
if (drained.amount() == 0 && toDrain < canFill) {
drained = helper.drain(inputOne, canFill, false);
}
if (drained.amount() > 0) {
ItemStack resultStack = drained.stack();
boolean emptyNow = helper.getContainedFluid(resultStack)
.map(FluidStack::isEmpty)
.orElse(true);
if (emptyNow) {
if (canMoveToOutput(resultStack, output)) {
tank.fill(FluidStack.create(contained.getFluid(), drained.amount()), false);
input.shrink(1);
if (input.isEmpty()) {
itemHandler.setItem(0, ItemStack.EMPTY);
} else {
itemHandler.setChanged();
}
addToOutput(resultStack, output);
}
} else {
if (input.getCount() == 1) {
tank.fill(FluidStack.create(contained.getFluid(), drained.amount()), false);
itemHandler.setItem(0, resultStack);
}
}
}
} else {
FluidStack tankFluid = tank.getFluid();
if (tankFluid.isEmpty()) return;
long toFill = Math.min(rate, tankFluid.getAmount());
IItemFluidHelper.TransferResult filled = helper.fill(inputOne, tankFluid, toFill, false);
// If partial fill failed, and we requested less than available fluid, try requesting more
if (filled.amount() == 0 && toFill < tankFluid.getAmount()) {
filled = helper.fill(inputOne, tankFluid, tankFluid.getAmount(), false);
}
if (filled.amount() > 0) {
ItemStack resultStack = filled.stack();
boolean fullNow = helper.fill(resultStack, tankFluid, 1, true).amount() == 0;
boolean tankWillBeEmpty = tankFluid.getAmount() - filled.amount() <= 0;
if (fullNow || tankWillBeEmpty) {
if (canMoveToOutput(resultStack, output)) {
tank.drain(filled.amount(), false);
input.shrink(1);
if (input.isEmpty()) {
itemHandler.setItem(0, ItemStack.EMPTY);
} else {
itemHandler.setChanged();
}
addToOutput(resultStack, output);
}
} else {
if (input.getCount() == 1) {
tank.drain(filled.amount(), false);
itemHandler.setItem(0, resultStack);
}
}
}
}
}
private boolean canMoveToOutput(ItemStack result, ItemStack output) {
if (output.isEmpty()) return true;
//? if >1.20.1 {
/*return ItemStack.isSameItemSameComponents(result, output) && output.getCount() + result.getCount() <= output.getMaxStackSize();
*///?} else {
return ItemStack.isSameItemSameTags(result, output) && output.getCount() + result.getCount() <= output.getMaxStackSize();
//?}
}
private void addToOutput(ItemStack result, ItemStack output) {
if (output.isEmpty()) {
itemHandler.setItem(1, result);
} else {
output.grow(result.getCount());
itemHandler.setChanged();
}
}
public boolean isEmptyForDrop() {
@@ -94,9 +270,18 @@ public class TankBlockEntity extends BlockEntity implements ExtendedMenuProvider
protected void saveAdditional(CompoundTag pTag, HolderLookup.Provider pRegistries) {
super.saveAdditional(pTag, pRegistries);
pTag.put("Inventory", itemHandler.createTag(pRegistries));
CompoundTag fluidTag = new CompoundTag();
fluidStack.write(pRegistries, fluidTag);
pTag.put("Fluid", fluidTag);
if (!fluidStack.isEmpty()) {
// Manually save fluid stack to avoid issues with Architectury's default serialization
CompoundTag fluidTag = new CompoundTag();
fluidTag.putString("id", BuiltInRegistries.FLUID.getKey(fluidStack.getFluid()).toString());
fluidTag.putLong("amount", fluidStack.getAmount());
if (fluidStack.getPatch() != null && !fluidStack.getPatch().isEmpty()) {
// Fallback to standard save if components are present
pTag.put("Fluid", FluidStack.CODEC.encodeStart(NbtOps.INSTANCE, fluidStack).getOrThrow());
} else {
pTag.put("Fluid", fluidTag);
}
}
}
@Override
@@ -104,7 +289,23 @@ public class TankBlockEntity extends BlockEntity implements ExtendedMenuProvider
super.loadAdditional(pTag, pRegistries);
itemHandler.fromTag(pTag.getList("Inventory", 10), pRegistries);
if (pTag.contains("Fluid")) {
this.fluidStack = FluidStack.read(pRegistries, pTag.getCompound("Fluid")).orElse(FluidStack.empty());
CompoundTag fluidTag = pTag.getCompound("Fluid");
if (fluidTag.contains("id") && fluidTag.contains("amount")) {
// Manual load
try {
// Use ResourceLocation.parse for 1.21.1
net.minecraft.world.level.material.Fluid fluid = BuiltInRegistries.FLUID.get(ResourceLocation.parse(fluidTag.getString("id")));
long amount = fluidTag.getLong("amount");
this.fluidStack = FluidStack.create(fluid, amount);
} catch (Exception e) {
this.fluidStack = FluidStack.empty();
}
} else {
// Standard load
this.fluidStack = FluidStack.CODEC.parse(NbtOps.INSTANCE, fluidTag).result().orElse(FluidStack.empty());
}
} else {
this.fluidStack = FluidStack.empty();
}
}
*///?} else {
@@ -112,9 +313,11 @@ public class TankBlockEntity extends BlockEntity implements ExtendedMenuProvider
protected void saveAdditional(CompoundTag pTag) {
super.saveAdditional(pTag);
pTag.put("Inventory", itemHandler.createTag());
CompoundTag fluidTag = new CompoundTag();
fluidStack.write(fluidTag);
pTag.put("Fluid", fluidTag);
if (!fluidStack.isEmpty()) {
CompoundTag fluidTag = new CompoundTag();
fluidStack.write(fluidTag);
pTag.put("Fluid", fluidTag);
}
}
@Override
@@ -123,6 +326,8 @@ public class TankBlockEntity extends BlockEntity implements ExtendedMenuProvider
itemHandler.fromTag(pTag.getList("Inventory", 10));
if (pTag.contains("Fluid")) {
this.fluidStack = FluidStack.read(pTag.getCompound("Fluid"));
} else {
this.fluidStack = FluidStack.empty();
}
}
//?}
@@ -6,7 +6,7 @@ public final class JRConfig {
public int spawnWeight = 10;
public boolean requirePower = true;
public int fePerSecond = 1000;
public int itemsPerSecond = 10;
public int itemsPerSecond = 100;
public int milliBucketsPerSecond = 1000;
public JRConfig() {
@@ -0,0 +1,121 @@
package net.cmr.jurassicrevived.networking;
import dev.architectury.fluid.FluidStack;
import dev.architectury.networking.NetworkManager;
import io.netty.buffer.Unpooled;
import net.cmr.jurassicrevived.Constants;
import net.cmr.jurassicrevived.screen.custom.TankMenu;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
//? if >1.20.1 {
/*import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
*///?}
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.Fluids;
public class ModPackets {
public static final ResourceLocation TANK_SYNC = Constants.rl("tank_sync");
public static void register() {
//? if >1.20.1 {
/*NetworkManager.registerReceiver(NetworkManager.Side.S2C, TANK_SYNC_TYPE, TANK_SYNC_STREAM_CODEC, (payload, context) -> {
FluidStack fluidStack = payload.fluidStack();
context.queue(() -> {
Player player = context.getPlayer();
if (player != null) {
AbstractContainerMenu menu = player.containerMenu;
if (menu instanceof TankMenu tankMenu) {
tankMenu.setFluid(fluidStack);
}
}
});
});
*///?} else {
NetworkManager.registerReceiver(NetworkManager.Side.S2C, TANK_SYNC, (buf, context) -> {
FluidStack fluidStack = FluidStack.read(buf);
context.queue(() -> {
Player player = context.getPlayer();
if (player != null) {
AbstractContainerMenu menu = player.containerMenu;
if (menu instanceof TankMenu tankMenu) {
tankMenu.setFluid(fluidStack);
}
}
});
});
//?}
}
public static void sendTankSync(ServerPlayer player, FluidStack fluidStack) {
//? if >1.20.1 {
/*NetworkManager.sendToPlayer(player, new TankSyncPayload(fluidStack));
*///?} else {
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
fluidStack.write(buf);
NetworkManager.sendToPlayer(player, TANK_SYNC, buf);
//?}
}
//? if >1.20.1 {
/*public static final CustomPacketPayload.Type<TankSyncPayload> TANK_SYNC_TYPE = new CustomPacketPayload.Type<>(TANK_SYNC);
public static final StreamCodec<RegistryFriendlyByteBuf, TankSyncPayload> TANK_SYNC_STREAM_CODEC = StreamCodec.of((buf, payload) -> payload.write(buf), TankSyncPayload::new);
public record TankSyncPayload(FluidStack fluidStack) implements CustomPacketPayload {
public TankSyncPayload(RegistryFriendlyByteBuf buf) {
this(readFluid(buf));
}
public void write(RegistryFriendlyByteBuf buf) {
if (fluidStack.isEmpty()) {
buf.writeBoolean(false);
} else {
buf.writeBoolean(true);
buf.writeResourceLocation(BuiltInRegistries.FLUID.getKey(fluidStack.getFluid()));
buf.writeLong(fluidStack.getAmount());
//? if >1.20.1 {
/^DataComponentPatch.STREAM_CODEC.encode(buf, fluidStack.getPatch());
^///?} else {
buf.writeNbt(fluidStack.getTag());
//?}
}
}
private static FluidStack readFluid(RegistryFriendlyByteBuf buf) {
if (!buf.readBoolean()) {
return FluidStack.empty();
}
ResourceLocation fluidId = buf.readResourceLocation();
long amount = buf.readLong();
//? if >1.20.1 {
/^DataComponentPatch patch = DataComponentPatch.STREAM_CODEC.decode(buf);
^///?} else {
CompoundTag tag = buf.readNbt();
//?}
Fluid fluid = BuiltInRegistries.FLUID.get(fluidId);
if (fluid == Fluids.EMPTY) {
return FluidStack.empty();
}
//? if >1.20.1 {
/^return FluidStack.create(fluid, amount, patch);
^///?} else {
FluidStack stack = FluidStack.create(fluid, amount);
stack.setTag(tag);
return stack;
//?}
}
@Override
public Type<? extends CustomPacketPayload> type() {
return TANK_SYNC_TYPE;
}
}
*///?}
}
@@ -3,6 +3,8 @@ package net.cmr.jurassicrevived.platform;
import net.cmr.jurassicrevived.Constants;
import net.cmr.jurassicrevived.platform.services.IPlatformHelper;
import net.cmr.jurassicrevived.platform.services.ITransferHelper;
import net.cmr.jurassicrevived.platform.services.IItemFluidHelper;
import java.util.ServiceLoader;
@@ -16,6 +18,8 @@ public class Services
// For example this can be used to check if the code is running on Forge vs Fabric, or to ask the modloader if another
// mod is loaded.
public static final IPlatformHelper PLATFORM = load(IPlatformHelper.class);
public static final ITransferHelper TRANSFER = load(ITransferHelper.class);
public static final IItemFluidHelper ITEM_FLUID = load(IItemFluidHelper.class);
// This code is used to load a service for the current environment. Your implementation of the service must be defined
// manually by including a text file in META-INF/services named with the fully qualified class name of the service.
@@ -0,0 +1,15 @@
package net.cmr.jurassicrevived.platform.services;
import dev.architectury.fluid.FluidStack;
import net.minecraft.world.item.ItemStack;
import java.util.Optional;
public interface IItemFluidHelper {
record TransferResult(long amount, ItemStack stack) {}
Optional<FluidStack> getContainedFluid(ItemStack stack);
TransferResult drain(ItemStack stack, long amount, boolean simulate);
TransferResult fill(ItemStack stack, FluidStack fluid, long amount, boolean simulate);
boolean isFluidHandler(ItemStack stack);
}
@@ -0,0 +1,16 @@
package net.cmr.jurassicrevived.platform.services;
import net.cmr.jurassicrevived.platform.transfer.PlatformItemHandler;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
import net.cmr.jurassicrevived.platform.transfer.PlatformEnergyHandler;
import net.cmr.jurassicrevived.platform.transfer.PlatformFluidHandler;
import java.util.Optional;
public interface ITransferHelper {
Optional<PlatformItemHandler> getItemHandler(Level level, BlockPos pos, Direction side);
Optional<PlatformFluidHandler> getFluidHandler(Level level, BlockPos pos, Direction side);
Optional<PlatformEnergyHandler> getEnergyHandler(Level level, BlockPos pos, Direction side);
}
@@ -0,0 +1,10 @@
package net.cmr.jurassicrevived.platform.transfer;
import dev.architectury.fluid.FluidStack;
public interface InternalFluidHandler {
FluidStack getFluid();
long getCapacity();
long fill(FluidStack stack, boolean simulate);
FluidStack drain(long amount, boolean simulate);
}
@@ -0,0 +1,33 @@
package net.cmr.jurassicrevived.platform.transfer;
import dev.architectury.fluid.FluidStack;
import java.util.List;
public class InternalFluidHandlerAdapter implements PlatformFluidHandler {
private final InternalFluidHandler handler;
public InternalFluidHandlerAdapter(InternalFluidHandler handler) {
this.handler = handler;
}
@Override
public Iterable<FluidStack> getExtractableFluids() {
FluidStack fluid = handler.getFluid();
return fluid.isEmpty() ? List.of() : List.of(fluid);
}
@Override
public long extract(FluidStack stack, long amount, boolean simulate) {
if (stack.isEmpty()) return 0;
return handler.drain(amount, simulate).getAmount();
}
@Override
public long insert(FluidStack stack, long amount, boolean simulate) {
if (stack.isEmpty()) return 0;
FluidStack toFill = stack.copy();
toFill.setAmount(amount);
return handler.fill(toFill, simulate);
}
}
@@ -0,0 +1,8 @@
package net.cmr.jurassicrevived.platform.transfer;
import net.minecraft.core.Direction;
import org.jetbrains.annotations.Nullable;
public interface InternalFluidProvider {
InternalFluidHandler getFluidHandler(@Nullable Direction side);
}
@@ -0,0 +1,6 @@
package net.cmr.jurassicrevived.platform.transfer;
public interface PlatformEnergyHandler {
int extract(int amount, boolean simulate);
int insert(int amount, boolean simulate);
}
@@ -0,0 +1,9 @@
package net.cmr.jurassicrevived.platform.transfer;
import dev.architectury.fluid.FluidStack;
public interface PlatformFluidHandler {
Iterable<FluidStack> getExtractableFluids();
long extract(FluidStack stack, long amount, boolean simulate);
long insert(FluidStack stack, long amount, boolean simulate);
}
@@ -0,0 +1,9 @@
package net.cmr.jurassicrevived.platform.transfer;
import net.minecraft.world.item.ItemStack;
public interface PlatformItemHandler {
Iterable<ItemStack> getExtractableStacks();
int extract(ItemStack stack, int amount, boolean simulate);
int insert(ItemStack stack, int amount, boolean simulate);
}
@@ -1,13 +1,18 @@
package net.cmr.jurassicrevived.screen.custom;
import dev.architectury.fluid.FluidStack;
import net.cmr.jurassicrevived.block.ModBlocks;
import net.cmr.jurassicrevived.block.entity.custom.TankBlockEntity;
import net.cmr.jurassicrevived.networking.ModPackets;
import net.cmr.jurassicrevived.platform.Services;
import net.cmr.jurassicrevived.screen.ModMenuTypes;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
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.ContainerLevelAccess;
import net.minecraft.world.inventory.DataSlot;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
@@ -16,6 +21,7 @@ import net.minecraft.world.level.block.entity.BlockEntity;
public class TankMenu extends AbstractContainerMenu {
public final TankBlockEntity blockEntity;
private final Level level;
private FluidStack fluidStack = FluidStack.empty();
public TankMenu(int pContainerId, Inventory inv, FriendlyByteBuf extraData) {
this(pContainerId, inv, inv.player.level().getBlockEntity(extraData.readBlockPos()));
@@ -25,20 +31,81 @@ public class TankMenu extends AbstractContainerMenu {
super(ModMenuTypes.TANK_MENU.get(), pContainerId);
this.blockEntity = ((TankBlockEntity) blockEntity);
this.level = inv.player.level();
// Initialize fluidStack with the current state of the block entity
// This ensures the client sees the correct fluid immediately if the BE is already synced
if (this.blockEntity != null) {
this.fluidStack = this.blockEntity.getFluid().copy();
}
addPlayerInventory(inv);
addPlayerHotbar(inv);
// Input Slot (0)
this.addSlot(new Slot(this.blockEntity.itemHandler, 0, 44, 34));
// Output Slot (1)
this.addSlot(new Slot(this.blockEntity.itemHandler, 1, 116, 34) {
this.addSlot(new Slot(this.blockEntity.itemHandler, 0, 44, 34) {
@Override
public int getMaxStackSize() {
return 1;
public boolean mayPlace(ItemStack stack) {
return Services.ITEM_FLUID.isFluidHandler(stack);
}
});
// Output Slot (1)
this.addSlot(new Slot(this.blockEntity.itemHandler, 1, 116, 34){
@Override
public boolean mayPlace(ItemStack stack) {
return false;
}
});
addDataSlot(new DataSlot() {
@Override
public int get() {
return 0;
}
@Override
public void set(int pValue) {
}
});
}
public void setFluid(FluidStack stack) {
this.fluidStack = stack;
}
public FluidStack getFluid() {
return this.fluidStack;
}
public void syncFluidToPlayers() {
if (this.level != null && !this.level.isClientSide()) {
for (Player player : this.level.players()) {
if (player.containerMenu == this && player instanceof ServerPlayer serverPlayer) {
ModPackets.sendTankSync(serverPlayer, this.fluidStack);
}
}
}
}
@Override
public void sendAllDataToRemote() {
super.sendAllDataToRemote();
// Ensure we have the latest fluid state from the block entity before syncing
if (this.blockEntity != null) {
this.fluidStack = this.blockEntity.getFluid().copy();
}
syncFluidToPlayers();
}
@Override
public void broadcastChanges() {
super.broadcastChanges();
if (this.blockEntity != null) {
FluidStack currentFluid = this.blockEntity.getFluid();
if (!currentFluid.equals(this.fluidStack)) {
this.fluidStack = currentFluid.copy();
syncFluidToPlayers();
}
}
}
private static final int HOTBAR_SLOT_COUNT = 9;
@@ -51,7 +51,7 @@ public class TankScreen extends AbstractContainerScreen<TankMenu> {
int x = (width - imageWidth) / 2;
int y = (height - imageHeight) / 2;
renderFluidTooltipArea(guiGraphics, pMouseX, pMouseY, x, y, menu.blockEntity.getFluid(), 80, 8, fluidRenderer);
renderFluidTooltipArea(guiGraphics, pMouseX, pMouseY, x, y, menu.getFluid(), 80, 8, fluidRenderer);
}
@Override
@@ -64,7 +64,7 @@ public class TankScreen extends AbstractContainerScreen<TankMenu> {
guiGraphics.blit(GUI_TEXTURE, x, y, 0, 0, imageWidth, imageHeight);
fluidRenderer.render(guiGraphics, x + 80, y + 8, menu.blockEntity.getFluid());
fluidRenderer.render(guiGraphics, x + 80, y + 8, menu.getFluid());
}
@Override