2022-02-01 01:14:21 +01:00
|
|
|
package com.simibubi.create.content.logistics.trains.entity;
|
|
|
|
|
2022-02-07 01:54:20 +01:00
|
|
|
import java.util.ArrayList;
|
2022-05-15 21:19:02 +02:00
|
|
|
import java.util.Collection;
|
2022-02-07 01:54:20 +01:00
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
2022-05-15 21:19:02 +02:00
|
|
|
import java.util.Map.Entry;
|
|
|
|
import java.util.Optional;
|
2022-02-07 01:54:20 +01:00
|
|
|
|
2022-02-01 01:14:21 +01:00
|
|
|
import org.apache.commons.lang3.tuple.Pair;
|
|
|
|
|
|
|
|
import com.simibubi.create.AllBlocks;
|
2022-05-26 19:00:03 +02:00
|
|
|
import com.simibubi.create.content.contraptions.components.structureMovement.AbstractContraptionEntity;
|
2022-02-01 01:14:21 +01:00
|
|
|
import com.simibubi.create.content.contraptions.components.structureMovement.AssemblyException;
|
|
|
|
import com.simibubi.create.content.contraptions.components.structureMovement.Contraption;
|
|
|
|
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionType;
|
2022-05-17 23:23:29 +02:00
|
|
|
import com.simibubi.create.content.contraptions.components.structureMovement.MountedStorageManager;
|
2022-02-01 01:14:21 +01:00
|
|
|
import com.simibubi.create.content.contraptions.components.structureMovement.NonStationaryLighter;
|
2022-02-05 02:06:31 +01:00
|
|
|
import com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls.ControlsBlock;
|
2022-04-08 02:22:14 +02:00
|
|
|
import com.simibubi.create.content.contraptions.components.structureMovement.render.ContraptionLighter;
|
2022-05-26 19:00:03 +02:00
|
|
|
import com.simibubi.create.content.contraptions.components.structureMovement.train.TrainCargoManager;
|
2022-02-07 01:54:20 +01:00
|
|
|
import com.simibubi.create.content.contraptions.processing.burner.BlazeBurnerBlock;
|
|
|
|
import com.simibubi.create.content.contraptions.processing.burner.BlazeBurnerBlock.HeatLevel;
|
2023-05-09 18:23:47 +02:00
|
|
|
import com.simibubi.create.content.logistics.trains.AbstractBogeyBlock;
|
2022-02-07 01:54:20 +01:00
|
|
|
import com.simibubi.create.foundation.utility.Couple;
|
|
|
|
import com.simibubi.create.foundation.utility.Iterate;
|
2022-02-02 01:21:28 +01:00
|
|
|
import com.simibubi.create.foundation.utility.Lang;
|
2022-02-01 01:14:21 +01:00
|
|
|
import com.simibubi.create.foundation.utility.NBTHelper;
|
2022-05-10 00:16:12 +02:00
|
|
|
import com.simibubi.create.foundation.utility.VecHelper;
|
2022-02-01 01:14:21 +01:00
|
|
|
|
|
|
|
import net.minecraft.core.BlockPos;
|
|
|
|
import net.minecraft.core.Direction;
|
2022-05-15 21:19:02 +02:00
|
|
|
import net.minecraft.core.Direction.Axis;
|
2022-02-01 01:14:21 +01:00
|
|
|
import net.minecraft.nbt.CompoundTag;
|
2022-03-09 02:21:20 +01:00
|
|
|
import net.minecraft.nbt.ListTag;
|
2022-02-07 01:54:20 +01:00
|
|
|
import net.minecraft.nbt.NbtUtils;
|
|
|
|
import net.minecraft.nbt.Tag;
|
2022-02-01 01:14:21 +01:00
|
|
|
import net.minecraft.world.level.Level;
|
2022-05-15 21:19:02 +02:00
|
|
|
import net.minecraft.world.level.block.Blocks;
|
2022-02-01 01:14:21 +01:00
|
|
|
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;
|
2022-05-15 21:19:02 +02:00
|
|
|
import net.minecraft.world.phys.AABB;
|
2022-03-11 23:37:41 +01:00
|
|
|
import net.minecraftforge.api.distmarker.Dist;
|
|
|
|
import net.minecraftforge.api.distmarker.OnlyIn;
|
2022-05-26 19:00:03 +02:00
|
|
|
import net.minecraftforge.fluids.FluidStack;
|
2022-05-17 23:23:29 +02:00
|
|
|
import net.minecraftforge.fluids.capability.IFluidHandler;
|
2022-07-10 18:22:47 +02:00
|
|
|
import net.minecraftforge.fluids.capability.templates.FluidTank;
|
2022-05-17 23:23:29 +02:00
|
|
|
import net.minecraftforge.items.IItemHandlerModifiable;
|
2022-07-10 18:22:47 +02:00
|
|
|
import net.minecraftforge.items.ItemStackHandler;
|
2022-02-01 01:14:21 +01:00
|
|
|
|
|
|
|
public class CarriageContraption extends Contraption {
|
|
|
|
|
|
|
|
private Direction assemblyDirection;
|
2022-02-05 02:06:31 +01:00
|
|
|
private boolean forwardControls;
|
|
|
|
private boolean backwardControls;
|
2022-05-17 23:23:29 +02:00
|
|
|
|
2022-02-07 01:54:20 +01:00
|
|
|
public Couple<Boolean> blazeBurnerConductors;
|
|
|
|
public Map<BlockPos, Couple<Boolean>> conductorSeats;
|
2022-05-10 00:16:12 +02:00
|
|
|
public ArrivalSoundQueue soundQueue;
|
2022-02-01 01:14:21 +01:00
|
|
|
|
2022-05-17 23:23:29 +02:00
|
|
|
protected MountedStorageManager storageProxy;
|
|
|
|
|
2022-03-08 03:51:03 +01:00
|
|
|
// during assembly only
|
2022-02-07 01:54:20 +01:00
|
|
|
private int bogeys;
|
|
|
|
private boolean sidewaysControls;
|
|
|
|
private BlockPos secondBogeyPos;
|
|
|
|
private List<BlockPos> assembledBlazeBurners;
|
|
|
|
|
2022-05-15 21:19:02 +02:00
|
|
|
// render
|
|
|
|
public int portalCutoffMin;
|
|
|
|
public int portalCutoffMax;
|
2023-05-09 18:23:47 +02:00
|
|
|
|
2022-07-10 18:22:47 +02:00
|
|
|
static final IItemHandlerModifiable fallbackItems = new ItemStackHandler();
|
|
|
|
static final IFluidHandler fallbackFluids = new FluidTank(0);
|
2022-05-15 21:19:02 +02:00
|
|
|
|
2022-02-07 01:54:20 +01:00
|
|
|
public CarriageContraption() {
|
|
|
|
conductorSeats = new HashMap<>();
|
|
|
|
assembledBlazeBurners = new ArrayList<>();
|
|
|
|
blazeBurnerConductors = Couple.create(false, false);
|
2022-05-10 00:16:12 +02:00
|
|
|
soundQueue = new ArrivalSoundQueue();
|
2022-05-15 21:19:02 +02:00
|
|
|
portalCutoffMin = Integer.MIN_VALUE;
|
|
|
|
portalCutoffMax = Integer.MAX_VALUE;
|
2022-05-26 19:00:03 +02:00
|
|
|
storage = new TrainCargoManager();
|
2022-05-10 00:16:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void setSoundQueueOffset(int offset) {
|
|
|
|
soundQueue.offset = offset;
|
2022-02-07 01:54:20 +01:00
|
|
|
}
|
2022-02-01 01:14:21 +01:00
|
|
|
|
|
|
|
public CarriageContraption(Direction assemblyDirection) {
|
2022-02-07 01:54:20 +01:00
|
|
|
this();
|
2022-02-01 01:14:21 +01:00
|
|
|
this.assemblyDirection = assemblyDirection;
|
|
|
|
this.bogeys = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean assemble(Level world, BlockPos pos) throws AssemblyException {
|
|
|
|
if (!searchMovedStructure(world, pos, null))
|
|
|
|
return false;
|
2022-02-02 01:21:28 +01:00
|
|
|
if (blocks.size() <= 1)
|
2022-02-01 01:14:21 +01:00
|
|
|
return false;
|
2022-02-02 01:21:28 +01:00
|
|
|
if (bogeys == 0)
|
|
|
|
return false;
|
|
|
|
if (bogeys > 2)
|
2022-07-05 16:49:55 +02:00
|
|
|
throw new AssemblyException(Lang.translateDirect("train_assembly.too_many_bogeys", bogeys));
|
2022-02-05 02:06:31 +01:00
|
|
|
if (sidewaysControls)
|
2022-07-05 16:49:55 +02:00
|
|
|
throw new AssemblyException(Lang.translateDirect("train_assembly.sideways_controls"));
|
2022-02-07 01:54:20 +01:00
|
|
|
|
|
|
|
for (BlockPos blazePos : assembledBlazeBurners)
|
|
|
|
for (Direction direction : Iterate.directionsInAxis(assemblyDirection.getAxis()))
|
|
|
|
if (inControl(blazePos, direction))
|
|
|
|
blazeBurnerConductors.set(direction != assemblyDirection, true);
|
|
|
|
for (BlockPos seatPos : getSeats())
|
|
|
|
for (Direction direction : Iterate.directionsInAxis(assemblyDirection.getAxis()))
|
|
|
|
if (inControl(seatPos, direction))
|
|
|
|
conductorSeats.computeIfAbsent(seatPos, p -> Couple.create(false, false))
|
|
|
|
.set(direction != assemblyDirection, true);
|
|
|
|
|
2022-02-01 01:14:21 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-02-07 01:54:20 +01:00
|
|
|
public boolean inControl(BlockPos pos, Direction direction) {
|
|
|
|
BlockPos controlsPos = pos.relative(direction);
|
|
|
|
if (!blocks.containsKey(controlsPos))
|
|
|
|
return false;
|
|
|
|
StructureBlockInfo info = blocks.get(controlsPos);
|
|
|
|
if (!AllBlocks.CONTROLS.has(info.state))
|
|
|
|
return false;
|
|
|
|
return info.state.getValue(ControlsBlock.FACING) == direction.getOpposite();
|
|
|
|
}
|
|
|
|
|
2022-05-17 23:23:29 +02:00
|
|
|
public void swapStorageAfterAssembly(CarriageContraptionEntity cce) {
|
|
|
|
// Ensure that the entity does not hold its inventory data, because the global
|
|
|
|
// carriage manages it instead
|
|
|
|
Carriage carriage = cce.getCarriage();
|
|
|
|
if (carriage.storage == null) {
|
2022-05-26 19:00:03 +02:00
|
|
|
carriage.storage = (TrainCargoManager) storage;
|
2022-05-17 23:23:29 +02:00
|
|
|
storage = new MountedStorageManager();
|
|
|
|
}
|
|
|
|
storageProxy = carriage.storage;
|
|
|
|
}
|
2022-05-26 19:00:03 +02:00
|
|
|
|
2022-05-17 23:23:29 +02:00
|
|
|
public void returnStorageForDisassembly(MountedStorageManager storage) {
|
|
|
|
this.storage = storage;
|
|
|
|
}
|
|
|
|
|
2022-02-01 01:14:21 +01:00
|
|
|
@Override
|
|
|
|
protected boolean isAnchoringBlockAt(BlockPos pos) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected Pair<StructureBlockInfo, BlockEntity> capture(Level world, BlockPos pos) {
|
|
|
|
BlockState blockState = world.getBlockState(pos);
|
|
|
|
|
2022-05-10 00:16:12 +02:00
|
|
|
if (ArrivalSoundQueue.isPlayable(blockState)) {
|
|
|
|
int anchorCoord = VecHelper.getCoordinate(anchor, assemblyDirection.getAxis());
|
|
|
|
int posCoord = VecHelper.getCoordinate(pos, assemblyDirection.getAxis());
|
|
|
|
soundQueue.add((posCoord - anchorCoord) * assemblyDirection.getAxisDirection()
|
|
|
|
.getStep(), toLocalPos(pos));
|
|
|
|
}
|
|
|
|
|
2023-05-09 18:23:47 +02:00
|
|
|
if (blockState.getBlock() instanceof AbstractBogeyBlock<?> bogey) {
|
|
|
|
boolean captureTE = bogey.captureTileEntityForTrain();
|
2022-02-01 01:14:21 +01:00
|
|
|
bogeys++;
|
|
|
|
if (bogeys == 2)
|
|
|
|
secondBogeyPos = pos;
|
2023-05-09 18:23:47 +02:00
|
|
|
return Pair.of(new StructureBlockInfo(pos, blockState, captureTE ? getTileEntityNBT(world, pos) : null),
|
|
|
|
captureTE ? world.getBlockEntity(pos) : null);
|
2022-02-01 01:14:21 +01:00
|
|
|
}
|
|
|
|
|
2022-02-07 01:54:20 +01:00
|
|
|
if (AllBlocks.BLAZE_BURNER.has(blockState)
|
|
|
|
&& blockState.getValue(BlazeBurnerBlock.HEAT_LEVEL) != HeatLevel.NONE)
|
|
|
|
assembledBlazeBurners.add(toLocalPos(pos));
|
|
|
|
|
2022-02-05 02:06:31 +01:00
|
|
|
if (AllBlocks.CONTROLS.has(blockState)) {
|
|
|
|
Direction facing = blockState.getValue(ControlsBlock.FACING);
|
|
|
|
if (facing.getAxis() != assemblyDirection.getAxis())
|
|
|
|
sidewaysControls = true;
|
2022-02-07 01:54:20 +01:00
|
|
|
else {
|
|
|
|
boolean forwards = facing == assemblyDirection;
|
|
|
|
if (forwards)
|
|
|
|
forwardControls = true;
|
|
|
|
else
|
|
|
|
backwardControls = true;
|
|
|
|
}
|
2022-02-05 02:06:31 +01:00
|
|
|
}
|
2022-02-01 01:14:21 +01:00
|
|
|
|
|
|
|
return super.capture(world, pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public CompoundTag writeNBT(boolean spawnPacket) {
|
|
|
|
CompoundTag tag = super.writeNBT(spawnPacket);
|
|
|
|
NBTHelper.writeEnum(tag, "AssemblyDirection", getAssemblyDirection());
|
2022-02-05 02:06:31 +01:00
|
|
|
tag.putBoolean("FrontControls", forwardControls);
|
|
|
|
tag.putBoolean("BackControls", backwardControls);
|
2022-02-07 01:54:20 +01:00
|
|
|
tag.putBoolean("FrontBlazeConductor", blazeBurnerConductors.getFirst());
|
|
|
|
tag.putBoolean("BackBlazeConductor", blazeBurnerConductors.getSecond());
|
2022-03-09 02:21:20 +01:00
|
|
|
ListTag list = NBTHelper.writeCompoundList(conductorSeats.entrySet(), e -> {
|
2022-02-07 01:54:20 +01:00
|
|
|
CompoundTag compoundTag = new CompoundTag();
|
|
|
|
compoundTag.put("Pos", NbtUtils.writeBlockPos(e.getKey()));
|
|
|
|
compoundTag.putBoolean("Forward", e.getValue()
|
|
|
|
.getFirst());
|
|
|
|
compoundTag.putBoolean("Backward", e.getValue()
|
|
|
|
.getSecond());
|
|
|
|
return compoundTag;
|
|
|
|
});
|
2022-03-09 02:21:20 +01:00
|
|
|
tag.put("ConductorSeats", list);
|
2022-05-10 00:16:12 +02:00
|
|
|
soundQueue.serialize(tag);
|
2022-02-01 01:14:21 +01:00
|
|
|
return tag;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void readNBT(Level world, CompoundTag nbt, boolean spawnData) {
|
|
|
|
assemblyDirection = NBTHelper.readEnum(nbt, "AssemblyDirection", Direction.class);
|
2022-02-05 02:06:31 +01:00
|
|
|
forwardControls = nbt.getBoolean("FrontControls");
|
|
|
|
backwardControls = nbt.getBoolean("BackControls");
|
2022-02-07 01:54:20 +01:00
|
|
|
blazeBurnerConductors =
|
|
|
|
Couple.create(nbt.getBoolean("FrontBlazeConductor"), nbt.getBoolean("BackBlazeConductor"));
|
|
|
|
conductorSeats.clear();
|
|
|
|
NBTHelper.iterateCompoundList(nbt.getList("ConductorSeats", Tag.TAG_COMPOUND),
|
|
|
|
c -> conductorSeats.put(NbtUtils.readBlockPos(c.getCompound("Pos")),
|
2022-03-09 02:21:20 +01:00
|
|
|
Couple.create(c.getBoolean("Forward"), c.getBoolean("Backward"))));
|
2022-05-10 00:16:12 +02:00
|
|
|
soundQueue.deserialize(nbt);
|
2022-02-01 01:14:21 +01:00
|
|
|
super.readNBT(world, nbt, spawnData);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean canBeStabilized(Direction facing, BlockPos localPos) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-05-27 15:40:49 +02:00
|
|
|
@Override
|
|
|
|
protected MountedStorageManager getStorageForSpawnPacket() {
|
|
|
|
return storageProxy;
|
|
|
|
}
|
2023-05-09 18:23:47 +02:00
|
|
|
|
2022-02-01 01:14:21 +01:00
|
|
|
@Override
|
|
|
|
protected ContraptionType getType() {
|
|
|
|
return ContraptionType.CARRIAGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-03-11 23:37:41 +01:00
|
|
|
@OnlyIn(Dist.CLIENT)
|
2022-02-01 01:14:21 +01:00
|
|
|
public ContraptionLighter<?> makeLighter() {
|
|
|
|
return new NonStationaryLighter<>(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Direction getAssemblyDirection() {
|
|
|
|
return assemblyDirection;
|
|
|
|
}
|
|
|
|
|
2022-02-05 02:06:31 +01:00
|
|
|
public boolean hasForwardControls() {
|
|
|
|
return forwardControls;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean hasBackwardControls() {
|
|
|
|
return backwardControls;
|
2022-02-01 01:14:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public BlockPos getSecondBogeyPos() {
|
|
|
|
return secondBogeyPos;
|
|
|
|
}
|
|
|
|
|
2022-05-15 21:19:02 +02:00
|
|
|
private Collection<BlockEntity> specialRenderedTEsOutsidePortal = new ArrayList<>();
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Collection<StructureBlockInfo> getRenderedBlocks() {
|
|
|
|
if (notInPortal())
|
|
|
|
return super.getRenderedBlocks();
|
|
|
|
|
|
|
|
specialRenderedTEsOutsidePortal = new ArrayList<>();
|
|
|
|
specialRenderedTileEntities.stream()
|
|
|
|
.filter(te -> !isHiddenInPortal(te.getBlockPos()))
|
|
|
|
.forEach(specialRenderedTEsOutsidePortal::add);
|
|
|
|
|
|
|
|
Collection<StructureBlockInfo> values = new ArrayList<>();
|
|
|
|
for (Entry<BlockPos, StructureBlockInfo> entry : blocks.entrySet()) {
|
|
|
|
BlockPos pos = entry.getKey();
|
|
|
|
if (withinVisible(pos))
|
|
|
|
values.add(entry.getValue());
|
|
|
|
else if (atSeam(pos))
|
|
|
|
values.add(new StructureBlockInfo(pos, Blocks.PURPLE_STAINED_GLASS.defaultBlockState(), null));
|
|
|
|
}
|
|
|
|
return values;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Collection<BlockEntity> getSpecialRenderedTEs() {
|
|
|
|
if (notInPortal())
|
|
|
|
return super.getSpecialRenderedTEs();
|
|
|
|
return specialRenderedTEsOutsidePortal;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Optional<List<AABB>> getSimplifiedEntityColliders() {
|
|
|
|
if (notInPortal())
|
|
|
|
return super.getSimplifiedEntityColliders();
|
|
|
|
return Optional.empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean isHiddenInPortal(BlockPos localPos) {
|
|
|
|
if (notInPortal())
|
|
|
|
return super.isHiddenInPortal(localPos);
|
|
|
|
return !withinVisible(localPos) || atSeam(localPos);
|
|
|
|
}
|
|
|
|
|
2022-05-17 23:23:29 +02:00
|
|
|
public boolean notInPortal() {
|
2022-05-15 21:19:02 +02:00
|
|
|
return portalCutoffMin == Integer.MIN_VALUE && portalCutoffMax == Integer.MAX_VALUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean atSeam(BlockPos localPos) {
|
|
|
|
Direction facing = assemblyDirection;
|
|
|
|
Axis axis = facing.getClockWise()
|
|
|
|
.getAxis();
|
|
|
|
int coord = axis.choose(localPos.getZ(), localPos.getY(), localPos.getX()) * -facing.getAxisDirection()
|
|
|
|
.getStep();
|
|
|
|
return coord == portalCutoffMin || coord == portalCutoffMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean withinVisible(BlockPos localPos) {
|
|
|
|
Direction facing = assemblyDirection;
|
|
|
|
Axis axis = facing.getClockWise()
|
|
|
|
.getAxis();
|
|
|
|
int coord = axis.choose(localPos.getZ(), localPos.getY(), localPos.getX()) * -facing.getAxisDirection()
|
|
|
|
.getStep();
|
|
|
|
return coord > portalCutoffMin && coord < portalCutoffMax;
|
|
|
|
}
|
|
|
|
|
2022-05-17 23:23:29 +02:00
|
|
|
@Override
|
|
|
|
public IItemHandlerModifiable getSharedInventory() {
|
2022-07-10 18:22:47 +02:00
|
|
|
return storageProxy == null ? fallbackItems : storageProxy.getItems();
|
2022-05-17 23:23:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public IFluidHandler getSharedFluidTanks() {
|
2022-07-10 18:22:47 +02:00
|
|
|
return storageProxy == null ? fallbackFluids : storageProxy.getFluids();
|
2022-05-17 23:23:29 +02:00
|
|
|
}
|
|
|
|
|
2022-05-26 19:00:03 +02:00
|
|
|
public void handleContraptionFluidPacket(BlockPos localPos, FluidStack containedFluid) {
|
|
|
|
storage.updateContainedFluid(localPos, containedFluid);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void tickStorage(AbstractContraptionEntity entity) {
|
|
|
|
if (entity.level.isClientSide)
|
|
|
|
storage.entityTick(entity);
|
|
|
|
else if (storageProxy != null)
|
|
|
|
storageProxy.entityTick(entity);
|
|
|
|
}
|
2022-05-17 23:23:29 +02:00
|
|
|
|
2022-02-01 01:14:21 +01:00
|
|
|
}
|