Add Chemical Vat multiplace

- ability to place entire vat levels in 1 click
This commit is contained in:
Daniel
2025-07-06 01:48:04 +03:00
parent 6e818ecb70
commit 73b30c9421
3 changed files with 301 additions and 8 deletions

View File

@@ -0,0 +1,197 @@
package com.drmangotea.tfmg.content.machinery.vat.base;
import com.drmangotea.tfmg.registry.TFMGBlockEntities;
import com.simibubi.create.api.connectivity.ConnectivityHandler;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.items.ItemStackHandler;
public class VatItem extends BlockItem {
String vatType;
public VatItem(Block block, Properties properties, String vatType) {
super(block, properties);
this.vatType = vatType;
}
public static class SteelVatItem extends VatItem {
public SteelVatItem(Block block, Properties properties) {
super(block, properties,"tfmg:steel_vat");
}
}
public static class CastIronVatItem extends VatItem {
public CastIronVatItem(Block block, Properties properties) {
super(block, properties,"tfmg:cast_iron_vat");
}
}
public static class FireproofVatItem extends VatItem {
public FireproofVatItem(Block block, Properties properties) {
super(block, properties,"tfmg:firebrick_lined_vat");
}
}
@Override
public InteractionResult place(BlockPlaceContext ctx) {
InteractionResult initialResult = super.place(ctx);
if (!initialResult.consumesAction())
return initialResult;
tryMultiPlace(ctx);
return initialResult;
}
@Override
protected boolean updateCustomBlockEntityTag(BlockPos pos, Level level, Player player,
ItemStack stack, BlockState state) {
CompoundTag nbt = stack.getTagElement("BlockEntityTag");
if (nbt == null) return false;
// 1. Preserve multi-block structure data
nbt.remove("Controller"); // Let the vat rebuild connectivity
nbt.remove("LastKnownPos");
// 2. Process inventories (input/output slots)
sanitizeInventory(nbt, "InputItems", 4);
sanitizeInventory(nbt, "OutputItems", 4);
// 3. Handle fluids via Create's SmartFluidTankBehaviour
if (nbt.contains("CreateData")) {
CompoundTag createData = nbt.getCompound("CreateData");
int capacity = VatBlockEntity.getCapacityMultiplier(); // Single-block capacity
// Clamp all tanks (both input and output)
clampTankGroup(createData, "InputTanks", capacity);
clampTankGroup(createData, "OutputTanks", capacity);
}
// 4. Clean transient and vat-specific data
nbt.remove("Luminosity");
nbt.remove("Machines"); // Machines must re-attach after placement
nbt.remove("Timer"); // Reset recipe progress
nbt.remove("RecipeId");
return super.updateCustomBlockEntityTag(pos, level, player, stack, state);
}
private void sanitizeInventory(CompoundTag nbt, String key, int slots) {
if (nbt.contains(key)) {
ItemStackHandler handler = new ItemStackHandler(slots);
handler.deserializeNBT(nbt.getCompound(key));
// Validate items (prevent overstacked/illegal items)
for (int i = 0; i < slots; i++) {
ItemStack stack = handler.getStackInSlot(i);
if (stack.getCount() > stack.getMaxStackSize()) {
stack.setCount(stack.getMaxStackSize());
}
}
nbt.put(key, handler.serializeNBT());
}
}
private void clampTankGroup(CompoundTag createData, String tankGroup, int capacity) {
if (!createData.contains(tankGroup)) return;
ListTag tanks = createData.getList(tankGroup, Tag.TAG_COMPOUND);
for (int i = 0; i < tanks.size(); i++) {
CompoundTag tankTag = tanks.getCompound(i);
if (tankTag.contains("TankContent")) {
FluidStack fluid = FluidStack.loadFluidStackFromNBT(tankTag.getCompound("TankContent"));
if (!fluid.isEmpty()) {
fluid.setAmount(Math.min(capacity, fluid.getAmount()));
tankTag.put("TankContent", fluid.writeToNBT(new CompoundTag()));
}
}
}
}
private void tryMultiPlace(BlockPlaceContext ctx) {
Player player = ctx.getPlayer();
if (player == null)
return;
if (player.isShiftKeyDown())
return;
Direction face = ctx.getClickedFace();
if (!face.getAxis()
.isVertical())
return;
ItemStack stack = ctx.getItemInHand();
Level world = ctx.getLevel();
BlockPos pos = ctx.getClickedPos();
BlockPos placedOnPos = pos.relative(face.getOpposite());
BlockState placedOnState = world.getBlockState(placedOnPos);
if (!(placedOnState.getBlock() instanceof VatBlock placedOnVat)) return;
if (!placedOnVat.vatType.equals(this.vatType)) return;
VatBlockEntity vatAt = ConnectivityHandler.partAt(
TFMGBlockEntities.CHEMICAL_VAT.get(), world, placedOnPos);
if (vatAt == null)
return;
VatBlockEntity controllerTE = vatAt.getControllerBE();
if (controllerTE == null)
return;
int width = controllerTE.getWidth();
if (width == 1)
return;
int vatsToPlace = 0;
BlockPos startPos = face == Direction.DOWN ? controllerTE.getBlockPos()
.below()
: controllerTE.getBlockPos()
.above(controllerTE.getHeight());
if (startPos.getY() != pos.getY())
return;
for (int xOffset = 0; xOffset < width; xOffset++) {
for (int zOffset = 0; zOffset < width; zOffset++) {
BlockPos offsetPos = startPos.offset(xOffset, 0, zOffset);
BlockState blockState = world.getBlockState(offsetPos);
if (VatBlock.isVat(blockState))
continue;
if (!blockState.canBeReplaced())
return;
vatsToPlace++;
}
}
if (!player.isCreative() && stack.getCount() < vatsToPlace)
return;
for (int xOffset = 0; xOffset < width; xOffset++) {
for (int zOffset = 0; zOffset < width; zOffset++) {
BlockPos offsetPos = startPos.offset(xOffset, 0, zOffset);
BlockState blockState = world.getBlockState(offsetPos);
if (VatBlock.isVat(blockState))
continue;
BlockPlaceContext context = BlockPlaceContext.at(ctx, offsetPos, face);
player.getPersistentData()
.putBoolean("SilenceTankSound", true);
super.place(context);
player.getPersistentData()
.remove("SilenceTankSound");
}
}
}
}

View File

@@ -0,0 +1,94 @@
package com.drmangotea.tfmg.content.machinery.vat.base;
import com.drmangotea.tfmg.base.TFMGSpriteShifts;
import com.simibubi.create.api.connectivity.ConnectivityHandler;
import com.simibubi.create.content.fluids.tank.FluidTankCTBehaviour;
import com.simibubi.create.foundation.block.connected.CTModel;
import com.simibubi.create.foundation.block.connected.CTSpriteShiftEntry;
import net.createmod.catnip.data.Iterate;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.client.model.data.ModelData;
import net.minecraftforge.client.model.data.ModelProperty;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class VatModel extends CTModel {
protected static final ModelProperty<CullData> CULL_PROPERTY = new ModelProperty<>();
public static VatModel steelVat(BakedModel originalModel) {
return new VatModel(originalModel, TFMGSpriteShifts.STEEL_VAT, TFMGSpriteShifts.STEEL_VAT_TOP,
TFMGSpriteShifts.STEEL_VAT_INNER);
}
public static VatModel castIronVat(BakedModel originalModel) {
return new VatModel(originalModel, TFMGSpriteShifts.CAST_IRON_VAT, TFMGSpriteShifts.CAST_IRON_VAT_TOP,
TFMGSpriteShifts.CAST_IRON_VAT_INNER);
}
public static VatModel fireproofVat(BakedModel originalModel) {
return new VatModel(originalModel, TFMGSpriteShifts.FIREPROOF_VAT, TFMGSpriteShifts.STEEL_VAT_TOP,
TFMGSpriteShifts.STEEL_VAT_INNER);
}
private VatModel(BakedModel originalModel, CTSpriteShiftEntry side, CTSpriteShiftEntry top,
CTSpriteShiftEntry inner) {
super(originalModel, new FluidTankCTBehaviour(side, top, inner));
}
@Override
protected ModelData.Builder gatherModelData(ModelData.Builder builder, BlockAndTintGetter world, BlockPos pos, BlockState state,
ModelData blockEntityData) {
super.gatherModelData(builder, world, pos, state, blockEntityData);
CullData cullData = new CullData();
for (Direction d : Iterate.horizontalDirections)
cullData.setCulled(d, ConnectivityHandler.isConnected(world, pos, pos.relative(d)));
return builder.with(CULL_PROPERTY, cullData);
}
@Override
public List<BakedQuad> getQuads(BlockState state, Direction side, RandomSource rand, ModelData extraData, RenderType renderType) {
if (side != null)
return Collections.emptyList();
List<BakedQuad> quads = new ArrayList<>();
for (Direction d : Iterate.directions) {
if (extraData.has(CULL_PROPERTY) && extraData.get(CULL_PROPERTY)
.isCulled(d))
continue;
quads.addAll(super.getQuads(state, d, rand, extraData, renderType));
}
quads.addAll(super.getQuads(state, null, rand, extraData, renderType));
return quads;
}
private class CullData {
boolean[] culledFaces;
public CullData() {
culledFaces = new boolean[4];
Arrays.fill(culledFaces, false);
}
void setCulled(Direction face, boolean cull) {
if (face.getAxis()
.isVertical())
return;
culledFaces[face.get2DDataValue()] = cull;
}
boolean isCulled(Direction face) {
if (face.getAxis()
.isVertical())
return false;
return culledFaces[face.get2DDataValue()];
}
}
}

View File

@@ -110,6 +110,8 @@ import com.drmangotea.tfmg.content.machinery.oil_processing.pumpjack.pumpjack.ha
import com.drmangotea.tfmg.content.machinery.oil_processing.surface_scanner.SurfaceScannerBlock;
import com.drmangotea.tfmg.content.machinery.vat.base.VatBlock;
import com.drmangotea.tfmg.content.machinery.vat.base.VatGenerator;
import com.drmangotea.tfmg.content.machinery.vat.base.VatItem;
import com.drmangotea.tfmg.content.machinery.vat.base.VatModel;
import com.drmangotea.tfmg.content.machinery.vat.electrode_holder.ElectrodeHolderBlock;
import com.drmangotea.tfmg.content.machinery.vat.industrial_mixer.IndustrialMixerBlock;
import com.simibubi.create.AllMountedStorageTypes;
@@ -457,10 +459,10 @@ public class TFMGBlocks {
.properties(p -> p.isRedstoneConductor((p1, p2, p3) -> true))
.transform(pickaxeOnly())
.blockstate(new VatGenerator()::generate)
.onRegister(CreateRegistrate.blockModel(() -> SteelFluidTankModel::steelVat))
.onRegister(CreateRegistrate.blockModel(() -> VatModel::steelVat))
.addLayer(() -> RenderType::cutoutMipped)
.item(SteelTankItem::new)
.model(AssetLookup.customBlockItemModel("_", "block_single_window"))
.item(VatItem.SteelVatItem::new)
.model(AssetLookup.customBlockItemModel("_", "block_single"))
.build()
.register();
public static final BlockEntry<VatBlock> CAST_IRON_CHEMICAL_VAT =
@@ -471,10 +473,10 @@ public class TFMGBlocks {
.properties(p -> p.isRedstoneConductor((p1, p2, p3) -> true))
.transform(pickaxeOnly())
.blockstate(new VatGenerator()::generate)
.onRegister(CreateRegistrate.blockModel(() -> SteelFluidTankModel::castIronVat))
.onRegister(CreateRegistrate.blockModel(() -> VatModel::castIronVat))
.addLayer(() -> RenderType::cutoutMipped)
.item(SteelTankItem::new)
.model(AssetLookup.customBlockItemModel("_", "block_single_window"))
.item(VatItem.CastIronVatItem::new)
.model(AssetLookup.customBlockItemModel("_", "block_single"))
.build()
.register();
public static final BlockEntry<VatBlock> FIREPROOF_CHEMICAL_VAT =
@@ -485,9 +487,9 @@ public class TFMGBlocks {
.properties(p -> p.isRedstoneConductor((p1, p2, p3) -> true))
.transform(pickaxeOnly())
.blockstate(new VatGenerator()::generate)
.onRegister(CreateRegistrate.blockModel(() -> SteelFluidTankModel::fireproofVat))
.onRegister(CreateRegistrate.blockModel(() -> VatModel::fireproofVat))
.addLayer(() -> RenderType::cutoutMipped)
.item(SteelTankItem::new)
.item(VatItem.FireproofVatItem::new)
.model(AssetLookup.customBlockItemModel("_", "block_single"))
.build()
.register();